Add mobile style from Mail app, ref #36

- Maximise the field width and align delete icons
- Added back button
- Don't load first contact when mobile layout
- Hide the contact list when showing contact details
This commit is contained in:
Jan-Christoph Borchardt 2016-02-18 22:10:56 +01:00 committed by skjnldsv
parent b20f0c5ab1
commit 4d85245bd4
6 changed files with 148 additions and 51 deletions

View File

@ -140,7 +140,7 @@ detailsitem input[type="date"],
detailsitem textarea,
.select-addressbook select,
.add-field {
width: 200px;
width: 245px;
}
detailsitem .icon-delete {
@ -393,6 +393,19 @@ li.addressBook-share-item span.shareeIdentifier {
opacity: .5;
}
ul.addressBookList li {
margin-bottom: 5px;
}
ul.addressBook-share-list {
margin-top: 8px;
margin-bottom: 12px;
}
ul.addressBook-share-list li {
margin-left: 15px;
}
#app-navigation .utils .action span {
cursor: pointer !important;
}
@ -404,14 +417,10 @@ li.addressBook-share-item span.shareeIdentifier {
opacity: 1;
}
/* SELECT2 styling - MOVE TO CORE FOR 9.1! */
detailsitem .select2-container {
width: 200px;
width: 245px;
}
.select2-container-multi .select2-choices {
border-color: #ddd !important;
@ -445,3 +454,63 @@ detailsitem .select2-container {
.select2-results .select2-result-label span {
cursor: pointer !important;
}
/* Mobile width < 768px */
@media only screen and (max-width: 768px) {
/* full width for message list on mobile */
.app-content-list {
width: 100%;
background: white;
position: relative;
z-index: 100;
}
/* overlay message detail on top of message list */
.app-content-detail {
background: #fff;
width: 100%;
left: 0;
height: 100%;
top: 0;
box-shadow: 0 0 100px rgba(100, 100, 100, .9);
position: absolute;
}
.wrapper-show:not(.mobile-show),
.contacts-list:not(.mobile-show) {
display: none;
}
#app-navigation-toggle.showdetails {
transform: translate(-50px, 0);
}
#app-navigation-toggle-back {
position: fixed;
display: inline-block !important;
top: 45px;
left: 0;
width: 44px;
height: 44px;
z-index: 149;
background-color: rgba(255, 255, 255, .7);
cursor: pointer;
opacity: .6;
transform: rotate(90deg);
}
/* end of media query */
}
.contact-details-wrapper {
position: relative;
background: white;
}
.wrapper-show {
z-index: 101;
}
#app-navigation-toggle-back {
display: none;
}

View File

