/* global BalanceView, TopUpLayoutView, CostControl, ConfigManager, ViewManager, debug, MozActivity, getTopUpTimeout, LazyLoader, sendBalanceThresholdNotification */ /* * The balance tab is in charge of show balance details and allows the use * to manually update or top up via USSD or code. * * It has four areas of drawing: balance, top up dialog, top up countdown and * error area. The update is done by demand when the application is shown or * when the user taps on the update button. Furthermore, the balance update * can be triggered by a successful top up request. */ 'use strict'; var BalanceTab = (function() { var view, updateButton; var topUpUSSD, topUp, topUpDialog, topUpCodeInput, sendCode, countdownSpan; var balanceView, topUpLayoutView; var costcontrol, initialized; function showTopUpWithCode(evt) { topUpWithCode(); } function setupTab() { if (initialized) { return; } CostControl.getInstance(function _onCostControl(instance) { costcontrol = instance; // we need to load topUpDialog to get elements from inside // it should be a separate function run on startup of the module // after HTML is generated topUpDialog = document.getElementById('topup-dialog'); var vm = new ViewManager(); // XXX: used to eagerly load top up dialog vm.loadPanel(topUpDialog); // HTML entities view = document.getElementById('balance-tab'); updateButton = document.getElementById('balance-tab-update-button'); sendCode = document.getElementById('topup-send-button'); topUpUSSD = document.getElementById('balance-tab-topup-ussd-button'); topUp = document.getElementById('balance-tab-topup-button'); topUpCodeInput = document.getElementById('topup-code-input'); countdownSpan = document.getElementById('top-up-countdown'); // Subviews topUpLayoutView = new TopUpLayoutView(topUpUSSD, topUp); var balanceConfig = ConfigManager.configuration.balance; balanceView = new BalanceView( document.getElementById('balance-tab-credit'), document.getElementById('balance-tab-time'), balanceConfig ? balanceConfig.minimum_delay : undefined ); window.addEventListener('localized', localize); // Configure updates document.addEventListener('visibilitychange', updateWhenVisible); updateButton.addEventListener('click', lockAndUpdateUI); ConfigManager.observe('lowLimit', toogleLimits, true); ConfigManager.observe('lowLimitThreshold', resetNotification, true); ConfigManager.observe('lastBalance', onBalance, true); ConfigManager.observe('errors', onBalanceTimeout, true); // Configure top up topUpUSSD.addEventListener('click', topUpWithUSSD); topUp.addEventListener('click', showTopUpWithCode); topUpCodeInput.addEventListener('input', toogleSend); sendCode.addEventListener('click', requestTopUp); ConfigManager.observe('waitingForTopUp', onConfirmation, true); ConfigManager.observe('errors', onTopUpErrors, true); toogleSend(); updateUI(); initialized = true; }); } function localize() { if (initialized) { updateUI(); } } function finalize() { if (!initialized) { return; } document.removeEventListener('visibilitychange', updateWhenVisible); updateButton.removeEventListener('click', lockAndUpdateUI); ConfigManager.removeObserver('lowLimit', toogleLimits); ConfigManager.removeObserver('lowLimitThreshold', resetNotification); ConfigManager.removeObserver('lastBalance', onBalance); ConfigManager.removeObserver('errors', onBalanceTimeout); topUpUSSD.removeEventListener('click', topUpWithUSSD); topUp.removeEventListener('click', showTopUpWithCode); topUpCodeInput.removeEventListener('input', toogleSend); sendCode.removeEventListener('click', requestTopUp); ConfigManager.removeObserver('waitingForTopUp', onConfirmation); ConfigManager.removeObserver('errors', onTopUpErrors); topUpLayoutSet = false; initialized = false; } // BALANCE ACTIONS // On showing the application function updateWhenVisible() { if (!document.hidden && initialized) { updateUI(); } else { clearInterval(topUpCountdown); } } // On tapping update function lockAndUpdateUI() { updateButton.disabled = true; updateUI(true); } // On changing low limit alert function toogleLimits(isEnabled, old, key, settings) { updateBalance(settings.lastBalance, isEnabled && settings.lowLimitThreshold); if (isEnabled) { sendBalanceThresholdNotification(settings.lastBalance, settings, false); updateBalance(settings.lastBalance, settings.lowLimitThreshold); } } // On changing the threshold for low limit function resetNotification() { ConfigManager.setOption({ 'lowLimitNotified': false }); ConfigManager.requestAll(function _onSettings(configuration, settings) { updateBalance(settings.lastBalance, settings.lowLimitThreshold); sendBalanceThresholdNotification(settings.lastBalance, settings, false); }); } // On balance update received function onBalance(balance, old, key, settings) { debug('Balance received:', balance); setBalanceMode(); updateBalance(balance, settings.lowLimit && settings.lowLimitThreshold); debug('Balance updated!'); } // On balance timeout function onBalanceTimeout(errors) { if (!errors.BALANCE_TIMEOUT) { return; } debug('Balance timeout!'); setBalanceMode('warning'); setError('balance_error'); // Error handled, disabling errors.BALANCE_TIMEOUT = false; ConfigManager.setOption({errors: errors}); } // TOP UP ACTIONS // On tapping Top Up and Pay function topUpWithUSSD() { var dialing; dialing = new MozActivity({ name: 'dial', data: { type: 'webtelephony/number', number: ConfigManager.configuration.topup.ussd_destination } }); } // On tapping Top Up with code function topUpWithCode(lastWasError) { window.location.hash = '##topup-dialog'; if (lastWasError) { setTopUpMode('incorrect_code'); } topUpCodeInput.focus(); } // On typing the code function toogleSend(evt) { sendCode.disabled = !topUpCodeInput.value.trim(); } // On tapping send code button var DIALOG_TIMEOUT = 10 * 1000; // 10s function requestTopUp() { sendCode.disabled = true; var code = topUpCodeInput.value.trim(); var requestObj = { type: 'topup', data: code }; costcontrol.request(requestObj, function _onTopUpRequest(result) { debug(result); sendCode.disabled = false; var status = result.status; if (status === 'success' || status === 'in_progress') { setTopUpMode('in_progress'); setTimeout(closeTopUp, DIALOG_TIMEOUT); } else if (status === 'error') { setTopUpMode(result.details); } }); } function closeTopUp() { // Remove from hash if (isTopUpShown()) { window.location.hash = '#'; } } function isTopUpShown() { return window.location.hash.split('#')[2] === topUpDialog.id; } // On confirmation SMS for top up received function onConfirmation(isWaiting) { if (isWaiting !== null) { return; } debug('TopUp confirmed!'); setTopUpMode('default'); topUpCodeInput.value = ''; updateUI(); } // On top up timeout or incorrect code function onTopUpErrors(errors) { debug('ERRORS:', errors); var mode; if (errors.TOPUP_TIMEOUT) { errors.TOPUP_TIMEOUT = false; mode = 'topup_timeout'; } if (errors.INCORRECT_TOPUP_CODE) { errors.INCORRECT_TOPUP_CODE = false; mode = 'incorrect_code'; } debug('Most important error: ', mode); // XXX: Very important to call this only if something has changed. If not, // infinite events are triggered. if (mode) { setTopUpMode(mode); ConfigManager.setOption({errors: errors}); } } // USER INTERFACE var topUpLayoutSet = false; function updateUI(force) { ConfigManager.requestAll(function _onSettings(configuration, settings) { if (!topUpLayoutSet) { topUpLayoutView.setupLayout(configuration.topup); topUpLayoutSet = true; } resetTopUpCountdown(); updateBalance(settings.lastBalance, settings.lowLimit && settings.lowLimitThreshold); var requestObj = { type: 'balance', force: !!force }; costcontrol.request(requestObj, function _onRequest(result) { debug(result); updateButton.disabled = false; var status = result.status; var balance = result.data; var isError = status === 'error' && result.details !== 'minimum_delay'; var isUpdating = status !== 'error'; if (isError) { setBalanceMode('warning'); setError(result.details); } else { setBalanceMode(isUpdating ? 'updating' : 'default'); setError(); } updateBalance(balance, settings.lowLimit && settings.lowLimitThreshold); }); }); } // Update the balance in balance view function updateBalance(balance, limit) { if (!balance) { debug('Balance not available'); balanceView.update(); return; } var isUpdating = view.classList.contains('updating'); balanceView.update(balance, isUpdating); // Limits: reaching zero / low limit if (balance.balance === 0) { view.classList.add('no-credit'); } else { view.classList.remove('no-credit'); if (limit && balance.balance < limit) { view.classList.add('low-credit'); } else { view.classList.remove('low-credit'); } } } // Set warning / updating modes var lastBalanceMode; function setBalanceMode(mode) { if (mode === lastBalanceMode) { return; } lastBalanceMode = mode; view.classList.remove('updating'); view.classList.remove('warning'); if (mode === 'warning') { view.classList.add('warning'); } if (mode === 'updating') { view.classList.add('updating'); } } // Control info messages in the top up dialog as well as the top up countdown // and error messages in the error area var lastTopUpMode; function setTopUpMode(mode) { if (lastTopUpMode === mode) { return; } lastTopUpMode = mode; // Messages in top up dialog var isShown; var defaultMessage = document.getElementById('topup-code-explanation'); var inprogressMessage = document.getElementById('topup-in-progress'); var errorMessage = document.getElementById('topup-error'); var incorrectCodeMessage = document.getElementById('topup-incorrect-code'); isShown = (mode === 'default' || mode === 'topup_timeout'); defaultMessage.hidden = !isShown; isShown = (mode === 'in_progress'); inprogressMessage.hidden = !isShown; isShown = (mode === 'request_fail' || mode === 'no_service'); errorMessage.hidden = !isShown; isShown = (mode === 'incorrect_code'); incorrectCodeMessage.hidden = !isShown; // Count down var countdownArea = document.getElementById('cost-control-topup-countdown'); isShown = (mode === 'in_progress'); countdownArea.hidden = !isShown; if (isShown) { resetTopUpCountdown(); } // Messages in error message area if (mode === 'topup_timeout') { setError(mode); } } var topUpCountdown, countdown; function resetTopUpCountdown() { getTopUpTimeout(function(timeout) { if (!timeout) { return; } countdown = Math.floor((timeout.getTime() - Date.now()) / 1000); if (countdown < 0) { return; } clearInterval(topUpCountdown); topUpCountdown = setInterval(function _updateCountdown() { var minutes = Math.floor(countdown / 60); var seconds = (countdown % 60).toFixed(0); var padding = ''; if (seconds < 10) { padding = '0'; } countdownSpan.textContent = (minutes + ' : ' + padding + seconds); if (countdown > 0) { countdown -= 1; } }, 1000); }); } var ERRORS = { 'airplane_mode': { priority: 1, string: 'airplane-mode-error-message2' }, 'no_service': { priority: 2, string: 'no-coverage-error-message2' }, 'no_coverage': { priority: 2, string: 'no-coverage-error-message' }, 'topup_timeout': { priority: 3, string: 'top-up-timed-out2' }, 'balance_error': { priority: 4, string: 'balance-error-message2' }, 'non_free_in_roaming': { priority: 4, string: 'on-roaming-message2' } }; var currentError = ''; // Decide which error should be shown taking in count error priorities function setError(error) { // Ignore showing message if the message is not registered if (error && !ERRORS[error]) { return; } debug('Error mode:', error ? error : 'no error'); var messageArea = document.getElementById('cost-control-message-area'); var message = document.getElementById('error-message-placeholder'); if (!error) { messageArea.hidden = true; message.removeAttribute('data-l10n-id'); message.textContent = ''; } else { messageArea.hidden = false; var curPriority = currentError ? ERRORS[currentError].priority : 0; var newPriority = ERRORS[error] ? ERRORS[error].priority : 0; if (newPriority >= curPriority) { message.setAttribute('data-l10n-id', ERRORS[error].string); currentError = error; } } } return { topUpWithCode: topUpWithCode, initialize: function() { var SCRIPTS_NEEDED = [ 'js/views/TopUpLayoutView.js', 'js/views/BalanceView.js' ]; LazyLoader.load(SCRIPTS_NEEDED, setupTab); }, finalize: finalize }; }()); BalanceTab.initialize();