Browse Source

Improved append message animation, improved dialog switch speed

master
Igor Zhukov 11 years ago
parent
commit
36eaccf950
  1. 10
      app/css/app.css
  2. 36
      app/js/controllers.js
  3. 54
      app/js/directives.js
  4. 65
      app/js/services.js
  5. 4
      app/partials/im.html
  6. 6
      gulpfile.js

10
app/css/app.css

@ -834,6 +834,16 @@ a.im_dialog:hover .im_dialog_date {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
.im_history_appending {
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
transition: all 0.2s;
}
.im_history { .im_history {
/*padding: 20px 0 0 3px;*/ /*padding: 20px 0 0 3px;*/
padding: 20px 0 0 0; padding: 20px 0 0 0;

36
app/js/controllers.js

@ -223,9 +223,7 @@ angular.module('myApp.controllers', [])
var offset = 0, var offset = 0,
maxID = 0, maxID = 0,
hasMore = false, hasMore = false;
startLimit = 20,
limit = 100;
MtpApiManager.invokeApi('account.updateStatus', {offline: false}); MtpApiManager.invokeApi('account.updateStatus', {offline: false});
$scope.$on('dialogs_need_more', function () { $scope.$on('dialogs_need_more', function () {
@ -278,14 +276,14 @@ angular.module('myApp.controllers', [])
maxID = 0; maxID = 0;
hasMore = false; hasMore = false;
AppMessagesManager.getDialogs($scope.search.query, maxID, startLimit).then(function (dialogsResult) { AppMessagesManager.getDialogs($scope.search.query, maxID).then(function (dialogsResult) {
$scope.dialogs = []; $scope.dialogs = [];
if (dialogsResult.dialogs.length) { if (dialogsResult.dialogs.length) {
offset += startLimit; offset += dialogsResult.dialogs.length;
maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message; maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message;
hasMore = offset < dialogsResult.count; hasMore = dialogsResult.count === null || offset < dialogsResult.count;
angular.forEach(dialogsResult.dialogs, function (dialog) { angular.forEach(dialogsResult.dialogs, function (dialog) {
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count)); $scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count));
@ -295,7 +293,7 @@ angular.module('myApp.controllers', [])
$scope.$broadcast('ui_dialogs_change'); $scope.$broadcast('ui_dialogs_change');
if (!$scope.search.query) { if (!$scope.search.query) {
AppMessagesManager.getDialogs('', maxID, limit); AppMessagesManager.getDialogs('', maxID);
} }
}, function (error) { }, function (error) {
@ -312,10 +310,10 @@ angular.module('myApp.controllers', [])
return; return;
} }
AppMessagesManager.getDialogs($scope.search.query, maxID, limit).then(function (dialogsResult) { AppMessagesManager.getDialogs($scope.search.query, maxID).then(function (dialogsResult) {
offset += limit; offset += dialogsResult.dialogs.length;
maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message; maxID = dialogsResult.dialogs[dialogsResult.dialogs.length - 1].top_message;
hasMore = offset < dialogsResult.count; hasMore = dialogsResult.count === null || offset < dialogsResult.count;
angular.forEach(dialogsResult.dialogs, function (dialog) { angular.forEach(dialogsResult.dialogs, function (dialog) {
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count)); $scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count));
@ -359,8 +357,6 @@ angular.module('myApp.controllers', [])
offset = 0, offset = 0,
hasMore = false, hasMore = false,
maxID = 0, maxID = 0,
startLimit = 20,
limit = 50,
inputMediaFilters = { inputMediaFilters = {
photos: 'inputMessagesFilterPhotos', photos: 'inputMessagesFilterPhotos',
video: 'inputMessagesFilterVideo', video: 'inputMessagesFilterVideo',
@ -421,12 +417,12 @@ angular.module('myApp.controllers', [])
var inputMediaFilter = $scope.mediaType && {_: inputMediaFilters[$scope.mediaType]}, var inputMediaFilter = $scope.mediaType && {_: inputMediaFilters[$scope.mediaType]},
getMessagesPromise = inputMediaFilter getMessagesPromise = inputMediaFilter
? AppMessagesManager.getSearch($scope.curDialog.inputPeer, '', inputMediaFilter, maxID, limit) ? AppMessagesManager.getSearch($scope.curDialog.inputPeer, '', inputMediaFilter, maxID)
: AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit); : AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID);
getMessagesPromise.then(function (historyResult) { getMessagesPromise.then(function (historyResult) {
offset += limit; offset += historyResult.history.length;
hasMore = offset < historyResult.count; hasMore = historyResult.count === null || offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1]; maxID = historyResult.history[historyResult.history.length - 1];
angular.forEach(historyResult.history, function (id) { angular.forEach(historyResult.history, function (id) {
@ -447,7 +443,7 @@ angular.module('myApp.controllers', [])
var curJump = ++jump, var curJump = ++jump,
inputMediaFilter = $scope.mediaType && {_: inputMediaFilters[$scope.mediaType]}, inputMediaFilter = $scope.mediaType && {_: inputMediaFilters[$scope.mediaType]},
getMessagesPromise = inputMediaFilter getMessagesPromise = inputMediaFilter
? AppMessagesManager.getSearch($scope.curDialog.inputPeer, '', inputMediaFilter, maxID, startLimit) ? AppMessagesManager.getSearch($scope.curDialog.inputPeer, '', inputMediaFilter, maxID)
: AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID); : AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID);
$scope.historyEmpty = false; $scope.historyEmpty = false;
@ -458,7 +454,7 @@ angular.module('myApp.controllers', [])
offset += historyResult.history.length; offset += historyResult.history.length;
$scope.historyEmpty = !historyResult.count; $scope.historyEmpty = !historyResult.count;
hasMore = offset < historyResult.count; hasMore = historyResult.count === null || offset < historyResult.count;
maxID = historyResult.history[historyResult.history.length - 1]; maxID = historyResult.history[historyResult.history.length - 1];
updateHistoryPeer(); updateHistoryPeer();
@ -702,7 +698,7 @@ angular.module('myApp.controllers', [])
$timeout(function () { $timeout(function () {
var text = $scope.draftMessage.text; var text = $scope.draftMessage.text;
if (!text.length) { if (!angular.isString(text) || !text.length) {
return false; return false;
} }
@ -982,7 +978,7 @@ angular.module('myApp.controllers', [])
photo: {_: 'inputChatPhotoEmpty'} photo: {_: 'inputChatPhotoEmpty'}
}).then(function (updateResult) { }).then(function (updateResult) {
onStatedMessage(updateResult); onStatedMessage(updateResult);
}).['finally'](function () { })['finally'](function () {
$scope.photo.updating = false; $scope.photo.updating = false;
}); });
}; };

54
app/js/directives.js

@ -149,6 +149,7 @@ angular.module('myApp.directives', ['myApp.filters'])
function link (scope, element, attrs) { function link (scope, element, attrs) {
var historyWrap = $('.im_history_wrap', element)[0], var historyWrap = $('.im_history_wrap', element)[0],
historyMessagesEl = $('.im_history_messages', element)[0],
historyEl = $('.im_history', element)[0], historyEl = $('.im_history', element)[0],
scrollableWrap = $('.im_history_scrollable_wrap', element)[0], scrollableWrap = $('.im_history_scrollable_wrap', element)[0],
scrollable = $('.im_history_scrollable', element)[0], scrollable = $('.im_history_scrollable', element)[0],
@ -173,8 +174,19 @@ angular.module('myApp.directives', ['myApp.filters'])
}, delay || 0); }, delay || 0);
} }
var animated = true, var transform = false,
trs = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'],
i = 0;
for (i = 0; i < trs.length; i++) {
if (trs[i] in historyMessagesEl.style) {
transform = trs[i];
break;
}
}
var animated = transform ? true : false,
curAnimation = false; curAnimation = false;
scope.$on('ui_history_append', function (e, options) { scope.$on('ui_history_append', function (e, options) {
if (!atBottom && !options.my) { if (!atBottom && !options.my) {
return; return;
@ -186,26 +198,29 @@ angular.module('myApp.directives', ['myApp.filters'])
$(scrollableWrap).addClass('im_history_to_bottom'); $(scrollableWrap).addClass('im_history_to_bottom');
} }
var wasH = scrollableWrap.scrollHeight;
onContentLoaded(function () { onContentLoaded(function () {
if (animated) { if (animated) {
curAnimation = true; curAnimation = true;
$(scrollableWrap).stop().animate({ $(historyMessagesEl).removeClass('im_history_appending');
scrollTop: scrollableWrap.scrollHeight - scrollableWrap.clientHeight scrollableWrap.scrollTop = scrollableWrap.scrollHeight;
}, { $(historyMessagesEl).css(transform, 'translate(0px, ' + (scrollableWrap.scrollHeight - wasH) + 'px)');
duration: 200, setTimeout(function () {
always: function () { $(historyMessagesEl).addClass('im_history_appending');
updateScroller(); $(historyMessagesEl).css(transform, 'translate(0px, 0px)');
setTimeout(function () {
curAnimation = false; curAnimation = false;
} $(historyMessagesEl).removeClass('im_history_appending');
}); updateBottomizer();
updateScroller(); }, 300);
}, 0);
} else { } else {
$(scrollableWrap).removeClass('im_history_to_bottom'); $(scrollableWrap).removeClass('im_history_to_bottom');
$(scrollable).css({bottom: ''}); $(scrollable).css({bottom: ''});
scrollableWrap.scrollTop = scrollableWrap.scrollHeight; scrollableWrap.scrollTop = scrollableWrap.scrollHeight;
$(historyWrap).nanoScroller(); updateBottomizer();
} }
$(historyWrap).nanoScroller();
}); });
}); });
@ -253,6 +268,10 @@ angular.module('myApp.directives', ['myApp.filters'])
updateScroller(); updateScroller();
moreNotified = false; moreNotified = false;
$timeout(function () {
$(scrollableWrap).trigger('scroll');
})
}); });
}); });
@ -302,6 +321,9 @@ angular.module('myApp.directives', ['myApp.filters'])
minHeight: historyH - 44 minHeight: historyH - 44
}); });
updateBottomizer();
if (heightOnly == true) return; if (heightOnly == true) return;
if (atBottom) { if (atBottom) {
onContentLoaded(function () { onContentLoaded(function () {
@ -312,6 +334,14 @@ angular.module('myApp.directives', ['myApp.filters'])
updateScroller(100); updateScroller(100);
} }
function updateBottomizer () {
$(historyMessagesEl).css({marginTop: 0});
if (historyMessagesEl.offsetHeight <= scrollableWrap.offsetHeight) {
$(historyMessagesEl).css({marginTop: (scrollableWrap.offsetHeight - historyMessagesEl.offsetHeight - 20 - 44) + 'px'});
}
$(historyWrap).nanoScroller();
}
$($window).on('resize', updateSizes); $($window).on('resize', updateSizes);
updateSizes(); updateSizes();

65
app/js/services.js

@ -690,19 +690,18 @@ angular.module('myApp.services', [])
} }
} }
if (curDialogStorage.count !== null && ( if (curDialogStorage.count !== null && curDialogStorage.dialogs.length == curDialogStorage.count ||
curDialogStorage.dialogs.length >= offset + limit || curDialogStorage.dialogs.length >= offset + (limit || 1)
curDialogStorage.dialogs.length == curDialogStorage.count ) {
)) {
return $q.when({ return $q.when({
count: curDialogStorage.count, count: curDialogStorage.count,
dialogs: curDialogStorage.dialogs.slice(offset, offset + limit) dialogs: curDialogStorage.dialogs.slice(offset, offset + (limit || 20))
}); });
} }
var deferred = $q.defer(); limit = limit || 20;
MtpApiManager.invokeApi('messages.getDialogs', { return MtpApiManager.invokeApi('messages.getDialogs', {
offset: offset, offset: offset,
limit: limit, limit: limit,
max_id: maxID || 0 max_id: maxID || 0
@ -735,17 +734,17 @@ angular.module('myApp.services', [])
top_message: dialog.top_message, top_message: dialog.top_message,
unread_count: dialog.unread_count unread_count: dialog.unread_count
}); });
if (historiesStorage[peerID] === undefined) {
historiesStorage[peerID] = {count: null, history: [dialog.top_message], pending: []}
}
}); });
deferred.resolve({ return {
count: curDialogStorage.count, count: curDialogStorage.count,
dialogs: curDialogStorage.dialogs.slice(offset, offset + limit) dialogs: curDialogStorage.dialogs.slice(offset, offset + limit)
}); };
}, function (error) {
deferred.reject(error);
}); });
return deferred.promise;
} }
function fillHistoryStorage (inputPeer, maxID, fullLimit, historyStorage) { function fillHistoryStorage (inputPeer, maxID, fullLimit, historyStorage) {
@ -807,12 +806,9 @@ angular.module('myApp.services', [])
var foundDialog = getDialogByPeerID(peerID); var foundDialog = getDialogByPeerID(peerID);
if (foundDialog && foundDialog[0] && foundDialog[0].unread_count > 1) { if (foundDialog && foundDialog[0] && foundDialog[0].unread_count > 1) {
unreadLimit = Math.min(1000, foundDialog[0].unread_count); unreadLimit = Math.min(1000, foundDialog[0].unread_count);
limit = Math.max(20, unreadLimit + 2); limit = unreadLimit;
} }
} }
if (!limit) {
limit = 20;
}
if (maxID > 0) { if (maxID > 0) {
for (offset = 0; offset < historyStorage.history.length; offset++) { for (offset = 0; offset < historyStorage.history.length; offset++) {
@ -822,17 +818,22 @@ angular.module('myApp.services', [])
} }
} }
if (historyStorage.count !== null && ( if (historyStorage.count !== null && historyStorage.history.length == historyStorage.count ||
historyStorage.history.length >= offset + limit || historyStorage.history.length >= offset + (limit || 1)
historyStorage.history.length == historyStorage.count ) {
)) {
return $q.when({ return $q.when({
count: historyStorage.count, count: historyStorage.count,
history: resultPending.concat(historyStorage.history.slice(offset, offset + limit)), history: resultPending.concat(historyStorage.history.slice(offset, offset + (limit || 20))),
unreadLimit: unreadLimit unreadLimit: unreadLimit
}); });
} }
if (unreadLimit) {
limit = Math.max(20, unreadLimit + 2);
}
limit = limit || 20;
return fillHistoryStorage(inputPeer, maxID, limit, historyStorage).then(function () { return fillHistoryStorage(inputPeer, maxID, limit, historyStorage).then(function () {
offset = 0; offset = 0;
if (maxID > 0) { if (maxID > 0) {
@ -858,7 +859,7 @@ angular.module('myApp.services', [])
filter: inputFilter || {_: 'inputMessagesFilterEmpty'}, filter: inputFilter || {_: 'inputMessagesFilterEmpty'},
min_date: 0, min_date: 0,
max_date: 0, max_date: 0,
limit: limit, limit: limit || 20,
max_id: maxID || 0 max_id: maxID || 0
}).then(function (searchResult) { }).then(function (searchResult) {
AppUsersManager.saveApiUsers(searchResult.users); AppUsersManager.saveApiUsers(searchResult.users);
@ -922,7 +923,7 @@ angular.module('myApp.services', [])
if (!foundDialog[0] || !foundDialog[0].unread_count) { if (!foundDialog[0] || !foundDialog[0].unread_count) {
if (!historyStorage && !historyStorage.history.length) { if (!historyStorage || !historyStorage.history.length) {
return false; return false;
} }
@ -2098,18 +2099,10 @@ angular.module('myApp.services', [])
return urlPromises[url]; return urlPromises[url];
} }
var deferred = $q.defer(); return urlPromises[url] = $http.get(url, {responseType: 'blob', transformRequest: null})
.then(function (response) {
$http.get(url, {responseType: 'blob', transformRequest: null}) return window.webkitURL.createObjectURL(response.data);
.then( });
function (response) {
deferred.resolve(window.webkitURL.createObjectURL(response.data));
}, function (error) {
deferred.reject(error);
}
);
return urlPromises[url] = deferred.promise;
} }
return { return {

4
app/partials/im.html

@ -88,7 +88,9 @@
<div class="im_history" ng-class="{im_history_selectable: selectActions}"> <div class="im_history" ng-class="{im_history_selectable: selectActions}">
<div class="im_history_empty" ng-show="historyEmpty &amp;&amp; !history.length">No messages to display</div> <div class="im_history_empty" ng-show="historyEmpty &amp;&amp; !history.length">No messages to display</div>
<div class="im_history_message_wrap" my-message ng-repeat="historyMessage in history"></div> <div class="im_history_messages">
<div class="im_history_message_wrap" my-message ng-repeat="historyMessage in history"></div>
</div>
</div> </div>
<div class="im_history_typing_wrap"> <div class="im_history_typing_wrap">

6
gulpfile.js

@ -27,12 +27,18 @@ gulp.task('usemin', ['templates', 'enable-production'], function() {
.pipe(gulp.dest('dist')); .pipe(gulp.dest('dist'));
}); });
// ulimit -n 10240 on OS X
gulp.task('imagemin', function() { gulp.task('imagemin', function() {
return gulp.src(['app/img/**/*', '!app/img/screenshot*', '!app/img/*.wav']) return gulp.src(['app/img/**/*', '!app/img/screenshot*', '!app/img/*.wav'])
.pipe($.imagemin()) .pipe($.imagemin())
.pipe(gulp.dest('dist/img')); .pipe(gulp.dest('dist/img'));
}); });
gulp.task('copy-images', function() {
return gulp.src(['app/img/**/*', '!app/img/screenshot*', '!app/img/*.wav'])
.pipe(gulp.dest('dist/img'));
});
gulp.task('copy', function() { gulp.task('copy', function() {
return es.concat( return es.concat(
gulp.src(['app/favicon.ico', 'app/favicon_unread.ico', 'app/manifest.webapp', 'app/manifest.json', 'app/**/*worker.js']) gulp.src(['app/favicon.ico', 'app/favicon_unread.ico', 'app/manifest.webapp', 'app/manifest.json', 'app/**/*worker.js'])

Loading…
Cancel
Save