@ -1,8 +1,19 @@
angular.module('contactsApp')
.controller('contactdetailsCtrl', function(ContactService, AddressBookService, vCardPropertiesService, $routeParams, $scope) {
.controller('contactdetailsCtrl', function(ContactService, AddressBookService, vCardPropertiesService, $route, $routeParams, $scope) {
var ctrl = this;
ctrl.loading = true;
ctrl.show = false;
ctrl.clearContact = function() {
$route.updateParams({
gid: $routeParams.gid,
uid: undefined
});
ctrl.show = false;
ctrl.contact = undefined;
};
ctrl.uid = $routeParams.uid;
ctrl.t = {
@ -35,11 +46,16 @@ angular.module('contactsApp')
ctrl.changeContact = function(uid) {
if (typeof uid === 'undefined') {
ctrl.show = false;
$('#app-navigation-toggle').removeClass('showdetails');
return;
}
ContactService.getById(uid).then(function(contact) {
ctrl.contact = contact;
ctrl.photo = ctrl.contact.photo();
ctrl.show = true;
$('#app-navigation-toggle').addClass('showdetails');
ctrl.addressBook = _.find(ctrl.addressBooks, function(book) {
return book.displayName === ctrl.contact.addressBookId;
});

View File

@ -6,6 +6,7 @@ angular.module('contactsApp')
ctrl.contactList = [];
ctrl.searchTerm = '';
ctrl.show = true;
ctrl.t = {
addContact : t('contacts', 'Add contact'),
@ -69,7 +70,8 @@ angular.module('contactsApp')
ContactService.getAll().then(function(contacts) {
$scope.$apply(function() {
ctrl.contacts = contacts;
if (!_.isEmpty(ctrl.contacts)) {
// If desktop version, load first contact (see css for min-width media query)
if (!_.isEmpty(ctrl.contacts) && $(window).width() > 768) {
ctrl.setSelectedId(_.sortBy(contacts, function(contact) {
return contact.fullName();
})[0].uid());
@ -78,7 +80,13 @@ angular.module('contactsApp')
});
});
$scope.$watch('ctrl.routeParams.uid', function(newValue) {
$scope.$watch('ctrl.routeParams.uid', function(newValue, oldValue) {
// Used for mobile view to clear the url
if(typeof oldValue != 'undefined' && typeof newValue == 'undefined') {
// no contact selected
ctrl.show = true;
return;
}
if(newValue === undefined) {
// we might have to wait until ng-repeat filled the contactList
if(ctrl.contactList && ctrl.contactList.length > 0) {
@ -98,6 +106,9 @@ angular.module('contactsApp')
unbindWatch(); // unbind as we only want one update
});
}
} else {
// displaying contact details
ctrl.show = false;
}
});
@ -106,7 +117,7 @@ angular.module('contactsApp')
ctrl.contactList = [];
// watch for next contactList update
var unbindWatch = $scope.$watch('ctrl.contactList', function() {
if(ctrl.contactList && ctrl.contactList.length > 0) {
if(ctrl.contactList && ctrl.contactList.length > 0 && $(window).width() > 768) {
$route.updateParams({
gid: $routeParams.gid,
uid: ctrl.contactList[0].uid()

View File

@ -1,9 +1,7 @@
<a class="app-content-list-item-link" ng-click="ctrl.openContact()">
<img class="app-content-list-item-icon contact__icon" ng-show="ctrl.contact.photo()!==undefined" data-ng-src="data:image/png;base64,{{ctrl.contact.photo()}}" />
<div class="app-content-list-item-icon contact__icon" ng-show="ctrl.contact.photo()===undefined" ng-style="{'background-color': (ctrl.contact.uid() | contactColor) }">{{ ctrl.contact.fullName() | firstCharacter }}</div>
<div class="app-content-list-item-star icon-star" data-starred="false"></div>
<div class="app-content-list-item-line-one" ng-class="{'no-line-two':!ctrl.contact.email()}">{{ctrl.contact.fullName()}}</div>
<div class="app-content-list-item-line-two">{{ctrl.contact.email()}}</div>
<img class="app-content-list-item-icon contact__icon" ng-show="ctrl.contact.photo()!==undefined" data-ng-src="data:image/png;base64,{{ctrl.contact.photo()}}" />
<div class="app-content-list-item-icon contact__icon" ng-show="ctrl.contact.photo()===undefined" ng-style="{'background-color': (ctrl.contact.uid() | contactColor) }">{{ ctrl.contact.fullName() | firstCharacter }}</div>
<div class="app-content-list-item-star icon-star" data-starred="false"></div>
<div class="app-content-list-item-line-one" ng-class="{'no-line-two':!ctrl.contact.email()}">{{ctrl.contact.fullName()}}</div>
<div class="app-content-list-item-line-two">{{ctrl.contact.email()}}</div>
</a>

View File

@ -1,35 +1,38 @@
<div ng-if="ctrl.contact===undefined && !ctrl.loading">
<div id="emptycontent" class="">
<div class="icon-contacts-dark"></div>
<h2>{{ctrl.t.noContacts}}</h2>
<div class="contact-details-wrapper wrapper-show" ng-class="{'mobile-show':ctrl.show}">
<div id="app-navigation-toggle-back" class="details-back icon-download" ng-click="ctrl.clearContact()"></div>
<div ng-if="ctrl.contact===undefined && !ctrl.loading">
<div id="emptycontent" class="">
<div class="icon-contacts-dark"></div>
<h2>{{ctrl.t.noContacts}}</h2>
</div>
</div>
<div ng-if="ctrl.contact!==undefined">
<header class="contactdetails__header" ng-style="{'background-color': (ctrl.contact.uid() | contactColor)}">
<img ng-if="ctrl.photo!==undefined" class="contactdetails__logo avatar" data-ng-src="data:image/png;base64,{{ctrl.photo}}" />
<h2>
<input type="text" id="details-fullName" class="contactdetails__name" placeholder="{{ctrl.t.placeholderName}}" autocomplete="off"
name="email" ng-model="ctrl.contact.fullName" ng-model-options="{ getterSetter: true, debounce: 500 }" ng-change="ctrl.updateContact()" value="" />
</h2>
<div>
<input type="text" id="details-org" class="contactdetails__org" placeholder="{{ctrl.t.placeholderOrg}}" autocomplete="off"
name="email" ng-model="ctrl.contact.org" ng-model-options="{ getterSetter: true, debounce: 500 }" ng-change="ctrl.updateContact()" value="" />
<input type="text" id="details-title" class="contactdetails__title" placeholder="{{ctrl.t.placeholderTitle}}" autocomplete="off"
name="email" ng-model="ctrl.contact.title" ng-model-options="{ getterSetter: true, debounce: 500 }" ng-change="ctrl.updateContact()" value="" />
</div>
<button ng-click="ctrl.deleteContact()" class="icon-delete-white" title="Delete"></button>
</header>
<section>
<div ng-repeat="prop in ctrl.contact.props | toArray | orderDetailItems:'$key'">
<detailsItem ng-repeat="propData in prop" name="prop.$key" data="propData" model="ctrl" index="$index" ng-class="[ 'details-item-' + prop.$key ]"></detailsItem>
</div>
<div class="select-addressbook" ng-if="ctrl.addressBooks.length > 1">
<select ng-model="ctrl.addressBook" ng-change="ctrl.changeAddressBook(ctrl.addressBook)" ng-options="book.displayName for book in ctrl.addressBooks">
</select>
</div>
<select class="add-field" ng-model="ctrl.field" ng-change="ctrl.addField(ctrl.field)">
<option value=''>{{ctrl.t.selectField}}</option>
<option ng-repeat="field in ctrl.fieldDefinitions | fieldFilter: ctrl.contact | orderBy : 'name'" value="{{field.id}}">{{field.name}}</option>
</select>
</section>
</div>
</div>
<div ng-if="ctrl.contact!==undefined">
<header class="contactdetails__header" ng-style="{'background-color': (ctrl.contact.uid() | contactColor)}">
<img ng-if="ctrl.photo!==undefined" class="contactdetails__logo avatar" data-ng-src="data:image/png;base64,{{ctrl.photo}}" />
<h2>
<input type="text" id="details-fullName" class="contactdetails__name" placeholder="{{ctrl.t.placeholderName}}" autocomplete="off"
name="email" ng-model="ctrl.contact.fullName" ng-model-options="{ getterSetter: true, debounce: 500 }" ng-change="ctrl.updateContact()" value="" />
</h2>
<div>
<input type="text" id="details-org" class="contactdetails__org" placeholder="{{ctrl.t.placeholderOrg}}" autocomplete="off"
name="email" ng-model="ctrl.contact.org" ng-model-options="{ getterSetter: true, debounce: 500 }" ng-change="ctrl.updateContact()" value="" />
<input type="text" id="details-title" class="contactdetails__title" placeholder="{{ctrl.t.placeholderTitle}}" autocomplete="off"
name="email" ng-model="ctrl.contact.title" ng-model-options="{ getterSetter: true, debounce: 500 }" ng-change="ctrl.updateContact()" value="" />
</div>
<button ng-click="ctrl.deleteContact()" class="icon-delete-white" title="Delete"></button>
</header>
<section>
<div ng-repeat="prop in ctrl.contact.props | toArray | orderDetailItems:'$key'" ng-class="prop.length > 1 ? '' : 'last-details'">
<detailsItem ng-repeat="propData in prop" name="prop.$key" data="propData" model="ctrl" index="$index" ng-class="[ 'details-item-' + prop.$key ]"></detailsItem>
</div>
<div class="select-addressbook" ng-if="ctrl.addressBooks.length > 1">
<select ng-model="ctrl.addressBook" ng-change="ctrl.changeAddressBook(ctrl.addressBook)" ng-options="book.displayName for book in ctrl.addressBooks">
</select>
</div>
<select class="add-field" ng-model="ctrl.field" ng-change="ctrl.addField(ctrl.field)">
<option value=''>{{ctrl.t.selectField}}</option>
<option ng-repeat="field in ctrl.fieldDefinitions | fieldFilter: ctrl.contact | orderBy : 'name'" value="{{field.id}}">{{field.name}}</option>
</select>
</section>
</div>

View File

@ -1,4 +1,4 @@
<div style="height: 90%" ng-class="{loading: ctrl.loading}">
<div style="height: 90%" class="contacts-list" ng-class="{loading: ctrl.loading, 'mobile-show': ctrl.show}">
<button ng-show="!ctrl.loading" class="app-content-list-button" type="button" name="button" ng-click="ctrl.createContact()">{{ctrl.t.addContact}}</button>
<div class="app-content-list-item"
ng-repeat="contact in ctrl.contactList = (ctrl.contacts | contactGroupFilter:ctrl.routeParams.gid | orderBy:'fullName()' | filter:query) track by contact.uid()"