Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
|
|
@ -0,0 +1,44 @@
|
|||
/* exported BalanceLowLimitView */
|
||||
'use strict';
|
||||
|
||||
function BalanceLowLimitView(lowLimitEnabler, lowLimitInput) {
|
||||
var self = this;
|
||||
|
||||
self._lowLimitEnabler = lowLimitEnabler;
|
||||
self._lowLimitInput = lowLimitInput;
|
||||
self._lowLimitEnabler.addEventListener('change', self);
|
||||
self._lowLimitInput.addEventListener('input', self);
|
||||
installProperties();
|
||||
self.validate();
|
||||
|
||||
function installProperties() {
|
||||
var disabled = false;
|
||||
Object.defineProperty(self, 'disabled', {
|
||||
get: function() {
|
||||
return disabled;
|
||||
},
|
||||
set: function(value) {
|
||||
disabled = value;
|
||||
self._lowLimitEnabler.disabled = disabled;
|
||||
self._lowLimitInput.disabled = disabled;
|
||||
self.validate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BalanceLowLimitView.prototype.handleEvent = function() {
|
||||
this.validate();
|
||||
};
|
||||
|
||||
BalanceLowLimitView.prototype.validate = function(e) {
|
||||
var numericValue = parseFloat(this._lowLimitInput.value, 10);
|
||||
|
||||
this._isValid = this.disabled || !this._lowLimitEnabler.checked ||
|
||||
!Number.isNaN(numericValue) && numericValue > 0;
|
||||
|
||||
this._lowLimitInput.classList[this._isValid ? 'remove' : 'add']('error');
|
||||
if (typeof this.onvalidation === 'function') {
|
||||
this.onvalidation({type: 'validation', isValid: this._isValid});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/* global _, Formatting */
|
||||
/* exported BalanceView */
|
||||
'use strict';
|
||||
|
||||
function BalanceView(balanceLabel, timestampLabel, minimumDelay) {
|
||||
|
||||
minimumDelay = minimumDelay || 0;
|
||||
|
||||
var viewTimestamp;
|
||||
|
||||
var timeHighlightColors = [
|
||||
{threshold: minimumDelay / 3, className: 'first-third'},
|
||||
{threshold: 2 * minimumDelay / 3, className: 'second-third'},
|
||||
{threshold: minimumDelay, className: 'third-third'},
|
||||
{threshold: 24 * 60 * 60 * 1000, className: 'yesterday'},
|
||||
{threshold: Infinity, className: 'before'}
|
||||
];
|
||||
|
||||
function update(balanceResult, isUpdating) {
|
||||
if (!balanceResult) {
|
||||
setBalanceToNotAvailable();
|
||||
} else {
|
||||
updateBalance(balanceResult.balance, balanceResult.currency);
|
||||
updateTimestamp(balanceResult.timestamp, isUpdating);
|
||||
}
|
||||
}
|
||||
|
||||
function setBalanceToNotAvailable() {
|
||||
balanceLabel.setAttribute('data-l10n-id', 'not-available');
|
||||
timestampLabel.innerHTML = '';
|
||||
}
|
||||
|
||||
function updateBalance(balance, currencySymbol) {
|
||||
balanceLabel.textContent = _('currency', {
|
||||
value: balance,
|
||||
currency: currencySymbol
|
||||
});
|
||||
}
|
||||
|
||||
function updateTimeFormat() {
|
||||
updateTimestamp(viewTimestamp);
|
||||
}
|
||||
|
||||
function updateTimestamp(timestamp, isUpdating) {
|
||||
timestampLabel.innerHTML = '';
|
||||
viewTimestamp = timestamp;
|
||||
|
||||
if (isUpdating) {
|
||||
timestampLabel.setAttribute('data-l10n-id', 'updating-ellipsis');
|
||||
} else if (typeof timestamp !== 'undefined') {
|
||||
var time = getTimeTagFor(timestamp);
|
||||
timestampLabel.appendChild(time);
|
||||
time.classList.add(getHighlightColor(timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeTagFor(timestamp) {
|
||||
var time = document.createElement('time');
|
||||
time.setAttribute('datetime', timestamp.toISOString());
|
||||
time.textContent = Formatting.formatTimeSinceNow(timestamp);
|
||||
return time;
|
||||
}
|
||||
|
||||
function getHighlightColor(timestamp) {
|
||||
if (!minimumDelay) {
|
||||
return 'first-third';
|
||||
}
|
||||
|
||||
var now = new Date(), then = new Date(timestamp);
|
||||
var age = now - then;
|
||||
for (var i = 0, l = timeHighlightColors.length; i < l; i++) {
|
||||
var category = timeHighlightColors[i];
|
||||
if (age < category.threshold) {
|
||||
return category.className;
|
||||
}
|
||||
}
|
||||
return timeHighlightColors[l - 1].className;
|
||||
}
|
||||
|
||||
window.addEventListener('timeformatchange', updateTimeFormat);
|
||||
|
||||
this.update = update;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
/* global Common */
|
||||
|
||||
function ConfirmDialog(container, viewManager) {
|
||||
this.id = container.id;
|
||||
this.container = container;
|
||||
this.viewManager = viewManager;
|
||||
this.message = container.querySelector('p');
|
||||
|
||||
this.okButton = container.querySelector('.danger');
|
||||
|
||||
this.cancelButton = container.querySelector('.close-reset-dialog');
|
||||
this.cancelButton.onclick = this.closeConfirmDialog.bind(this);
|
||||
}
|
||||
|
||||
ConfirmDialog.prototype.setMessage = function(l10nId, data) {
|
||||
Common.localize(this.message, l10nId, data || {});
|
||||
};
|
||||
|
||||
ConfirmDialog.prototype.updateAcceptAction = function(acceptAction) {
|
||||
this.okButton.onclick = acceptAction;
|
||||
};
|
||||
|
||||
ConfirmDialog.prototype.showConfirmDialog = function() {
|
||||
this.viewManager.changeViewTo(this.id, '#settings-view');
|
||||
};
|
||||
|
||||
ConfirmDialog.prototype.closeConfirmDialog = function() {
|
||||
this.viewManager.closeCurrentView();
|
||||
};
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
/* global Common */
|
||||
/* global _ */
|
||||
|
||||
function NonReadyScreen(container) {
|
||||
this.id = container.id;
|
||||
this.container = container;
|
||||
this.activity = container.querySelector('progress');
|
||||
this.header = container.querySelector('h1');
|
||||
this.message = container.querySelector('p');
|
||||
this.button = container.querySelector('button');
|
||||
|
||||
this.button.onclick = Common.closeApplication;
|
||||
}
|
||||
|
||||
// Configure the non ready screen depending on the card status. For
|
||||
// absent, pinRequired and pukRequired, shows a message and give the
|
||||
// user the opportunity to close the app. For other states, it shows
|
||||
// a progress spinner.
|
||||
NonReadyScreen.prototype.updateForState = function(cardState) {
|
||||
switch (cardState) {
|
||||
case null:
|
||||
case 'absent':
|
||||
case 'unknown':
|
||||
case 'pinRequired':
|
||||
case 'pukRequired':
|
||||
case 'airplaneMode':
|
||||
this.setMessageMode(cardState);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.setWaitingMode();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
NonReadyScreen.prototype.setMessageMode = function(cardState) {
|
||||
this.activity.hidden = true;
|
||||
[this.header, this.message, this.button.parentNode].forEach(function (el) {
|
||||
el.hidden = false;
|
||||
});
|
||||
|
||||
var messageId = this.getMessageIdFor(cardState);
|
||||
if (messageId) {
|
||||
var header = _('widget-' + messageId + '-heading');
|
||||
var msg = _('widget-' + messageId + '-meta');
|
||||
this.header.textContent = header;
|
||||
this.message.textContent = msg;
|
||||
}
|
||||
};
|
||||
|
||||
NonReadyScreen.prototype.setWaitingMode = function() {
|
||||
this.activity.hidden = false;
|
||||
[this.header, this.message, this.button.parentNode].forEach(function (el) {
|
||||
el.hidden = true;
|
||||
});
|
||||
};
|
||||
|
||||
NonReadyScreen.prototype.getMessageIdFor = function(cardState) {
|
||||
var message;
|
||||
|
||||
// SIM is absent
|
||||
if (!cardState || cardState === 'absent') {
|
||||
message = 'no-sim3';
|
||||
|
||||
// SIM is locked
|
||||
} else if (
|
||||
cardState === 'pinRequired' ||
|
||||
cardState === 'pukRequired'
|
||||
) {
|
||||
message = 'sim2-locked';
|
||||
} else if (cardState === 'airplaneMode') {
|
||||
message = 'airplane-mode';
|
||||
} else if (cardState === 'unknown') {
|
||||
message = 'unknown';
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
'use strict';
|
||||
|
||||
/* global Common, Settings, Formatting */
|
||||
|
||||
function ResetMenuDialog(container, viewManager) {
|
||||
this.id = container.id;
|
||||
this.container = container;
|
||||
this.viewManager = viewManager;
|
||||
this.dataUsage = {mobile:0, wifi:0};
|
||||
this.resetModes = ['wifi', 'mobile', 'all'];
|
||||
container.addEventListener('gaiamenu-cancel',
|
||||
this.closeResetMenuDialog.bind(this));
|
||||
}
|
||||
|
||||
ResetMenuDialog.prototype.initializeResetModes = function(confirmResetDialog) {
|
||||
this.resetModes.forEach(function(resetMode) {
|
||||
var newButton = document.createElement('button');
|
||||
newButton.id = 'reset-' + resetMode + '-data-usage';
|
||||
newButton.type = 'button';
|
||||
newButton.classList.add('reset-data-button');
|
||||
newButton.dataset.l10nId = 'reset-' + resetMode;
|
||||
newButton.onclick = function() {
|
||||
this.updateConfirmDialog(resetMode, confirmResetDialog);
|
||||
}.bind(this);
|
||||
this.container.appendChild(newButton);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ResetMenuDialog.prototype.updateConfirmDialog = function(type, confirmDialog) {
|
||||
var mobileUsage =
|
||||
Formatting.formatData(Formatting.roundData(this.dataUsage.mobile));
|
||||
var wifiUsage =
|
||||
Formatting.formatData(Formatting.roundData(this.dataUsage.wifi));
|
||||
var data = {
|
||||
mobileData: mobileUsage,
|
||||
wifiData: wifiUsage
|
||||
};
|
||||
var l10nId = 'reset-' + type + '-confirmation-warning';
|
||||
confirmDialog.setMessage(l10nId, data);
|
||||
function resetAction() {
|
||||
// Reset data type, take in count spent offsets to fix the charts
|
||||
Common.resetData(type);
|
||||
Settings.updateUI();
|
||||
confirmDialog.closeConfirmDialog();
|
||||
}
|
||||
confirmDialog.updateAcceptAction(resetAction);
|
||||
confirmDialog.showConfirmDialog();
|
||||
};
|
||||
|
||||
ResetMenuDialog.prototype.showResetMenuDialog = function(dataUsage) {
|
||||
this.dataUsage = dataUsage;
|
||||
this.viewManager.changeViewTo(this.id, '#settings-view');
|
||||
};
|
||||
|
||||
ResetMenuDialog.prototype.closeResetMenuDialog = function() {
|
||||
this.viewManager.closeCurrentView();
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/* exported TopUpLayoutView */
|
||||
'use strict';
|
||||
|
||||
function TopUpLayoutView(ussdTopUpButton, codeTopUpButton) {
|
||||
|
||||
function setupLayout(topUpConfig) {
|
||||
var ussdTopUpDisabled = !topUpConfig || !topUpConfig.ussd_destination;
|
||||
var codeTopUpDisabled = !topUpConfig || !topUpConfig.destination;
|
||||
ussdTopUpButton.disabled = ussdTopUpDisabled;
|
||||
ussdTopUpButton.hidden = ussdTopUpDisabled;
|
||||
codeTopUpButton.disabled = codeTopUpDisabled;
|
||||
codeTopUpButton.hidden = codeTopUpDisabled;
|
||||
}
|
||||
|
||||
this.setupLayout = setupLayout;
|
||||
}
|
||||
261
assets/webapps/costcontrol.gaiamobile.org/js/views/appdetail.js
Normal file
261
assets/webapps/costcontrol.gaiamobile.org/js/views/appdetail.js
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/* global _, debug, CostControl, Common, Formatting, ChartUtils,
|
||||
SimManager, ConfigManager */
|
||||
/* jshint -W120 */
|
||||
|
||||
/**
|
||||
* The App Detail view is in charge of usage charts and settings for an
|
||||
* individual app.
|
||||
*/
|
||||
'use strict';
|
||||
var AppDetailView = (function() {
|
||||
var costcontrol,
|
||||
initialized,
|
||||
els,
|
||||
app,
|
||||
localizedAppName,
|
||||
model;
|
||||
|
||||
function initialize() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
els = {};
|
||||
|
||||
CostControl.getInstance(function(instance) {
|
||||
costcontrol = instance;
|
||||
|
||||
// Get elements
|
||||
els.view = document.getElementById('appdetail-view');
|
||||
els.header = els.view.querySelector('gaia-header');
|
||||
els.headerHeading = els.header.querySelector('h1');
|
||||
els.usage = els.view.querySelector('.usage');
|
||||
els.graphicArea = els.usage.querySelector('#app-graphic-area');
|
||||
els.backgroundLayer =
|
||||
els.graphicArea.querySelector('#app-background-layer');
|
||||
els.wifiLayer = els.graphicArea.querySelector('#app-wifi-layer');
|
||||
els.mobileLayer = els.graphicArea.querySelector('#app-mobile-layer');
|
||||
els.axisLayer = els.graphicArea.querySelector('#app-axis-layer');
|
||||
els.todayLayer = els.graphicArea.querySelector('#app-today-layer');
|
||||
els.warningLayer = els.graphicArea.querySelector('#app-warning-layer');
|
||||
els.limitsLayer = els.graphicArea.querySelector('#app-limits-layer');
|
||||
els.graphicPattern =
|
||||
els.graphicArea.querySelector('#app-graphic-pattern');
|
||||
els.dataUsedSince =
|
||||
els.usage.querySelector('p[data-l10n-id="data-used-since"]');
|
||||
els.dataUsedThisWeek =
|
||||
els.usage.querySelector('p[data-l10n-id="data-used-this-week"]');
|
||||
els.dataUsedThisMonth =
|
||||
els.usage.querySelector('p[data-l10n-id="data-used-this-month"]');
|
||||
|
||||
// Setup the model
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
debug('First time setup for model');
|
||||
model = {
|
||||
height: ChartUtils.toDevicePixels(els.graphicArea.clientHeight),
|
||||
width: ChartUtils.toDevicePixels(els.graphicArea.clientWidth),
|
||||
originX: Math.floor(
|
||||
ChartUtils.toDevicePixels(els.graphicArea.clientWidth) * 0.15),
|
||||
endX: Math.floor(
|
||||
ChartUtils.toDevicePixels(els.graphicArea.clientWidth) * 0.95),
|
||||
axis: {
|
||||
Y: {
|
||||
lower: 0,
|
||||
margin: 0.20
|
||||
},
|
||||
X: {
|
||||
lower: ChartUtils.calculateLowerDate(settings),
|
||||
upper: ChartUtils.calculateUpperDate(settings)
|
||||
}
|
||||
},
|
||||
limits: {
|
||||
enabled: settings.dataLimit,
|
||||
value: ChartUtils.getLimitInBytes(settings),
|
||||
dataLimitValue: settings.dataLimitValue,
|
||||
dataLimitUnit: settings.dataLimitUnit
|
||||
},
|
||||
data: {
|
||||
wifi: {
|
||||
enabled: true
|
||||
},
|
||||
mobile: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
todayLabel: {}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Attach event listeners
|
||||
window.addEventListener('viewchanged', function(evt) {
|
||||
if (evt.detail.id === 'appdetail-view') {
|
||||
setAppManifestURL(evt.detail.params.manifestURL);
|
||||
}
|
||||
});
|
||||
|
||||
els.header.addEventListener('action', function(evt) {
|
||||
var type = evt.detail && evt.detail.type;
|
||||
if (type === 'back') {
|
||||
window.location.hash = '#';
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
function setAppManifestURL(manifestURL) {
|
||||
// Get App object
|
||||
app = Common.getApp(manifestURL);
|
||||
|
||||
// Update app usage data
|
||||
updateAppUsageData(app);
|
||||
|
||||
// Localize app name
|
||||
localizedAppName = Common.getLocalizedAppName(app);
|
||||
|
||||
// Update the localized app name throughout the view
|
||||
els.headerHeading.textContent = localizedAppName;
|
||||
}
|
||||
|
||||
function updateAppUsageData(app) {
|
||||
// Hide usage element until it has been updated/localized.
|
||||
els.usage.classList.remove('in');
|
||||
|
||||
var usageRequest = {
|
||||
type: 'datausage',
|
||||
apps: [app.manifestURL]
|
||||
};
|
||||
|
||||
// Request usage for this specific app
|
||||
costcontrol.request(usageRequest, function(result) {
|
||||
if (result.status !== 'success') {
|
||||
console.error('Error requesting data usage. This should not happen.');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = result.data;
|
||||
|
||||
var mobileDataTotal = data.mobile.total;
|
||||
var formattedMobileDataTotal = Formatting.formatData(
|
||||
Formatting.roundData(mobileDataTotal));
|
||||
|
||||
// Localize the data usage info
|
||||
els.dataUsedSince.textContent =
|
||||
_('data-used-since', {
|
||||
'amount': formattedMobileDataTotal,
|
||||
'start-date':
|
||||
Formatting.getFormattedDate(data.start,
|
||||
Formatting.formatters.longDate)
|
||||
});
|
||||
els.dataUsedThisWeek.textContent =
|
||||
_('data-used-this-week', {
|
||||
'amount': formattedMobileDataTotal
|
||||
});
|
||||
els.dataUsedThisMonth.textContent =
|
||||
_('data-used-this-month', {
|
||||
'amount': formattedMobileDataTotal
|
||||
});
|
||||
|
||||
els.dataUsedSince.hidden = true;
|
||||
els.dataUsedThisWeek.hidden = true;
|
||||
els.dataUsedThisMonth.hidden = true;
|
||||
|
||||
// Update the charts
|
||||
prepareChartData(result, function() {
|
||||
|
||||
// Render the charts
|
||||
renderChart();
|
||||
|
||||
// Fade in usage element
|
||||
els.usage.classList.add('in');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareChartData(result, callback) {
|
||||
if (result.status === 'success') {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
debug('Updating model');
|
||||
var modelData = result.data;
|
||||
model.data.wifi.samples = modelData.wifi.samples;
|
||||
model.data.wifi.total = modelData.wifi.total;
|
||||
model.data.wifi.apps = modelData.wifi.apps;
|
||||
|
||||
model.data.mobile.samples = modelData.mobile.samples;
|
||||
model.data.mobile.total = modelData.mobile.total;
|
||||
model.data.mobile.apps = modelData.mobile.apps;
|
||||
|
||||
model.limits.enabled = settings.dataLimit;
|
||||
model.limits.value = ChartUtils.getLimitInBytes(settings);
|
||||
model.limits.dataLimitValue = settings.dataLimitValue;
|
||||
model.axis.X.upper = ChartUtils.calculateUpperDate(settings);
|
||||
model.axis.X.lower = ChartUtils.calculateLowerDate(settings);
|
||||
ChartUtils.expandModel(model);
|
||||
|
||||
// Show correct usage text label
|
||||
switch (settings.trackingPeriod) {
|
||||
case 'weekly':
|
||||
els.dataUsedSince.hidden = true;
|
||||
els.dataUsedThisWeek.hidden = false;
|
||||
els.dataUsedThisMonth.hidden = true;
|
||||
break;
|
||||
case 'monthly':
|
||||
els.dataUsedSince.hidden = true;
|
||||
els.dataUsedThisWeek.hidden = true;
|
||||
els.dataUsedThisMonth.hidden = false;
|
||||
break;
|
||||
default:
|
||||
els.dataUsedSince.hidden = false;
|
||||
els.dataUsedThisWeek.hidden = true;
|
||||
els.dataUsedThisMonth.hidden = true;
|
||||
break;
|
||||
}
|
||||
|
||||
debug('Rendering');
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error('Error requesting data usage. This should not happen.');
|
||||
}
|
||||
}
|
||||
|
||||
function renderChart() {
|
||||
ChartUtils.drawBackgroundLayer(els.backgroundLayer, model, true);
|
||||
ChartUtils.drawTodayLayer(els.todayLayer, model);
|
||||
ChartUtils.drawAxisLayer(els.axisLayer, model, true);
|
||||
ChartUtils.drawDataLayer(els.wifiLayer, model, 'wifi', {
|
||||
stroke: ChartUtils.WIFI_CHART_STROKE,
|
||||
fill: ChartUtils.WIFI_CHART_FILL
|
||||
});
|
||||
ChartUtils.drawDataLayer(els.mobileLayer, model, 'mobile', {
|
||||
stroke: ChartUtils.MOBILE_CHART_STROKE,
|
||||
fill: ChartUtils.MOBILE_CHART_FILL,
|
||||
pattern: els.graphicPattern
|
||||
});
|
||||
ChartUtils.drawWarningLayer(els.warningLayer, model);
|
||||
ChartUtils.drawLimits(els.limitsLayer, model, true);
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
};
|
||||
}());
|
||||
|
||||
AppDetailView.initialize();
|
||||
468
assets/webapps/costcontrol.gaiamobile.org/js/views/balance.js
Normal file
468
assets/webapps/costcontrol.gaiamobile.org/js/views/balance.js
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
/* 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();
|
||||
492
assets/webapps/costcontrol.gaiamobile.org/js/views/datausage.js
Normal file
492
assets/webapps/costcontrol.gaiamobile.org/js/views/datausage.js
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
/* global debug, ConfigManager, CostControl, Formatting,
|
||||
SimManager, Common, ChartUtils */
|
||||
/* jshint -W120 */
|
||||
|
||||
/*
|
||||
* The data usage tab is in charge of usage charts of mobile and wi-fi networks.
|
||||
*
|
||||
* It has several canvas areas layered one above the others.
|
||||
*/
|
||||
'use strict';
|
||||
var DataUsageTab = (function() {
|
||||
|
||||
var graphicArea, graphicPattern;
|
||||
var backgroundLayer, wifiLayer, mobileLayer, axisLayer, todayLayer,
|
||||
warningLayer, limitsLayer;
|
||||
var wifiOverview, mobileOverview;
|
||||
var wifiToggle, mobileToggle;
|
||||
var appList, noData;
|
||||
|
||||
var costcontrol, initialized, model;
|
||||
|
||||
function setupTab() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
CostControl.getInstance(function _onCostControl(instance) {
|
||||
costcontrol = instance;
|
||||
|
||||
// HTML entities
|
||||
graphicArea = document.getElementById('graphic-area');
|
||||
graphicPattern = document.getElementById('graphic-pattern');
|
||||
backgroundLayer = document.getElementById('background-layer');
|
||||
wifiLayer = document.getElementById('wifi-layer');
|
||||
mobileLayer = document.getElementById('mobile-layer');
|
||||
axisLayer = document.getElementById('axis-layer');
|
||||
todayLayer = document.getElementById('today-layer');
|
||||
warningLayer = document.getElementById('warning-layer');
|
||||
limitsLayer = document.getElementById('limits-layer');
|
||||
wifiOverview = document.getElementById('wifiOverview');
|
||||
mobileOverview = document.getElementById('mobileOverview');
|
||||
wifiToggle = document.getElementById('wifiCheck');
|
||||
mobileToggle = document.getElementById('mobileCheck');
|
||||
appList = document.getElementById('app-usage-list');
|
||||
noData = document.getElementById('app-usage-no-data');
|
||||
|
||||
window.addEventListener('localized', localize);
|
||||
|
||||
// Update and chart visibility
|
||||
document.addEventListener('visibilitychange', updateWhenVisible);
|
||||
wifiToggle.addEventListener('change', toggleWifi);
|
||||
mobileToggle.addEventListener('change', toggleMobile);
|
||||
|
||||
// Setup the model
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
debug('First time setup for model');
|
||||
model = {
|
||||
height: ChartUtils.toDevicePixels(graphicArea.clientHeight),
|
||||
width: ChartUtils.toDevicePixels(graphicArea.clientWidth),
|
||||
originX: Math.floor(
|
||||
ChartUtils.toDevicePixels(graphicArea.clientWidth) * 0.15),
|
||||
endX: Math.floor(
|
||||
ChartUtils.toDevicePixels(graphicArea.clientWidth) * 0.95),
|
||||
axis: {
|
||||
Y: {
|
||||
lower: 0,
|
||||
margin: 0.20
|
||||
},
|
||||
X: {
|
||||
lower: ChartUtils.calculateLowerDate(settings),
|
||||
upper: ChartUtils.calculateUpperDate(settings)
|
||||
}
|
||||
},
|
||||
limits: {
|
||||
enabled: settings.dataLimit,
|
||||
value: ChartUtils.getLimitInBytes(settings),
|
||||
dataLimitValue: settings.dataLimitValue,
|
||||
dataLimitUnit: settings.dataLimitUnit
|
||||
},
|
||||
data: {
|
||||
wifi: {
|
||||
enabled: true
|
||||
},
|
||||
mobile: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
todayLabel: {}
|
||||
};
|
||||
ChartUtils.expandModel(model);
|
||||
resetButtonState(settings);
|
||||
|
||||
ConfigManager.observe('dataLimit', toggleDataLimit, true);
|
||||
ConfigManager.observe('dataLimitValue', setDataLimit, true);
|
||||
ConfigManager.observe('lastCompleteDataReset', updateDataUsage, true);
|
||||
ConfigManager.observe('lastDataReset', updateDataUsage, true);
|
||||
ConfigManager.observe('nextReset', changeNextReset, true);
|
||||
|
||||
function finishInit() {
|
||||
initialized = true;
|
||||
updateDataUsage();
|
||||
}
|
||||
|
||||
Common.loadApps()
|
||||
.then(finishInit)
|
||||
.catch(function(reason) {
|
||||
debug(reason);
|
||||
finishInit();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function localize() {
|
||||
if (initialized) {
|
||||
ChartUtils.drawTodayLayer(todayLayer, model);
|
||||
ChartUtils.drawAxisLayer(axisLayer, model, mobileToggle.checked);
|
||||
ChartUtils.drawLimits(limitsLayer, model, mobileToggle.checked);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.removeEventListener('visibilitychange', updateWhenVisible);
|
||||
wifiToggle.removeEventListener('click', toggleWifi);
|
||||
mobileToggle.removeEventListener('click', toggleMobile);
|
||||
ConfigManager.removeObserver('dataLimit', toggleDataLimit);
|
||||
ConfigManager.removeObserver('dataLimitValue', setDataLimit);
|
||||
ConfigManager.removeObserver('lastCompleteDataReset', updateDataUsage);
|
||||
ConfigManager.removeObserver('lastDataReset', updateDataUsage);
|
||||
ConfigManager.removeObserver('nextReset', changeNextReset);
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
function resetButtonState(settings) {
|
||||
var isMobileChartVisible = settings.isMobileChartVisible;
|
||||
if (typeof isMobileChartVisible === 'undefined') {
|
||||
isMobileChartVisible = true;
|
||||
}
|
||||
if (isMobileChartVisible !== mobileToggle.checked) {
|
||||
mobileToggle.checked = isMobileChartVisible;
|
||||
toggleMobile();
|
||||
}
|
||||
|
||||
var isWifiChartVisible = settings.isWifiChartVisible;
|
||||
if (typeof isWifiChartVisible === 'undefined') {
|
||||
isWifiChartVisible = false;
|
||||
}
|
||||
if (isWifiChartVisible !== wifiToggle.checked) {
|
||||
wifiToggle.checked = isWifiChartVisible;
|
||||
toggleWifi();
|
||||
}
|
||||
}
|
||||
|
||||
// UPDATES
|
||||
|
||||
// On visibility change
|
||||
function updateWhenVisible(evt) {
|
||||
if (!document.hidden) {
|
||||
updateDataUsage();
|
||||
}
|
||||
}
|
||||
|
||||
function requestDataUsage(perApp) {
|
||||
return new Promise(function(resolve) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
|
||||
var request = { type: 'datausage' };
|
||||
if (perApp) {
|
||||
request.apps = Common.allApps.map(function(app) {
|
||||
return app.manifestURL;
|
||||
});
|
||||
}
|
||||
|
||||
costcontrol.request(request, resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function maybeRequestPerAppUsage() {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!model.data.mobile.total) {
|
||||
noData.hidden = false;
|
||||
appList.hidden = true;
|
||||
// Repeated here to not rely on the deferred `updateApps()` call which
|
||||
// would add an annoying delay until clearing the data.
|
||||
clearAppList();
|
||||
|
||||
} else {
|
||||
noData.hidden = true;
|
||||
appList.hidden = false;
|
||||
|
||||
// Bug 1064491: request per-app data usage on the next tick of
|
||||
// the main loop to avoid performance regression in startup
|
||||
setTimeout(function() {
|
||||
requestDataUsage(true).then(updateApps);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCharts(result) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (result.status === 'success') {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
var modelData = result.data;
|
||||
model.data.wifi.samples = modelData.wifi.samples;
|
||||
model.data.wifi.total = modelData.wifi.total;
|
||||
|
||||
model.data.mobile.samples = modelData.mobile.samples;
|
||||
model.data.mobile.total = modelData.mobile.total;
|
||||
|
||||
model.limits.enabled = settings.dataLimit;
|
||||
model.limits.value = ChartUtils.getLimitInBytes(settings);
|
||||
model.limits.dataLimitValue = settings.dataLimitValue;
|
||||
model.axis.X.upper = ChartUtils.calculateUpperDate(settings);
|
||||
model.axis.X.lower = ChartUtils.calculateLowerDate(settings);
|
||||
ChartUtils.expandModel(model);
|
||||
resolve();
|
||||
|
||||
debug('Rendering');
|
||||
drawCharts();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
reject(new Error(
|
||||
'Error requesting data usage. This should not happen.'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateApps(result) {
|
||||
if (result.status === 'success') {
|
||||
model.data.wifi.apps = result.data.wifi.apps;
|
||||
model.data.mobile.apps = result.data.mobile.apps;
|
||||
drawApps(model);
|
||||
}
|
||||
}
|
||||
|
||||
// OBSERVERS
|
||||
|
||||
function toggleDataLimit(value) {
|
||||
model.limits.enabled = value;
|
||||
ChartUtils.drawBackgroundLayer(backgroundLayer, model,
|
||||
mobileToggle.checked);
|
||||
ChartUtils.drawAxisLayer(axisLayer, model, mobileToggle.checked);
|
||||
ChartUtils.drawLimits(limitsLayer, model, mobileToggle.checked);
|
||||
ChartUtils.drawWarningLayer(warningLayer, model);
|
||||
}
|
||||
|
||||
function setDataLimit(value, old, key, settings) {
|
||||
model.limits.value = ChartUtils.getLimitInBytes(settings);
|
||||
model.limits.dataLimitValue = settings.dataLimitValue;
|
||||
ChartUtils.expandModel(model);
|
||||
drawCharts();
|
||||
}
|
||||
|
||||
function updateDataUsage() {
|
||||
requestDataUsage()
|
||||
.then(updateCharts)
|
||||
.then(maybeRequestPerAppUsage);
|
||||
}
|
||||
|
||||
function changeNextReset(value, old, key, settings) {
|
||||
model.axis.X.upper = ChartUtils.calculateUpperDate(settings);
|
||||
model.axis.X.lower = ChartUtils.calculateLowerDate(settings);
|
||||
ChartUtils.expandModel(model);
|
||||
drawCharts();
|
||||
}
|
||||
|
||||
// USER INTERFACE
|
||||
|
||||
// On tapping on wifi toggle
|
||||
function toggleWifi() {
|
||||
var isChecked = wifiToggle.checked;
|
||||
wifiLayer.hidden = !isChecked;
|
||||
// save wifi toggled state
|
||||
ConfigManager.setOption({ isWifiChartVisible: isChecked });
|
||||
|
||||
if (model) {
|
||||
drawApps(model);
|
||||
}
|
||||
}
|
||||
|
||||
// On tapping on mobile toggle
|
||||
function toggleMobile() {
|
||||
var isChecked = mobileToggle.checked;
|
||||
mobileLayer.hidden = !isChecked;
|
||||
warningLayer.hidden = !isChecked;
|
||||
limitsLayer.hidden = !isChecked;
|
||||
// save wifi toggled state
|
||||
ConfigManager.setOption({ isMobileChartVisible: isChecked });
|
||||
|
||||
if (model) {
|
||||
ChartUtils.drawBackgroundLayer(backgroundLayer, model, isChecked);
|
||||
ChartUtils.drawAxisLayer(axisLayer, model, isChecked);
|
||||
ChartUtils.drawLimits(limitsLayer, model, isChecked);
|
||||
drawApps(model);
|
||||
}
|
||||
}
|
||||
|
||||
function drawCharts() {
|
||||
// Update overview
|
||||
var wifiData = Formatting.roundData(model.data.wifi.total);
|
||||
var mobileData = Formatting.roundData(model.data.mobile.total);
|
||||
wifiOverview.textContent = Formatting.formatData(wifiData);
|
||||
mobileOverview.textContent = Formatting.formatData(mobileData);
|
||||
|
||||
// Render the charts
|
||||
ChartUtils.drawBackgroundLayer(backgroundLayer, model,
|
||||
mobileToggle.checked);
|
||||
ChartUtils.drawTodayLayer(todayLayer, model);
|
||||
ChartUtils.drawAxisLayer(axisLayer, model, mobileToggle.checked);
|
||||
ChartUtils.drawDataLayer(wifiLayer, model, 'wifi', {
|
||||
stroke: ChartUtils.WIFI_CHART_STROKE,
|
||||
fill: ChartUtils.WIFI_CHART_FILL
|
||||
});
|
||||
ChartUtils.drawDataLayer(mobileLayer, model, 'mobile', {
|
||||
stroke: ChartUtils.MOBILE_CHART_STROKE,
|
||||
fill: ChartUtils.MOBILE_CHART_FILL,
|
||||
pattern: graphicPattern
|
||||
});
|
||||
ChartUtils.drawWarningLayer(warningLayer, model);
|
||||
ChartUtils.drawLimits(limitsLayer, model, mobileToggle.checked);
|
||||
drawApps(model);
|
||||
}
|
||||
|
||||
var cachedAppItems = {};
|
||||
function drawApps(model) {
|
||||
|
||||
function createAppItem(app) {
|
||||
var isSystem = app.manifestURL === Common.SYSTEM_MANIFEST;
|
||||
var appElement = document.createElement('li');
|
||||
appElement.className = 'app-item';
|
||||
|
||||
var linkElement = document.createElement('a');
|
||||
if (!isSystem) {
|
||||
linkElement.href = '##appdetail-view?manifestURL=' + app.manifestURL;
|
||||
}
|
||||
appElement.appendChild(linkElement);
|
||||
|
||||
var imgElement = document.createElement('img');
|
||||
imgElement.className = 'app-image';
|
||||
imgElement.src = Common.getAppIcon(app);
|
||||
imgElement.setAttribute('role', 'presentation');
|
||||
imgElement.onerror = function() {
|
||||
console.warn('Unable to load icon: ' + this.src);
|
||||
this.src = '/style/images/app/icons/default.png';
|
||||
};
|
||||
linkElement.appendChild(imgElement);
|
||||
|
||||
var appInfoElement = document.createElement('div');
|
||||
appInfoElement.className = 'app-info';
|
||||
linkElement.appendChild(appInfoElement);
|
||||
|
||||
var nameElement = document.createElement('div');
|
||||
nameElement.className = 'app-info-row app-name';
|
||||
nameElement.textContent = Common.getLocalizedAppName(app);
|
||||
appInfoElement.appendChild(nameElement);
|
||||
|
||||
var barElement = document.createElement('div');
|
||||
barElement.className = 'app-info-row app-usage-bar';
|
||||
barElement.setAttribute('role', 'presentation');
|
||||
appInfoElement.appendChild(barElement);
|
||||
|
||||
var usedBarElement = document.createElement('div');
|
||||
usedBarElement.className = 'app-usage-bar-used';
|
||||
usedBarElement.setAttribute('role', 'presentation');
|
||||
barElement.appendChild(usedBarElement);
|
||||
|
||||
var usageElement = document.createElement('div');
|
||||
usageElement.className = 'app-info-row app-usage-total';
|
||||
appInfoElement.appendChild(usageElement);
|
||||
|
||||
return appElement;
|
||||
}
|
||||
|
||||
function updateAppItemUsage(appItem, total) {
|
||||
var usedBarElement =
|
||||
appItem.getElementsByClassName('app-usage-bar-used')[0];
|
||||
var totalElement =
|
||||
appItem.getElementsByClassName('app-usage-total')[0];
|
||||
|
||||
var barTotal = mobileTotal;
|
||||
if (model.limits.enabled && model.limits.value !== null) {
|
||||
barTotal = Math.max(barTotal, model.limits.value);
|
||||
}
|
||||
|
||||
var usedPercent = (total / barTotal) * 100;
|
||||
usedBarElement.style.width = usedPercent + '%';
|
||||
totalElement.textContent = '' + Formatting.formatData(
|
||||
Formatting.roundData(total));
|
||||
}
|
||||
|
||||
// Front-end workaround for Bug 1083680: Noticeable difference between by
|
||||
// application breakdown totals and the total displayed in chart and widget.
|
||||
// This method adds the residual traffic (the traffic that cannot be not
|
||||
// allocated to an app) to the System application.
|
||||
function fixResidualTraffic() {
|
||||
var breakdownTotal = 0;
|
||||
mobileApps[Common.SYSTEM_MANIFEST] = {total: 0};
|
||||
if (manifests.length > 0) {
|
||||
breakdownTotal =
|
||||
manifests.reduce(function(accumulatedTraffic, appManifest) {
|
||||
return accumulatedTraffic + mobileApps[appManifest].total;
|
||||
}, 0);
|
||||
}
|
||||
var residualTraffic = mobileTotal - breakdownTotal;
|
||||
// System traffic is the residual traffic
|
||||
if (residualTraffic > 0) {
|
||||
mobileApps[Common.SYSTEM_MANIFEST].total = residualTraffic;
|
||||
if (!manifests[Common.SYSTEM_MANIFEST]) {
|
||||
manifests.push(Common.SYSTEM_MANIFEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearAppList();
|
||||
|
||||
var mobileTotal = model.data.mobile.total;
|
||||
var mobileApps = model.data.mobile.apps;
|
||||
if (!mobileApps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out apps that have not used any data yet.
|
||||
var manifests = Object.keys(mobileApps).filter(function(key) {
|
||||
return mobileApps[key].total > 0;
|
||||
});
|
||||
|
||||
// Note: The second premise (mobileTotal === 0) must be removed when the
|
||||
// fixResidualTraffic method will be eliminated.
|
||||
if (manifests.length === 0 && mobileTotal === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fixResidualTraffic();
|
||||
|
||||
// Sort by total data usage, descending
|
||||
manifests.sort(function(a, b) {
|
||||
return mobileApps[b].total - mobileApps[a].total;
|
||||
});
|
||||
|
||||
var fragment = document.createDocumentFragment();
|
||||
manifests.forEach(function(manifestURL) {
|
||||
var app = Common.allApps.find(function(app) {
|
||||
return app.manifestURL === manifestURL;
|
||||
});
|
||||
|
||||
if (!app) {
|
||||
debug('No app with manifest URL: ' + manifestURL);
|
||||
return;
|
||||
}
|
||||
|
||||
var appTotal = mobileApps[manifestURL].total;
|
||||
var appItem = cachedAppItems[manifestURL];
|
||||
if (!appItem) {
|
||||
appItem = cachedAppItems[manifestURL] = createAppItem(app);
|
||||
}
|
||||
|
||||
updateAppItemUsage(appItem, appTotal);
|
||||
fragment.appendChild(appItem);
|
||||
});
|
||||
|
||||
appList.appendChild(fragment);
|
||||
}
|
||||
|
||||
function clearAppList() {
|
||||
appList.innerHTML = '';
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: setupTab,
|
||||
finalize: finalize
|
||||
};
|
||||
}());
|
||||
|
||||
DataUsageTab.initialize();
|
||||
127
assets/webapps/costcontrol.gaiamobile.org/js/views/telephony.js
Normal file
127
assets/webapps/costcontrol.gaiamobile.org/js/views/telephony.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/* global CostControl, ConfigManager, debug,
|
||||
Formatting, SimManager, Common */
|
||||
/*
|
||||
* The telephony tab is in charge of show telephony and billing cycle
|
||||
* information.
|
||||
*
|
||||
* It has two areas of drawing: one for the counters and another for
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var TelephonyTab = (function() {
|
||||
var costcontrol, initialized = false;
|
||||
var view, smscount, calltime, time, resetDate;
|
||||
var telephonyPeriod = { begin: null, end: null };
|
||||
|
||||
function setupTab() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
CostControl.getInstance(function _onCostControl(instance) {
|
||||
costcontrol = instance;
|
||||
|
||||
// HTML entities
|
||||
view = document.getElementById('telephony-tab');
|
||||
smscount = document.getElementById('telephony-smscount');
|
||||
calltime = document.getElementById('telephony-calltime');
|
||||
time = document.getElementById('telephony-time');
|
||||
resetDate = document.getElementById('reset-date');
|
||||
|
||||
window.addEventListener('localized', localize);
|
||||
|
||||
// Configure updates
|
||||
document.addEventListener('visibilitychange', updateWhenVisible, true);
|
||||
ConfigManager.observe('lastTelephonyActivity', updateCounters, true);
|
||||
ConfigManager.observe('lastTelephonyReset', updateUI, true);
|
||||
ConfigManager.observe('nextReset', updateNextReset, true);
|
||||
|
||||
// Timeformat
|
||||
window.addEventListener('timeformatchange', function () {
|
||||
updateTimePeriod(
|
||||
telephonyPeriod.begin, null, null, telephonyPeriod.end);
|
||||
});
|
||||
|
||||
updateUI();
|
||||
initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
function localize() {
|
||||
if (initialized) {
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.removeEventListener('visibilitychange', updateWhenVisible);
|
||||
ConfigManager.removeObserver('lastTelephonyActivity', updateCounters);
|
||||
ConfigManager.removeObserver('lastTelephonyReset', updateUI);
|
||||
ConfigManager.removeObserver('nextReset', updateNextReset);
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
function updateWhenVisible() {
|
||||
if (!document.hidden && initialized) {
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
var requestObj = { type: 'telephony' };
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
costcontrol.request(requestObj, function _afterRequest(result) {
|
||||
var telephonyActivity = result.data;
|
||||
debug('Last telephony activity:', telephonyActivity);
|
||||
updateTimePeriod(settings.lastTelephonyReset, null, null, settings);
|
||||
updateCounters(telephonyActivity);
|
||||
updateNextReset(settings.nextReset, null, null, settings);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateTimePeriod(lastReset, old, key, settings) {
|
||||
telephonyPeriod.begin = lastReset;
|
||||
telephonyPeriod.end = settings.lastTelephonyActivity.timestamp;
|
||||
time.innerHTML = '';
|
||||
time.appendChild(Formatting.formatTimeHTML(lastReset, telephonyPeriod.end));
|
||||
}
|
||||
|
||||
function updateCounters(activity) {
|
||||
Common.localize(smscount, 'magnitude', {
|
||||
value: activity.smscount,
|
||||
unit: 'SMS'
|
||||
});
|
||||
Common.localize(calltime, 'magnitude', {
|
||||
value: Formatting.computeTelephonyMinutes(activity),
|
||||
unit: 'min.'
|
||||
});
|
||||
}
|
||||
|
||||
function updateNextReset(reset, old, key, settings) {
|
||||
var billingCycle = document.getElementById('billing-cycle');
|
||||
if (settings.trackingPeriod === 'never') {
|
||||
billingCycle.hidden = true;
|
||||
} else {
|
||||
billingCycle.hidden = false;
|
||||
var content = Formatting.getFormattedDate(settings.nextReset,
|
||||
Formatting.formatters.shortDate);
|
||||
resetDate.textContent = content;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: setupTab,
|
||||
finalize: finalize
|
||||
};
|
||||
}());
|
||||
|
||||
TelephonyTab.initialize();
|
||||
Loading…
Reference in a new issue