diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json index b1b6cc4d1a777c0ff23053e84a83ee75688a9b3c..95c563544db5fc4c60edefb303ff199abe4fb5f6 100644 --- a/www/i18n/locale-en.json +++ b/www/i18n/locale-en.json @@ -94,7 +94,7 @@ "NODE_HELP": "server.domain.com:port", "USE_LOCAL_STORAGE": "Enable local storage", "HISTORY_SETTINGS": "My Account", - "DISPLAY_UD_HISTORY": "Show received dividends ?", + "DISPLAY_UD_HISTORY": "Display produced dividends ?", "AUTHENTICATION_SETTINGS": "Authentication", "REMEMBER_ME": "Remember me", "PLUGINS_SETTINGS": "Extensions", @@ -169,14 +169,14 @@ "SUMMARY": "Received certifications", "LIST": "Détails of received certifications", "RECEIVED": "Received certifications", - "RECEIVED_BY": "Certifications received by {{uid}}", + "RECEIVED_BY": "Certifications received by {{uid}}" }, "GIVEN_CERTIFICATIONS": { "TITLE": "{{uid}} - Certifications sent", "SUMMARY": "Sent certifications", "LIST": "Détails of sent certifications", "SENT": "Sent certifications", - "SENT_BY": "Certifications sent by {{uid}}", + "SENT_BY": "Certifications sent by {{uid}}" } }, "LOGIN": { @@ -194,6 +194,9 @@ "BALANCE": "Balance", "LAST_TX": "Last transactions", "NO_TX": "No transaction", + "SHOW_MORE_TX": "Show more", + "SHOW_ALL_TX": "Show all", + "TX_FROM_DATE": "(current limit to {{fromTime|formatFromNowShort}})", "PENDING_TX": "Pending transactions", "ERROR_TX": "Transaction not executed", "ERROR_TX_SENT": "Sent transactions", diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index 597f3c972259955ad7a5cdbd13ce8db551c72596..70b459a0c18d71d158738ef5bc9174351fa31c83 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -59,7 +59,7 @@ "CURRENCY": "Monnaie", "CURRENCIES": "Monnaies", "ACCOUNT": "Mon compte", - "TRANSFER": "Payer", + "TRANSFER": "Virement", "SCAN": "Scanner", "SETTINGS": "Paramètres" }, @@ -94,7 +94,7 @@ "NODE_HELP": "server.domain.com:port", "USE_LOCAL_STORAGE": "Activer le stockage local", "HISTORY_SETTINGS": "Mon compte", - "DISPLAY_UD_HISTORY": "Voir les dividendes reçus ?", + "DISPLAY_UD_HISTORY": "Afficher les dividendes produits ?", "AUTHENTICATION_SETTINGS": "Authentification", "REMEMBER_ME": "Se souvenir de moi", "PLUGINS_SETTINGS": "Extensions", @@ -194,6 +194,9 @@ "BALANCE": "Solde", "LAST_TX": "Dernières transactions", "NO_TX": "Aucune transaction", + "SHOW_MORE_TX": "Afficher plus", + "SHOW_ALL_TX": "Afficher tout", + "TX_FROM_DATE": "(limite actuelle à {{fromTime|formatFromNowShort}})", "PENDING_TX": "Transactions en cours de traitement", "ERROR_TX": "Transactions non executées", "ERROR_TX_SENT": "Transactions envoyées", @@ -243,7 +246,7 @@ "BTN_SEND": "Envoyer", "BTN_ADD_COMMENT": "Saisir un commentaire ?", "MODAL": { - "TITLE": "Paiement" + "TITLE": "Virement" } }, "ERROR": { @@ -270,7 +273,7 @@ "GET_CURRENCY_PARAMETER": "Echec de la récupération des règles de la monnaie.", "GET_CURRENCIES_FAILED": "Impossible de charger la liste des monnaies. Veuillez ressayer plus tard.", "GET_CURRENCY_FAILED": "Chargement de la monnaie impossible.", - "SEND_TX_FAILED": "Echec du paiement.", + "SEND_TX_FAILED": "Echec du virement.", "ALL_SOURCES_USED": "Veuillez attendre le calcul du prochain bloc (Toutes vos sources de monnaie ont été utilisées).", "NOT_ENOUGH_SOURCES": "Pas assez de change pour envoyer ce montant en une seule transaction.<br/>Montant maximum : {{amount}} {{unit}}<sub>{{subUnit}}</sub>.", "ACCOUNT_CREATION_FAILED": "Echec de la création du compte membre.", diff --git a/www/index.html b/www/index.html index d7238ef14342127940998ebaee68e0fd33089b9d..081c514ab350b36613146250343b9566b059daaa 100644 --- a/www/index.html +++ b/www/index.html @@ -106,6 +106,7 @@ <script src="dist/dist_js/plugins/es/js/services/social-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/user-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/message-services.js"></script> + <script src="dist/dist_js/plugins/es/js/services/modal-services.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/common-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/settings-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/wot-controllers.js"></script> diff --git a/www/js/app.js b/www/js/app.js index 78b96e1022e008bcbd65520ca96655a43cda6ec5..21557e0eaac4a5be50a38203db4d40d06df91302 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -57,6 +57,7 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngAnimate', }; }) + .filter('formatDuration', function() { return function(input) { return input ? moment(moment().utc().valueOf() + parseInt(input)*1000).startOf('minute').fromNow() : ''; @@ -67,7 +68,13 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngAnimate', return function(input) { if (!input) {return null;} var duration = moment(0).startOf('minute').from(moment(parseInt(input)*1000), true); - return duration.split(" ").slice(-1)[0]; // keep only the last word (e.g. remove "un" "a"...) + return duration.split(' ').slice(-1)[0]; // keep only last words (e.g. remove "un" "a"...) + }; + }) + + .filter('formatFromNowShort', function() { + return function(input) { + return input ? moment(parseInt(input)*1000).startOf('minute').fromNow(true) : ''; }; }) diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js index 0ef63ee87f5edbda7d8ca240df9e22f30a785406..ea092632f153c23569bd76224163bdec812124a4 100644 --- a/www/js/controllers/app-controllers.js +++ b/www/js/controllers/app-controllers.js @@ -163,8 +163,13 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $ }); }; - // Login - $scope.login = function(state) { + // Login and go to wallet + $scope.login = function() { + $scope.loginAndGo('app.view_wallet'); + }; + + // Login and go to a state + $scope.loginAndGo = function(state) { if (!Wallet.isLogin()) { $scope.showLoginModal() .then(function(walletData){ @@ -174,6 +179,9 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $ } }); } + else { + $state.go(state); + } }; // Show login modal diff --git a/www/js/controllers/wallet-controllers.js b/www/js/controllers/wallet-controllers.js index 90f980ec08ac10d9d3b22d763448c3568de874ee..b1796a9786cdbc999dbee6e4656f13f1bf47a52d 100644 --- a/www/js/controllers/wallet-controllers.js +++ b/www/js/controllers/wallet-controllers.js @@ -32,8 +32,8 @@ angular.module('cesium.wallet.controllers', ['cesium.services', 'cesium.currency .controller('WalletTxErrorCtrl', WalletTxErrorController) ; -function WalletController($scope, $q, $ionicPopup, $timeout, $state, screenmatch, - UIUtils, Wallet, $translate, $ionicPopover, Modals, csSettings) { +function WalletController($scope, $q, $ionicPopup, $timeout, $state, $ionicHistory, screenmatch, + UIUtils, Wallet, $translate, $ionicPopover, Modals, csSettings, BMA) { 'ngInject'; $scope.walletData = null; @@ -42,20 +42,24 @@ function WalletController($scope, $q, $ionicPopup, $timeout, $state, screenmatch $scope.showDetails = false; $scope.loading = true; - $scope.$on('$ionicView.enter', function(e, $state) { + $scope.$on('$ionicView.enter', function() { $scope.loadWallet() .then(function(walletData) { - if (!walletData) { - $state.go('app.home'); - } $scope.walletData = walletData; $scope.updateView(); $scope.loading=false; $scope.showFab('fab-transfer'); $scope.showQRCode('qrcode', walletData.pubkey, 1100); UIUtils.loading.hide(); // loading could have be open (e.g. new account) + }) + .catch(function(err){ + if ('CANCELLED' === err) { + $ionicHistory.nextViewOptions({ + historyRoot: true + }); + $state.go('app.home'); + } }); - }); $ionicPopover.fromTemplateUrl('templates/wallet/popover_actions.html', { @@ -273,7 +277,7 @@ function WalletController($scope, $q, $ionicPopup, $timeout, $state, screenmatch // Updating wallet data $scope.doUpdate = function() { UIUtils.loading.show(); - Wallet.refreshData() + return Wallet.refreshData() .then(function() { $scope.updateView(); UIUtils.loading.hide(); @@ -324,21 +328,40 @@ function WalletController($scope, $q, $ionicPopup, $timeout, $state, screenmatch }); }; - // TODO: remove auto add account when done - /*$timeout(function() { + $scope.showMoreTx = function(fromTime) { + + fromTime = fromTime || + ($scope.walletData.tx.fromTime - csSettings.data.walletHistoryTimeSecond) || + (Math.trunc(new Date().getTime() / 1000) - 2 * csSettings.data.walletHistoryTimeSecond); + + UIUtils.loading.show(); + return Wallet.refreshData({tx: {enable: true,fromTime: fromTime}}) + .then(function() { + $scope.updateView(); + UIUtils.loading.hide(); + }) + .catch(function(err) { + // If http rest limitation: wait then retry + if (err.ucode == BMA.errorCodes.HTTP_LIMITATION) { + $timeout(function() { + return $scope.showMoreTx(); + }, 2000); + } + else { + UIUtils.onError('ERROR.REFRESH_WALLET_DATA')(err); + } + }); + }; - $scope.newAccount(); - }, 400); - */ } -function WalletTxErrorController($scope, $rootScope, $ionicPopup, $timeout, UIUtils, Wallet) { +function WalletTxErrorController($scope, $timeout, UIUtils, Wallet) { 'ngInject'; $scope.walletData = null; - $scope.$on('$ionicView.enter', function(e, $state) { + $scope.$on('$ionicView.enter', function(e) { $scope.loadWallet() .then(function(walletData) { $scope.walletData = walletData; diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js index 14034d7c114a0f1d1d9a09d072e93b3e1ec226d8..5ee895cebc93e8fe761c7447715561a8dee7f957 100644 --- a/www/js/services/bma-services.js +++ b/www/js/services/bma-services.js @@ -14,7 +14,8 @@ angular.module('cesium.bma.services', ['ngResource', 'cesium.http.services', 'ce NO_MATCHING_MEMBER: 2004, NO_IDTY_MATCHING_PUB_OR_UID: 2021, MEMBERSHIP_ALREADY_SEND: 2007, - IDENTITY_SANDBOX_FULL: 1007 + IDENTITY_SANDBOX_FULL: 1007, + HTTP_LIMITATION: 1006 }, regex = { USER_ID: "[A-Za-z0-9_-]+", diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index 52ab55ecfa6e64be455d880ca2648ccd18bc6beb..1bc807a56874a1715a254f201c6e5fa18a37ca3d 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -12,35 +12,11 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser constants = { STORAGE_KEY: "CESIUM_DATA" }, - data = { - pubkey: null, - keypair: { - signSk: null, - signPk: null - }, - uid: null, - balance: 0, - sources: null, - sourcesIndexByKey: null, - currency: null, - parameters: null, - currentUD: null, - medianTime: null, - tx: { - history: [], - pendings: [], - errors: [] - }, - requirements: {}, - isMember: false, - loaded: false, - blockUid: null, - avatar: null, - }, + data = {}, api = new Api(this, 'WalletService-' + id), - resetData = function() { + resetData = function(init) { data.pubkey= null; data.keypair ={ signSk: null, @@ -63,11 +39,15 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser data.isMember = false; data.loaded = false; data.blockUid = null; - data.avatar = null; - if (!csSettings.data.useLocalStorage) { - csSettings.reset(); + if (init) { + api.data.raise.init(data); + } + else { + if (!csSettings.data.useLocalStorage) { + csSettings.reset(); + } + api.data.raise.reset(data); } - api.data.raise.reset(data); }, reduceTxAndPush = function(txArray, result, processedTxMap, excludePending) { @@ -252,14 +232,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser return data; }, - isSourceEquals = function(arg1, arg2) { - return arg1.identifier == arg2.identifier && - arg1.noffset == arg2.noffset && - arg1.type == arg2.type && - arg1.base == arg2.base && - arg1.amount == arg2.amount; - }, - resetRequirements = function() { data.requirements = { needSelf: true, @@ -363,7 +335,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }); }, - loadTransactions = function() { + loadTransactions = function(fromTime) { return $q(function(resolve, reject) { var txHistory = []; var udHistory = []; @@ -371,14 +343,14 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser var now = new Date().getTime(); var nowInSec = Math.trunc(now / 1000); - var fromTime = nowInSec - csSettings.data.walletHistoryTimeSecond; + fromTime = fromTime || (nowInSec - csSettings.data.walletHistoryTimeSecond); var processedTxMap = {}; var reduceTx = function(res){ reduceTxAndPush(res.history.sent, txHistory, processedTxMap, true/*exclude pending*/); reduceTxAndPush(res.history.received, txHistory, processedTxMap, true/*exclude pending*/); reduceTxAndPush(res.history.sending, txHistory, processedTxMap, true/*exclude pending*/); - reduceTxAndPush(res.history.pending, txPendings, processedTxMap, false/*exclude pending*/); + reduceTxAndPush(res.history.pending, txPendings, processedTxMap, false/*include pending*/); }; var jobs = [ @@ -387,17 +359,27 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser .then(reduceTx) ]; + // get TX history since - var sliceTime = csSettings.data.walletHistorySliceSecond; - for(var i = fromTime - (fromTime % sliceTime); i - sliceTime < nowInSec; i += sliceTime) { - jobs.push(BMA.tx.history.times({pubkey: data.pubkey, from: i, to: i+sliceTime-1}) + if (fromTime !== -1) { + var sliceTime = csSettings.data.walletHistorySliceSecond; + for(var i = fromTime - (fromTime % sliceTime); i - sliceTime < nowInSec; i += sliceTime) { + jobs.push(BMA.tx.history.times({pubkey: data.pubkey, from: i, to: i+sliceTime-1}) + .then(reduceTx) + ); + } + + jobs.push(BMA.tx.history.timesNoCache({pubkey: data.pubkey, from: nowInSec - (nowInSec % sliceTime), to: nowInSec+999999999}) + .then(reduceTx)); + } + + // get all TX + else { + jobs.push(BMA.tx.history.all({pubkey: data.pubkey}) .then(reduceTx) ); } - jobs.push(BMA.tx.history.timesNoCache({pubkey: data.pubkey, from: nowInSec - (nowInSec % sliceTime), to: nowInSec+999999999}) - .then(reduceTx)); - // get UD history if (csSettings.data.showUDHistory) { jobs.push( @@ -405,6 +387,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser .then(function(res){ udHistory = !res.history || !res.history.history ? [] : res.history.history.reduce(function(res, ud){ + if (ud.time < fromTime) return res; // skip to old UD var amount = (ud.base > 0) ? ud.amount * Math.pow(10, ud.base) : ud.amount; return res.concat({ time: ud.time, @@ -626,49 +609,48 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser refreshData = function(options) { if (!options) { options = { - uds: true, + ud: true, requirements: true, sources: true, - tx: true + tx: { + enable: true, + fromTime: data.tx ? data.tx.fromTime : undefined // keep previous time + }, + sigStock: true }; } - return $q(function(resolve, reject){ - var jobs = []; - // Get UDs - if (options.uds) { - jobs.push(loadUDs()); - } - // Get requirements - if (options.uds) { - jobs.push(loadRequirements() - .then(function() { - finishLoadRequirements(); - })); - } - if (options.sources || options.tx) { - // Get sources - jobs.push(loadSources()); - // Get transactions - jobs.push(loadTransactions()); - } + var jobs = []; - // Load sigStock - jobs.push(loadSigStock()); + // Get UDs + if (options.ud) jobs.push(loadUDs()); - // API extension - jobs.push(api.data.raisePromise.load(data)); + // Get requirements + if (options.requirements) { + jobs.push(loadRequirements() + .then(function() { + finishLoadRequirements(); + })); + } - $q.all(jobs) - .then(function() { - // Process transactions and sources - processTransactionsAndSources() - .then(function(){ - resolve(data); - }) - .catch(function(err){reject(err);}); - }) - .catch(function(err){reject(err);}); + if (options.sources || (options.tx && options.tx.enable)) { + // Get sources + jobs.push(loadSources()); + + // Get transactions + jobs.push(loadTransactions(options.tx.fromTime)); + } + + // Load sigStock + if (options.sigStock) jobs.push(loadSigStock()); + + // API extension + if (options.api) jobs.push(api.data.raisePromise.load(data, options)); + + return $q.all(jobs) + .then(function() { + // Process transactions and sources + return processTransactionsAndSources(); }); }, @@ -1157,11 +1139,15 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Register extension points api.registerEvent('data', 'login'); api.registerEvent('data', 'logout'); + api.registerEvent('data', 'init'); api.registerEvent('data', 'load'); api.registerEvent('data', 'reset'); csSettings.api.data.on.changed($rootScope, store); + // init data + resetData(true); + return { id: id, data: data, diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js index 80e074cd6056cea74405023892debd32484ac851..77a2d847db0359175c63b0b87359e73562680407 100644 --- a/www/js/services/wot-services.js +++ b/www/js/services/wot-services.js @@ -457,7 +457,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic } }) .catch(function(err){ - if (err && err.ucode === 1006) { + if (err && err.ucode === BMA.errorCodes.HTTP_LIMITATION) { resolve(result); } else { diff --git a/www/plugins/es/i18n/locale-en.json b/www/plugins/es/i18n/locale-en.json index 42e764dc5ea381305629cb135b998e4bdba8e89b..b86fd9e3a0427631d28e5c56f3ed233be1ba4c73 100644 --- a/www/plugins/es/i18n/locale-en.json +++ b/www/plugins/es/i18n/locale-en.json @@ -24,25 +24,43 @@ "POPOVER_SHARE_TITLE": "Message #{{number}}" }, "MESSAGE": { - "BTN_RESPOND": "Respond", + "REMOVE_CONFIRMATION": "Are you sure you want to delete this message ?<br/><br/> This is irreversible.", + "REPLY_TITLE_PREFIX": "Re: ", + "FORWARD_TITLE_PREFIX": "Fw: ", + "BTN_REPLY": "Reply", "BTN_COMPOSE": "New message", "INBOX": { - "NO_MESSAGE": "No message", + "NO_MESSAGE": "No message received", "TITLE": "Messages" }, "COMPOSE": { "TITLE": "New message", + "TITLE_REPLY": "Reply", "SUB_TITLE": "New message", "TO": "To", "OBJECT": "Object", "OBJECT_HELP": "Object", "ENCRYPTED_HELP": "Please note this message will by encrypt before sending. Only you and recipient could read it.", "MESSAGE": "Message", - "MESSAGE_HELP": "Message content" + "MESSAGE_HELP": "Message content", + "CONTENT_CONFIRMATION": "No message content.<br/><br/>Are your sure you want to send this message ?" + }, + "VIEW": { + "TITLE": "Message", + "SENDER": "Sent by", + "NO_CONTENT": "Empty message" + }, + "INFO": { + "MESSAGE_REMOVED": "Message supprimé" }, "ERROR": { "SEND_MSG_FAILED": "Error while sending message.", - "SEARCH_FAILED": "Error while loading messages." + "SEARCH_FAILED": "Error while loading messages.", + "LOAD_MESSAGE_FAILED": "Error while loading message.", + "MESSAGE_NOT_READABLE": "Unable to read message.", + "USER_NOT_RECIPIENT": "You are not the recipient of this message: unable to read it.", + "NOT_AUTHENTICATED_MESSAGE": "The authenticity of the message is not certain or its content is corrupted.", + "REMOVE_MESSAGE_FAILED": "Error while deleting message" } }, "MARKET": { @@ -67,7 +85,7 @@ "TITLE": "Ad", "MENU_TITLE": "Options", "POPOVER_SHARE_TITLE": "Ad {{title}}", - "REMOVE_CONFIRMATION" : "Are you sure you want to delete this ad ?<br/><br/> This is irrevocable." + "REMOVE_CONFIRMATION" : "Are you sure you want to delete this ad ?<br/><br/> This is irreversible." }, "TYPE": { "TITLE": "New ad", @@ -125,7 +143,7 @@ "LOCATION": "Address:", "MENU_TITLE": "Options", "POPOVER_SHARE_TITLE": "{{title}}", - "REMOVE_CONFIRMATION" : "Are you sure you want to delete this record ?<br/><br/>This is irrevocable." + "REMOVE_CONFIRMATION" : "Are you sure you want to delete this record ?<br/><br/>This is irreversible." }, "TYPE": { "TITLE": "New", diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json index fc58c850ef6a5875028295d4d6a77d12fdf95b9d..fa98bc10d835d37c5c2e122efad7f29130d25a98 100644 --- a/www/plugins/es/i18n/locale-fr-FR.json +++ b/www/plugins/es/i18n/locale-fr-FR.json @@ -24,25 +24,43 @@ "POPOVER_SHARE_TITLE": "Message #{{number}}" }, "MESSAGE": { - "BTN_RESPOND": "Répondre", + "REMOVE_CONFIRMATION": "Etes-vous sur de vouloir supprimer ce message ?<br/><br/>Cette opération est irréversible.", + "REPLY_TITLE_PREFIX": "Rep: ", + "FORWARD_TITLE_PREFIX": "Tr: ", + "BTN_REPLY": "Répondre", "BTN_COMPOSE": "Nouveau message", "INBOX": { - "NO_MESSAGE": "Aucun message", + "NO_MESSAGE": "Aucun message reçus", "TITLE": "Messages" }, "COMPOSE": { "TITLE": "Nouveau message", + "TITLE_REPLY": "Répondre", "SUB_TITLE": "Nouveau message", "TO": "A", "OBJECT": "Objet", "OBJECT_HELP": "Objet", - "ENCRYPTED_HELP": "Veuillez noter que ce message sera crypté avant son envoi. Seul le destinataire (et vous) pourrez le lire.", + "ENCRYPTED_HELP": "Veuillez noter que ce message sera crypté avant envoi. Seul le destinataire pourra le lire.", "MESSAGE": "Message", - "MESSAGE_HELP": "Contenu du message" + "MESSAGE_HELP": "Contenu du message", + "CONTENT_CONFIRMATION": "Le contenu du message est vide.<br/><br/>Voulez-vous néanmoins envoyer le message ?" + }, + "VIEW": { + "TITLE": "Message", + "SENDER": "Envoyé par", + "NO_CONTENT": "Message vide" + }, + "INFO": { + "MESSAGE_REMOVED": "Message supprimé" }, "ERROR": { "SEND_MSG_FAILED": "Erreur lors de l'envoi du message.", - "SEARCH_FAILED": "Erreur lors de la récupération des messages." + "SEARCH_FAILED": "Erreur lors de la récupération des messages.", + "LOAD_MESSAGE_FAILED": "Erreur lors de la récupération du message.", + "MESSAGE_NOT_READABLE": "Lecture du message impossible.", + "USER_NOT_RECIPIENT": "Vous n'etes pas le destinataire de ce message : déchiffrement impossible.", + "NOT_AUTHENTICATED_MESSAGE": "L'authenticité du message est douteuse ou son contenu est corrompu.", + "REMOVE_MESSAGE_FAILED": "Erreur de suppression du message" } }, "MARKET": { @@ -67,7 +85,7 @@ "TITLE": "Annonce", "MENU_TITLE": "Options", "POPOVER_SHARE_TITLE": "Annonce {{title}}", - "REMOVE_CONFIRMATION" : "Etes-vous sur de vouloir supprimer cette annonce ?<br/><br/>Cette opération est irrévocable." + "REMOVE_CONFIRMATION" : "Etes-vous sur de vouloir supprimer cette annonce ?<br/><br/>Cette opération est irréversible." }, "TYPE": { "TITLE": "Nouvelle annonce", @@ -125,7 +143,7 @@ "LOCATION": "Adresse :", "MENU_TITLE": "Options", "POPOVER_SHARE_TITLE": "{{title}}", - "REMOVE_CONFIRMATION" : "Etes-vous sur de vouloir supprimer cette fiche ?<br/><br/>Cette opération est irrévocable." + "REMOVE_CONFIRMATION" : "Etes-vous sur de vouloir supprimer cette fiche ?<br/><br/>Cette opération est irréversible." }, "TYPE": { "TITLE": "Nouveau référencement", diff --git a/www/plugins/es/js/controllers/market-controllers.js b/www/plugins/es/js/controllers/market-controllers.js index 7059300caa466bbd3e8dacb91622de22f5e87ca0..49c613b4b568a922dab414f30b1068321a298f46 100644 --- a/www/plugins/es/js/controllers/market-controllers.js +++ b/www/plugins/es/js/controllers/market-controllers.js @@ -545,13 +545,7 @@ function ESMarketRecordViewController($scope, $anchorScroll, $ionicPopover, $sta $scope.actionsPopover.hide(); } - // translate - var translations; - $translate(['MARKET.VIEW.REMOVE_CONFIRMATION', 'MARKET.INFO.RECORD_REMOVED']) - .then(function(res) { - translations = res; - return UIUtils.alert.confirm(res['MARKET.VIEW.REMOVE_CONFIRMATION']); - }) + UIUtils.alert.confirm('MARKET.VIEW.REMOVE_CONFIRMATION') .then(function(confirm) { if (confirm) { esMarket.record.remove($scope.id) @@ -560,7 +554,7 @@ function ESMarketRecordViewController($scope, $anchorScroll, $ionicPopover, $sta historyRoot: true }); $state.go('app.market_lookup'); - UIUtils.toast.show(translations['MARKET.INFO.RECORD_REMOVED']); + UIUtils.toast.show('MARKET.INFO.RECORD_REMOVED'); }) .catch(UIUtils.onError('MARKET.ERROR.REMOVE_RECORD_FAILED')); } diff --git a/www/plugins/es/js/controllers/message-controllers.js b/www/plugins/es/js/controllers/message-controllers.js index 763640a3cfb1e7572cf0ec414572ef4fdedee39b..a7330dedec5a706ec80fd4125d68c178b57cf17b 100644 --- a/www/plugins/es/js/controllers/message-controllers.js +++ b/www/plugins/es/js/controllers/message-controllers.js @@ -6,7 +6,6 @@ angular.module('cesium.es.message.controllers', ['cesium.es.services', 'cesium.e $stateProvider .state('app.user_message', { - cache: false, url: "/user/message", views: { 'menuContent': { @@ -27,6 +26,17 @@ angular.module('cesium.es.message.controllers', ['cesium.es.services', 'cesium.e } }) + .state('app.user_view_message', { + cache: false, + url: "/user/message/view/:id", + views: { + 'menuContent': { + templateUrl: "plugins/es/templates/message/view_message.html", + controller: 'ESMessageViewCtrl' + } + } + }) + ; }) @@ -36,26 +46,36 @@ angular.module('cesium.es.message.controllers', ['cesium.es.services', 'cesium.e .controller('ESMessageComposeModalCtrl', ESMessageComposeModalController) + .controller('ESMessageViewCtrl', ESMessageViewController) + + + ; -function ESMessageInboxController($scope, $rootScope, $timeout, $q, ModalUtils, UIUtils, esMessage, CryptoUtils) { +function ESMessageInboxController($scope, $rootScope, $state, $timeout, $translate, $ionicHistory, ModalUtils, UIUtils, esMessage) { 'ngInject'; $scope.loading = true; $scope.messages = []; - $scope.$on('$ionicView.enter', function(e, $state) { + $scope.$on('$ionicView.enter', function(e) { $scope.loadWallet() - .then(function(walletData) { - if ($scope.loading) { + .then(function() { + if (!$scope.entered) { + $scope.entered = true; $scope.load(); } $scope.showFab('fab-add-message-record'); }) .catch(function(err) { - // TODO + if ('CANCELLED' === err) { + $ionicHistory.nextViewOptions({ + historyRoot: true + }); + $state.go('app.home'); + } }); }); @@ -68,27 +88,12 @@ function ESMessageInboxController($scope, $rootScope, $timeout, $q, ModalUtils, sort: { "time" : "desc" }, - query: { - bool: { - filter: { - //term: {issuer: $rootScope.walletData.pubkey}, - term: {recipient: $rootScope.walletData.pubkey} - } - } - }, + query: {bool: {filter: {term: {recipient: $rootScope.walletData.pubkey}}}}, from: offset, size: size, _source: esMessage.fields.commons }; - //request.query.bool = {}; - - var filters = []; - //filters.push({match : { issuer: $rootScope.walletData.pubkey}}); - //filters.push({match : { recipient: $rootScope.walletData.pubkey}}); - //filters.push({match_phrase: { location: $scope.search.location}}); - //request.query.bool.should = filters; - return $scope.doRequest(request); }; @@ -100,6 +105,17 @@ function ESMessageInboxController($scope, $rootScope, $timeout, $q, ModalUtils, $scope.messages = messages; UIUtils.loading.hide(); $scope.loading = false; + + if (messages.length > 0) { + // Set Motion + $timeout(function() { + UIUtils.motion.ripple({ + startVelocity: 3000 + }); + // Set Ink + UIUtils.ink(); + }, 10); + } }) .catch(function(err) { UIUtils.onError('MESSAGE.ERROR.SEARCH_FAILED')(err); @@ -108,31 +124,22 @@ function ESMessageInboxController($scope, $rootScope, $timeout, $q, ModalUtils, }); }; - /*$scope.doDecryption = function() { - - return esMessage.search(request) - .then(function(res) { - if (res.hits.total === 0) { - $scope.messages = []; + $scope.delete = function(index) { + var message = $scope.messages[index]; + if (!message) return; + + UIUtils.alert.confirm('MESSAGE.REMOVE_CONFIRMATION') + .then(function(confirm) { + if (confirm) { + esMessage.remove(message.id) + .then(function () { + $scope.messages.splice(index,1); // remove from messages array + UIUtils.toast.show('MESSAGE.INFO.MESSAGE_REMOVED'); + }) + .catch(UIUtils.onError('MESSAGE.ERROR.REMOVE_MESSAGE_FAILED')); } - else { - var messages = res.hits.hits.reduce(function(result, hit) { - var message = hit._source; - - // decrypt - return result.concat(message) - }, []); - $scope.messages = messages; - } - UIUtils.loading.hide(); - $scope.loading = false; - }) - .catch(function(err) { - UIUtils.onError('MESSAGE.ERROR.SEARCH_FAILED')(err); - $scope.messages = []; - $scope.loading = false; }); - };*/ + }; /* -- Modals -- */ @@ -147,23 +154,43 @@ function ESMessageInboxController($scope, $rootScope, $timeout, $q, ModalUtils, }); }; - // TODO : for DEV only - $timeout(function() { - //$scope.showNewMessageModal(); + $scope.showReplyModal = function(index) { + var message = $scope.messages[index]; + if (!message) return; + + $translate('MESSAGE.REPLY_TITLE_PREFIX') + .then(function (prefix) { + var content = message.content ? message.content.replace(/^/g, ' > ') : null; + content = content ? content.replace(/\n/g, '\n > ') : null; + content = content ? content +'\n' : null; + return esModals.showMessageCompose({ + destPub: message.pubkey, + destUid: message.name||message.uid, + title: prefix + message.title, + content: content, + isReply: true + }); + }); + } + + // for DEV only + /*$timeout(function() { + $scope.showNewMessageModal(); }, 900); + */ } -function ESMessageComposeController($scope, $rootScope, $ionicHistory, $timeout, $focus, $q, Modals, UIUtils, CryptoUtils, Wallet, esHttp, esMessage) { +function ESMessageComposeController($scope, $ionicHistory, Modals, UIUtils, CryptoUtils, Wallet, esHttp, esMessage) { 'ngInject'; - ESMessageComposeModalController.call(this, $scope, $rootScope, $timeout, $focus, $q, Modals, UIUtils, CryptoUtils, Wallet, esHttp, esMessage); + ESMessageComposeModalController.call(this, $scope, Modals, UIUtils, CryptoUtils, Wallet, esHttp, esMessage); - $scope.$on('$ionicView.enter', function(e, $state) { - if (!!$state.stateParams && !!$state.stateParams.pubkey) { - $scope.formData.destPub = $state.stateParams.pubkey; + $scope.$on('$ionicView.enter', function(e, state) { + if (!!state.stateParams && !!state.stateParams.pubkey) { + $scope.formData.destPub = state.stateParams.pubkey; if (!!$state.stateParams.uid) { - $scope.destUid = $state.stateParams.uid; + $scope.destUid = state.stateParams.uid; $scope.destPub = ''; } else { @@ -175,6 +202,14 @@ function ESMessageComposeController($scope, $rootScope, $ionicHistory, $timeout, $scope.loadWallet() .then(function() { UIUtils.loading.hide(); + }) + .catch(function(err){ + if (err === 'CANCELLED') { + $ionicHistory.nextViewOptions({ + historyRoot: true + }); + $state.go('app.home'); + } }); }); @@ -188,23 +223,35 @@ function ESMessageComposeController($scope, $rootScope, $ionicHistory, $timeout, } -function ESMessageComposeModalController($scope, $rootScope, $timeout, $focus, $q, Modals, UIUtils, CryptoUtils, Wallet, esHttp, esMessage) { +function ESMessageComposeModalController($scope, Modals, UIUtils, CryptoUtils, Wallet, esHttp, esMessage, parameters) { 'ngInject'; $scope.formData = { - title: null, - content: null, - destPub: null + title: parameters ? parameters.title : null, + content: parameters ? parameters.content : null, + destPub: parameters ? parameters.destPub : null }; + $scope.destUid = parameters ? parameters.destUid : null; + $scope.destPub = (parameters && !parameters.destUid) ? parameters.destPub : null; + $scope.isResponse = parameters ? parameters.isResponse : false; - $scope.doSend = function() { + $scope.doSend = function(forceNoContent) { $scope.form.$submitted=true; if(!$scope.form.$valid) { return; } - UIUtils.loading.show(); + // Ask user confirmation if no content + if (!forceNoContent && (!$scope.formData.content || !$scope.formData.content.trim().length)) { + return UIUtils.alert.confirm('MESSAGE.COMPOSE.CONTENT_CONFIRMATION') + .then(function(confirm) { + if (confirm) { + $scope.doSend(true); + } + }); + } + UIUtils.loading.show(); var data = { issuer: Wallet.data.pubkey, recipient: $scope.formData.destPub, @@ -250,7 +297,7 @@ function ESMessageComposeModalController($scope, $rootScope, $timeout, $focus, $ // TODO : for DEV only - $timeout(function() { + /*$timeout(function() { $scope.formData.destPub = 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU'; $scope.formData.title = 'test'; $scope.formData.content = 'test'; @@ -260,4 +307,107 @@ function ESMessageComposeModalController($scope, $rootScope, $timeout, $focus, $ //$scope.doSend(); }, 800); }, 100); + */ +} + + +function ESMessageViewController($scope, $state, $timeout, $translate, $ionicHistory, UIUtils, esModals, esMessage) { + 'ngInject'; + + $scope.formData = {}; + $scope.id = null; + $scope.loading = true; + + $scope.$on('$ionicView.enter', function (e, state) { + if (state.stateParams && state.stateParams.id) { // Load by id + if ($scope.loading) { // prevent reload if same id + $scope.load(state.stateParams.id); + } + + $scope.showFab('fab-view-message-reply'); + } + else { + $state.go('app.user_message'); + } + }); + + $scope.load = function(id) { + + $scope.loadWallet() + .then(function(){ + UIUtils.loading.hide(); + + return esMessage.get({id: id}) + .then(function(message) { + + if (!message.valid) { + + return UIUtils.alert.error(!$scope.isUserPubkey(message.pubkey) ? 'MESSAGE.ERROR.USER_NOT_RECIPIENT': 'MESSAGE.ERROR.NOT_AUTHENTICATED_MESSAGE', + 'MESSAGE.ERROR.MESSAGE_NOT_READABLE') + .then(function() { + $state.go('app.user_message'); + }); + } + + $scope.formData = message; + $scope.canDelete = true; + $scope.loading = false; + + // Set Motion (only direct children, to exclude .lazy-load children) + $timeout(function () { + UIUtils.motion.fadeSlideIn({ + startVelocity: 3000 + }); + }, 10); + }) + .catch(UIUtils.onError('MESSAGE.ERROR.LOAD_MESSAGE_FAILED')); + }) + .catch(function(err){ + if (err === 'CANCELLED') { + $ionicHistory.nextViewOptions({ + historyRoot: true + }); + $state.go('app.user_message'); + } + }); + }; + + $scope.delete = function() { + if ($scope.actionsPopover) { + $scope.actionsPopover.hide(); + } + + UIUtils.alert.confirm('MESSAGE.REMOVE_CONFIRMATION') + .then(function(confirm) { + if (confirm) { + esMessage.remove($scope.formData.id) + .then(function () { + $ionicHistory.nextViewOptions({ + historyRoot: true + }); + $state.go('app.user_message'); + UIUtils.toast.show('MESSAGE.INFO.MESSAGE_REMOVED'); + }) + .catch(UIUtils.onError('MESSAGE.ERROR.REMOVE_MESSAGE_FAILED')); + } + }); + }; + + /* -- Modals -- */ + + $scope.showReplyModal = function() { + $translate('MESSAGE.REPLY_TITLE_PREFIX') + .then(function (prefix) { + var content = $scope.formData.content ? $scope.formData.content.replace(/^/g, ' > ') : null; + content = content ? content.replace(/\n/g, '\n > ') : null; + content = content ? content +'\n' : null; + return esModals.showMessageCompose({ + destPub: $scope.formData.pubkey, + destUid: $scope.formData.name||$scope.formData.uid, + title: prefix + $scope.formData.title, + content: content, + isReply: true + }); + }); + }; } diff --git a/www/plugins/es/js/controllers/user-controllers.js b/www/plugins/es/js/controllers/user-controllers.js index 552a6f9d320f463d49dc965fa8f824277e249ef8..8351422b2ddae5d7e9afedcdb872a6996df558cc 100644 --- a/www/plugins/es/js/controllers/user-controllers.js +++ b/www/plugins/es/js/controllers/user-controllers.js @@ -18,7 +18,7 @@ angular.module('cesium.es.user.controllers', ['cesium.es.services']) ; -function ProfileController($scope, $rootScope, UIUtils, $timeout, esUser, $filter, $focus, $q, SocialUtils, $translate, $ionicHistory) { +function ProfileController($scope, $rootScope, UIUtils, $timeout, esUser, $state, $focus, $q, SocialUtils, $translate, $ionicHistory) { 'ngInject'; $scope.loading = true; @@ -34,7 +34,7 @@ function ProfileController($scope, $rootScope, UIUtils, $timeout, esUser, $filte url: null }; - $scope.$on('$ionicView.enter', function(e, $state) { + $scope.$on('$ionicView.enter', function(e) { $scope.loading = true; // to avoid the call of doSave() $scope.loadWallet() .then(function(walletData) { @@ -65,6 +65,11 @@ function ProfileController($scope, $rootScope, UIUtils, $timeout, esUser, $filte // removeIf(device) $focus('profile-name'); // endRemoveIf(device) + }) + .catch(function(err){ + if (err === 'CANCELLED') { + $state.go('app.home'); + } }); }); diff --git a/www/plugins/es/js/services.js b/www/plugins/es/js/services.js index 6d937aeb30686d45ad9c2ac2966a9b6e32bcf5a5..84ff57d836c5859571ec47ad957b91ed6b655903 100644 --- a/www/plugins/es/js/services.js +++ b/www/plugins/es/js/services.js @@ -7,6 +7,7 @@ angular.module('cesium.es.services', [ 'cesium.es.registry.services', 'cesium.es.market.services', 'cesium.es.user.services', - 'cesium.es.message.services' + 'cesium.es.message.services', + 'cesium.es.modal.services' ]) ; diff --git a/www/plugins/es/js/services/message-services.js b/www/plugins/es/js/services/message-services.js index bb4b06994633f73976cd397a879729d338ad6503..6becd622e8efeae0755c95850f2b8f6649d994a9 100644 --- a/www/plugins/es/js/services/message-services.js +++ b/www/plugins/es/js/services/message-services.js @@ -32,6 +32,21 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' } } + function onWalletInit(data) { + data.messages = data.messages || {}; + data.messages.count = null; + } + + function onWalletReset(data) { + if (data.keypair) { + delete data.keypair.boxSk; + delete data.keypair.boxPk; + }; + if (data.messages) { + delete data.messages; + } + } + function onWalletLoad(data, resolve, reject) { if (!data || !data.pubkey) { if (resolve) { @@ -40,23 +55,65 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' return; } - if (data.keypair) { - var boxKeypair = CryptoUtils.box.keypair.fromSignKeypair(data.keypair) - data.keypair.boxSk = boxKeypair.boxSk; - data.keypair.boxPk = boxKeypair.boxPk; + var lastMessageTime = csSettings.data && csSettings.data.plugins && csSettings.data.plugins.es ? + csSettings.data.plugins.es.lastMessageTime : + undefined; + + // Count new messages + countNewMessages(data.pubkey, lastMessageTime) + .then(function(count){ + data.messages = data.messages || {}; + data.messages.count = count; + console.debug('[ES] [message] Detecting ' + count + (lastMessageTime ? ' unread' : '') + ' messages'); + if(resolve) resolve(data); + }) + .catch(function(err){ + reject(err); + if(reject) reject(data); + }); + } + + + function getBoxKeypair(keypair) { + keypair = keypair || (Wallet.isLogin() ? Wallet.data.keypair : keypair); + if (!keypair) { + throw new Error('no keypair, and user not connected.'); } - resolve(data); + if (keypair.boxPk && keypair.boxSk) { + return keypair; + } + var boxKeypair = CryptoUtils.box.keypair.fromSignKeypair(keypair); + Wallet.data.keypair.boxSk = boxKeypair.boxSk; + Wallet.data.keypair.boxPk = boxKeypair.boxPk; + console.debug("[ES] Secret box keypair successfully computed"); + return data.keypair; } - function onWalletReset(data) { - if (data.keypair) { - delete data.keypair.boxSk; - delete data.keypair.boxPk; + function countNewMessages(pubkey, fromTime) { + pubkey = pubkey || (Wallet.isLogin() ? Wallet.data.pubkey : pubkey); + if (!pubkey) { + throw new Error('no pubkey, and user not connected.'); + } + + var request = { + size: 0, + query: {constant_score: {filter: [{term: {recipient: pubkey}}]}} }; + + // Add time filter + if (fromTime) { + request.query.constant_score.filter.push({range: {time: {gt: fromTime}}}); + } + + return esHttp.post(host, port, '/message/record/_search')(request) + .then(function(res) { + return res.hits ? res.hits.total : 0; + }); } + function sendMessage(message, keypair) { - var boxKeypair = CryptoUtils.box.keypair.fromSignKeypair(keypair); + var boxKeypair = getBoxKeypair(keypair); // Get recipient var recipientPk = CryptoUtils.util.decode_base58(message.recipient); @@ -107,12 +164,13 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' var walletPubkey = Wallet.isLogin() ? Wallet.data.pubkey : null; var messages = res.hits.hits.reduce(function(result, hit) { var msg = hit._source; + msg.id = hit._id; msg.pubkey = msg.issuer !== walletPubkey ? msg.issuer : msg.recipient; msg.uid = uids[msg.pubkey]; return result.concat(msg); }, []); - console.debug('[message] Loading ' + messages.length + ' messages'); + console.debug('[ES] [message] Loading ' + messages.length + ' messages'); return decryptMessages(messages, keypair) .then(function(){ return esUser.profile.fillAvatars(messages, 'pubkey'); @@ -121,17 +179,47 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' }); } - function decryptMessages(messages, recipientSignKeypair) { + function getAndDecrypt(params, keypair) { + return esHttp.get(host, port, '/message/record/:id')(params) + .then(function(hit) { + if (!hit.found) { + return; + } + else { + var walletPubkey = Wallet.isLogin() ? Wallet.data.pubkey : null; + var msg = hit._source; + msg.id = hit._id; + msg.pubkey = msg.issuer !== walletPubkey ? msg.issuer : msg.recipient; + + // Get uid (if member) + return BMA.wot.member.get(msg.pubkey) + .then(function(user) { + msg.uid = user ? user.uid : user; + // Decrypt message + return decryptMessages([msg], keypair) + }) + .then(function(){ + // Fill avatar + return esUser.profile.fillAvatars([msg], 'pubkey'); + }) + .then(function() { + return msg; + }); + } + }) + } + + function decryptMessages(messages, keypair) { var jobs = []; var now = new Date().getTime(); - var recipientBoxKeypair = CryptoUtils.box.keypair.fromSignKeypair(recipientSignKeypair); - var issuerBoxPksCache = {}; + var boxKeypair = getBoxKeypair(keypair); + var issuerBoxPks = {}; // a map used as cache var messages = messages.reduce(function(result, message) { - var issuerBoxPk = issuerBoxPksCache[message.issuer]; + var issuerBoxPk = issuerBoxPks[message.issuer]; if (!issuerBoxPk) { issuerBoxPk = CryptoUtils.box.keypair.pkFromSignPk(CryptoUtils.util.decode_base58(message.issuer)); - issuerBoxPksCache[message.issuer] = issuerBoxPk; // fill box pk cache + issuerBoxPks[message.issuer] = issuerBoxPk; // fill box pk cache } var nonce = CryptoUtils.util.decode_base58(message.nonce); @@ -139,22 +227,22 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' message.valid = true; // title - jobs.push(CryptoUtils.box.open(message.title, nonce, issuerBoxPk, recipientBoxKeypair.boxSk) + jobs.push(CryptoUtils.box.open(message.title, nonce, issuerBoxPk, boxKeypair.boxSk) .then(function(title) { message.title = title; }) .catch(function(err){ - console.warn('[message] invalid cypher title'); + console.warn('[ES] [message] invalid cypher title'); message.valid = false; })); // content - jobs.push(CryptoUtils.box.open(message.content, nonce, issuerBoxPk, recipientBoxKeypair.boxSk) + jobs.push(CryptoUtils.box.open(message.content, nonce, issuerBoxPk, boxKeypair.boxSk) .then(function(content) { message.content = content; }) .catch(function(err){ - console.warn('[message] invalid cypher content'); + console.warn('[ES] [message] invalid cypher content'); message.valid = false; })); return result.concat(message); @@ -162,41 +250,58 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' return $q.all(jobs) .then(function() { - console.debug('[message] Messages decrypted in ' + (new Date().getTime() - now) + 'ms'); + console.debug('[ES] [message] All messages decrypted in ' + (new Date().getTime() - now) + 'ms'); return messages; }); } - function addListeners() { - console.debug("[ES] Enable message service listeners"); + function removeListeners() { + console.debug("[ES] Disable message service listeners"); - // Extend Wallet.loadData() and WotService.loadData() - listeners = [ - Wallet.api.data.on.load($rootScope, onWalletLoad, this), - Wallet.api.data.on.reset($rootScope, onWalletReset, this), - ]; - } + _.forEach(listeners, function(remove){ + remove(); + }); + listeners = []; + } + + function addListeners() { + console.debug("[ES] Enable message service listeners"); - function isEnable() { - return csSettings.data.plugins && - csSettings.data.plugins.es && - host && csSettings.data.plugins.es.enable; + // Extend Wallet.loadData() and WotService.loadData() + listeners = [ + Wallet.api.data.on.login($rootScope, onWalletLoad, this), + Wallet.api.data.on.load($rootScope, onWalletLoad, this), + Wallet.api.data.on.init($rootScope, onWalletInit, this), + Wallet.api.data.on.reset($rootScope, onWalletReset, this), + ]; + } + + function isEnable() { + return csSettings.data.plugins && + csSettings.data.plugins.es && + host && csSettings.data.plugins.es.enable; + } + + function refreshListeners() { + var enable = isEnable(); + if (!enable && listeners && listeners.length > 0) { + removeListeners(); + } + else if (enable && (!listeners || listeners.length === 0)) { + addListeners(); } + } - function refreshListeners() { - var enable = isEnable(); - if (!enable && listeners && listeners.length > 0) { - removeListeners(); - } - else if (enable && (!listeners || listeners.length === 0)) { - addListeners(); - } + // Listen for settings changed + csSettings.api.data.on.changed($rootScope, function(){ + refreshListeners(); + if (isEnable() && !Wallet.data.messages) { + onWalletLoad(Wallet.data); } + }); - // Listen for settings changed - csSettings.api.data.on.changed($rootScope, function(){ - refreshListeners(); - }); + // Default action + refreshListeners(); return { copy: copy, @@ -205,7 +310,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', ' }, search: esHttp.post(host, port, '/message/record/_search'), searchAndDecrypt: searchAndDecrypt, - get: esHttp.get(host, port, '/message/record/:id'), + get: getAndDecrypt, send: sendMessage, remove: esHttp.record.remove(host, port, 'message', 'record'), fields: { diff --git a/www/plugins/es/js/services/modal-services.js b/www/plugins/es/js/services/modal-services.js new file mode 100644 index 0000000000000000000000000000000000000000..922e6baf62b798b398d42cbbf6a995c79be28fa8 --- /dev/null +++ b/www/plugins/es/js/services/modal-services.js @@ -0,0 +1,15 @@ +angular.module('cesium.es.modal.services', ['cesium.modal.services', 'cesium.es.message.services']) + +.factory('esModals', function(ModalUtils) { + 'ngInject'; + + function showMessageCompose(parameters) { + return ModalUtils.show('plugins/es/templates/message/modal_compose.html','ESMessageComposeModalCtrl', + parameters, {focusFirstInput: true}); + } + + return { + showMessageCompose: showMessageCompose + }; + +}); diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js index 096f5b4c13ae23e7fa2fc69dcda0c1d9d95d3ee5..4562bf6e1fb7aba1682118315e9769a1044bc299 100644 --- a/www/plugins/es/js/services/user-services.js +++ b/www/plugins/es/js/services/user-services.js @@ -63,6 +63,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se data.avatar = null; data.avatarStyle = null; data.profile = null; + data.name = null; } function onWotLoad(data, resolve, reject) { diff --git a/www/plugins/es/templates/menu_extend.html b/www/plugins/es/templates/menu_extend.html index 495771e3e9af0c7cb20d2226b29efc707438f5f6..0288b43c427cc645ed97c07bb592b1040a2c089b 100644 --- a/www/plugins/es/templates/menu_extend.html +++ b/www/plugins/es/templates/menu_extend.html @@ -42,29 +42,21 @@ <ng-if ng-if="enable && extensionPoint === 'menu-user'"> <!-- user profile --> <ion-item menu-close class="item item-icon-left" active-link="active" - ng-if="$root.login" - href="#/app/user/profile/edit"> - <i class="icon ion-person"></i> - <span translate>MENU.USER_PROFILE</span> - </ion-item> - <ion-item menu-close class="item item-icon-left item-menu-disable" active-link="active" - ng-if="!$root.login" - ng-click="login('app.user_edit_profile')"> + active-link-path-prefix="#/app/user/profile" + ng-class="{'item-menu-disable': !$root.login}" + ng-click="loginAndGo('app.user_edit_profile')"> <i class="icon ion-person"></i> <span translate>MENU.USER_PROFILE</span> </ion-item> <ion-item menu-close class="item item-icon-left" active-link="active" - ng-if="$root.login" - href="#/app/user/message"> - <i class="icon ion-email"></i> - <span translate>MENU.USER_MESSAGE</span> - </ion-item> - <ion-item menu-close class="item item-icon-left item-menu-disable" active-link="active" - ng-if="!$root.login" - ng-click="login('app.user_message')"> + active-link-path-prefix="#/app/user/message" + ng-class="{'item-menu-disable': !$root.login}" + ng-click="loginAndGo('app.user_message')"> <i class="icon ion-email"></i> <span translate>MENU.USER_MESSAGE</span> + <span class="badge badge-positive" + ng-if="$root.walletData.messages.count">{{$root.walletData.messages.count}}</span> </ion-item> </ng-if> diff --git a/www/plugins/es/templates/message/compose.html b/www/plugins/es/templates/message/compose.html index e0ffa5432aa971840d0563a507df974de5516d52..561a693d0f8489e0b2434c6eb905770d72df69fa 100644 --- a/www/plugins/es/templates/message/compose.html +++ b/www/plugins/es/templates/message/compose.html @@ -1,7 +1,8 @@ <ion-view left-buttons="leftButtons" id="composeMessage"> <ion-nav-title> - <span class="visible-xs visible-sm" translate>MESSAGE.COMPOSE.TITLE</span> + <span class="visible-xs visible-sm" nf=if="!isReply" translate>MESSAGE.COMPOSE.TITLE</span> + <span class="visible-xs visible-sm" nf=if="isReply" translate>MESSAGE.COMPOSE.TITLE_REPLY</span> </ion-nav-title> <ion-nav-buttons side="secondary"> diff --git a/www/plugins/es/templates/message/inbox.html b/www/plugins/es/templates/message/inbox.html index 3888d9007e7ec9984695709aa8d1d8ad2dc83459..a551a83a4b482586e79f328d936554431aba1466 100644 --- a/www/plugins/es/templates/message/inbox.html +++ b/www/plugins/es/templates/message/inbox.html @@ -11,10 +11,8 @@ <ion-content class="padding no-padding-xs"> - - + <!-- Buttons bar--> <ion-list> - <!-- Buttons bar--> <div class="item large-button-bar hidden-xs hidden-sm"> <button class="button button-small button-calm icon ion-compose" ng-click="showNewMessageModal()"> @@ -25,14 +23,18 @@ <div class="center" ng-if="loading"> <ion-spinner icon="android"></ion-spinner> </div> + </ion-list> - <div class="padding gray" ng-if="!loading && messages.length===0 " translate> + <ion-list class="animate-ripple" ng-if="!loading"> + + <div class="padding gray" ng-if="messages.length===0 " translate> MESSAGE.INBOX.NO_MESSAGE </div> <ion-item - class="item item-border-large item-avatar ink" - ng-repeat="msg in messages"> + class="item item-border-large item-avatar item-icon-right ink" + ng-repeat="msg in messages" + ui-sref="app.user_view_message({id:msg.id})"> <img ng-if="msg.avatar" class="item-image" ng-src="{{::msg.avatar.src}}"> <i ng-if="!msg.avatar && msg.uid" class="item-image icon ion-person"></i> @@ -41,19 +43,25 @@ <a class="positive" ng-if="msg.name||msg.uid" ui-sref="app.wot_view_identity({pubkey:msg.pubkey, uid:msg.name||msg.uid})"> - <i class="icon ion-person"></i> + <i class="ion-person"></i> {{msg.name||msg.uid}} </a> <a class="gray" ng-if="!msg.name && !msg.uid" ui-sref="app.wot_view_identity({pubkey:msg.pubkey})"> - <i class="icon ion-key"></i> + <i class="ion-key"></i> {{msg.pubkey|formatPubkey}} </a> </h3> <h2 >{{msg.title}}</h2> <p>{{msg.content}}</p> - <ion-option-button class="button-positive" - translate>MESSAGE.BTN_RESPOND</ion-option-button> + <i class="icon ion-ios-arrow-right "></i> + <ion-option-button class="button-stable" + ng-click="showResponseModal($index)" + translate>MESSAGE.BTN_REPLY</ion-option-button> + <ion-option-button class="button-assertive" + ng-click="delete($index)" + translate>COMMON.BTN_DELETE</ion-option-button> + </ion-item> </ion-list> </ion-content> diff --git a/www/plugins/es/templates/message/modal_compose.html b/www/plugins/es/templates/message/modal_compose.html index bad72d80856b42340414213d2f42ca069d69a3bd..72fbbc95ceea63f90f5c909ecb821d3c908fe3af 100644 --- a/www/plugins/es/templates/message/modal_compose.html +++ b/www/plugins/es/templates/message/modal_compose.html @@ -1,7 +1,8 @@ <ion-modal-view id="transfer" class="modal-full-height"> <ion-header-bar class="bar-positive"> <button class="button button-clear visible-xs" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button> - <h1 class="title" translate>MESSAGE.COMPOSE.TITLE</h1> + <h1 class="title" nf=if="!isReply" translate>MESSAGE.COMPOSE.TITLE</h1> + <h1 class="title" nf=if="isReply" translate>MESSAGE.COMPOSE.TITLE_REPLY</h1> <button class="button button-icon button-clear icon ion-android-send visible-xs" ng-click="doSend()"> </button> diff --git a/www/plugins/es/templates/message/view_message.html b/www/plugins/es/templates/message/view_message.html new file mode 100644 index 0000000000000000000000000000000000000000..39e3e0b9c67afe5c107ddc05c423dd1e4ad8fbe5 --- /dev/null +++ b/www/plugins/es/templates/message/view_message.html @@ -0,0 +1,92 @@ +<ion-view left-buttons="leftButtons"> + <ion-nav-title> + <span translate>MESSAGE.VIEW.TITLE</span> + </ion-nav-title> + + <ion-nav-buttons side="secondary"> + <!--<button class="button button-bar button-icon button-clear visible-xs visible-sm" ng-click="edit()" ng-if="canEdit"> + <i class="icon ion-android-create"></i> + </button>--> + <button class="button button-bar button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm" + ng-click="actionsPopover.show($event)"> + </button> + </ion-nav-buttons> + + <ion-content scroll="true"> + + <div class="row no-padding"> + <div class="col col-20 hidden-xs hidden-sm"> </div> + + <div class="col no-padding"> + + <div class="center padding" ng-if="loading"> + <ion-spinner icon="android"></ion-spinner> + </div> + + <ion-list class="animate-fade-slide-in item-text-wrap"> + + + <div class="item" ng-class="{'item-avatar': formData.avatar}"> + <img ng-if="formData.avatar" class="item-image" ng-src="{{::formData.avatar.src}}"> + <h1 ng-bind-html="formData.title"></h1> + <h4> + <i class="ion-clock"></i> + <span translate>MESSAGE.VIEW.SENDER</span> + <a class="positive" ui-sref="app.wot_view_identity({pubkey:formData.pubkey, uid:formData.uid})"> + <span ng-if="formData.uid"> + <i class="ion-person"></i> + {{::formData.name||formData.uid}} + </span> + <span ng-if="!formData.uid"> + <i class="ion-key"></i> + {{::formData.pubkey|formatPubkey}} + </span> + </a> + <span> + {{formData.time|formatFromNow}} + <span class="gray hidden-xs">| + {{formData.time | formatDate}} + </span> + </span> + </h4> + </div> + + <!-- content --> + <ion-item> + <h2> + <span class="text-keep-lines" ng-bind-html="formData.content"></span> + </h2> + + <div class="padding gray" ng-if="!formData.content" translate> + MESSAGE.VIEW.NO_CONTENT + </div> + </ion-item> + + <!-- Buttons bar--> + <div class="item large-button-bar hidden-xs hidden-sm"> + <button class="button button-small button-stable icon-left ink-dark" + ng-click="delete()"> + <i class="icon ion-trash-a assertive"></i> + <span class="assertive"> {{'COMMON.BTN_DELETE' | translate}}</span> + </button> + <button class="button button-small button-stable icon ion-reply" + ng-click="showReplyModal()"> + {{'MESSAGE.BTN_REPLY' | translate}} + </button> + <!--<button class="button button-small button-stable icon ion-reply" + ng-click="showForwardModal()"> + {{'MESSAGE.BTN_FORWARD' | translate}} + </button>--> + </div> + </ion-list> + </div> + + <div class="col col-20 hidden-xs hidden-sm"> </div> + </div> + </ion-content> + + <button id="fab-view-message-reply" + class="button button-fab button-fab-bottom-right button-calm icon ion-reply visible-xs visible-sm spin" + ng-click="showReplyModal()"> + </button> +</ion-view> diff --git a/www/templates/menu.html b/www/templates/menu.html index 1041f3fda54b571f2f49661db70a7302eabd8515..4ec5122a6779e1191ba9e4ce54a2a037f0e5799e 100644 --- a/www/templates/menu.html +++ b/www/templates/menu.html @@ -69,11 +69,10 @@ <!-- USER Section --> <div class="item item-divider"></div> - <ion-item menu-close class="item item-icon-left item-menu-disable" ng-click="login('app.view_wallet')" ng-if="!$root.login"> - <i class="icon ion-card"></i> - <span translate>MENU.ACCOUNT</span> - </ion-item> - <ion-item menu-close class="item item-icon-left" active-link="active" href="#/app/wallet" ng-if="$root.login"> + <ion-item menu-close class="item item-icon-left" active-link="active" + active-link-path-prefix="#/app/wallet" + ng-click="loginAndGo('app.view_wallet')" + ng-class="{'item-menu-disable': !$root.login}"> <i class="icon ion-card"></i> <span translate>MENU.ACCOUNT</span> </ion-item> @@ -90,7 +89,7 @@ <div class="item item-divider" ng-if="$root.login"></div> <ion-item menu-close class="item item-button-right" ng-if="$root.login"> <span translate>MENU.TRANSFER</span> - <button class="button button-energized-900 ink-dark" ng-click="showTransferModal()"> + <button class="button button-positive ink-dark" ng-click="showTransferModal()"> <i class="icon ion-paper-airplane"></i> </button> </ion-item> diff --git a/www/templates/wallet/view_wallet.html b/www/templates/wallet/view_wallet.html index ae8aa37b1900154b02ddc67a3d93f0a2fd33fd2d..a24a74b05364531996ef3d2ce1b95eebe2bdd009 100644 --- a/www/templates/wallet/view_wallet.html +++ b/www/templates/wallet/view_wallet.html @@ -226,6 +226,15 @@ <span ng-if="$root.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> </div > </span> + + <div class="item" ng-if="walletData.tx.fromTime > 0"> + <p> + <a ng-click="showMoreTx()" translate>ACCOUNT.SHOW_MORE_TX</a> + <span class="gray" translate="ACCOUNT.TX_FROM_DATE" translate-values="{fromTime: walletData.tx.fromTime}"></span> + <span class="gray">|</span> + <a ng-click="showMoreTx(-1)" translate>ACCOUNT.SHOW_ALL_TX</a> + </p> + </div> </div> <div class="col col-20 hidden-xs hidden-sm">