Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
468 lines
14 KiB
JavaScript
Executable file
468 lines
14 KiB
JavaScript
Executable file
/* 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();
|