diff --git a/app/css/app.css b/app/css/app.css index 1c522484..d0914199 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -169,6 +169,9 @@ input[type="number"] { .btn-link:hover { background: #f2f6fa; } +.btn-link.dropdown-toggle:hover { + background: none; +} .tg_page_head .navbar { min-height: 44px; @@ -879,6 +882,9 @@ a.tg_radio_on:hover i.icon-radio { color: #808080; margin-bottom: 18px; } +.im_dialogs_import_phonebook { + margin-top: 10px; +} .im_dialogs_col { margin-right: -7px; @@ -2742,7 +2748,6 @@ a:hover .icon-twitter { border: 1px solid #F2F2F2; border-radius: 3px; padding: 6px 20px 6px 30px; - margin-bottom: 0; margin: 0; } .is_1x .contacts_modal_search_field { @@ -3320,9 +3325,47 @@ ce671b orange } .countries_modal_search { - padding: 0 20px 12px; + padding: 0 0 12px; + margin: 0 20px; + position: relative; +} + +.countries_modal_search_field { + font-size: 12px; + line-height: normal; + background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat; + background-size: 42px 710px; + border: 1px solid #F2F2F2; + border-radius: 3px; + padding: 6px 20px 6px 30px; + margin-bottom: 0; margin: 0; } +.is_1x .countries_modal_search_field { + background-image: url(../img/icons/IconsetW_1x.png); +} +.countries_modal_search_field:focus, +.countries_modal_search_field:active { + background-color: #FFF; +} +.countries_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) -15px -192px no-repeat; + background-size: 42px 710px; + opacity: 0.6; +} +.is_1x .countries_modal_search_clear { + background-image: url(../img/icons/IconsetW_1x.png); +} +.countries_modal_search_clear:hover { + opacity: 1; +} .countries_modal_wrap .modal-body { padding: 14px 0; diff --git a/app/index.html b/app/index.html index 4ca7a10d..a1f45dbd 100644 --- a/app/index.html +++ b/app/index.html @@ -27,8 +27,9 @@ - + + @@ -45,7 +46,6 @@ - diff --git a/app/js/controllers.js b/app/js/controllers.js index 65375a62..334beaf2 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -285,7 +285,7 @@ angular.module('myApp.controllers', []) ChangelogNotifyService.checkUpdate(); }) - .controller('AppImDialogsController', function ($scope, $location, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ErrorService) { + .controller('AppImDialogsController', function ($scope, $location, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, PhonebookContactsService, ErrorService) { // console.log('init controller'); @@ -293,6 +293,7 @@ angular.module('myApp.controllers', []) $scope.contacts = []; $scope.search = {}; $scope.contactsLoaded = false; + $scope.phonebookAvailable = PhonebookContactsService.isAvailable(); var offset = 0, maxID = 0, @@ -347,13 +348,21 @@ angular.module('myApp.controllers', []) $scope.$watch('search.query', loadDialogs); $scope.importContact = function () { - AppUsersManager.openImportContact().then(function () { - if (contactsShown) { + AppUsersManager.openImportContact().then(function (foundContact) { + if (contactsShown && foundContact) { loadDialogs(); } }); }; + $scope.importPhonebook = function () { + PhonebookContactsService.openPhonebookImport().result.then(function (foundContacts) { + if (contactsShown && foundContacts.length) { + loadDialogs(); + } + }) + }; + function loadDialogs () { offset = 0; maxID = 0; @@ -1920,11 +1929,13 @@ angular.module('myApp.controllers', []) }; }) - .controller('ImportContactModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager) { + .controller('ImportContactModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, ErrorService, PhonebookContactsService) { if ($scope.importContact === undefined) { $scope.importContact = {}; } + $scope.phonebookAvailable = PhonebookContactsService.isAvailable(); + $scope.doImport = function () { if ($scope.importContact && $scope.importContact.phone) { $scope.progress = {enabled: true}; @@ -1933,6 +1944,11 @@ angular.module('myApp.controllers', []) $scope.importContact.first_name || '', $scope.importContact.last_name || '' ).then(function (foundUserID) { + if (!foundUserID) { + ErrorService.show({ + error: {code: 404, type: 'USER_NOT_USING_TELEGRAM'} + }); + } $modalInstance.close(foundUserID); })['finally'](function () { delete $scope.progress.enabled; @@ -1940,6 +1956,16 @@ angular.module('myApp.controllers', []) } }; + $scope.importPhonebook = function () { + PhonebookContactsService.openPhonebookImport().result.then(function (foundContacts) { + if (foundContacts) { + $modalInstance.close(foundContacts[0]); + } else { + $modalInstance.dismiss(); + } + }) + }; + }) .controller('CountrySelectModalController', function ($scope, $modalInstance, $rootScope, SearchIndexManager) { @@ -1976,3 +2002,108 @@ angular.module('myApp.controllers', []) }); }) + + + .controller('PhonebookModalController', function ($scope, $modalInstance, $rootScope, AppUsersManager, PhonebookContactsService, SearchIndexManager, ErrorService) { + + $scope.search = {}; + $scope.phonebook = []; + $scope.selectedContacts = {}; + $scope.selectedCount = 0; + $scope.slice = {limit: 20, limitDelta: 20}; + $scope.progress = {enabled: false}; + $scope.multiSelect = true; + + var searchIndex = SearchIndexManager.createIndex(), + phonebookReady = false; + + PhonebookContactsService.getPhonebookContacts().then(function (phonebook) { + for (var i = 0; i < phonebook.length; i++) { + SearchIndexManager.indexObject(i, phonebook[i].first_name + ' ' + phonebook[i].last_name + ' ' + phonebook[i].phones.join(' '), searchIndex); + } + $scope.phonebook = phonebook; + $scope.toggleSelection(true); + phonebookReady = true; + updateList(); + }); + + function updateList () { + var filtered = false, + results = {}; + + if (angular.isString($scope.search.query) && $scope.search.query.length) { + filtered = true; + results = SearchIndexManager.search($scope.search.query, searchIndex); + + $scope.contacts = []; + for (var i = 0; i < $scope.phonebook.length; i++) { + if (!filtered || results[i]) { + $scope.contacts.push($scope.phonebook[i]); + } + } + } else { + $scope.contacts = $scope.phonebook; + } + + $scope.slice.limit = 20; + } + + $scope.$watch('search.query', function (newValue) { + if (phonebookReady) { + updateList(); + } + }); + + $scope.contactSelect = function (i) { + if (!$scope.multiSelect) { + return $modalInstance.close($scope.phonebook[i]); + } + if ($scope.selectedContacts[i]) { + delete $scope.selectedContacts[i]; + $scope.selectedCount--; + } else { + $scope.selectedContacts[i] = true; + $scope.selectedCount++; + } + }; + + $scope.toggleSelection = function (fill) { + if (!$scope.selectedCount || fill) { + $scope.selectedCount = $scope.phonebook.length; + for (var i = 0; i < $scope.phonebook.length; i++) { + $scope.selectedContacts[i] = true; + } + } else { + $scope.selectedCount = 0; + $scope.selectedContacts = {}; + } + }; + + $scope.submitSelected = function () { + if ($scope.selectedCount <= 0) { + $modalInstance.dismiss(); + } + + var selectedContacts = []; + angular.forEach($scope.selectedContacts, function (t, i) { + selectedContacts.push($scope.phonebook[i]); + }); + + ErrorService.confirm({ + type: 'CONTACTS_IMPORT_PERFORM' + }).then(function () { + $scope.progress.enabled = true; + AppUsersManager.importContacts(selectedContacts).then(function (foundContacts) { + if (!foundContacts.length) { + ErrorService.show({ + error: {code: 404, type: 'USERS_NOT_USING_TELEGRAM'} + }); + } + $modalInstance.close(foundContacts); + })['finally'](function () { + $scope.progress.enabled = false; + }); + }); + }; + + }) diff --git a/app/js/init.js b/app/js/init.js index a4117158..fc998367 100644 --- a/app/js/init.js +++ b/app/js/init.js @@ -22,7 +22,7 @@ setTimeout(function () {callback(result)}, 10); }; - if (!window.applicationCache || !window.addEventListener) { + if (!window.applicationCache || Config.App.packaged || !window.addEventListener) { return; } diff --git a/app/js/lib/config.js b/app/js/lib/config.js index 1bad9ff1..ab950efb 100644 --- a/app/js/lib/config.js +++ b/app/js/lib/config.js @@ -21,7 +21,8 @@ Config = window.Config || {}; Config.App = { id: 2496, hash: '8da85b0d5bfe62527e5b244c209159c3', - version: '0.1.5' + version: '0.1.5', + packaged: false }; Config.Modes = { diff --git a/app/js/services.js b/app/js/services.js index 7961abe1..4bd584b4 100644 --- a/app/js/services.js +++ b/app/js/services.js @@ -130,7 +130,7 @@ angular.module('myApp.services', []) }; }) -.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager) { +.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService) { var users = {}, cachedPhotoLocations = {}, contactsFillPromise, @@ -307,7 +307,39 @@ angular.module('myApp.services', []) onContactUpdated(foundUserID = importedContact.user_id, true); }); - return foundUserID; + return foundUserID ? 1 : 0; + }); + }; + + function importContacts (contacts) { + var inputContacts = [], + i, j; + + for (i = 0; i < contacts.length; i++) { + for (j = 0; j < contacts[i].phones.length; j++) { + inputContacts.push({ + _: 'inputPhoneContact', + client_id: (i << 16 | j).toString(10), + phone: contacts[i].phones[j], + first_name: contacts[i].first_name, + last_name: contacts[i].last_name + }); + } + } + + return MtpApiManager.invokeApi('contacts.importContacts', { + contacts: inputContacts, + replace: false + }).then(function (importedContactsResult) { + saveApiUsers(importedContactsResult.users); + + var result = []; + angular.forEach(importedContactsResult.imported, function (importedContact) { + onContactUpdated(importedContact.user_id, true); + result.push(importedContact.user_id); + }); + + return result; }); }; @@ -348,9 +380,6 @@ angular.module('myApp.services', []) windowClass: 'import_contact_modal_window' }).result.then(function (foundUserID) { if (!foundUserID) { - ErrorService.show({ - error: {code: 404, type: 'USER_NOT_USING_TELEGRAM'} - }); return $q.reject(); } return foundUserID; @@ -402,6 +431,7 @@ angular.module('myApp.services', []) getUserSearchText: getUserSearchText, hasUser: hasUser, importContact: importContact, + importContacts: importContacts, deleteContacts: deleteContacts, wrapForFull: wrapForFull, openUser: openUser, @@ -409,6 +439,79 @@ angular.module('myApp.services', []) } }) +.service('PhonebookContactsService', function ($q, $modal, $sce) { + + var phonebookContactsPromise; + + return { + isAvailable: isAvailable, + openPhonebookImport: openPhonebookImport, + getPhonebookContacts: getPhonebookContacts + } + + function isAvailable () { + return window.navigator && window.navigator.mozContacts && window.navigator.mozContacts.getAll; + } + + function openPhonebookImport () { + return $modal.open({ + templateUrl: 'partials/phonebook_modal.html', + controller: 'PhonebookModalController', + windowClass: 'phonebook_modal_window' + }); + } + + function getPhonebookContacts () { + if (phonebookContactsPromise) { + return phonebookContactsPromise; + } + + var deferred = $q.defer(), + contacts = [], + request = window.navigator.mozContacts.getAll({}), + count = 0; + + request.onsuccess = function () { + if (this.result) { + var contact = { + id: count, + first_name: (this.result.givenName || []).join(' '), + last_name: (this.result.familyName || []).join(' '), + phones: [] + }; + + for (var i = 0; i < this.result.tel.length; i++) { + contact.phones.push(this.result.tel[i].value); + } + if (this.result.photo) { + contact.photo = URL.createObjectURL(this.result.photo[0]); + } else { + contact.photo = 'img/placeholders/UserAvatar' + ((Math.abs(count) % 8) + 1) + '@2x.png'; + } + contact.photo = $sce.trustAsResourceUrl(contact.photo); + + count++; + contacts.push(contact); + } + + if (!this.result || count >= 1000) { + deferred.resolve(contacts); + return; + } + + this.continue(); + } + + request.onerror = function (e) { + console.log('phonebook error', e, e.type, e.message); + deferred.reject(e); + } + + return phonebookContactsPromise = deferred.promise; + } + +}) + .service('AppChatsManager', function ($rootScope, $modal, MtpApiFileManager, MtpApiManager, AppUsersManager, RichTextProcessor) { var chats = {}, cachedPhotoLocations = {}; diff --git a/app/manifest.webapp b/app/manifest.webapp index 0b7abc4a..8caf8ab0 100644 --- a/app/manifest.webapp +++ b/app/manifest.webapp @@ -2,6 +2,7 @@ "name": "Webogram", "description": "Webogram – UNOFFICIAL Telegram Web App.\nMore info & source code here: https://github.com/zhukov/webogram", "version": "0.1.5", + "type": "privileged", "launch_path": "/index.html", "developer": { "name": "Igor Zhukov", @@ -12,7 +13,11 @@ ], "permissions": { "desktop-notification": { - "description": "To show new message notifications etc" + "description": "Required to show new message notifications" + }, + "contacts": { + "description": "Required to import phonebook contacts", + "access": "readonly" } }, "icons": { diff --git a/app/partials/confirm_modal.html b/app/partials/confirm_modal.html index 1bb62a7b..9adc3d9f 100644 --- a/app/partials/confirm_modal.html +++ b/app/partials/confirm_modal.html @@ -21,6 +21,7 @@ Are you sure to send file(s) from clipboard? Are you sure you want to delete the message? + We will now send selected contacts to Telegram servers in order to find your friends in Telegram.
Get started by adding a contact to chat with
+