diff --git a/app/css/app.css b/app/css/app.css index 2df1de0b..7f3172ae 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -226,6 +226,7 @@ fieldset[disabled] .btn-tg.active { } + .img_fullsize_with_progress_wrap { position: relative; /*margin: 0 auto 20px;*/ @@ -286,7 +287,8 @@ fieldset[disabled] .btn-tg.active { .im_page_wrap { background: #FFF; - max-width: 1000px; + /*max-width: 1000px;*/ + width: 1000px; margin: 0 auto; -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1); @@ -328,11 +330,13 @@ fieldset[disabled] .btn-tg.active { /* Dialogs list */ - +.im_dialogs_col { + width: 315px; +} .im_dialogs_col .nano > .pane { background : rgba(0,0,0,.0); width : 12px; - right: -7px; + right: 0px; -webkit-transition : .2s; -moz-transition : .2s; -o-transition : .2s; @@ -372,15 +376,10 @@ fieldset[disabled] .btn-tg.active { .im_dialogs_wrap { - overflow: visible; - overflow-x: visible !important; - overflow-y: visible; } .im_dialogs_scrollable_wrap { - /*border-right: 2px solid #E9EBED;*/ - padding: 0 12px; + padding: 0 19px 0 12px; outline: none ! important; - overflow-x: visible; } .im_dialogs_scrollable_wrap .nav-stacked > li + li { margin-top: 0; @@ -538,6 +537,9 @@ a.im_dialog:hover .im_dialog_message_text { -moz-box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.12); box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.12); margin-right: 15px; + + position: relative; + z-index: 2; } .im_history_panel { padding: 10px 4px 0; @@ -1104,7 +1106,6 @@ img.img_fullsize { .chat_modal_window .modal-dialog { max-width: 506px; } - .chat_modal_wrap .modal-body { padding: 23px 25px 15px; } @@ -1253,4 +1254,32 @@ img.img_fullsize { .error_modal_description { text-align: center; padding: 40px 20px; +} + + + +.photo_modal_error { + color: #999; + position: absolute; + width: 100%; + top: 50%; + margin-top: -20px; + padding: 0 20px; + text-align: center; + font-size: 1.4em; + line-height: 160%; +} + +.video_full_error { + border-radius: 10px; + overflow: hidden; + background: rgba(0,0,0,0.6); + color: #FFF; + position: absolute; + top: 50%; + margin: -40px 10px 0; + padding: 10px 10px; + text-align: center; + font-size: 1.4em; + line-height: 160%; } \ No newline at end of file diff --git a/app/favicon_unread.ico b/app/favicon_unread.ico new file mode 100644 index 00000000..c5b64a42 Binary files /dev/null and b/app/favicon_unread.ico differ diff --git a/app/img/placeholders/PhotoThumbConversation.gif b/app/img/placeholders/PhotoThumbConversation.gif new file mode 100644 index 00000000..75b945d2 Binary files /dev/null and b/app/img/placeholders/PhotoThumbConversation.gif differ diff --git a/app/img/placeholders/PhotoThumbModal.gif b/app/img/placeholders/PhotoThumbModal.gif new file mode 100644 index 00000000..75b945d2 Binary files /dev/null and b/app/img/placeholders/PhotoThumbModal.gif differ diff --git a/app/img/placeholders/VideoThumbConversation.gif b/app/img/placeholders/VideoThumbConversation.gif new file mode 100644 index 00000000..75b945d2 Binary files /dev/null and b/app/img/placeholders/VideoThumbConversation.gif differ diff --git a/app/img/placeholders/VideoThumbModal.gif b/app/img/placeholders/VideoThumbModal.gif new file mode 100644 index 00000000..75b945d2 Binary files /dev/null and b/app/img/placeholders/VideoThumbModal.gif differ diff --git a/app/index.html b/app/index.html index 23625b3d..e60b8b7e 100644 --- a/app/index.html +++ b/app/index.html @@ -7,7 +7,7 @@ - + @@ -33,14 +33,14 @@ - + - - - - - + + + + + diff --git a/app/js/app.js b/app/js/app.js index af77b933..319c0af6 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -25,7 +25,7 @@ angular.module('myApp', [ config(['$locationProvider', '$routeProvider', '$compileProvider', function($locationProvider, $routeProvider, $compileProvider) { var icons = {}, reverseIcons = {}, i, j, hex, name, dataItem, - ranges = [[0x1f600, 0x1f637], [0x270a, 0x270c], [0x1f446, 0x1f450]]; + ranges = [[0x1f600, 0x1f637], [0x261d, 0x263f], [0x270a, 0x270c], [0x1f446, 0x1f450]]; for (j in ranges) { for (i = ranges[j][0]; i <= ranges[j][1]; i++) { diff --git a/app/js/controllers.js b/app/js/controllers.js index 8810b7d5..cb48e56a 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -121,7 +121,8 @@ angular.module('myApp.controllers', []) $scope.isLoggedIn = true; $scope.logOut = function () { MtpApiManager.logOut().then(function () { - location.href = 'login'; + location.hash = '/login'; + location.reload(); }); } @@ -487,8 +488,17 @@ angular.module('myApp.controllers', []) }; }) - .controller('ChatModalController', function ($scope, AppUsersManager, AppChatsManager, fullChat) { - $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, fullChat); + .controller('ChatModalController', function ($scope, $timeout, AppUsersManager, AppChatsManager, MtpApiManager) { + $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, {}); + + MtpApiManager.invokeApi('messages.getFullChat', { + chat_id: $scope.chatID + }).then(function (result) { + AppChatsManager.saveApiChats(result.chats); + AppUsersManager.saveApiUsers(result.users); + + $scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, result.full_chat); + }); }) diff --git a/app/js/directives.js b/app/js/directives.js index 144b3ac1..73cda09b 100644 --- a/app/js/directives.js +++ b/app/js/directives.js @@ -16,7 +16,7 @@ angular.module('myApp.directives', ['myApp.filters']) restrict: 'AE', scope: true, translude: false, - templateUrl: 'partials/dialog.html?1' + templateUrl: 'partials/dialog.html?2' }; }) @@ -25,7 +25,7 @@ angular.module('myApp.directives', ['myApp.filters']) restrict: 'AE', scope: true, translude: false, - templateUrl: 'partials/message.html?2' + templateUrl: 'partials/message.html?3' }; }) @@ -399,6 +399,10 @@ angular.module('myApp.directives', ['myApp.filters']) \ \ \ +
\ +
\ +
{{error.text}}
\ +
\ ', scope: { fullPhoto: '=', @@ -411,8 +415,9 @@ angular.module('myApp.directives', ['myApp.filters']) fullLoaded = false; + imgElement.attr('src', scope.fullPhoto.placeholder || 'img/blank.gif'); + if (!scope.fullPhoto.location) { - imgElement.attr('src', scope.fullPhoto.placeholder || ''); return; } @@ -450,10 +455,12 @@ angular.module('myApp.directives', ['myApp.filters']) }, function (e) { dLog('Download image failed', e, scope.fullPhoto.location); scope.progress.enabled = false; - imgElement - .attr('src', scope.fullPhoto.placeholder || '') - .removeClass('thumb_blurred'); + if (e && e.type == 'FS_BROWSER_UNSUPPORTED') { + scope.error = {html: 'Your browser doesn\'t support LocalFileSystem feature which is needed to display this image.
Please, install Google Chrome or use mobile app instead.'}; + } else { + scope.error = {text: 'Download failed', error: e}; + } }, function (progress) { scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); }); @@ -487,6 +494,10 @@ angular.module('myApp.directives', ['myApp.filters']) \ \ \ +
\ +
\ +
{{error.text}}
\ +
\ ', scope: { video: '=' @@ -519,9 +530,16 @@ angular.module('myApp.directives', ['myApp.filters']) scope.player.quicktime = hasQt; scope.player.src = $sce.trustAsResourceUrl(url); }, function (e) { - dLog('Download image failed', e, scope.fullPhoto.location); + dLog('Download video failed', e, scope.video); scope.progress.enabled = false; scope.player.src = ''; + + if (e && e.type == 'FS_BROWSER_UNSUPPORTED') { + scope.error = {html: 'Your browser doesn\'t support LocalFileSystem feature which is needed to play this video.
Please, install Google Chrome or use mobile app instead.'}; + } else { + scope.error = {text: 'Video download failed', error: e}; + } + }, function (progress) { scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); }); diff --git a/app/js/filters.js b/app/js/filters.js index af444a35..37611f01 100644 --- a/app/js/filters.js +++ b/app/js/filters.js @@ -52,7 +52,14 @@ angular.module('myApp.filters', []) }]) .filter('dateOrTime', ['$filter', function($filter) { + var cachedDates = {}; + return function (timestamp) { + + if (cachedDates[timestamp]) { + return cachedDates[timestamp]; + } + var ticks = timestamp * 1000, diff = Math.abs(+new Date() - ticks), format = 'HH:mm'; @@ -63,7 +70,7 @@ angular.module('myApp.filters', []) else if (diff > 43200000) { // 12 hours format = 'EEE'; } - return $filter('date')(ticks, format); + return cachedDates[timestamp] = $filter('date')(ticks, format); } }]) diff --git a/app/js/lib/mtproto.js b/app/js/lib/mtproto.js index 75315d8b..22d192d7 100644 --- a/app/js/lib/mtproto.js +++ b/app/js/lib/mtproto.js @@ -61,6 +61,40 @@ function bytesFromHex (hexString) { return bytes; } +function bytesToBase64 (bytes) { + var mod3, result = ''; + + for (var nLen = bytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + mod3 = nIdx % 3; + nUint24 |= bytes[nIdx] << (16 >>> mod3 & 24); + if (mod3 === 2 || nLen - nIdx === 1) { + result += String.fromCharCode( + uint6ToBase64(nUint24 >>> 18 & 63), + uint6ToBase64(nUint24 >>> 12 & 63), + uint6ToBase64(nUint24 >>> 6 & 63), + uint6ToBase64(nUint24 & 63) + ); + nUint24 = 0; + } + } + + return result.replace(/A(?=A$|$)/g, '='); +} + +function uint6ToBase64 (nUint6) { + return nUint6 < 26 + ? nUint6 + 65 + : nUint6 < 52 + ? nUint6 + 71 + : nUint6 < 62 + ? nUint6 - 4 + : nUint6 === 62 + ? 43 + : nUint6 === 63 + ? 47 + : 65; +} + function bytesCmp (bytes1, bytes2) { var len = bytes1.length; if (len != bytes2.length) { @@ -120,14 +154,7 @@ function bytesFromBigInt (bigInt, len) { } function bytesToArrayBuffer (b) { - var len = b.length, - array = new Uint8Array(len); - - for (var i = 0; i < len; ++i) { - array[i] = b[i]; - } - - return array.buffer; + return (new Uint8Array(b)).buffer; } function bytesFromArrayBuffer (buffer) { @@ -1482,14 +1509,21 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato this.serverSalt = serverSalt; + // if (1 == dcID) { + // var self = this; + // (function () { + // dLog('update server salt'); + // self.serverSalt = [0,0,0,0,0,0,0,0]; + // setTimeout(arguments.callee, nextRandomInt(2000, 12345)); + // })(); + // } + this.sessionID = new Array(8); MtpSecureRandom.nextBytes(this.sessionID); - if (true) { - this.sessionID[0] = 0xA; - this.sessionID[1] = 0xB; - this.sessionID[3] = 0xC; - this.sessionID[4] = 0xD; + if (false) { + this.sessionID[0] = 0xAB; + this.sessionID[1] = 0xCD; } this.seqNo = 0; @@ -1594,7 +1628,14 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato if (this.longPollPending && (new Date().getTime()) < this.longPollPending) { return false; } - this.sendLongPoll(); + var self = this; + AppConfigManager.get('dc').then(function (baseDcID) { + if (baseDcID != self.dcID && self.cleanupSent()) { + // console.warn('send long-poll for guest DC is delayed', self.dcID); + return; + } + self.sendLongPoll(); + }); }; MtpNetworker.prototype.sendLongPoll = function() { @@ -1741,22 +1782,24 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato } this.pendingAcks = []; - var self = this; + var self = this; this.sendEncryptedRequest(message).then(function (result) { - self.parseResponse(result.data); - - if (noResponseMsgs.length) { - $timeout(function () { - angular.forEach(noResponseMsgs, function (msgID) { - if (self.sentMessages[msgID]) { - var deferred = self.sentMessages[msgID].deferred; - delete self.sentMessages[msgID]; - deferred.resolve(); - } - }); - }, 200); - } + self.parseResponse(result.data).then(function (response) { + // dLog('Server response', self.dcID, response); + + self.processMessage(response.response, response.messageID, response.sessionID); + + angular.forEach(noResponseMsgs, function (msgID) { + if (self.sentMessages[msgID]) { + var deferred = self.sentMessages[msgID].deferred; + delete self.sentMessages[msgID]; + deferred.resolve(); + } + }); + + self.cleanupSent(); + }); }); }; @@ -1833,7 +1876,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato var dataLength = responseBuffer.byteLength - deserializer.getOffset(); var encryptedData = deserializer.fetchRawBytes(dataLength, 'encrypted_data'); - this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) { + return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) { var buffer = bytesToArrayBuffer(dataWithPadding); var deserializer = new TLDeserialization(buffer, {mtproto: true}); @@ -1848,7 +1891,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato var offset = deserializer.getOffset(); - MtpSha1Service.hash(dataWithPadding.slice(0, offset)).then(function (dataHashed) { + return MtpSha1Service.hash(dataWithPadding.slice(0, offset)).then(function (dataHashed) { if (!bytesCmp(msgKey, dataHashed.slice(-16))) { throw new Error('server msgKey mismatch'); } @@ -1858,9 +1901,12 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato var response = deserializer.fetchObject('', 'INPUT'); - // dLog('Server response', response); - - self.processResponse(response, messageID, sessionID, seqNo); + return { + response: response, + messageID: messageID, + sessionID: sessionID, + seqNo: seqNo + }; }); }); }; @@ -1908,10 +1954,35 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato this.sheduleRequest(100); }; - MtpNetworker.prototype.processResponse = function (response, messageID, sessionID, seqNo) { - return this.processMessage(response, messageID, sessionID); + MtpNetworker.prototype.cleanupSent = function () { + var self = this; + var notEmpty = false; + // dLog('clean start', this.dcID/*, this.sentMessages*/); + angular.forEach(this.sentMessages, function(message, msgID) { + // dLog('clean iter', msgID, message); + if (message.notContentRelated && self.pendingMessages[msgID] === undefined) { + // dLog('clean notContentRelated', msgID); + delete self.sentMessages[msgID]; + } + else if (message.container) { + for (var i = 0; i < message.inner.length; i++) { + if (self.sentMessages[message.inner[i]] !== undefined) { + // dLog('clean failed, found', msgID, message.inner[i], self.sentMessages[message.inner[i]].seq_no); + notEmpty = true; + return; + } + } + // dLog('clean container', msgID); + delete self.sentMessages[msgID]; + } else { + notEmpty = true; + } + }); + + return !notEmpty; }; + MtpNetworker.prototype.processMessageAck = function (messageID) { var sentMessage = this.sentMessages[messageID]; if (sentMessage && !sentMessage.acked) { @@ -1948,6 +2019,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato break; case 'bad_server_salt': + dLog('bad server salt', message); var sentMsg = this.sentMessages[message.bad_msg_id]; if (!sentMsg || sentMsg.seq_no != message.bad_msg_seqno) { dLog(message.bad_msg_id, message.bad_msg_seqno); @@ -2005,7 +2077,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato } } else { if (deferred) { - dLog('rpc response', message.result); + dLog('rpc response', message.result._); sentMessage.deferred.resolve(message.result); } if (sentMessage.isAPI) { @@ -2077,14 +2149,13 @@ factory('MtpApiManager', function (AppConfigManager, MtpAuthorizer, MtpNetworker return $q.when(cachedNetworkers[dcID]); } - var deferred = $q.defer(), - ak = 'dc' + dcID + '_auth_key', + var akk = 'dc' + dcID + '_auth_key', ssk = 'dc' + dcID + '_server_salt'; - AppConfigManager.get(ak, ssk).then(function (result) { + return AppConfigManager.get(akk, ssk).then(function (result) { if (cachedNetworkers[dcID] !== undefined) { - return deferred.resolve(cachedNetworkers[dcID]); + return cachedNetworkers[dcID]; } var authKeyHex = result[0], @@ -2094,26 +2165,21 @@ factory('MtpApiManager', function (AppConfigManager, MtpAuthorizer, MtpNetworker var authKey = bytesFromHex(authKeyHex); var serverSalt = bytesFromHex(serverSaltHex); - return deferred.resolve(cachedNetworkers[dcID] = MtpNetworkerFactory.getNetworker(dcID, authKey, serverSalt)); + return cachedNetworkers[dcID] = MtpNetworkerFactory.getNetworker(dcID, authKey, serverSalt); } - MtpAuthorizer.auth(dcID).then(function (auth) { + return MtpAuthorizer.auth(dcID).then(function (auth) { var storeObj = {}; - storeObj[ak] = bytesToHex(auth.authKey); + storeObj[akk] = bytesToHex(auth.authKey); storeObj[ssk] = bytesToHex(auth.serverSalt); AppConfigManager.set(storeObj); - deferred.resolve( - cachedNetworkers[dcID] = MtpNetworkerFactory.getNetworker(dcID, auth.authKey, auth.serverSalt) - ); + return cachedNetworkers[dcID] = MtpNetworkerFactory.getNetworker(dcID, auth.authKey, auth.serverSalt); }, function (error) { dLog('Get networker error', error, error.stack); - deferred.reject(error); + return error; }); - }); - - return deferred.promise; }; function mtpInvokeApi (method, params, options) { @@ -2189,7 +2255,12 @@ factory('MtpApiManager', function (AppConfigManager, MtpAuthorizer, MtpNetworker }); } + function getBaseDcID () { + return baseDcID || false; + } + return { + getBaseDcID: getBaseDcID, getUserID: mtpGetUserID, invokeApi: mtpInvokeApi, setUserAuth: mtpSetUserAuth, @@ -2205,22 +2276,28 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { var cachedSavePromises = {}; var cachedDownloadPromises = {}; - var downloadPull = []; + var downloadPulls = {}; var downloadActive = 0; var downloadLimit = 5; - function downloadRequest(cb, activeDelta) { + function downloadRequest(dcID, cb, activeDelta) { + if (downloadPulls[dcID] === undefined) { + downloadPulls[dcID] = []; + } + var downloadPull = downloadPulls[dcID]; var deferred = $q.defer(); downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta}); - downloadCheck(); + downloadCheck(dcID); return deferred.promise; }; var index = 0; - function downloadCheck() { - if (downloadActive >= downloadLimit || !downloadPull.length) { + function downloadCheck(dcID) { + var downloadPull = downloadPulls[dcID]; + + if (downloadActive >= downloadLimit || !downloadPull || !downloadPull.length) { return false; } @@ -2233,13 +2310,13 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { data.cb() .then(function (result) { downloadActive -= activeDelta; - downloadCheck(); + downloadCheck(dcID); data.deferred.resolve(result); }, function (error) { downloadActive -= activeDelta; - downloadCheck(); + downloadCheck(dcID); data.deferred.reject(error); }) @@ -2250,10 +2327,14 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { return $q.when(cachedFS); } - var deferred = $q.defer(); - $window.requestFileSystem = $window.requestFileSystem || $window.webkitRequestFileSystem; + if (!$window.requestFileSystem) { + return $q.reject({type: 'FS_BROWSER_UNSUPPORTED', description: 'requestFileSystem not present'}); + } + + var deferred = $q.defer(); + $window.requestFileSystem($window.TEMPORARY, 5*1024*1024, function (fs) { cachedFS = fs; deferred.resolve(); @@ -2368,7 +2449,7 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { }, doDownload = function () { cachedFS.root.getFile(fileName, {create: true}, function(fileEntry) { - var downloadPromise = downloadRequest(function () { + var downloadPromise = downloadRequest(location.dc_id, function () { // dLog('next small promise'); return MtpApiManager.invokeApi('upload.getFile', { location: angular.extend({}, location, {_: 'inputFileLocation'}), @@ -2400,7 +2481,19 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { } }, errorHandler); }, doDownload); - }, errorHandler); + }, function (error) { + + downloadRequest(location.dc_id, function () { + // dLog('next small promise'); + return MtpApiManager.invokeApi('upload.getFile', { + location: angular.extend({}, location, {_: 'inputFileLocation'}), + offset: 0, + limit: 0 + }, {dcID: location.dc_id}); + }).then(function (result) { + deferred.resolve('data:image/jpeg;base64,' + bytesToBase64(result.bytes)) + }, errorHandler); + }); return cachedDownloadPromises[fileName] = deferred.promise; } @@ -2415,6 +2508,7 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { } var deferred = $q.defer(), + cacheFileWriter, errorHandler = function (error) { console.error(error); // dLog('fail'); @@ -2433,7 +2527,7 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { for (var offset = 0; offset < size; offset += limit) { writeFileDeferred = $q.defer(); (function (isFinal, offset, writeFileDeferred, writeFilePromise) { - return downloadRequest(function () { + return downloadRequest(dcID, function () { // dLog('next big promise'); return MtpApiManager.invokeApi('upload.getFile', { location: location, diff --git a/app/js/services.js b/app/js/services.js index 36610521..b69ec370 100644 --- a/app/js/services.js +++ b/app/js/services.js @@ -309,7 +309,7 @@ angular.module('myApp.services', []) var chatFull = angular.copy(fullChat), chat = getChat(id); - if (chatFull.participants._ == 'chatParticipants') { + if (chatFull.participants && chatFull.participants._ == 'chatParticipants') { angular.forEach(chatFull.participants.participants, function(participant){ participant.user = AppUsersManager.getUser(participant.user_id); participant.userPhoto = AppUsersManager.getUserPhoto(participant.user_id, 'User'); @@ -335,21 +335,10 @@ angular.module('myApp.services', []) scope.chatID = chatID; var modalInstance = $modal.open({ - templateUrl: 'partials/chat_modal.html?1', + templateUrl: 'partials/chat_modal.html?2', controller: 'ChatModalController', windowClass: 'chat_modal_window', - scope: scope, - resolve: { - fullChat: function () { - return MtpApiManager.invokeApi('messages.getFullChat', { - chat_id: chatID - }).then(function (result) { - saveApiChats(result.chats); - AppUsersManager.saveApiUsers(result.users); - return result.full_chat; - }) - } - } + scope: scope }); } @@ -1172,7 +1161,7 @@ angular.module('myApp.services', []) height = 100, thumbPhotoSize = choosePhotoSize(photo, width, height), thumb = { - placeholder: 'img/placeholders/PhotoThumbConversation.jpg', + placeholder: 'img/placeholders/PhotoThumbConversation.gif', width: width, height: height }; @@ -1200,7 +1189,7 @@ angular.module('myApp.services', []) fullHeight = $($window).height() - 150, fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight), full = { - placeholder: 'img/placeholders/PhotoThumbModal.jpg', + placeholder: 'img/placeholders/PhotoThumbModal.gif', width: fullWidth, height: fullHeight }; @@ -1272,7 +1261,7 @@ angular.module('myApp.services', []) height = 100, thumbPhotoSize = video.thumb, thumb = { - placeholder: 'img/placeholders/VideoThumbConversation.jpg', + placeholder: 'img/placeholders/VideoThumbConversation.gif', width: width, height: height }; @@ -1299,7 +1288,7 @@ angular.module('myApp.services', []) fullHeight = $($window).height() - 150, fullPhotoSize = video, full = { - placeholder: 'img/placeholders/VideoThumbModal.jpg', + placeholder: 'img/placeholders/VideoThumbModal.gif', width: fullWidth, height: fullHeight, }; @@ -1865,7 +1854,10 @@ angular.module('myApp.services', []) var notificationsShown = []; var notificationsCount = 0; var peerSettings = {}; + var faviconEl = $('link[rel="icon"]'); + var titleBackup = document.title, + faviconBackup = faviconEl.attr('href'), titlePromise; $rootScope.$watch('idle.isIDLE', function (newVal) { @@ -1875,6 +1867,7 @@ angular.module('myApp.services', []) if (!newVal) { notificationsCount = 0; document.title = titleBackup; + faviconEl.attr('href', faviconBackup); notificationsClear(); } else { titleBackup = document.title; @@ -1884,8 +1877,10 @@ angular.module('myApp.services', []) // dLog('check title', notificationsCount, time % 2000 > 1000); if (!notificationsCount || time % 2000 > 1000) { document.title = titleBackup; + faviconEl.attr('href', faviconBackup); } else { document.title = notificationsCount + ' notifications'; + faviconEl.attr('href', 'favicon_unread.ico'); } }, 1000); } diff --git a/app/partials/chat_modal.html b/app/partials/chat_modal.html index c78ea381..d5460a71 100644 --- a/app/partials/chat_modal.html +++ b/app/partials/chat_modal.html @@ -8,13 +8,13 @@