Browse Source

Added basic contacts modal, improved read messages handling, improved mobile landscape experience

master
Igor Zhukov 10 years ago
parent
commit
f8ff892ccc
  1. 127
      app/css/app.css
  2. 2
      app/js/background.js
  3. 37
      app/js/controllers.js
  4. 19
      app/js/directives.js
  5. 102
      app/js/services.js
  6. 4
      app/partials/chat_modal.html
  7. 36
      app/partials/contacts_modal.html
  8. 3
      app/partials/head.html
  9. 2
      app/partials/im.html
  10. 4
      app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js

127
app/css/app.css

@ -444,7 +444,6 @@ fieldset[disabled] .btn-tg.active { @@ -444,7 +444,6 @@ fieldset[disabled] .btn-tg.active {
.is_1x .im_dialogs_search_field {
background-image: url(../img/icons/IconsetW_1x.png?2);
}
.im_dialogs_search_field:focus,
.im_dialogs_search_field:active {
background-color: #FFF;
@ -617,8 +616,8 @@ a.im_dialog:hover .im_dialog_date { @@ -617,8 +616,8 @@ a.im_dialog:hover .im_dialog_date {
}
.im_history_col .nano > .pane {
/*background : rgba(0,0,0,.0);*/
background: #E9EBED;
background : rgba(3,36,64,0.08);
/*background: #E9EBED;*/
width : 9px;
top: 10px;
/*margin-top: 10px;*/
@ -632,7 +631,8 @@ a.im_dialog:hover .im_dialog_date { @@ -632,7 +631,8 @@ a.im_dialog:hover .im_dialog_date {
border-radius : 2px;
}
.im_history_col .nano > .pane > .slider {
background: #B3BFC7;
background : rgba(3,46,79,0.22);
/*background: #B3BFC7;*/
margin: 0;
-moz-border-radius : 2px;
-webkit-border-radius : 2px;
@ -1271,7 +1271,7 @@ div.im_panel_own_photo { @@ -1271,7 +1271,7 @@ div.im_panel_own_photo {
margin-top: -7px;
margin-left: 43px;
}
.modal_participant_online {
.status_online {
background: #6DBF69;
border: 1px solid #FFF;
display: block;
@ -1577,12 +1577,10 @@ img.img_fullsize { @@ -1577,12 +1577,10 @@ img.img_fullsize {
max-width: auto;
box-shadow: none;
-webkit-box-shadow: none;
margin: 30px auto 10px;
margin: 30px auto 20px;
}
.im_page_footer {
display: none;
}
.im_panel_own_photo,
.im_panel_peer_photo {
display: none;
@ -1595,6 +1593,40 @@ img.img_fullsize { @@ -1595,6 +1593,40 @@ img.img_fullsize {
}
}
@media (max-height: 480px) {
.navbar {
min-height: 40px;
}
.tg_page_head .navbar > .container .navbar-brand {
padding: 4.5px 15px;
display: block;
/*overflow: hidden;
width: 49px;
height: 36px;*/
}
.navbar-nav > li > a,
.tg_page_head .navbar-quick-nav a {
padding-top: 10px;
padding-bottom: 10px;
}
.navbar-toggle {
margin-top: 2px;
margin-bottom: 2px;
}
.im_page_footer {
display: none;
}
.im_send_panel_wrap {
padding-bottom: 12px;
}
.emoji-wysiwyg-editor {
min-height: 34px;
max-height: 150px;
}
}
@media (max-width: 640px) {
.im_dialog_peer {
white-space: normal;
@ -1726,4 +1758,81 @@ img.img_fullsize { @@ -1726,4 +1758,81 @@ img.img_fullsize {
.settings_user_phone,
.settings_version {
color: #999;
}
/* Contacts modal */
.contacts_modal_window .modal-dialog {
max-width: 506px;
}
.contacts_modal_wrap .modal-body {
padding: 23px 25px 15px;
}
.contacts_modal_search {
padding: 0 0 14px;
position: relative;
}
.contacts_modal_search_field {
font-size: 12px;
line-height: normal;
background: #F2F2F2 url(../img/icons/IconsetW.png?1) -6px -205px no-repeat;
background-size: 42px 430px;
border: 1px solid #F2F2F2;
border-radius: 3px;
padding: 6px 20px 6px 30px;
margin-bottom: 0;
margin: 0;
}
.is_1x .contacts_modal_search_field {
background-image: url(../img/icons/IconsetW_1x.png?2);
}
.contacts_modal_search_field:focus,
.contacts_modal_search_field:active {
background-color: #FFF;
}
.contacts_modal_search_clear {
position: absolute;
right: 9px;
margin-top: -23px;
color: #999;
width: 13px;
height: 13px;
vertical-align: text-top;
background: url(../img/icons/IconsetW.png?1) -15px -192px no-repeat;
background-size: 42px 430px;
opacity: 0.6;
}
.is_1x .contacts_modal_search_clear {
background-image: url(../img/icons/IconsetW_1x.png?2);
}
.contacts_modal_search_clear:hover {
opacity: 1;
}
.contacts_modal_contact_wrap {
padding: 8px 7px;
border-top: 1px solid #F0F0F0;
}
.contacts_modal_contact_wrap:first-child {
border-top: 0;
}
.contacts_modal_contact_name {
display: block;
color: #3C6E97;
font-weight: bold;
margin: 1px 0 2px;
}
.non_osx .contacts_modal_contact_name {
font-size: 12px;
}
.contacts_modal_contact_status {
color: #999;
}
.contacts_modal_contact_photo {
width: 40px;
height: 40px;
margin-right: 10px;
overflow: hidden;
}

2
app/js/background.js

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
chrome.app.runtime.onLaunched.addListener(function(launchData) {
chrome.app.window.create('../index.html', {
bounds: {
width: 1100,
width: 900,
height: 700
},
minWidth: 320,

37
app/js/controllers.js

@ -169,6 +169,15 @@ angular.module('myApp.controllers', []) @@ -169,6 +169,15 @@ angular.module('myApp.controllers', [])
});
}
$scope.openContacts = function () {
$modal.open({
templateUrl: 'partials/contacts_modal.html?3',
controller: 'ContactsModalController',
scope: $rootScope.$new(),
windowClass: 'contacts_modal_window'
});
}
updateCurDialog();
function updateCurDialog() {
@ -307,7 +316,8 @@ angular.module('myApp.controllers', []) @@ -307,7 +316,8 @@ angular.module('myApp.controllers', [])
hasMore = false,
maxID = 0,
startLimit = 20,
limit = 50;
limit = 50,
jump = 0;
function applyDialogSelect (newPeer) {
newPeer = newPeer || $scope.curDialog.peer || '';
@ -356,7 +366,6 @@ angular.module('myApp.controllers', []) @@ -356,7 +366,6 @@ angular.module('myApp.controllers', [])
if (!hasMore || !offset) {
return;
}
// console.trace('load history');
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit).then(function (historyResult) {
offset += limit;
@ -378,7 +387,11 @@ angular.module('myApp.controllers', []) @@ -378,7 +387,11 @@ angular.module('myApp.controllers', [])
offset = 0;
maxID = 0;
var curJump = ++jump;
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, startLimit).then(function (historyResult) {
if (curJump != jump) return;
offset += startLimit;
hasMore = offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1];
@ -781,5 +794,25 @@ angular.module('myApp.controllers', []) @@ -781,5 +794,25 @@ angular.module('myApp.controllers', [])
}
})
.controller('ContactsModalController', function ($scope, AppUsersManager) {
$scope.contacts = [];
$scope.search = [];
$scope.$watch('search.query', function (newValue) {
AppUsersManager.getContacts(newValue).then(function (contactsList) {
$scope.contacts = [];
angular.forEach(contactsList, function(userID) {
var contact = {
userID: userID,
user: AppUsersManager.getUser(userID),
userPhoto: AppUsersManager.getUserPhoto(userID, 'User')
}
$scope.contacts.push(contact);
});
});
})
})

19
app/js/directives.js

@ -37,12 +37,10 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -37,12 +37,10 @@ angular.module('myApp.directives', ['myApp.filters'])
function link (scope, element, attrs) {
// console.log('init directive', element);
var dialogsWrap = $('.im_dialogs_wrap', element)[0],
scrollableWrap = $('.im_dialogs_scrollable_wrap', element)[0],
headWrap = $('.tg_page_head')[0],
footer = $('.im_page_footer')[0],
// dialogsSearch = $('im_dialogs_search', element)[0],
moreNotified = false;
onContentLoaded(function () {
@ -84,8 +82,11 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -84,8 +82,11 @@ angular.module('myApp.directives', ['myApp.filters'])
function updateSizes () {
$(element).css({
height: $($window).height() - footer.offsetHeight - 122
height: $($window).height() - footer.offsetHeight - (headWrap ? headWrap.offsetHeight : 50) - 72
});
if (!headWrap) {
headWrap = $('.tg_page_head')[0];
}
}
$($window).on('resize', updateSizes);
@ -720,3 +721,13 @@ angular.module('myApp.directives', ['myApp.filters']) @@ -720,3 +721,13 @@ angular.module('myApp.directives', ['myApp.filters'])
});
}
})
.directive('myFocused', function(){
return {
link: function(scope, element, attrs) {
setTimeout(function () {
element[0].focus();
}, 100);
}
};
});

102
app/js/services.js

@ -118,8 +118,58 @@ angular.module('myApp.services', []) @@ -118,8 +118,58 @@ angular.module('myApp.services', [])
};
})
.service('AppUsersManager', function ($rootScope, $modal, MtpApiFileManager, MtpApiManager, RichTextProcessor) {
var users = {};
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager) {
var users = {},
contactsFillPromise,
contactsIndex = SearchIndexManager.createIndex();
function fillContacts () {
if (contactsFillPromise) {
return contactsFillPromise;
}
return contactsFillPromise = MtpApiManager.invokeApi('contacts.getContacts', {
hash: ''
}).then(function (result) {
var contactsList = [],
userID, searchText, i;
saveApiUsers(result.users);
for (var i = 0; i < result.contacts.length; i++) {
userID = result.contacts[i].user_id;
contactsList.push(userID);
SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex);
}
return contactsList;
});
}
function getUserSearchText (id) {
var user = users[id];
if (!user) {
return false;
}
return (user.first_name || '') + ' ' + (user.last_name || '') + ' ' + (user.phone || '');
}
function getContacts (query) {
return fillContacts().then(function (contactsList) {
if (angular.isString(query) && query.length) {
var results = SearchIndexManager.search(query, contactsIndex),
filteredContactsList = [];
for (var i = 0; i < contactsList.length; i++) {
if (results[contactsList[i]]) {
filteredContactsList.push(contactsList[i])
}
}
contactsList = filteredContactsList;
}
return contactsList;
});
};
function saveApiUsers (apiUsers) {
angular.forEach(apiUsers, saveApiUser);
@ -137,6 +187,9 @@ angular.module('myApp.services', []) @@ -137,6 +187,9 @@ angular.module('myApp.services', [])
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || 'DELETED';
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || 'DELETED';
}
apiUser.sortName = $.trim((apiUser.last_name || '') + ' ' + apiUser.first_name);
apiUser.sortStatus = apiUser.status && (apiUser.status.expires || apiUser.status.was_online) || 0;
if (users[apiUser.id] === undefined) {
users[apiUser.id] = apiUser;
@ -229,9 +282,11 @@ angular.module('myApp.services', []) @@ -229,9 +282,11 @@ angular.module('myApp.services', [])
// console.log('on apiUpdate', update);
switch (update._) {
case 'updateUserStatus':
var userID = update.user_id;
if (users[userID]) {
users[userID].status = update.status;
var userID = update.user_id,
user = users[userID];
if (user) {
user.status = update.status;
user.sortStatus = update.status && (update.status.expires || update.status.was_online) || 0;
$rootScope.$broadcast('user_update', userID);
}
break;
@ -248,11 +303,13 @@ angular.module('myApp.services', []) @@ -248,11 +303,13 @@ angular.module('myApp.services', [])
return {
getContacts: getContacts,
saveApiUsers: saveApiUsers,
saveApiUser: saveApiUser,
getUser: getUser,
getUserPhoto: getUserPhoto,
getUserString: getUserString,
getUserSearchText: getUserSearchText,
hasUser: hasUser,
wrapForFull: wrapForFull,
openUser: openUser
@ -379,8 +436,7 @@ angular.module('myApp.services', []) @@ -379,8 +436,7 @@ angular.module('myApp.services', [])
getPeerSearchText: function (peerID) {
var text;
if (peerID > 0) {
var user = AppUsersManager.getUser(peerID);
text = (user.first_name || '') + ' ' + (user.last_name || '') + ' ' + (user.phone || '');
text = AppUsersManager.getUserSearchText(peerID);
} else if (peerID < 0) {
var chat = AppChatsManager.getChat(-peerID);
text = chat.title || '';
@ -463,6 +519,7 @@ angular.module('myApp.services', []) @@ -463,6 +519,7 @@ angular.module('myApp.services', [])
}
function search (query, searchIndex) {
console.time('search');
var shortIndexes = searchIndex.shortIndexes,
fullTexts = searchIndex.fullTexts;
@ -499,11 +556,12 @@ angular.module('myApp.services', []) @@ -499,11 +556,12 @@ angular.module('myApp.services', [])
}
}
console.timeEnd('search');
return newFoundObjs;
}
})
.service('AppMessagesManager', function ($q, $rootScope, $filter, $sanitize, $location, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager) {
.service('AppMessagesManager', function ($q, $rootScope, $location, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager) {
var messagesStorage = {};
var messagesForHistory = {};
@ -706,7 +764,7 @@ angular.module('myApp.services', []) @@ -706,7 +764,7 @@ angular.module('myApp.services', [])
}
function readHistory (inputPeer) {
// console.log('start read');
// console.trace('start read');
var peerID = AppPeersManager.getPeerID(inputPeer),
historyStorage = historiesStorage[peerID],
foundDialog = getDialogByPeerID(peerID);
@ -718,7 +776,8 @@ angular.module('myApp.services', []) @@ -718,7 +776,8 @@ angular.module('myApp.services', [])
return false;
}
var wasUnread = false;
var messageID,
message;
// console.log(historyStorage);
for (i = 0; i < historyStorage.history.length; i++) {
messageID = historyStorage.history[i];
@ -727,11 +786,10 @@ angular.module('myApp.services', []) @@ -727,11 +786,10 @@ angular.module('myApp.services', [])
if (message && !message.out) {
if (message.unread) {
// console.log('unread');
wasUnread = true;
} else if (!wasUnread) {
// console.log('bad2');
return false;
break;
}
// console.log('bad2', message);
return false;
}
}
@ -743,6 +801,7 @@ angular.module('myApp.services', []) @@ -743,6 +801,7 @@ angular.module('myApp.services', [])
return processAffectedHistory(inputPeer, affectedHistory, 'messages.readHistory');
}).then(function () {
if (foundDialog[0]) {
// console.log('done read history', peerID);
foundDialog[0].unread_count = 0;
$rootScope.$broadcast('dialog_unread', {peerID: peerID, count: 0});
}
@ -768,7 +827,7 @@ angular.module('myApp.services', []) @@ -768,7 +827,7 @@ angular.module('myApp.services', [])
}
function flushHistory (inputPeer) {
// console.log('start read');
// console.log('start flush');
var peerID = AppPeersManager.getPeerID(inputPeer),
historyStorage = historiesStorage[peerID];
@ -1242,7 +1301,7 @@ angular.module('myApp.services', []) @@ -1242,7 +1301,7 @@ angular.module('myApp.services', [])
}
$rootScope.$on('apiUpdate', function (e, update) {
console.log('on apiUpdate', update);
// console.log('on apiUpdate', update);
switch (update._) {
case 'updateMessageID':
pendingByMessageID[update.id] = update.random_id;
@ -1295,10 +1354,13 @@ angular.module('myApp.services', []) @@ -1295,10 +1354,13 @@ angular.module('myApp.services', [])
dialog = {peerID: peerID, unread_count: 0, top_message: false}
}
if (!message.out && message.unread) {
// console.log('inc unread count', dialog.unread_count);
dialog.unread_count++;
}
dialog.top_message = message.id;
// console.log('new message', message, peerID, historyStorage, foundDialog, dialog);
SearchIndexManager.indexObject(peerID, AppPeersManager.getPeerSearchText(peerID), dialogsIndex);
dialogsStorage.dialogs.unshift(dialog);
@ -1321,8 +1383,8 @@ angular.module('myApp.services', []) @@ -1321,8 +1383,8 @@ angular.module('myApp.services', [])
for (i = 0; i < update.messages.length; i++) {
messageID = update.messages[i];
message = messagesStorage[messageID];
// console.log('read', messageID, message);
if (message) {
// console.log('read', messageID, message.unread, message);
if (message && message.unread) {
message.unread = false;
if (messagesForHistory[messageID]) {
messagesForHistory[messageID].unread = false;
@ -2015,8 +2077,8 @@ angular.module('myApp.services', []) @@ -2015,8 +2077,8 @@ angular.module('myApp.services', [])
}
if (seqStart != curState.seq + 1) {
// console.log('seq hole', seqStart, curState.seq);
if (seqStart != curState.seq) {
if (seqStart > curState.seq) {
console.warn('Seq hole', seqStart, curState.seq);
getDifference();
}
return false;

4
app/partials/chat_modal.html

@ -48,14 +48,14 @@ @@ -48,14 +48,14 @@
<h5 class="chat_modal_members_header">Members</h5>
<div class="chat_modal_members_list">
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants">
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants | orderBy:'-user.sortStatus'">
<a ng-click="openUser(participant.user_id)" class="chat_modal_participant_photo pull-left">
<img
class="chat_modal_participant_photo"
my-load-thumb
thumb="participant.userPhoto"
/>
<i class="icon modal_participant_online" ng-show="participant.user.status._ == 'userStatusOnline'"></i>
<i class="icon status_online" ng-show="participant.user.status._ == 'userStatusOnline'"></i>
</a>
<div class="chat_modal_participant_name">
<a ng-click="openUser(participant.user.id)" ng-bind-html="participant.user.rFullName"></a>

36
app/partials/contacts_modal.html

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
<div class="contacts_modal_wrap">
<div class="modal-header">
<a class="modal-close-link visible-xs" ng-click="$close()">Close</a>
<h4 class="modal-title">Contacts</h4>
</div>
<div class="modal-body">
<div class="contacts_modal_search">
<input class="form-control contacts_modal_search_field" my-focused type="search" placeholder="Search" ng-model="search.query"/>
<a class="contacts_modal_search_clear" ng-click="search.query = ''" ng-show="search.query.length"></a>
</div>
<div class="contacts_modal_members_list">
<div class="contacts_modal_contact_wrap clearfix" ng-repeat="contact in contacts | orderBy:'user.sortName' track by contact.userID">
<a ng-click="openUser(contact.userID)" class="contacts_modal_contact_photo pull-left">
<img
class="contacts_modal_contact_photo"
my-load-thumb
thumb="contact.userPhoto"
/>
<i class="icon status_online" ng-show="contact.user.status._ == 'userStatusOnline'"></i>
</a>
<div class="contacts_modal_contact_name">
<a ng-click="openUser(contact.user.id)" ng-bind-html="contact.user.rFullName"></a>
</div>
<div class="contacts_modal_contact_status">{{contact.user | userStatus}}</div>
</div>
</div>
</div>
</div>

3
app/partials/head.html

@ -21,8 +21,9 @@ @@ -21,8 +21,9 @@
<div class="navbar-collapse" collapse="navbarCollapsed">
<ul class="nav navbar-nav navbar-right">
<li><a href="https://github.com/zhukov/webogram" target="_blank">About</a></li>
<li ng-if="isLoggedIn"><a href="" ng-click="openContacts()">Contacts</a></li>
<li ng-if="isLoggedIn"><a href="" ng-click="openSettings()">Settings</a></li>
<li><a href="https://github.com/zhukov/webogram" target="_blank">About</a></li>
</ul>
</div>
</div>

2
app/partials/im.html

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
<div class="im_dialogs_wrap nano">
<div class="im_dialogs_scrollable_wrap content">
<ul class="nav nav-pills nav-stacked">
<li class="im_dialog_wrap" my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs" ng-class="{active: curDialog.peerID == dialogMessage.peerID}"></li>
<li class="im_dialog_wrap" my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs track by dialogMessage.peerID" ng-class="{active: curDialog.peerID == dialogMessage.peerID}"></li>
</ul>
</div>
</div>

4
app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js vendored

@ -1068,13 +1068,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap @@ -1068,13 +1068,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/modal/backdrop.html",
"<div class=\"modal-backdrop fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1040 + index*10}\"></div>");
"<div class=\"modal-backdrop fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1040 + index*10 + 1}\"></div>");
}]);
angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/modal/window.html",
"<div tabindex=\"-1\" class=\"modal fade {{ windowClass }}\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" +
" <div class=\"modal_close_wrap visible-md\" ng-click=\"close($event)\">\n" +
" <div class=\"modal_close_wrap visible-md visible-lg\" ng-click=\"close($event)\">\n" +
" <div class=\"modal_close\"></div>\n" +
" </div>\n" +
" <div class=\"modal-dialog\"><div class=\"modal-content\" ng-transclude></div></div>\n" +

Loading…
Cancel
Save