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.
38
gaia/apps/costcontrol/build/build.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var utils = require('utils');
|
||||
|
||||
function removeDesktopOnlyFolder(appStageDir) {
|
||||
var desktopOnlyDir = utils.getFile(appStageDir, 'js', 'desktop-only');
|
||||
|
||||
if (desktopOnlyDir.exists()) {
|
||||
desktopOnlyDir.remove(true);
|
||||
}
|
||||
}
|
||||
|
||||
function removeDesktopOnlyScripts(appStageDir) {
|
||||
var desktopOnlyScriptsRegex = /<script.+desktop\-only.+<\/script>/g;
|
||||
var filesToProcess = [
|
||||
'index.html',
|
||||
'fte.html',
|
||||
'message_handler.html',
|
||||
'settings.html'
|
||||
];
|
||||
|
||||
filesToProcess.forEach(function(fileName) {
|
||||
var file = utils.getFile(appStageDir, fileName);
|
||||
utils.writeContent(
|
||||
file,
|
||||
utils.getFileContent(file).replace(desktopOnlyScriptsRegex, '')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
exports.execute = function(options) {
|
||||
utils.copyToStage(options);
|
||||
|
||||
removeDesktopOnlyFolder(options.STAGE_APP_DIR);
|
||||
removeDesktopOnlyScripts(options.STAGE_APP_DIR);
|
||||
};
|
||||
26
gaia/apps/costcontrol/debug.html
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
<!-- XXX: Only available if DEBUGGING === true -->
|
||||
<header>
|
||||
<h2>Development</h2>
|
||||
</header>
|
||||
<ul class="settings">
|
||||
<li>
|
||||
<gaia-switch class="settings-option" data-option="fte">
|
||||
<label>Enable first time experience</label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li>
|
||||
<gaia-switch class="settings-option" data-option="lowLimitNotified">
|
||||
<label>Low limit already notified</label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li>
|
||||
<gaia-switch class="settings-option" data-option="zeroBalanceNotified">
|
||||
<label>Zero balance already notified</label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li>
|
||||
<gaia-switch class="settings-option" data-option="dataUsageNotified">
|
||||
<label>Data usage already notified</label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
</ul>
|
||||
34
gaia/apps/costcontrol/elements/appdetail-view.html
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
<element name="appdetail-view" extends="section">
|
||||
<template>
|
||||
<gaia-header action="back">
|
||||
<h1>{{localized-app-name}}</h1>
|
||||
</gaia-header>
|
||||
|
||||
<div class="view-scroll">
|
||||
<section class="usage fade">
|
||||
<section id="app-graphic-area" class="graph" role="img" aria-labelledby="app-axis-layer app-today-layer app-limits-layer app-warning-layer">
|
||||
<canvas id="app-background-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="app-wifi-layer" hidden aria-hidden="true"></canvas>
|
||||
<canvas id="app-mobile-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="app-axis-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="app-today-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="app-warning-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="app-limits-layer" aria-hidden="true"></canvas>
|
||||
<img hidden alt="" src="./style/images/app/ui/datausage/pattern.png" id="app-graphic-pattern" />
|
||||
</section>
|
||||
|
||||
<p class="info" data-l10n-id="data-used-since" hidden>
|
||||
{{amount}} of data used since {{start-date}}
|
||||
</p>
|
||||
<p class="info" data-l10n-id="data-used-this-week" hidden>
|
||||
{{amount}} of data used this week
|
||||
</p>
|
||||
<p class="info" data-l10n-id="data-used-this-month" hidden>
|
||||
{{amount}} of data used this month
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" defer src="js/views/appdetail.js"></script>
|
||||
</template>
|
||||
</element>
|
||||
61
gaia/apps/costcontrol/elements/balance-tab.html
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
<element name="balance-tab" extends="section">
|
||||
<template>
|
||||
|
||||
<section class="content">
|
||||
|
||||
<div id="cost-control-topup-countdown" class="countdown" hidden>
|
||||
<p>
|
||||
<span id="top-up-countdown">
|
||||
00:00
|
||||
</span>
|
||||
|
||||
<span data-l10n-id="top-up-in-progress2">
|
||||
Your top-up is being processed.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="cost-control-message-area" class="error-messages" hidden>
|
||||
<p id="error-message-placeholder">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="cost-control-info-area" class="overview">
|
||||
<ul class="buttons-list end">
|
||||
<li>
|
||||
<button class="icon icon-reload" id="balance-tab-update-button" data-l10n-id="update-button">
|
||||
Update
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="info">
|
||||
<div id="cost-control-icon" class="icons">
|
||||
<span class="warning">
|
||||
</span>
|
||||
<progress>
|
||||
</progress>
|
||||
</div>
|
||||
<p id="balance-tab-credit">
|
||||
</p>
|
||||
<p id="balance-tab-time">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="buttons-list">
|
||||
<li>
|
||||
<button class="icon icon-topup" id="balance-tab-topup-ussd-button" data-l10n-id="topup-ussd-button2" hidden>
|
||||
Open top-up menu
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="icon icon-topupcode" id="balance-tab-topup-button" data-l10n-id="topup-button2" hidden>
|
||||
Use top-up code
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<script type="text/javascript" defer src="js/views/balance.js"></script>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
</element>
|
||||
54
gaia/apps/costcontrol/elements/datausage-tab.html
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
<element name="datausage-tab" extends="section">
|
||||
<template>
|
||||
|
||||
<div class="view-scroll">
|
||||
<section class="content">
|
||||
<section id="graphic-area" class="graph" role="img" aria-labelledby="axis-layer today-layer limits-layer warning-layer">
|
||||
<canvas id="background-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="wifi-layer" hidden aria-hidden="true"></canvas>
|
||||
<canvas id="mobile-layer" hidden aria-hidden="true"></canvas>
|
||||
<canvas id="axis-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="today-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="warning-layer" aria-hidden="true"></canvas>
|
||||
<canvas id="limits-layer" aria-hidden="true"></canvas>
|
||||
<img hidden alt="" src="./style/images/app/ui/datausage/pattern.png" id="graphic-pattern" />
|
||||
</section>
|
||||
|
||||
<ul class="toggle-data" data-type="list">
|
||||
<li id="mobileItem">
|
||||
<gaia-checkbox id="mobileCheck">
|
||||
<label>
|
||||
<span class="data-origin-label" data-l10n-id="mobile-usage"></span>
|
||||
<span class="overview" id="mobileOverview"></span>
|
||||
<span></span>
|
||||
</label>
|
||||
</gaia-checkbox>
|
||||
</li>
|
||||
<li id="wifiItem">
|
||||
<gaia-checkbox id="wifiCheck">
|
||||
<label>
|
||||
<span class="data-origin-label" data-l10n-id="wifi-usage">-</span>
|
||||
<span class="overview" id="wifiOverview"></span>
|
||||
<span></span>
|
||||
</label>
|
||||
</gaia-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<header id="app-usage-header">
|
||||
<h2 data-l10n-id="app-usage-list-header">Mobile App Usage</h2>
|
||||
</header>
|
||||
|
||||
<div id="app-usage-no-data" data-l10n-id="app-usage-no-data" hidden>
|
||||
You have not used any mobile data yet. Usage for each application will
|
||||
show up here when you do.
|
||||
</div>
|
||||
|
||||
<ul id="app-usage-list">
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
<script type="text/javascript" defer src="js/views/datausage.js"></script>
|
||||
|
||||
</template>
|
||||
</element>
|
||||
38
gaia/apps/costcontrol/elements/telephony-tab.html
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
<element name="telephony-tab" extends="section">
|
||||
<template>
|
||||
|
||||
<section>
|
||||
<ul>
|
||||
<li>
|
||||
<p id="telephony-time">
|
||||
</p>
|
||||
<dl>
|
||||
<dt data-l10n-id="call-made">
|
||||
Call made
|
||||
</dt>
|
||||
<dd>
|
||||
<span id="telephony-calltime">
|
||||
</span>
|
||||
</dd>
|
||||
<dt data-l10n-id="sms-sent">
|
||||
SMS Sent
|
||||
</dt>
|
||||
<dd>
|
||||
<span id="telephony-smscount">
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li id="billing-cycle">
|
||||
<dl>
|
||||
<dt data-l10n-id="billing-cycle-ends">Billing cycle ends</dt>
|
||||
<dd><time id="reset-date">26 Dec</time></dd>
|
||||
</dl>
|
||||
<p class="meta" data-l10n-id="tracking-will-reset">Tracking will reset on this date</p>
|
||||
</li>
|
||||
</ul>
|
||||
<script type="text/javascript" defer src="js/views/telephony.js"></script>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
</element>
|
||||
36
gaia/apps/costcontrol/elements/topup-dialog.html
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
<element name="topup-dialog" extends="section">
|
||||
<template>
|
||||
|
||||
<gaia-header id="topup-header" action="close">
|
||||
<h1 data-l10n-id="top-up"></h1>
|
||||
<button id="topup-send-button" data-l10n-id="send">Send</button>
|
||||
</gaia-header>
|
||||
<section class="content">
|
||||
<div class="info">
|
||||
<h2 data-l10n-id="enter-top-up-code">
|
||||
Enter the top up code
|
||||
</h2>
|
||||
<p id="topup-code-explanation" data-l10n-id="top-up-code-explanation2">
|
||||
Find it on the scratch card or directly in your receipt.
|
||||
</p>
|
||||
<p id="topup-incorrect-code" hidden data-l10n-id="incorrect-code2">
|
||||
Make sure the code is correct and try again.
|
||||
</p>
|
||||
<p id="topup-error" hidden data-l10n-id="topup-error2">
|
||||
There was a problem sending the code. Please, try again.
|
||||
</p>
|
||||
<section hidden class="loading" id="topup-in-progress">
|
||||
<progress>
|
||||
</progress>
|
||||
<p data-l10n-id="wait-for-confirmation">
|
||||
You'll receive a confirmation message
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<form>
|
||||
<input id="topup-code-input" type="number" />
|
||||
</form>
|
||||
|
||||
</template>
|
||||
</element>
|
||||
477
gaia/apps/costcontrol/fte.html
Executable file
|
|
@ -0,0 +1,477 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-color" content="#eeeeee">
|
||||
<title data-l10n-id="fte">Usage First Time Experience</title>
|
||||
|
||||
<!-- Building blocks -->
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/buttons.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/input_areas.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/layout.css">
|
||||
|
||||
<!-- App and Views -->
|
||||
<link rel="stylesheet" type="text/css" href="style/app.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/settings.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/firsttime.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/datalimits.css">
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css">
|
||||
|
||||
<!-- Localization -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/costcontrol.{locale}.properties">
|
||||
<link rel="localization" href="/shared/locales/date/date.{locale}.properties">
|
||||
|
||||
<!-- Firefox Desktop shims -->
|
||||
<script defer src="js/desktop-only/navigator_moz_settings.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_icc_manager.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_mobile_connections.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_network_stats.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_alarms.js"></script>
|
||||
<script defer src="js/desktop-only/async_storage.js"></script>
|
||||
|
||||
<!-- Shared code -->
|
||||
<script type="text/javascript" src="shared/js/l10n.js"></script>
|
||||
<script type="text/javascript" defer src="shared/js/format.js"></script>
|
||||
<script type="text/javascript" src="shared/js/async_storage.js"></script>
|
||||
<script type="text/javascript" src="shared/js/settings_listener.js"></script>
|
||||
<script type="text/javascript" src="shared/js/lazy_loader.js"></script>
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<script defer src="/shared/elements/config.js"></script>
|
||||
<script defer src="/shared/js/component_utils.js"></script>
|
||||
<script defer src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
|
||||
<script defer src="/shared/elements/gaia_buttons/script.js"></script>
|
||||
<script defer src="/shared/elements/gaia_select/script.js"></script>
|
||||
<script defer src="/shared/elements/gaia_switch/script.js"></script>
|
||||
<script defer src="/shared/elements/gaia_radio/script.js"></script>
|
||||
|
||||
<!-- Specific code -->
|
||||
<script type="text/javascript" defer src="js/fte.js"></script>
|
||||
</head>
|
||||
|
||||
<body role="application" class="theme-settings skin-organic">
|
||||
|
||||
<!-- First time experience -->
|
||||
<section id="firsttime-view" class="view step-1">
|
||||
|
||||
<!-- Global Step 1 -->
|
||||
<section role="region" class="view step" data-viewport="" id="step-1">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-welcome-title"></h1>
|
||||
</gaia-header>
|
||||
<section class="content vbox">
|
||||
<div class="authed-sim" hidden>
|
||||
<h2 data-l10n-id="fte-authed-sim">Save money by keeping track of your phone and internet usage.</h2>
|
||||
</div>
|
||||
<div class="nonauthed-sim" hidden>
|
||||
<h2 data-l10n-id="fte-nonauthed-sim">Save money by keeping track of your internet usage.</h2>
|
||||
</div>
|
||||
<div class="no-sim" hidden>
|
||||
<h2 data-l10n-id="fte-no-sim">Get clear information about how much internet you're using.</h2>
|
||||
</div>
|
||||
<p data-l10n-id="fte-sim-info2" class="info">Information shown on usage comes directly from this device. Check with your mobile service provider for detailed account information.</p>
|
||||
</section>
|
||||
<gaia-buttons>
|
||||
<button class="recommend icon icon-next" data-navigation="next" data-l10n-id="next">Next</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="step-2">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-plantype-title"></h1>
|
||||
</gaia-header>
|
||||
<ul class="content choices">
|
||||
<li class="active">
|
||||
<gaia-radio name="plan" id="prepaid-plan" value="prepaid">
|
||||
<label data-l10n-id="im-prepaid"></label>
|
||||
</gaia-radio>
|
||||
</li>
|
||||
<li class="active">
|
||||
<gaia-radio name="plan" id="postpaid-plan" value="postpaid">
|
||||
<label data-l10n-id="im-postpaid"></label>
|
||||
</gaia-radio>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-plantype-hint">Select the type of contract you have with your mobile service provider.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button id="to-step-2" class="recommend icon icon-next" data-navigation="next" data-l10n-id="next">Next</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Prepaid step 2 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="prepaid-step-2">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-prepaid2-title"></h1>
|
||||
</gaia-header>
|
||||
<section class="content">
|
||||
<ul class="settings">
|
||||
<li class="active">
|
||||
<gaia-switch id="low-limit" class="settings-option" data-option="lowLimit">
|
||||
<label data-l10n-id="low-balance-alert"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<label class="end" for="low-limit-input" data-l10n-id="low-balance-explanation2">When balance is below</label>
|
||||
<div class="low-limit-container hbox middle">
|
||||
<input id="low-limit-input" class="settings-option" data-option="lowLimitThreshold" data-disable-on="lowLimit=false" type="number" step="any">
|
||||
<span id="currency" dir="auto"></span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-prepaid2-hint">Set an alert to avoid running out of credit.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button id="low-limit-next-button" class="recommend icon icon-next" data-navigation="next" data-l10n-id="next">Next</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Prepaid step 3 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="prepaid-step-3">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-prepaid3-title"></h1>
|
||||
</gaia-header>
|
||||
<section class="content">
|
||||
<ul class="settings">
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="reset-report">Reset report</label>
|
||||
<select class="settings-option" data-option="trackingPeriod">
|
||||
<option value="monthly" data-l10n-id="monthly">Monthly</option>
|
||||
<option value="weekly" data-l10n-id="weekly">Weekly</option>
|
||||
<option value="never" data-l10n-id="never">Never</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select id="pre3-select-weekday" class="settings-option" data-option="resetTime" data-hide-on="trackingPeriod!=weekly" data-disable-on="trackingPeriod!=weekly">
|
||||
<option class="monday" value="1" data-l10n-id="monday">Monday</option>
|
||||
<option value="2" data-l10n-id="tuesday">Tuesday</option>
|
||||
<option value="3" data-l10n-id="wednesday">Wednesday</option>
|
||||
<option value="4" data-l10n-id="thursday">Thursday</option>
|
||||
<option value="5" data-l10n-id="friday">Friday</option>
|
||||
<option value="6" data-l10n-id="saturday">Saturday</option>
|
||||
<option class="sunday" value="0" data-l10n-id="sunday">Sunday</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select class="settings-option icon icon-dialog" data-option="resetTime" data-hide-on="trackingPeriod!=monthly" data-disable-on="trackingPeriod!=monthly">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
<option value="24">24</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
<option value="28">28</option>
|
||||
<option value="29">29</option>
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="active">
|
||||
<gaia-switch class="settings-option" data-option="dataLimit">
|
||||
<label data-l10n-id="data-limit-alert"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<p data-l10n-id="data-limit-value2">When use is above</p>
|
||||
<button class="settings-option icon icon-dialog" data-widget-type="data-limit" data-disable-on="dataLimit=false" data-hide-on="dataLimit=false">
|
||||
<span class="tag"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-prepaid3-hint">Receive reports about your Internet usage and set an alert to avoid using too much.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button class="recommend" data-navigation="finish" span data-l10n-id="lets-go">Let’s go!</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Postpaid step 2 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="postpaid-step-2">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-postpaid2-title"></h1>
|
||||
</gaia-header>
|
||||
<ul class="content settings">
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="reset-report">Reset report</label>
|
||||
<select class="settings-option" data-option="trackingPeriod">
|
||||
<option value="monthly" data-l10n-id="monthly">Monthly</option>
|
||||
<option value="weekly" data-l10n-id="weekly">Weekly</option>
|
||||
<option value="never" data-l10n-id="never">Never</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select id="post2-select-weekday" class="settings-option" data-option="resetTime" data-hide-on="trackingPeriod!=weekly" data-disable-on="trackingPeriod!=weekly">
|
||||
<option class="monday" value="1" data-l10n-id="monday">Monday</option>
|
||||
<option value="2" data-l10n-id="tuesday">Tuesday</option>
|
||||
<option value="3" data-l10n-id="wednesday">Wednesday</option>
|
||||
<option value="4" data-l10n-id="thursday">Thursday</option>
|
||||
<option value="5" data-l10n-id="friday">Friday</option>
|
||||
<option value="6" data-l10n-id="saturday">Saturday</option>
|
||||
<option class="sunday" value="0" data-l10n-id="sunday">Sunday</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select class="settings-option icon icon-dialog" data-option="resetTime" data-hide-on="trackingPeriod!=monthly" data-disable-on="trackingPeriod!=monthly">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
<option value="24">24</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
<option value="28">28</option>
|
||||
<option value="29">29</option>
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-postpaid2-hint">Receive reports about your phone and Internet usage.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button class="recommend icon icon-next" data-navigation="next" data-l10n-id="next">Next</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Postpaid step 3 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="postpaid-step-3">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-postpaid3-title"></h1>
|
||||
</gaia-header>
|
||||
<section class="content">
|
||||
<ul class="settings">
|
||||
<li class="active">
|
||||
<gaia-switch class="settings-option" data-option="dataLimit">
|
||||
<label data-l10n-id="data-limit-alert"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<p data-l10n-id="data-limit-value2">When use is above</p>
|
||||
<button class="settings-option icon icon-dialog" data-widget-type="data-limit" data-disable-on="dataLimit=false" data-hide-on="dataLimit=false">
|
||||
<span class="tag"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-postpaid3-hint">Set an alert to avoid using too much data.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button class="recommend" data-navigation="finish" data-l10n-id="lets-go">Let’s go!</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Non VIVO step 2 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="non-vivo-step-1">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-onlydata2-title"></h1>
|
||||
</gaia-header>
|
||||
<ul class="content settings">
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="reset-report">Reset report</label>
|
||||
<select class="settings-option" data-option="trackingPeriod">
|
||||
<option value="monthly" data-l10n-id="monthly">Monthly</option>
|
||||
<option value="weekly" data-l10n-id="weekly">Weekly</option>
|
||||
<option value="never" data-l10n-id="never">Never</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select id="non2-select-weekday" class="settings-option" data-option="resetTime" data-hide-on="trackingPeriod!=weekly" data-disable-on="trackingPeriod!=weekly">
|
||||
<option class="monday" value="1" data-l10n-id="monday">Monday</option>
|
||||
<option value="2" data-l10n-id="tuesday">Tuesday</option>
|
||||
<option value="3" data-l10n-id="wednesday">Wednesday</option>
|
||||
<option value="4" data-l10n-id="thursday">Thursday</option>
|
||||
<option value="5" data-l10n-id="friday">Friday</option>
|
||||
<option value="6" data-l10n-id="saturday">Saturday</option>
|
||||
<option class="sunday" value="0" data-l10n-id="sunday">Sunday</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select class="settings-option icon icon-dialog" data-option="resetTime" data-hide-on="trackingPeriod!=monthly" data-disable-on="trackingPeriod!=monthly">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
<option value="24">24</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
<option value="28">28</option>
|
||||
<option value="29">29</option>
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-onlydata2-hint">Set an alert to avoid using too much data.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button class="recommend icon icon-next" data-navigation="next" data-l10n-id="next">Next</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
|
||||
<!-- Non VIVO step 3 -->
|
||||
<section role="region" class="view step gaia-list" data-viewport="right" id="non-vivo-step-2">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="fte-onlydata3-title"></h1>
|
||||
</gaia-header>
|
||||
<ul class="content settings">
|
||||
<li class="active">
|
||||
<gaia-switch class="settings-option" data-option="dataLimit">
|
||||
<label data-l10n-id="data-limit-alert"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<p data-l10n-id="data-limit-value2">When use is above</p>
|
||||
<button class="settings-option icon icon-dialog" data-widget-type="data-limit" data-disable-on="dataLimit=false" data-hide-on="dataLimit=false">
|
||||
<span class="tag"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<p data-l10n-id="fte-onlydata3-hint">Set an alert to avoid using too much data.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<gaia-buttons data-items="2">
|
||||
<button data-navigation="back" class="icon icon-previous" data-l10n-id="back">Back</button>
|
||||
<button class="recommend" data-navigation="finish" data-l10n-id="lets-go">Let’s go!</button>
|
||||
</gaia-buttons>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- Data usage limits -->
|
||||
<section aria-labelledby="limit-dialog-heading" id="data-limit-dialog" role="dialog" class="view" data-viewport="bottom">
|
||||
<gaia-header id="limit-dialog-header" action="close">
|
||||
<h1 id="limit-dialog-heading" data-l10n-id="data-limit-header"></h1>
|
||||
<button id="data-usage-done-button" data-l10n-id="done" class="recommend">Done</button>
|
||||
</gaia-header>
|
||||
<section class="content" class="vbox">
|
||||
<div class="info">
|
||||
<h2 data-l10n-id="data-limit-dialog-title2">Enter a mobile data limit alert</h2>
|
||||
<p data-l10n-id="data-limit-dialog-explanation2">
|
||||
Connection speeds reduce after the limit is reached. Check with your data provider to set an appropriate alert level.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<form>
|
||||
<ul class="buttons-list" role="presentation">
|
||||
<li role="presentation">
|
||||
<label for="data-limit-input" data-l10n-id="data-limit-input-label">
|
||||
<input id="data-limit-input" type="text" x-inputmode="numeric" maxlength="6" />
|
||||
</label>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button class="icon icon-dialog switch-unit-button">
|
||||
<span class="tag"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<iframe id="message-handler" src=""></iframe>
|
||||
</body>
|
||||
</html>
|
||||
157
gaia/apps/costcontrol/index.html
Executable file
|
|
@ -0,0 +1,157 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-group" content="theme-settings">
|
||||
<meta name="theme-color" content="var(--header-background)">
|
||||
<title data-l10n-id="usage">
|
||||
Usage
|
||||
</title>
|
||||
|
||||
<!-- Building blocks -->
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/headers.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/buttons.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/input_areas.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/lists.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/progress_activity.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/tabs.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/confirm.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/layout.css">
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<link rel="localization" href="/shared/locales/date/date.{locale}.properties">
|
||||
<link rel="localization" href="/shared/elements/gaia_menu/locales/gaia_menu.{locale}.properties">
|
||||
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
|
||||
<script defer src="/shared/js/component_utils.js"></script>
|
||||
<script defer src="/shared/elements/gaia_checkbox/script.js"></script>
|
||||
|
||||
<!-- App and Views -->
|
||||
<link rel="stylesheet" type="text/css" href="style/app.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/settings.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/datausage.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/topup.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/timestamp-highlights.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/balance.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/telephony.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/appdetail.css">
|
||||
|
||||
<!-- Localization -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/costcontrol.{locale}.properties">
|
||||
|
||||
<!-- Firefox Desktop shims -->
|
||||
<script defer src="js/desktop-only/navigator_moz_settings.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_icc_manager.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_mobile_connections.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_apps_mgmt.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_network_stats.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_alarms.js"></script>
|
||||
<script defer src="js/desktop-only/async_storage.js"></script>
|
||||
|
||||
<script type="text/javascript" defer src="shared/js/l10n.js"></script>
|
||||
<!-- TODO: Lazyly loaded, remove when mozHour12 is part of the platform
|
||||
<script type="text/javascript" defer src="shared/js/date_time_helper.js"></script>
|
||||
-->
|
||||
<script type="text/javascript" defer src="shared/js/async_storage.js"></script>
|
||||
<script type="text/javascript" defer src="shared/js/accessibility_helper.js"></script>
|
||||
<script type="text/javascript" defer src="shared/js/settings_listener.js"></script>
|
||||
<script type="text/javascript" defer src="shared/js/lazy_loader.js"></script>
|
||||
<script type="text/javascript" defer src="shared/js/format.js"></script>
|
||||
<script type="text/javascript" defer src="/shared/js/url_helper.js"></script>
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<script defer src="/shared/elements/config.js"></script>
|
||||
<script defer src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
|
||||
|
||||
<script type="text/javascript" defer src="js/app.js"></script>
|
||||
<script type="text/javascript" defer src="js/application_init.js"></script>
|
||||
|
||||
<link rel="import" href="/elements/balance-tab.html">
|
||||
<link rel="import" href="/elements/telephony-tab.html">
|
||||
<link rel="import" href="/elements/topup-dialog.html">
|
||||
<link rel="import" href="/elements/datausage-tab.html">
|
||||
<link rel="import" href="/elements/appdetail-view.html">
|
||||
|
||||
<!-- Include the app icon in the application.zip -->
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_84.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_126.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_142.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_189.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_284.png"/>
|
||||
</head>
|
||||
<body role="application" class="theme-settings">
|
||||
|
||||
<section id="splash_section" class="window splash">
|
||||
</section>
|
||||
|
||||
<section id="main" role="region" class="window skin-organic non-ready">
|
||||
<div id="tabpanel">
|
||||
<!-- XXX: The order of sections is important (tabs should be place above
|
||||
the overlays) and we should distinguish between TABS and OVERLAY VIEWS -->
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="usage"></h1>
|
||||
<button class="settings-button" data-icon="settings" data-l10n-id="settings-button"></button>
|
||||
</gaia-header>
|
||||
|
||||
<!-- Bottom filters (Telephony / Cost Control and Data Usage) -->
|
||||
<ul id="tabs" role="tablist" class="bb-tablist" data-type="filter" data-items="2">
|
||||
<li id="balance-tab-filter" role="presentation">
|
||||
<a id="balance-tab-control" data-l10n-id="balance" href="#balance-tab" role="tab" aria-controls="balance-tab">
|
||||
Balance
|
||||
</a>
|
||||
</li>
|
||||
<li id="telephony-tab-filter" role="presentation" hidden>
|
||||
<a id="telephony-tab-control" data-l10n-id="phone" href="#telephony-tab" role="tab" aria-controls="telephony-tab">
|
||||
Phone
|
||||
</a>
|
||||
</li>
|
||||
<li id="datausage-tab-filter" role="presentation">
|
||||
<a id="datausage-tab-control" data-l10n-id="internet" href="#datausage-tab" role="tab" aria-controls="datausage-tab">
|
||||
Internet
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- BALANCE TAB -->
|
||||
<section is="balance-tab" aria-labelledby="balance-tab-control" role="region tabpanel" id="balance-tab" class="bb-tabpanel view" data-viewport="left" hidden></section>
|
||||
|
||||
<!-- TELEPHONY TAB -->
|
||||
<section is="telephony-tab" aria-labelledby="telephony-tab-control" role="region tabpanel" id="telephony-tab" class="bb-tabpanel view" data-viewport="left" hidden></section>
|
||||
|
||||
<!-- DATA USAGE TAB -->
|
||||
<section is="datausage-tab" aria-labelledby="datausage-tab-control" id="datausage-tab" role="region tabpanel" class="bb-tabpanel view" data-viewport="right" hidden></section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- The top up input -->
|
||||
<section is="topup-dialog" role="dialog" id="topup-dialog" class="view" data-viewport="bottom" hidden></section>
|
||||
|
||||
<!-- App Detail view -->
|
||||
<section is="appdetail-view" id="appdetail-view" class="view" data-viewport="right" hidden></section>
|
||||
|
||||
<!-- Settings view -->
|
||||
<section id="settings-view" class="view" data-viewport="bottom">
|
||||
<iframe id="settings-view-placeholder"></iframe>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
<iframe id="message-handler" src="">
|
||||
</iframe>
|
||||
<iframe id="fte_view" class="window non-ready"></iframe>
|
||||
|
||||
<!-- SIM non ready screen -->
|
||||
<form role="dialog" class="view" data-type="confirm" data-viewport="ethereal" id="non-ready-screen">
|
||||
<section>
|
||||
<h1>Card is not ready</h1>
|
||||
<p>The button will close the application.</p>
|
||||
<progress hidden></progress>
|
||||
</section>
|
||||
<menu>
|
||||
<button class="full" data-l10n-id="ok">Ok</button>
|
||||
</menu>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
538
gaia/apps/costcontrol/js/app.js
Executable file
|
|
@ -0,0 +1,538 @@
|
|||
/* global BalanceTab, ConfigManager, Common, NonReadyScreen, SimManager,
|
||||
debug, CostControl, TelephonyTab, ViewManager, LazyLoader,
|
||||
AirplaneModeHelper, setNextReset, asyncStorage */
|
||||
/* exported CostControlApp */
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* The application is in charge of display detailed information about the usage.
|
||||
* Application has three tabs configured independently plus a settings view.
|
||||
*
|
||||
* This module is only in charge of manage the different tabs but the specific
|
||||
* behaviour is delegated to each tab (in /js/views directory).
|
||||
*
|
||||
* It is important to explain how the URL schema works. Cost Control
|
||||
* uses the concept of card to refer a tab or a view. There are two main layers:
|
||||
* the tab layer and the overlay layer. The first one is used to display tabs,
|
||||
* the second one is used to display settings or other overlaying dialogs.
|
||||
*
|
||||
* The URL schema is:
|
||||
* #tab-id[#overlay-id]
|
||||
*
|
||||
* So changing the hash to:
|
||||
* #balance-tab#settings-view
|
||||
* makes Cost Control to change to the balance view and show the settings.
|
||||
*
|
||||
* We can close the overlay layer by setting only the balance layer:
|
||||
* #datausage-tab
|
||||
* this makes Cost Control to change to the datausage tab at the same time it
|
||||
* closes the card in the overlay layer.
|
||||
*
|
||||
* If you want to preserve a layer but changing the other, use the empty string
|
||||
* as the id of the layer you want to preserve.
|
||||
* For instance, you want to only close the overlay layer but not affecting the
|
||||
* tab layer:
|
||||
* #
|
||||
*
|
||||
* Or you want to change tab layer but without affecting the overlay, use:
|
||||
* #telephony-tab#
|
||||
*
|
||||
* Remember: you cannot close the tab layer!
|
||||
*/
|
||||
|
||||
var CostControlApp = (function() {
|
||||
|
||||
var costcontrol,
|
||||
initialized = false,
|
||||
hashFromLastRun,
|
||||
hashFromNotification;
|
||||
var vmanager;
|
||||
|
||||
// Set the application in waiting for SIM mode. During this mode, the
|
||||
// application shows a dialog informing about the current situation of the
|
||||
// SIM. Once ready, callback is executed.
|
||||
function waitForSIMReady(callback) {
|
||||
SimManager.requestDataSimIcc(function _onIccId(dataSim) {
|
||||
var dataSimIcc = dataSim.icc;
|
||||
var cardState = dataSimIcc.cardState;
|
||||
|
||||
// SIM not ready
|
||||
if (cardState !== 'ready') {
|
||||
showNonReadyScreen(cardState);
|
||||
debug('SIM not ready:', cardState);
|
||||
dataSimIcc.oncardstatechange = function() {
|
||||
waitForSIMReady(callback);
|
||||
};
|
||||
|
||||
// SIM is ready
|
||||
} else {
|
||||
hideNotReadyScreen();
|
||||
debug('SIM ready. ICCID:', dataSim.iccId);
|
||||
dataSimIcc.oncardstatechange = undefined;
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
// In case we can not get a valid ICCID.
|
||||
}, function _errorNoSim() {
|
||||
console.warn('Error when trying to get the ICC, SIM not detected.');
|
||||
LazyLoader.load(['/shared/js/airplane_mode_helper.js'], function() {
|
||||
AirplaneModeHelper.ready(function() {
|
||||
var fakeState = null;
|
||||
if (AirplaneModeHelper.getStatus() === 'enabled') {
|
||||
console.warn('The airplaneMode is enabled.');
|
||||
fakeState = 'airplaneMode';
|
||||
window.addEventListener('airplaneModeDisabled',
|
||||
function _onAirplanemodeDisabled(evt) {
|
||||
if (evt.detail && evt.detail.serviceId === 'data') {
|
||||
waitForSIMReady(startApp);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
showNonReadyScreen(fakeState);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Displays a faked modal dialog that can be automatically close when the SIM
|
||||
// is ready. A second call if it is already shown will only update the
|
||||
// message.
|
||||
var nonReadyScreen;
|
||||
function showNonReadyScreen(cardState) {
|
||||
|
||||
if (isApplicationLocalized) {
|
||||
realshowNonReadyScreen(cardState);
|
||||
} else {
|
||||
window.addEventListener('localized', function _onlocalized() {
|
||||
window.removeEventListener('localized', _onlocalized);
|
||||
realshowNonReadyScreen(cardState);
|
||||
});
|
||||
}
|
||||
|
||||
function realshowNonReadyScreen(messageId) {
|
||||
debug('Showing non-ready screen.');
|
||||
if (!nonReadyScreen) {
|
||||
nonReadyScreen =
|
||||
new NonReadyScreen(document.getElementById('non-ready-screen'));
|
||||
}
|
||||
nonReadyScreen.updateForState(cardState);
|
||||
vmanager.changeViewTo(nonReadyScreen.id);
|
||||
}
|
||||
}
|
||||
|
||||
function hideNotReadyScreen(status) {
|
||||
debug('Hiding non-ready screen.');
|
||||
if (vmanager.getCurrentView() === 'non-ready-screen') {
|
||||
vmanager.closeCurrentView();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX: See the module documentation for details about URL schema
|
||||
var tabmanager, settingsVManager;
|
||||
|
||||
function _onHashChange(evt) {
|
||||
var parser = document.createElement('a');
|
||||
parser.href = evt.oldURL;
|
||||
var oldHash = parser.hash.split('#');
|
||||
parser.href = evt.newURL;
|
||||
var newHash = parser.hash.split('#');
|
||||
|
||||
if (newHash.length > 3) {
|
||||
console.error('Cost Control bad URL schema');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('URL schema before normalizing:', newHash);
|
||||
|
||||
var normalized = false;
|
||||
if (!newHash[1] && oldHash[1]) {
|
||||
newHash[1] = oldHash[1];
|
||||
normalized = true;
|
||||
}
|
||||
|
||||
if (newHash.length === 3 && newHash[2] === '') {
|
||||
if (oldHash.length === 3) {
|
||||
newHash[2] = oldHash[2];
|
||||
} else {
|
||||
newHash = newHash.slice(0, 2);
|
||||
}
|
||||
normalized = true;
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
debug('URL schema after normalization:', newHash);
|
||||
window.location.hash = newHash.join('#');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newHash[1]) {
|
||||
tabmanager.changeViewTo(newHash[1]);
|
||||
}
|
||||
|
||||
if (newHash.length < 3) {
|
||||
vmanager.closeCurrentView();
|
||||
} else {
|
||||
vmanager.changeViewTo(newHash[2], '#tabpanel');
|
||||
}
|
||||
}
|
||||
|
||||
function setupCardHandler() {
|
||||
// View managers for dialogs and settings
|
||||
tabmanager = new ViewManager(
|
||||
['balance-tab', 'telephony-tab', { id: 'datausage-tab', tab: 'right' }]
|
||||
);
|
||||
settingsVManager = new ViewManager();
|
||||
|
||||
// View handler
|
||||
window.addEventListener('hashchange', _onHashChange);
|
||||
}
|
||||
|
||||
function loadMessageHandler() {
|
||||
var messageHandlerFrame = document.getElementById('message-handler');
|
||||
var thereIsNextReset = ConfigManager.option('nextReset');
|
||||
var alreadyLoaded = messageHandlerFrame.src
|
||||
.contains('message_handler.html');
|
||||
|
||||
if (alreadyLoaded && thereIsNextReset) {
|
||||
setNextReset(ConfigManager.option('nextReset'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (thereIsNextReset) {
|
||||
window.addEventListener('messagehandlerready', function _setNextReset() {
|
||||
window.removeEventListener('messagehandlerready', _setNextReset);
|
||||
setNextReset(ConfigManager.option('nextReset'));
|
||||
});
|
||||
}
|
||||
messageHandlerFrame.src = 'message_handler.html';
|
||||
}
|
||||
|
||||
function _onDataSimChange() {
|
||||
// Before restarting the app, it's necessary remove the cached values of
|
||||
// costcontrol and config to force an update.
|
||||
CostControl.reset();
|
||||
ConfigManager.setConfig(null);
|
||||
SimManager.requestDataSimIcc(startApp);
|
||||
}
|
||||
|
||||
function startApp(callback) {
|
||||
// If customMode is not ready and it was activated on previous execution,
|
||||
// we have to change the setup plan to no plan.
|
||||
if (!ConfigManager.supportCustomizeMode) {
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
ConfigManager.requestSettings(dataSim.iccId, function(settings) {
|
||||
if (settings.trackingPeriod === 'custom') {
|
||||
ConfigManager.setOption({ trackingPeriod: 'never' });
|
||||
// Removing manually if a nextReset alarm exists
|
||||
// XXX: This is not part of configuration by SIM so we bypass
|
||||
// ConfigManager
|
||||
asyncStorage.getItem('nextResetAlarm', function(id) {
|
||||
// There is already an alarm, remove it
|
||||
debug('Current nextResetAlarm', id + '.', id ? 'Removing.' : '');
|
||||
if (id) {
|
||||
navigator.mozAlarms.remove(id);
|
||||
}
|
||||
asyncStorage.setItem('nextResetAlarm', null, function() {
|
||||
ConfigManager.setOption({ nextReset: null });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (SimManager.isMultiSim()) {
|
||||
window.addEventListener('dataSlotChange', _onDataSimChange);
|
||||
}
|
||||
CostControl.getInstance(function _onCostControlReady(instance) {
|
||||
if (ConfigManager.option('fte')) {
|
||||
startFTE();
|
||||
return;
|
||||
}
|
||||
|
||||
loadMessageHandler();
|
||||
|
||||
costcontrol = instance;
|
||||
if (!initialized) {
|
||||
setupApp(callback);
|
||||
} else {
|
||||
loadSettings();
|
||||
updateUI(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var isApplicationLocalized = false;
|
||||
window.addEventListener('localized', function _onLocalize() {
|
||||
isApplicationLocalized = true;
|
||||
if (initialized) {
|
||||
updateUI();
|
||||
loadSettings();
|
||||
}
|
||||
});
|
||||
|
||||
function setupApp(callback) {
|
||||
setupCardHandler();
|
||||
|
||||
// Configure settings buttons
|
||||
var settingsButtons = document.querySelectorAll('.settings-button');
|
||||
Array.prototype.forEach.call(settingsButtons,
|
||||
function _eachSettingsButton(button) {
|
||||
button.addEventListener('click', function _showSettings() {
|
||||
window.location.hash = '##settings-view';
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Handle 'open activity' sent by the user via the widget
|
||||
navigator.mozSetMessageHandler('activity',
|
||||
function _handleActivity(activityRequest) {
|
||||
var name = activityRequest.source.name;
|
||||
switch (name) {
|
||||
case 'costcontrol/balance':
|
||||
window.location.hash = '#balance-tab';
|
||||
break;
|
||||
case 'costcontrol/telephony':
|
||||
window.location.hash = '#telephony-tab';
|
||||
break;
|
||||
case 'costcontrol/data_usage':
|
||||
window.location.hash = '#datausage-tab';
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// When a notification is received
|
||||
window.navigator.mozSetMessageHandler('notification',
|
||||
function _onNotification(notification) {
|
||||
if (!notification.clicked) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug('Notification was clicked!');
|
||||
|
||||
navigator.mozApps.getSelf().onsuccess = function _onAppReady(evt) {
|
||||
var app = evt.target.result;
|
||||
app.launch();
|
||||
|
||||
var type = notification.data;
|
||||
debug('Notification type:', type);
|
||||
handleNotification(type);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Check card state when visible
|
||||
document.addEventListener('visibilitychange',
|
||||
function _onVisibilityChange(evt) {
|
||||
if (!document.hidden && initialized) {
|
||||
waitForSIMReady();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
updateUI(callback);
|
||||
ConfigManager.observe('plantype', updateUI, true);
|
||||
|
||||
initialized = true;
|
||||
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
// Load settings in background
|
||||
function loadSettings() {
|
||||
window.performance.mark('loadSettingsStart');
|
||||
document.getElementById('settings-view-placeholder').src = 'settings.html';
|
||||
}
|
||||
|
||||
function handleNotification(type) {
|
||||
switch (type) {
|
||||
case 'topUpError':
|
||||
BalanceTab.topUpWithCode(true);
|
||||
break;
|
||||
case 'lowBalance':
|
||||
case 'zeroBalance':
|
||||
window.location.hash = '#balance-tab';
|
||||
hashFromNotification = '#balance-tab';
|
||||
break;
|
||||
case 'dataUsage':
|
||||
hashFromNotification = '#datausage-tab';
|
||||
tabmanager.changeViewTo('datausage-tab');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var currentMode;
|
||||
function updateUI(callback) {
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
ConfigManager.requestSettings(dataSim.iccId,
|
||||
function _onSettings(settings) {
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
var newHash = window.location.hash;
|
||||
var tabs = document.getElementById('tabs');
|
||||
var dataUsageTab = document.getElementById('datausage-tab');
|
||||
|
||||
debug('App UI mode: ', mode);
|
||||
|
||||
// Layout
|
||||
if (mode !== currentMode) {
|
||||
currentMode = mode;
|
||||
// Stand alone mode when data usage only
|
||||
if (mode === 'DATA_USAGE_ONLY') {
|
||||
tabs.hidden = true;
|
||||
dataUsageTab.classList.add('standalone');
|
||||
newHash = '#datausage-tab';
|
||||
|
||||
// Two tabs mode
|
||||
} else {
|
||||
dataUsageTab.classList.remove('standalone');
|
||||
tabs.hidden = false;
|
||||
document.getElementById('balance-tab-filter')
|
||||
.hidden = (mode !== 'PREPAID');
|
||||
|
||||
document.getElementById('telephony-tab-filter')
|
||||
.hidden = (mode !== 'POSTPAID');
|
||||
|
||||
// If it was showing the left tab, force changing to the
|
||||
// proper left view
|
||||
if (!isDataUsageTabShown()) {
|
||||
newHash = (mode === 'PREPAID') ? '#balance-tab#' :
|
||||
'#telephony-tab#';
|
||||
}
|
||||
}
|
||||
window.location.hash = hashFromNotification ||
|
||||
hashFromLastRun ||
|
||||
newHash;
|
||||
hashFromLastRun = hashFromNotification = null;
|
||||
|
||||
// XXX: Break initialization to allow Gecko to render the animation on
|
||||
// time.
|
||||
requestAnimationFrame(function continueLoading() {
|
||||
document.getElementById('main').classList.remove('non-ready');
|
||||
|
||||
if (mode === 'PREPAID') {
|
||||
if (typeof TelephonyTab !== 'undefined') {
|
||||
TelephonyTab.finalize();
|
||||
}
|
||||
if (typeof BalanceTab !== 'undefined') {
|
||||
BalanceTab.initialize();
|
||||
}
|
||||
} else if (mode === 'POSTPAID') {
|
||||
if (typeof BalanceTab !== 'undefined') {
|
||||
BalanceTab.finalize();
|
||||
}
|
||||
if (typeof TelephonyTab !== 'undefined') {
|
||||
TelephonyTab.initialize();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
(typeof callback === 'function') && callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isDataUsageTabShown() {
|
||||
return window.location.hash.split('#')[1] === 'datausage-tab';
|
||||
}
|
||||
|
||||
function onFteFinished(e) {
|
||||
if (e.origin !== Common.COST_CONTROL_APP) {
|
||||
return;
|
||||
}
|
||||
var type = e.data.type;
|
||||
if (type === 'fte_finished') {
|
||||
window.removeEventListener('message', onFteFinished);
|
||||
document.getElementById('splash_section').hidden = 'true';
|
||||
|
||||
// Only hide the FTE view when everything in the UI is ready
|
||||
ConfigManager.requestAll(function() {
|
||||
startApp(Common.closeFTE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function startFTE() {
|
||||
window.addEventListener('message', onFteFinished);
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
Common.startFTE(mode);
|
||||
}
|
||||
|
||||
function initApp() {
|
||||
// The location hash keeps its current value even if the app is killed by
|
||||
// the oom killer.
|
||||
hashFromLastRun = window.location.hash;
|
||||
window.location.hash = '';
|
||||
vmanager = new ViewManager();
|
||||
waitForSIMReady(startApp);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
var SCRIPTS_NEEDED = [
|
||||
'js/utils/debug.js',
|
||||
'js/common.js',
|
||||
'js/views/NonReadyScreen.js',
|
||||
'js/utils/toolkit.js',
|
||||
'js/view_manager.js'
|
||||
];
|
||||
// Check if the mandatory APIs to work exist.
|
||||
if (!window.navigator.mozMobileConnections ||
|
||||
!window.navigator.mozIccManager ||
|
||||
!window.navigator.mozNetworkStats) {
|
||||
LazyLoader.load(SCRIPTS_NEEDED, function _showError() {
|
||||
vmanager = new ViewManager();
|
||||
showNonReadyScreen(null);
|
||||
});
|
||||
} else {
|
||||
SCRIPTS_NEEDED = [
|
||||
'js/sim_manager.js',
|
||||
'js/utils/debug.js',
|
||||
'shared/js/date_time_helper.js',
|
||||
'js/utils/chart.js',
|
||||
'js/utils/formatting.js',
|
||||
'js/utils/toolkit.js',
|
||||
'js/settings/networkUsageAlarm.js',
|
||||
'js/common.js',
|
||||
'js/costcontrol.js',
|
||||
'js/config/config_manager.js',
|
||||
'js/views/NonReadyScreen.js',
|
||||
'js/view_manager.js'
|
||||
];
|
||||
LazyLoader.load(SCRIPTS_NEEDED, initApp);
|
||||
}
|
||||
// Tell audio channel manager that we want to adjust the notification
|
||||
// channel if the user press the volumeup/volumedown buttons in Usage.
|
||||
if (navigator.mozAudioChannelManager) {
|
||||
navigator.mozAudioChannelManager.volumeControlChannel = 'notification';
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
costcontrol = null;
|
||||
initialized = false;
|
||||
vmanager = null;
|
||||
tabmanager = null;
|
||||
settingsVManager = null;
|
||||
currentMode = null;
|
||||
isApplicationLocalized = false;
|
||||
window.removeEventListener('dataSlotChange', _onDataSimChange);
|
||||
window.removeEventListener('hashchange', _onHashChange);
|
||||
window.removeEventListener('message', onFteFinished);
|
||||
window.location.hash = '';
|
||||
nonReadyScreen = null;
|
||||
},
|
||||
showBalanceTab: function _showBalanceTab() {
|
||||
window.location.hash = '#balance-tab';
|
||||
},
|
||||
showDataUsageTab: function _showDataUsageTab() {
|
||||
window.location.hash = '#datausage-tab';
|
||||
},
|
||||
showAppDetailView: function _showAppDetailView() {
|
||||
window.location.hash = '##appdetail-view';
|
||||
},
|
||||
_onHashChange: _onHashChange
|
||||
};
|
||||
}());
|
||||
4
gaia/apps/costcontrol/js/application_init.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
/* global CostControlApp */
|
||||
'use strict';
|
||||
|
||||
CostControlApp.init();
|
||||
456
gaia/apps/costcontrol/js/common.js
Executable file
|
|
@ -0,0 +1,456 @@
|
|||
/* global _, debug, ConfigManager, Toolkit, SimManager */
|
||||
/* exported addAlarmTimeout, setNextReset, addNetworkUsageAlarm,
|
||||
getTopUpTimeout, Common, sendBalanceThresholdNotification
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function addAlarmTimeout(type, delay) {
|
||||
var handlerContainer = document.getElementById('message-handler');
|
||||
return handlerContainer.contentWindow.addAlarmTimeout(type, delay);
|
||||
}
|
||||
|
||||
function setNextReset(when, callback) {
|
||||
var handlerContainer = document.getElementById('message-handler');
|
||||
return handlerContainer ?
|
||||
handlerContainer.contentWindow.setNextReset(when, callback) :
|
||||
window.setNextReset(when, callback);
|
||||
}
|
||||
|
||||
function getTopUpTimeout(callback) {
|
||||
var handlerContainer = document.getElementById('message-handler');
|
||||
return handlerContainer ?
|
||||
handlerContainer.contentWindow.getTopUpTimeout(callback) :
|
||||
window.getTopUpTimeout(callback);
|
||||
}
|
||||
|
||||
function addNetworkUsageAlarm(dataInterface, dataLimit, onsuccess, onerror) {
|
||||
var handlerContainer = document.getElementById('message-handler');
|
||||
if (handlerContainer) {
|
||||
handlerContainer.contentWindow
|
||||
.addNetworkUsageAlarm(dataInterface, dataLimit, onsuccess, onerror);
|
||||
} else {
|
||||
window.addNetworkUsageAlarm(dataInterface, dataLimit, onsuccess, onerror);
|
||||
}
|
||||
}
|
||||
|
||||
function sendBalanceThresholdNotification(remaining, settings, callback) {
|
||||
var handlerContainer = document.getElementById('message-handler');
|
||||
if (handlerContainer) {
|
||||
handlerContainer.contentWindow
|
||||
.sendBalanceThresholdNotification(remaining, settings, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function resetTelephony(callback) {
|
||||
ConfigManager.setOption({
|
||||
lastTelephonyReset: new Date(),
|
||||
lastTelephonyActivity: {
|
||||
calltime: 0,
|
||||
smscount: 0,
|
||||
timestamp: new Date()
|
||||
}
|
||||
}, callback);
|
||||
}
|
||||
|
||||
var Common = {
|
||||
|
||||
COST_CONTROL_APP: 'app://costcontrol.gaiamobile.org',
|
||||
|
||||
DATA_USAGE_WARNING: 0.8,
|
||||
|
||||
allApps: null,
|
||||
|
||||
allAppsLoaded: false,
|
||||
|
||||
allNetworkInterfaces: null,
|
||||
|
||||
allNetworkInterfaceLoaded: false,
|
||||
//XXX: Group of apps, whose traffic will be added to the system app.
|
||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1079609
|
||||
specialApps: [
|
||||
'app://search.gaiamobile.org/manifest.webapp'
|
||||
],
|
||||
|
||||
SYSTEM_MANIFEST: 'app://system.gaiamobile.org/manifest.webapp',
|
||||
|
||||
BROWSER_APP: {
|
||||
manifestURL: 'app://browser.gaiamobile.org/manifest.webapp',
|
||||
origin: '',
|
||||
manifest: {
|
||||
icons: {
|
||||
'84': '/shared/resources/branding/browser_84.png',
|
||||
'126': '/shared/resources/branding/browser_126.png',
|
||||
'142': '/shared/resources/branding/browser_142.png',
|
||||
'189': '/shared/resources/branding/browser_189.png',
|
||||
'284': '/shared/resources/branding/browser_284.png'
|
||||
},
|
||||
name: 'browser'
|
||||
}
|
||||
},
|
||||
|
||||
startFTE: function(mode) {
|
||||
var iframe = document.getElementById('fte_view');
|
||||
|
||||
window.addEventListener('message', function handler(e) {
|
||||
if (e.origin !== Common.COST_CONTROL_APP) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data.type === 'fte_ready') {
|
||||
window.removeEventListener('message', handler);
|
||||
|
||||
iframe.classList.remove('non-ready');
|
||||
|
||||
// PERFORMANCE MARKERS
|
||||
// Designates that the app's *core* chrome or navigation interface
|
||||
// exists in the DOM and is marked as ready to be displayed.
|
||||
window.performance.mark('navigationLoaded');
|
||||
|
||||
// Designates that the app's *core* chrome or navigation interface
|
||||
// has its events bound and is ready for user interaction.
|
||||
window.performance.mark('navigationInteractive');
|
||||
|
||||
// Designates that the app is visually loaded (e.g.: all of the
|
||||
// "above-the-fold" content exists in the DOM and is marked as
|
||||
// ready to be displayed).
|
||||
window.performance.mark('visuallyLoaded');
|
||||
|
||||
// Designates that the app has its events bound for the minimum
|
||||
// set of functionality to allow the user to interact with the
|
||||
// "above-the-fold" content.
|
||||
window.performance.mark('contentInteractive');
|
||||
|
||||
// Start up ended when FTE ready
|
||||
window.performance.mark('fullyLoaded');
|
||||
}
|
||||
});
|
||||
|
||||
iframe.src = '/fte.html' + '#' + mode;
|
||||
},
|
||||
|
||||
closeFTE: function() {
|
||||
var iframe = window.parent.document.getElementById('fte_view');
|
||||
iframe.classList.add('non-ready');
|
||||
iframe.src = '';
|
||||
},
|
||||
|
||||
startApp: function() {
|
||||
parent.postMessage({
|
||||
type: 'fte_finished',
|
||||
data: ''
|
||||
}, Common.COST_CONTROL_APP);
|
||||
},
|
||||
|
||||
closeApplication: function() {
|
||||
return setTimeout(function _close() {
|
||||
debug('Closing.');
|
||||
window.close();
|
||||
});
|
||||
},
|
||||
|
||||
modalAlert: function(message) {
|
||||
alert(message);
|
||||
},
|
||||
|
||||
get localize() {
|
||||
return navigator.mozL10n.setAttributes;
|
||||
},
|
||||
|
||||
// Returns whether exists an nsIDOMNetworkStatsInterfaces object
|
||||
// that meet the argument function criteria
|
||||
getInterface: function getInterface(findFunction) {
|
||||
if (!Common.allNetworkInterfaceLoaded) {
|
||||
debug('Network interfaces are not ready yet');
|
||||
var header = _('data-usage');
|
||||
var msg = _('loading-interface-data');
|
||||
this.modalAlert(header + '\n' + msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Common.allNetworkInterfaces) {
|
||||
return Common.allNetworkInterfaces.find(findFunction);
|
||||
}
|
||||
},
|
||||
|
||||
getDataSIMInterface: function _getDataSIMInterface(iccId) {
|
||||
if (!iccId) {
|
||||
console.warn('Undefined icc identifier, unable get data interface');
|
||||
return;
|
||||
}
|
||||
if (iccId) {
|
||||
var findCurrentInterface = function(networkInterface) {
|
||||
if (networkInterface.id === iccId) {
|
||||
return networkInterface;
|
||||
}
|
||||
};
|
||||
return this.getInterface(findCurrentInterface);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
getWifiInterface: function _getWifiInterface() {
|
||||
var findWifiInterface = function(networkInterface) {
|
||||
if (networkInterface.type === navigator.mozNetworkStats.WIFI) {
|
||||
return networkInterface;
|
||||
}
|
||||
};
|
||||
return this.getInterface(findWifiInterface);
|
||||
},
|
||||
|
||||
loadApps: function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (Common.allAppsLoaded) {
|
||||
resolve(Common.allApps);
|
||||
return;
|
||||
}
|
||||
|
||||
var request = window.navigator.mozApps.mgmt.getAll();
|
||||
request.onsuccess = function(event) {
|
||||
var appList = event.target.result;
|
||||
// XXX : The data traffic of the filtered apps will be automatically
|
||||
// counted as residual data. This traffic is added to the system app
|
||||
// on the drawApps method located on "js/views/datausage.js"
|
||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1084010#c0
|
||||
Common.allApps = appList.filter(function(app) {
|
||||
return Common.specialApps.indexOf(app.manifestURL) === -1;
|
||||
});
|
||||
Common.allApps.push(Common.BROWSER_APP);
|
||||
Common.allAppsLoaded = true;
|
||||
resolve(Common.allApps);
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
reject(new Error('Apps could not be loaded'));
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
loadNetworkInterfaces: function(onsuccess, onerror) {
|
||||
var networks = navigator.mozNetworkStats.getAvailableNetworks();
|
||||
|
||||
networks.onsuccess = function() {
|
||||
Common.allNetworkInterfaces = networks.result;
|
||||
Common.allNetworkInterfaceLoaded = true;
|
||||
if (onsuccess) {
|
||||
onsuccess();
|
||||
}
|
||||
};
|
||||
|
||||
networks.onerror = function() {
|
||||
console.error('Error when trying to load network interfaces');
|
||||
if (onerror) {
|
||||
onerror();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getApp: function(manifestURL) {
|
||||
return this.allApps.find(function(app) {
|
||||
return app.manifestURL === manifestURL;
|
||||
});
|
||||
},
|
||||
|
||||
getAppManifest: function(app) {
|
||||
return app.manifest || app.updateManifest;
|
||||
},
|
||||
|
||||
getLocalizedAppName: function(app) {
|
||||
// If is System App returns label others
|
||||
if (app.manifestURL === Common.SYSTEM_MANIFEST) {
|
||||
return _('data-usage-other-apps');
|
||||
}
|
||||
// Browser app does not exist, we have to provide the localized app name
|
||||
if (app.manifestURL === Common.BROWSER_APP.manifestURL) {
|
||||
return _('data-usage-browser-app');
|
||||
}
|
||||
var manifest = this.getAppManifest(app);
|
||||
var userLang = document.documentElement.lang;
|
||||
var locales = manifest.locales;
|
||||
var localized = locales && locales[userLang] && locales[userLang].name;
|
||||
|
||||
return localized || manifest.name;
|
||||
},
|
||||
|
||||
getAppIcon: function(app) {
|
||||
var manifest = this.getAppManifest(app);
|
||||
var icons = manifest.icons;
|
||||
var defaultImage = '../style/images/app/icons/default.png';
|
||||
|
||||
if (!icons || !Object.keys(icons).length) {
|
||||
return defaultImage;
|
||||
}
|
||||
|
||||
// The preferred size is 30 by the default. If we use HDPI device, we may
|
||||
// use the image larger than 30 * 1.5 = 45 pixels.
|
||||
var preferredIconSize = 30 * (window.devicePixelRatio || 1);
|
||||
var preferredSize = Number.MAX_VALUE;
|
||||
var max = 0;
|
||||
|
||||
for (var size in icons) {
|
||||
size = parseInt(size, 10);
|
||||
if (size > max) {
|
||||
max = size;
|
||||
}
|
||||
|
||||
if (size >= preferredIconSize && size < preferredSize) {
|
||||
preferredSize = size;
|
||||
}
|
||||
}
|
||||
// If there is an icon matching the preferred size, we return the result,
|
||||
// if there isn't, we will return the maximum available size.
|
||||
if (preferredSize === Number.MAX_VALUE) {
|
||||
preferredSize = max;
|
||||
}
|
||||
|
||||
var url = icons[preferredSize];
|
||||
if (url) {
|
||||
return !(/^(http|https|data):/.test(url)) ? app.origin + url : url;
|
||||
} else {
|
||||
return defaultImage;
|
||||
}
|
||||
},
|
||||
|
||||
getDataLimit: function _getDataLimit(settings) {
|
||||
var multiplier = (settings.dataLimitUnit === 'MB') ?
|
||||
1000000 : 1000000000;
|
||||
return settings.dataLimitValue * multiplier;
|
||||
},
|
||||
|
||||
resetData: function _resetData(mode, onsuccess, onerror) {
|
||||
// Get all availabe Interfaces
|
||||
var wifiInterface = Common.getWifiInterface();
|
||||
|
||||
// Ask reset for all available Interfaces
|
||||
var wifiClearRequest, mobileClearRequest;
|
||||
|
||||
// onerror callback builder
|
||||
var getOnErrorFor = function(networkInterface) {
|
||||
return function() {
|
||||
if (wifiClearRequest) {
|
||||
wifiClearRequest.onerror = undefined;
|
||||
}
|
||||
if (mobileClearRequest) {
|
||||
mobileClearRequest.onerror = undefined;
|
||||
}
|
||||
(typeof onerror === 'function') && onerror(networkInterface);
|
||||
};
|
||||
};
|
||||
if ((mode === 'all' || mode === 'wifi') && wifiInterface) {
|
||||
wifiClearRequest = navigator.mozNetworkStats.clearStats(wifiInterface);
|
||||
wifiClearRequest.onerror = getOnErrorFor('wi-Fi');
|
||||
}
|
||||
if (mode === 'all' || mode === 'mobile') {
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
var currentSimcardInterface = Common.getDataSIMInterface(dataSim.iccId);
|
||||
if (currentSimcardInterface) {
|
||||
mobileClearRequest = navigator.mozNetworkStats
|
||||
.clearStats(currentSimcardInterface);
|
||||
mobileClearRequest.onerror = getOnErrorFor('simcard');
|
||||
mobileClearRequest.onsuccess = function _restoreDataLimitAlarm() {
|
||||
ConfigManager.requestSettings(dataSim.iccId,
|
||||
function _onSettings(settings) {
|
||||
if (settings.dataLimit) {
|
||||
// Restore network alarm
|
||||
addNetworkUsageAlarm(currentSimcardInterface,
|
||||
Common.getDataLimit(settings),
|
||||
function _addNetworkUsageAlarmOK() {
|
||||
ConfigManager.setOption({ 'dataUsageNotified': false });
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set last Reset
|
||||
if (mode === 'all') {
|
||||
ConfigManager.setOption({ lastCompleteDataReset: new Date() });
|
||||
} else {
|
||||
// Else clausure prevents running the update event twice
|
||||
ConfigManager.setOption({ lastDataReset: new Date() });
|
||||
}
|
||||
|
||||
// call onsuccess
|
||||
if (typeof onsuccess === 'function') {
|
||||
onsuccess();
|
||||
}
|
||||
},
|
||||
|
||||
resetAll: function _resetAll(callback) {
|
||||
function logResetDataError(networkInterface) {
|
||||
console.log('Error when trying to reset ' + networkInterface +
|
||||
' interface');
|
||||
}
|
||||
|
||||
Common.resetData('all', thenResetTelephony, logResetDataError);
|
||||
|
||||
function thenResetTelephony() {
|
||||
resetTelephony(callback);
|
||||
}
|
||||
},
|
||||
|
||||
// Next automatic reset date based on user preferences
|
||||
updateNextReset: function _updateNextReset(trackingPeriod, value, callback) {
|
||||
if (trackingPeriod === 'never') {
|
||||
setNextReset(null, callback); // remove any alarm
|
||||
return;
|
||||
}
|
||||
|
||||
var nextReset, today = new Date();
|
||||
|
||||
// Recalculate with month period
|
||||
if (trackingPeriod === 'monthly') {
|
||||
var month, year;
|
||||
var monthday = parseInt(value, 10);
|
||||
month = today.getMonth();
|
||||
year = today.getFullYear();
|
||||
if (today.getDate() >= monthday) {
|
||||
month = (month + 1) % 12;
|
||||
if (month === 0) {
|
||||
year++;
|
||||
}
|
||||
}
|
||||
nextReset = new Date(year, month, monthday);
|
||||
if (monthday !== nextReset.getDate()) {
|
||||
var LAST_DAY_OF_PREVIOUS_MONTH = 0;
|
||||
// If monthday is not equal to nextReset day, it means that the selected
|
||||
// reset day does not exist (e.g. 30 Feb). In this case, the reset day
|
||||
// must be the last day of the previous month
|
||||
nextReset.setDate(LAST_DAY_OF_PREVIOUS_MONTH);
|
||||
}
|
||||
|
||||
// Recalculate with week period
|
||||
} else if (trackingPeriod === 'weekly') {
|
||||
var oneDay = 24 * 60 * 60 * 1000;
|
||||
var weekday = parseInt(value, 10);
|
||||
var daysToTarget = weekday - today.getDay();
|
||||
if (daysToTarget <= 0) {
|
||||
daysToTarget = 7 + daysToTarget;
|
||||
}
|
||||
nextReset = new Date();
|
||||
nextReset.setTime(nextReset.getTime() + oneDay * daysToTarget);
|
||||
Toolkit.toMidnight(nextReset);
|
||||
}
|
||||
|
||||
// remove oldAlarm and set the new one
|
||||
setNextReset(nextReset, callback);
|
||||
},
|
||||
|
||||
localizeWeekdaySelector: function _localizeWeekdaySelector(selector) {
|
||||
var weekStartsOnMonday =
|
||||
parseInt(navigator.mozL10n.get('firstDayOfTheWeek'), 10) === 1;
|
||||
debug('Week starts on monday?', weekStartsOnMonday);
|
||||
var monday = selector.querySelector('.monday');
|
||||
var sunday = selector.querySelector('.sunday');
|
||||
var list = monday.parentNode;
|
||||
if (weekStartsOnMonday) {
|
||||
debug('Monday, Tuesday...');
|
||||
list.insertBefore(monday, list.childNodes[0]); // monday is the first
|
||||
list.appendChild(sunday); // sunday is the last
|
||||
} else {
|
||||
debug('Sunday, Monday...');
|
||||
list.insertBefore(sunday, list.childNodes[0]); // sunday is the first
|
||||
list.insertBefore(monday, sunday.nextSibling); // monday is the second
|
||||
}
|
||||
}
|
||||
};
|
||||
373
gaia/apps/costcontrol/js/config/config_manager.js
Executable file
|
|
@ -0,0 +1,373 @@
|
|||
/* global debug, SimManager, LazyLoader, asyncStorage, deepCopy */
|
||||
/* exported ConfigManager */
|
||||
'use strict';
|
||||
|
||||
var ConfigManager = (function() {
|
||||
|
||||
var today = new Date();
|
||||
|
||||
var DEFAULT_SETTINGS = {
|
||||
'dataLimit': false,
|
||||
'dataLimitValue': 1,
|
||||
'dataLimitUnit': 'GB',
|
||||
'errors': {
|
||||
'INCORRECT_TOPUP_CODE': false,
|
||||
'BALANCE_TIMEOUT': false,
|
||||
'TOPUP_TIMEOUT': false
|
||||
},
|
||||
'fte': true,
|
||||
'waitingForBalance': null,
|
||||
'waitingForTopUp': null,
|
||||
'lastBalance': null,
|
||||
'lastBalanceRequest': null,
|
||||
'lastTopUpRequest': null,
|
||||
'lastDataUsage': {
|
||||
'timestamp': today,
|
||||
'start': today,
|
||||
'end': today,
|
||||
'today': today,
|
||||
'wifi': {
|
||||
'apps': {},
|
||||
'total': 0
|
||||
},
|
||||
'mobile': {
|
||||
'apps': {},
|
||||
'total': 0
|
||||
}
|
||||
},
|
||||
'lastTelephonyActivity': {
|
||||
'timestamp': today,
|
||||
'calltime': 0,
|
||||
'smscount': 0
|
||||
},
|
||||
'lastTelephonyReset': today,
|
||||
'lastDataReset': today,
|
||||
'lastCompleteDataReset': today,
|
||||
'lowLimit': false,
|
||||
'lowLimitThreshold': false,
|
||||
'lowLimitNotified': false,
|
||||
'zeroBalanceNotified': false,
|
||||
'dataUsageNotified': false,
|
||||
'nextReset': null,
|
||||
'plantype': 'postpaid',
|
||||
'resetTime': 1,
|
||||
'trackingPeriod': 'monthly',
|
||||
'isMobileChartVisible': true,
|
||||
'isWifiChartVisible': false
|
||||
};
|
||||
|
||||
var CONFIG_CACHE = [];
|
||||
|
||||
function getApplicationMode() {
|
||||
if (noConfigFound) {
|
||||
return 'DATA_USAGE_ONLY';
|
||||
}
|
||||
return settings.plantype.toUpperCase();
|
||||
}
|
||||
|
||||
var configuration, configurationIndex, noConfigFound = false;
|
||||
function setConfig(newConfiguration) {
|
||||
configuration = newConfiguration;
|
||||
debug('Provider configuration done!');
|
||||
}
|
||||
|
||||
// Load the operator configuration according to MCC_MNC pair
|
||||
function requestConfiguration(currentDataIcc, callback) {
|
||||
if (!currentDataIcc || !currentDataIcc.iccInfo) {
|
||||
console.error('No iccInfo available');
|
||||
return;
|
||||
}
|
||||
|
||||
function returnConfiguration() {
|
||||
if (typeof callback === 'function') {
|
||||
callback(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration) {
|
||||
setTimeout(function() {
|
||||
returnConfiguration();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (configurationIndex) {
|
||||
loadConfiguration(currentDataIcc, returnConfiguration);
|
||||
} else {
|
||||
loadConfigurationIndex(function onIndex() {
|
||||
loadConfiguration(currentDataIcc, returnConfiguration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfiguration(currentDataIcc, callback) {
|
||||
var configFilePath = getConfigFilePath(currentDataIcc);
|
||||
if (CONFIG_CACHE[configFilePath]) {
|
||||
ConfigManager.setConfig(CONFIG_CACHE[configFilePath]);
|
||||
(typeof callback === 'function') && callback();
|
||||
} else {
|
||||
LazyLoader.load(configFilePath, function() {
|
||||
// configuration variable is set by calling setConfig() inside the
|
||||
// configuration file.
|
||||
CONFIG_CACHE[configFilePath] = configuration;
|
||||
(typeof callback === 'function') && callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigFilePath(currentDataIcc) {
|
||||
noConfigFound = false;
|
||||
var mcc = currentDataIcc.iccInfo.mcc;
|
||||
var mnc = currentDataIcc.iccInfo.mnc;
|
||||
var key = mcc + '_' + mnc;
|
||||
var configDir = configurationIndex[key];
|
||||
if (!configDir) {
|
||||
configDir = 'default';
|
||||
noConfigFound = true;
|
||||
}
|
||||
return 'js/config/' + configDir + '/config.js';
|
||||
}
|
||||
|
||||
function loadConfigurationIndex(callback) {
|
||||
LazyLoader.getJSON('/js/config/index.json').then(function(json) {
|
||||
configurationIndex = json;
|
||||
if (configurationIndex === null) { // TODO Remove workaround when
|
||||
// Bug 1069808 is fixed.
|
||||
console.error('Error loading the configuration index!' +
|
||||
'Response from LazyLoader was null.');
|
||||
configurationIndex = {};
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
setTimeout(function() {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}, function(error) {
|
||||
console.error('Error loading the configuration index! ' +
|
||||
'Error code: ' + error);
|
||||
configurationIndex = {};
|
||||
});
|
||||
}
|
||||
|
||||
// Let's serialize dates
|
||||
// XXX: To avoid random error:
|
||||
// "DataCloneError: The object could not be cloned."
|
||||
// {file:
|
||||
// "app://costcontrol.gaiamobile.org/shared/js/async_storage.js" line: 90}]
|
||||
Date.prototype.toJSON = function() {
|
||||
return {'__date__': this.toISOString()};
|
||||
};
|
||||
function settingsReviver(k, v) {
|
||||
if (v === null || typeof v !== 'object' || !v.hasOwnProperty('__date__')) {
|
||||
return v;
|
||||
}
|
||||
|
||||
return new Date(v.__date__);
|
||||
}
|
||||
|
||||
// Load stored settings
|
||||
var NO_ICCID = 'NOICCID';
|
||||
var settings;
|
||||
function requestSettings(iccIdInfo, callback) {
|
||||
var currentICCID = iccIdInfo || NO_ICCID;
|
||||
asyncStorage.getItem(currentICCID, function _wrapGetItem(localSettings) {
|
||||
// No entry: set defaults
|
||||
try {
|
||||
settings = JSON.parse(localSettings, settingsReviver);
|
||||
} catch (e) {
|
||||
// If we can't parse the settings, use the default ones
|
||||
}
|
||||
|
||||
if (settings === null) {
|
||||
settings = deepCopy(DEFAULT_SETTINGS);
|
||||
debug('Storing default settings for ICCID:', currentICCID);
|
||||
asyncStorage.setItem(currentICCID, JSON.stringify(settings),
|
||||
callback && callback.bind(null, settings));
|
||||
} else if (callback) {
|
||||
callback(settings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Provides vendor configuration and settings
|
||||
function requestAll(callback) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
requestConfiguration(dataSimIcc.icc,
|
||||
function _afterConfig(configuration) {
|
||||
requestSettings(dataSimIcc.iccId, function _afterSettings(settings) {
|
||||
if (callback) {
|
||||
callback(configuration, settings, dataSimIcc.iccId);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Produce a optionchange for the given option
|
||||
function dispatchOptionChange(name, value, oldValue, settings) {
|
||||
var event = new CustomEvent('optionchange', { detail: {
|
||||
name: name,
|
||||
value: value,
|
||||
oldValue: oldValue,
|
||||
settings: settings
|
||||
} });
|
||||
window.dispatchEvent(event);
|
||||
debug('Event optionchange dispatched for', name);
|
||||
}
|
||||
|
||||
// Set setting options asynchronously and dispatch an event for every
|
||||
// affected option.
|
||||
function setOption(options, callback) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
// If settings is not ready, load and retry
|
||||
if (!settings) {
|
||||
requestSettings(dataSimIcc.iccId, function _afterEnsuringSettings() {
|
||||
setOption(options, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Store former values and update with new ones
|
||||
var formerValue = {};
|
||||
for (var name in options) {
|
||||
if (options.hasOwnProperty(name)) {
|
||||
formerValue[name] = settings[name];
|
||||
settings[name] = options[name];
|
||||
}
|
||||
}
|
||||
|
||||
var currentICCID = dataSimIcc.iccId || NO_ICCID;
|
||||
asyncStorage.setItem(currentICCID, JSON.stringify(settings),
|
||||
function _onSet() {
|
||||
requestSettings(dataSimIcc.iccId, function _onSettings(settings) {
|
||||
for (var name in options) {
|
||||
if (options.hasOwnProperty(name)) {
|
||||
dispatchOptionChange(name, settings[name], formerValue[name],
|
||||
settings);
|
||||
}
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Part of the synchronous interface, return or set a setting.
|
||||
function syncOption(name, value) {
|
||||
var oldValue = settings[name];
|
||||
if (value === undefined) {
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
var update = {};
|
||||
update[name] = value;
|
||||
setOption(update);
|
||||
}
|
||||
|
||||
var callbacks;
|
||||
// Function in charge of dispatch the events to the observers
|
||||
function callCallbacks(evt) {
|
||||
debug('Option', evt.detail.name, 'has changed!');
|
||||
var callbackCollection = callbacks[evt.detail.name] || [];
|
||||
for (var i = 0; i < callbackCollection.length; i++) {
|
||||
var callback = callbackCollection[i];
|
||||
if (callback) {
|
||||
callback(evt.detail.value, evt.detail.oldValue, evt.detail.name,
|
||||
evt.detail.settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Installs an observer to call when the setting has changed. It produces
|
||||
// an initial call to the callback unless you provide true as the last
|
||||
// parameter.
|
||||
function syncObserve(name, callback, avoidInitialCall) {
|
||||
debug('Installing observer for', name);
|
||||
|
||||
// XXX: initialize this only if an observer is added
|
||||
if (callbacks === undefined) {
|
||||
callbacks = {};
|
||||
|
||||
// Keeps the synchronization when the application need to communicate
|
||||
// instant information. Local storage event is used. The key sync is
|
||||
// read to determine which option has changed and a settingchange event
|
||||
// for that option is dispatched.
|
||||
window.addEventListener('storage', function _onSync(evt) {
|
||||
if (evt.key === 'sync') {
|
||||
var name = evt.newValue.split('#')[0];
|
||||
var oldValue = settings ? settings[name] : undefined;
|
||||
debug('Synchronization request for', name, 'received!');
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(newSettings) {
|
||||
settings = newSettings;
|
||||
dispatchOptionChange(name, settings[name], oldValue, settings);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('optionchange', callCallbacks);
|
||||
}
|
||||
|
||||
if (callbacks[name] === undefined) {
|
||||
callbacks[name] = [];
|
||||
}
|
||||
|
||||
if (callbacks[name].indexOf(callback) < 0) {
|
||||
callbacks[name].push(callback);
|
||||
avoidInitialCall = avoidInitialCall || false;
|
||||
if (!avoidInitialCall && callback) {
|
||||
callback(settings[name], null, name, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncRemoveObserver(name, callback) {
|
||||
var callbackCollection = callbacks[name];
|
||||
if (!callbackCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = callbackCollection.indexOf(callback);
|
||||
if (index > -1) {
|
||||
callbackCollection.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function defaultValue(name) {
|
||||
return deepCopy(DEFAULT_SETTINGS[name]);
|
||||
}
|
||||
|
||||
return {
|
||||
getApplicationMode: getApplicationMode,
|
||||
setConfig: setConfig,
|
||||
requestAll: requestAll,
|
||||
requestSettings: requestSettings,
|
||||
setOption: setOption,
|
||||
defaultValue: defaultValue,
|
||||
|
||||
// XXX:These methods are the synchronous/cached interface. You need to
|
||||
// call requestAll() or requestSettings() to force cache renewal.
|
||||
// If you want to use observe() and option() as a getter, you need to setup
|
||||
// the cache by calling one of the former methods before. Only once is
|
||||
// enough.
|
||||
option: syncOption,
|
||||
observe: syncObserve,
|
||||
removeObserver: syncRemoveObserver,
|
||||
|
||||
// XXX: Once loaded via requestConfiguration() / requestAll() it is ensured
|
||||
// it wont change so you can use this to access OEM confguration in a
|
||||
// synchronous fashion.
|
||||
get configuration() { return configuration; },
|
||||
// XXX: Bug 1126178-[CostControl] Remove ConfigManager.supportCustomizeMode
|
||||
// when the functionality is ready
|
||||
supportCustomizeMode: false
|
||||
};
|
||||
|
||||
}());
|
||||
4
gaia/apps/costcontrol/js/config/default/config.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
/* global ConfigManager */
|
||||
'use strict';
|
||||
|
||||
ConfigManager.setConfig({});
|
||||
9
gaia/apps/costcontrol/js/config/index.json
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"724_6": "vivo",
|
||||
"724_10": "vivo",
|
||||
"724_11": "vivo",
|
||||
"724_23": "vivo",
|
||||
"732_123": "movistar_colombia",
|
||||
"748_07": "movistar_uruguay",
|
||||
"716_06": "movistar_peru"
|
||||
}
|
||||
22
gaia/apps/costcontrol/js/config/movistar_colombia/config.js
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
/* global ConfigManager */
|
||||
'use strict';
|
||||
|
||||
var MovistarColombiaConfig = {
|
||||
provider: 'Movistar',
|
||||
is_free: true,
|
||||
is_roaming_free: true,
|
||||
credit: { currency: '$' },
|
||||
balance: {
|
||||
destination: '611',
|
||||
text: 'SALDO,A',
|
||||
senders: ['612', '611'],
|
||||
regexp: 'Saldo total:\\$([0-9,]+)\\.([0-9]{2})',
|
||||
minimum_delay: 3 * 60 * 60 * 1000 // 3h
|
||||
},
|
||||
topup: {
|
||||
ussd_destination: '*611#'
|
||||
},
|
||||
default_low_limit_threshold: 2000
|
||||
};
|
||||
|
||||
ConfigManager.setConfig(MovistarColombiaConfig);
|
||||
19
gaia/apps/costcontrol/js/config/movistar_peru/config.js
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
/* global ConfigManager */
|
||||
'use strict';
|
||||
|
||||
var MovistarPeruConfig = {
|
||||
provider: 'Movistar',
|
||||
is_free: true,
|
||||
credit: { currency: 'S/.' },
|
||||
balance: {
|
||||
destination: '600',
|
||||
text: 'S',
|
||||
senders: ['515'],
|
||||
regexp: '([0-9,]+)\\.([0-9]{2})',
|
||||
zero_regexp: '(Usted no tiene saldo en sus cuentas)',
|
||||
minimum_delay: 3 * 60 * 60 * 1000 // 3h
|
||||
},
|
||||
default_low_limit_threshold: 1
|
||||
};
|
||||
|
||||
ConfigManager.setConfig(MovistarPeruConfig);
|
||||
23
gaia/apps/costcontrol/js/config/movistar_uruguay/config.js
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
/* global ConfigManager */
|
||||
'use strict';
|
||||
|
||||
var MovistarUruguayConfig = {
|
||||
provider: 'Movistar',
|
||||
is_free: true,
|
||||
is_roaming_free: true,
|
||||
credit: { currency: '$' },
|
||||
balance: {
|
||||
destination: '222',
|
||||
text: 'SALDO',
|
||||
senders: ['222'],
|
||||
regexp: 'Recarga \\$([0-9,]+)(?:\\.([0-9]+))?',
|
||||
zero_regexp: '(Su saldo actual es cero)',
|
||||
minimum_delay: 3 * 60 * 60 * 1000 // 3h
|
||||
},
|
||||
topup: {
|
||||
ussd_destination: '*222*1*1#'
|
||||
},
|
||||
default_low_limit_threshold: 0
|
||||
};
|
||||
|
||||
ConfigManager.setConfig(MovistarUruguayConfig);
|
||||
126
gaia/apps/costcontrol/js/config/vivo/config.js
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
/* global Format, ConfigManager */
|
||||
|
||||
'use strict';
|
||||
|
||||
var VivoConfig = (function() {
|
||||
|
||||
var RESERVED = '0';
|
||||
var OPERATION_TYPE_REQUEST = '01';
|
||||
var OPERATION_CHECK_BALANCE = '0102';
|
||||
var MESSAGE_INIT_WIDTH = 32;
|
||||
|
||||
var THOUSANDS_PADDING = 4;
|
||||
var TENS_PADDING = 2;
|
||||
|
||||
function getDateTime() {
|
||||
var now = new Date();
|
||||
var date = [
|
||||
Format.padLeft(now.getUTCFullYear(), THOUSANDS_PADDING, '0'),
|
||||
Format.padLeft(now.getUTCMonth() + 1, TENS_PADDING, '0'),
|
||||
Format.padLeft(now.getUTCDate(), TENS_PADDING, '0')
|
||||
].join('');
|
||||
var time = [
|
||||
Format.padLeft(now.getUTCHours(), TENS_PADDING, '0'),
|
||||
Format.padLeft(now.getUTCMinutes(), TENS_PADDING, '0'),
|
||||
Format.padLeft(now.getUTCSeconds(), TENS_PADDING, '0')
|
||||
].join('');
|
||||
return [date, time];
|
||||
}
|
||||
|
||||
/*
|
||||
* VIVO request messages are formed by the concatenation of fixed length
|
||||
* fields:
|
||||
* - 32-length initial padding, all '0' (zero)
|
||||
* - 3-length system code. Currently only 'OWD' (FirefoOS) is supported
|
||||
* - 8-length date string in the form of YYYYMMDD
|
||||
* - 6-length time string in the form of HHMMSS
|
||||
* - 2-length operation type
|
||||
* - 1-length total number of SMS for the request
|
||||
* - 1-length number of the current SMS being transmitted (starting in 1)
|
||||
* - 1-length reserved character. It is '0'
|
||||
* - 4-length operation code
|
||||
*
|
||||
* Example of balance request (ignore blank spaces):
|
||||
* 00000000000000000000000000000000 OWD 20130530 132006 01 1 1 0 0102
|
||||
*
|
||||
* A request can include parameters in a last field of variable length from
|
||||
* 0 to 80 characters.
|
||||
*/
|
||||
function generateCheckBalanceMessage() {
|
||||
var messageInit = (new Array(MESSAGE_INIT_WIDTH + 1)).join('0');
|
||||
var systemCode = 'OWD';
|
||||
var dateTime = getDateTime();
|
||||
var date = dateTime[0];
|
||||
var time = dateTime[1];
|
||||
var operationType = OPERATION_TYPE_REQUEST;
|
||||
var totalMessageCount = '1';
|
||||
var currentMessageCount = '1';
|
||||
var requestedOperation = OPERATION_CHECK_BALANCE;
|
||||
|
||||
return [
|
||||
messageInit,
|
||||
systemCode,
|
||||
date,
|
||||
time,
|
||||
operationType,
|
||||
totalMessageCount,
|
||||
currentMessageCount,
|
||||
RESERVED,
|
||||
requestedOperation
|
||||
].join('');
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* A VIVO answer include specific fields from the related request in such
|
||||
* a way these fields form a unique ID to match each answer with its request.
|
||||
*
|
||||
* For example, compare this pair of request, response:
|
||||
* 00000000000000000000000000000000 OWD 20130530 132006 01 1 1 0 0102
|
||||
* 20130530 132006 01 1 1 0 0102 60.45
|
||||
*
|
||||
* Current value for the regexp used to validate a balance answer is not
|
||||
* checking the unique ID since this requires to store this ID and make it
|
||||
* to survive the application closes. Since access to configuration
|
||||
* properties must be synchronous, we should use some kind of synchronous
|
||||
* storage. As DOMStorage is discouraged and IndexedDB synchronous API is
|
||||
* not ready we can not currently implement this constrain.
|
||||
*/
|
||||
var balanceRegularExpression = [
|
||||
'^[0-9]{14}', // we admit any sequence of 14 numbers. See TODO above.
|
||||
OPERATION_TYPE_REQUEST,
|
||||
'1', '1',
|
||||
RESERVED,
|
||||
OPERATION_CHECK_BALANCE,
|
||||
'([0-9]+)\\.([0-9]{1,2})' // matches the remaining balance
|
||||
].join('');
|
||||
|
||||
return {
|
||||
provider: 'Vivo',
|
||||
is_free: true,
|
||||
is_roaming_free: true,
|
||||
credit: { currency: 'R$' },
|
||||
balance: {
|
||||
destination: '4850',
|
||||
get text() {
|
||||
return generateCheckBalanceMessage();
|
||||
},
|
||||
senders: ['4850'],
|
||||
regexp: balanceRegularExpression,
|
||||
minimum_delay: 3 * 60 * 60 * 1000 // 3h
|
||||
},
|
||||
topup: {
|
||||
destination: '7000',
|
||||
ussd_destination: '*321#',
|
||||
text: '&code',
|
||||
senders: ['1515', '7000'],
|
||||
confirmation_regexp:
|
||||
'Voce recarregou R\\$\\s*([0-9]+)(?:[,\\.]([0-9]+))?',
|
||||
incorrect_code_regexp:
|
||||
'(Favor enviar|envie novamente|Verifique) o codigo de recarga'
|
||||
},
|
||||
default_low_limit_threshold: 3
|
||||
};
|
||||
}());
|
||||
|
||||
ConfigManager.setConfig(VivoConfig);
|
||||
720
gaia/apps/costcontrol/js/costcontrol.js
Executable file
|
|
@ -0,0 +1,720 @@
|
|||
/* global debug, ConfigManager, Toolkit, addAlarmTimeout, Common, LazyLoader,
|
||||
SimManager, IACManager, DEBUGGING
|
||||
*/
|
||||
/* exported CostControl */
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* CostControl is the singleton in charge of provide data to the views by using
|
||||
* asynchronous requests. Views get the object by calling
|
||||
* CostControl.getInstance() method.
|
||||
*
|
||||
* The CostControl instance has allow to perform requests:
|
||||
* balance, topup, telephony, datausage
|
||||
*
|
||||
* And get the application mode:
|
||||
* DATA_USAGE_ONLY, PREPAID; POSTPAID
|
||||
*
|
||||
*/
|
||||
|
||||
var CostControl = (function() {
|
||||
|
||||
var costcontrol;
|
||||
function getInstance(onready) {
|
||||
debug('Initializing Cost Control');
|
||||
|
||||
function goOn() {
|
||||
debug('Cost Control already ready!');
|
||||
onready(costcontrol);
|
||||
}
|
||||
|
||||
// Force a reload of Costcontrol when the dataSlotChange event is lost
|
||||
if (costcontrol) {
|
||||
if (SimManager.isMultiSim()) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
if (costcontrol.iccId === dataSimIcc.iccId) {
|
||||
goOn();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
goOn();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function setupCostControl(configuration, settings, iccId) {
|
||||
costcontrol = {
|
||||
iccId: iccId,
|
||||
request: request,
|
||||
lastDataResults: {},
|
||||
lastDataResultsPerApp: {},
|
||||
isBalanceRequestSMS: isBalanceRequestSMS
|
||||
};
|
||||
|
||||
debug('Cost Control ready!');
|
||||
onready(costcontrol);
|
||||
}
|
||||
|
||||
loadAPIs();
|
||||
ConfigManager.requestAll(setupCostControl);
|
||||
}
|
||||
|
||||
var mobileMessageManager, statistics, isSendingBalanceRequest = false;
|
||||
function loadAPIs() {
|
||||
if ('mozMobileMessage' in window.navigator) {
|
||||
mobileMessageManager = window.navigator.mozMobileMessage;
|
||||
}
|
||||
|
||||
if ('mozNetworkStats' in window.navigator) {
|
||||
statistics = window.navigator.mozNetworkStats;
|
||||
}
|
||||
|
||||
debug('APIs loaded!');
|
||||
}
|
||||
|
||||
// OTHER LOGIC
|
||||
|
||||
// Check if a SMS matches the form of a balance request
|
||||
function isBalanceRequestSMS(message, configuration) {
|
||||
return message.receiver === configuration.balance.destination;
|
||||
}
|
||||
|
||||
// Perform a request. They must be specified via a request object with:
|
||||
// type: datausage, balance, topup or telephony
|
||||
// data: in case of topup, the code for topup
|
||||
//
|
||||
// In a nutshell, request() method check if there are some service issues or
|
||||
// another operation in progress and, if not, dispatch the request to another
|
||||
// specific handler.
|
||||
function request(requestObj, callback) {
|
||||
ConfigManager.requestAll(function _onInfo(configuration, settings) {
|
||||
debug('Request for:', requestObj.type);
|
||||
|
||||
var force = requestObj.force;
|
||||
var result = {};
|
||||
|
||||
// Only type is set here
|
||||
result.type = requestObj.type;
|
||||
function _requestDataStatistics() {
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
requestDataStatistics(configuration, settings, callback, dataSim,
|
||||
result, requestObj);
|
||||
});
|
||||
}
|
||||
|
||||
function _requestBalance(connection) {
|
||||
// Check service
|
||||
var issues = getServiceIssues(configuration, settings,
|
||||
connection);
|
||||
var canBeIgnoredByForcing = (issues === 'minimum_delay' && force);
|
||||
if (issues && !canBeIgnoredByForcing) {
|
||||
result.status = 'error';
|
||||
result.details = issues;
|
||||
result.data = settings.lastBalance;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var costIssues = getCostIssues(configuration, connection);
|
||||
if (!force && costIssues) {
|
||||
result.status = 'error';
|
||||
result.details = costIssues;
|
||||
result.data = settings.lastBalance;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO To avoid concurrence problems, the best solution would be to
|
||||
// implement a request queue instead of the isSendingBalanceRequest lock
|
||||
// Check in-progress
|
||||
var isWaiting = settings.waitingForBalance !== null;
|
||||
var timeout = Toolkit.checkEnoughDelay(BALANCE_TIMEOUT,
|
||||
settings.lastBalanceRequest);
|
||||
if ((isWaiting && !timeout) || isSendingBalanceRequest) {
|
||||
result.status = 'in_progress';
|
||||
result.data = settings.lastBalance;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
isSendingBalanceRequest = true;
|
||||
|
||||
// Dispatch
|
||||
LazyLoader.load('js/iac_manager.js', function() {
|
||||
requestBalance(configuration, settings, callback, result);
|
||||
});
|
||||
}
|
||||
|
||||
function _requestTopUp(connection) {
|
||||
// Check service
|
||||
var issuesTopUp = getServiceIssues(configuration, settings,
|
||||
connection);
|
||||
if (issuesTopUp && issuesTopUp !== 'minimum_delay') {
|
||||
result.status = 'error';
|
||||
result.details = issuesTopUp;
|
||||
result.data = settings.lastDataUsage;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var costIssuesTopUp = getCostIssues(configuration, connection);
|
||||
if (!force && costIssuesTopUp) {
|
||||
result.status = 'error';
|
||||
result.details = costIssuesTopUp;
|
||||
result.data = settings.lastBalance;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check in-progress
|
||||
var isWaitingTopUp = settings.waitingForTopUp !== null;
|
||||
var timeoutTopUp = Toolkit.checkEnoughDelay(BALANCE_TIMEOUT,
|
||||
settings.lastTopUpRequest);
|
||||
if (isWaitingTopUp && !timeoutTopUp && !force) {
|
||||
result.status = 'in_progress';
|
||||
result.data = settings.lastDataUsage;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch
|
||||
var code = requestObj.data;
|
||||
LazyLoader.load('js/iac_manager.js', function() {
|
||||
requestTopUp(configuration, settings, code, callback, result);
|
||||
});
|
||||
}
|
||||
switch (requestObj.type) {
|
||||
case 'balance':
|
||||
SimManager.requestDataConnection(_requestBalance);
|
||||
break;
|
||||
|
||||
case 'topup':
|
||||
SimManager.requestDataConnection(_requestTopUp);
|
||||
break;
|
||||
|
||||
case 'datausage':
|
||||
// Dispatch
|
||||
if (!Common.allNetworkInterfaceLoaded) {
|
||||
Common.loadNetworkInterfaces(_requestDataStatistics,
|
||||
_requestDataStatistics);
|
||||
} else {
|
||||
_requestDataStatistics();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'telephony':
|
||||
// Can not fail: only dispatch
|
||||
result.data = settings.lastTelephonyActivity;
|
||||
result.status = 'success';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
// Check service status and return the most representative issue if there is
|
||||
function getServiceIssues(configuration, settings, connection) {
|
||||
|
||||
if (!connection || !connection.data ||
|
||||
!connection.voice || !connection.voice.connected) {
|
||||
return 'no_service';
|
||||
}
|
||||
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
if (mode !== 'PREPAID') {
|
||||
return 'no_service';
|
||||
}
|
||||
|
||||
if (connection.voice.relSignalStrength === null) {
|
||||
return 'no_coverage';
|
||||
}
|
||||
|
||||
if (configuration.balance.minimum_delay) {
|
||||
var isMinimumDelayHonored = Toolkit.checkEnoughDelay(
|
||||
configuration.balance.minimum_delay,
|
||||
settings.lastBalanceRequest
|
||||
);
|
||||
if (!isMinimumDelayHonored) {
|
||||
return 'minimum_delay';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check cost issues and return
|
||||
function getCostIssues(configuration, connection) {
|
||||
var inRoaming = connection.voice.roaming;
|
||||
if (inRoaming && configuration.is_roaming_free !== true) {
|
||||
return 'non_free_in_roaming';
|
||||
}
|
||||
|
||||
if (!inRoaming && configuration.is_free !== true) {
|
||||
return 'non_free';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Send a request SMS and set timeouts for interrupting waiting for response
|
||||
var BALANCE_TIMEOUT = 5 * 60 * 1000; // Should be 5 min
|
||||
function requestBalance(configuration, settings, callback, result) {
|
||||
debug('Requesting balance...');
|
||||
result.data = settings.lastBalance;
|
||||
|
||||
function sendSMS() {
|
||||
debug('After IAC broadcast ask for starting - balance');
|
||||
// Send request SMS
|
||||
var newSMS = mobileMessageManager.send(
|
||||
configuration.balance.destination,
|
||||
configuration.balance.text
|
||||
);
|
||||
|
||||
newSMS.onsuccess = function _onSuccess() {
|
||||
debug('Request SMS sent! Waiting for response.');
|
||||
|
||||
if (!DEBUGGING) {
|
||||
mobileMessageManager.delete(newSMS.result.id);
|
||||
}
|
||||
|
||||
// Add the timeout
|
||||
var newAlarm = addAlarmTimeout('balanceTimeout', BALANCE_TIMEOUT);
|
||||
|
||||
newAlarm.onsuccess = function _alarmSet(evt) {
|
||||
var id = evt.target.result;
|
||||
debug('Timeout for balance (', id, ') update set to:',
|
||||
BALANCE_TIMEOUT);
|
||||
|
||||
ConfigManager.setOption(
|
||||
{
|
||||
'waitingForBalance': id,
|
||||
'lastBalanceRequest': new Date()
|
||||
},
|
||||
function _onSet() {
|
||||
isSendingBalanceRequest = false;
|
||||
result.status = 'success';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
newAlarm.onerror = function _alarmFailedToSet(evt) {
|
||||
ConfigManager.setOption(
|
||||
{
|
||||
'lastBalanceRequest': new Date()
|
||||
},
|
||||
function _onSet() {
|
||||
isSendingBalanceRequest = false;
|
||||
debug('Failed to set timeout for balance request!');
|
||||
result.status = 'error';
|
||||
result.details = 'timeout_fail';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
newSMS.onerror = function _onError() {
|
||||
isSendingBalanceRequest = false;
|
||||
debug('Request SMS failed! But returning stored balance.');
|
||||
IACManager.broadcastEndOfSMSQuery('balance').then(function(msg) {
|
||||
debug('After IAC broadcast ask for ending - balance');
|
||||
});
|
||||
result.status = 'error';
|
||||
result.details = 'request_fail';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
};
|
||||
debug('Balance out of date. Requesting fresh data...');
|
||||
}
|
||||
IACManager.init(configuration);
|
||||
IACManager.broadcastStartOfSMSQuery('balance').then(sendSMS, sendSMS);
|
||||
}
|
||||
|
||||
// Send a top up SMS and set timeouts for interrupting waiting for response
|
||||
var TOPUP_TIMEOUT = 5 * 60 * 1000; // Should be 5 min
|
||||
function requestTopUp(configuration, settings, code, callback, result) {
|
||||
debug('Requesting TopUp with code', code, '...');
|
||||
|
||||
function sendSMS() {
|
||||
debug('After IAC broadcast ask for starting - topup');
|
||||
|
||||
// TODO: Ensure is free
|
||||
var newSMS = mobileMessageManager.send(
|
||||
configuration.topup.destination,
|
||||
configuration.topup.text.replace(/\&code/g, code)
|
||||
);
|
||||
|
||||
newSMS.onsuccess = function _onSuccess() {
|
||||
debug('TopUp SMS sent! Waiting for response.');
|
||||
|
||||
if (!DEBUGGING) {
|
||||
mobileMessageManager.delete(newSMS.result.id);
|
||||
}
|
||||
|
||||
// Add the timeout (if fail, do not inform the callback)
|
||||
var newAlarm = addAlarmTimeout('topupTimeout', TOPUP_TIMEOUT);
|
||||
|
||||
newAlarm.onsuccess = function _alarmSet(evt) {
|
||||
var id = evt.target.result;
|
||||
debug('Timeout for TopUp (', id, ') update set to:', TOPUP_TIMEOUT);
|
||||
|
||||
// XXX: waitingForTopUp can be null if no waiting or distinct
|
||||
// than null to indicate the unique id of the timeout waiting
|
||||
// for the response message
|
||||
ConfigManager.setOption(
|
||||
{
|
||||
'waitingForTopUp': id,
|
||||
'lastTopUpRequest': new Date()
|
||||
},
|
||||
function _onSet() {
|
||||
result.status = 'success';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
newAlarm.onerror = function _alarmFailedToSet(evt) {
|
||||
debug('Failed to set timeout for TopUp request!');
|
||||
result.status = 'error';
|
||||
result.details = 'timeout_fail';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
newSMS.onerror = function _onError() {
|
||||
debug('TopUp SMS failed!');
|
||||
IACManager.broadcastEndOfSMSQuery('topup').then(function(msg) {
|
||||
debug('After IAC broadcast ask for ending - topup');
|
||||
});
|
||||
result.status = 'error';
|
||||
result.details = 'request_fail';
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
IACManager.init(configuration);
|
||||
IACManager.broadcastStartOfSMSQuery('topup').then(sendSMS, sendSMS);
|
||||
}
|
||||
|
||||
// XXX: pending on bug XXX to get statistics by SIM
|
||||
// Ask statistics API for mobile and wifi data usage
|
||||
var DAY = 24 * 3600 * 1000; // 1 day
|
||||
function requestDataStatistics(configuration, settings, callback, dataSimIcc,
|
||||
result, requestParameters) {
|
||||
debug('Statistics out of date. Requesting fresh data...');
|
||||
var apps = requestParameters && requestParameters.apps;
|
||||
var start = requestParameters && requestParameters.startDate;
|
||||
var end = requestParameters && requestParameters.endDate;
|
||||
|
||||
if (!start) {
|
||||
var maxAge = 1000 * statistics.maxStorageAge;
|
||||
var minimumStart = new Date(Date.now() - maxAge);
|
||||
debug('The max age for samples is ' + minimumStart);
|
||||
|
||||
// If settings.lastCompleteDataReset is not set let's use the past week.
|
||||
// This is only for not breaking dogfooders build and this can be remove
|
||||
// at some point in the future (and since this sentence has been said
|
||||
// multiple times this code will probably stay here for a while).
|
||||
start = new Date(settings.lastCompleteDataReset ||
|
||||
Date.now() - 7 * DAY);
|
||||
if (start < minimumStart) {
|
||||
console.warn('Start date is surpassing the maximum age for the ' +
|
||||
'samples. Setting to ' + minimumStart);
|
||||
start = minimumStart;
|
||||
}
|
||||
}
|
||||
|
||||
start = Toolkit.toMidnight(start);
|
||||
|
||||
if (!end) {
|
||||
var today = Toolkit.toMidnight(new Date());
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setTime(today.getTime() + DAY);
|
||||
|
||||
end = Toolkit.toMidnight(settings.nextReset ?
|
||||
new Date(settings.nextReset.getTime() - DAY) :
|
||||
tomorrow);
|
||||
}
|
||||
if (start > end) {
|
||||
console.error('Start date is higher than end date. This must not ' +
|
||||
'happen. Changed end date to day after startDate');
|
||||
end = new Date(start.getTime() + DAY);
|
||||
}
|
||||
|
||||
var wifiInterface = Common.getWifiInterface();
|
||||
var currentSimcardNetwork = Common.getDataSIMInterface(dataSimIcc.iccId);
|
||||
|
||||
var wifiRequests, simRequests;
|
||||
var pendingRequests = 0;
|
||||
|
||||
function checkForCompletion() {
|
||||
pendingRequests--;
|
||||
if (pendingRequests === 0) {
|
||||
updateDataUsage();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDataUsage() {
|
||||
var lastDataUsage = {
|
||||
timestamp: new Date(),
|
||||
start: start,
|
||||
end: end,
|
||||
today: today,
|
||||
wifi: {
|
||||
apps: {},
|
||||
total: 0,
|
||||
samples: []
|
||||
},
|
||||
mobile: {
|
||||
apps: {},
|
||||
total: 0,
|
||||
samples: []
|
||||
}
|
||||
};
|
||||
|
||||
function saveLastDataUsage(perApp) {
|
||||
// XXX: raw samples are apparently too much to persist,
|
||||
// so we only store aggregate totals
|
||||
|
||||
var savedUsage = {};
|
||||
Object.keys(lastDataUsage).forEach(function(key) {
|
||||
var value = lastDataUsage[key];
|
||||
if (key === 'wifi' || key === 'mobile') {
|
||||
|
||||
// If saving per app totals we should retain the totals for the
|
||||
// global query, persisted in settings by the time of this call.
|
||||
if (perApp) {
|
||||
value = settings.lastDataUsage[key];
|
||||
}
|
||||
|
||||
savedUsage[key] = { apps: {}, total: value.total };
|
||||
Object.keys(value.apps).forEach(function(manifest) {
|
||||
savedUsage[key].apps[manifest] = {
|
||||
total: value.apps[manifest].total
|
||||
};
|
||||
});
|
||||
} else {
|
||||
savedUsage[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
ConfigManager.setOption({ 'lastDataUsage': savedUsage },
|
||||
function _onSetItem() {
|
||||
debug('Statistics up to date and stored.');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Aggregate fine-grained app specific samples into a top level per-day
|
||||
// and per-network sample list
|
||||
function aggregateSamples(network) {
|
||||
if (!network.apps) {
|
||||
return;
|
||||
}
|
||||
|
||||
var manifestURLs = Object.keys(network.apps);
|
||||
if (manifestURLs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var samplesByDate = {};
|
||||
var today = Toolkit.toMidnight(new Date());
|
||||
// offset in milliseconds
|
||||
var offset = today.getTimezoneOffset() * 60 * 1000;
|
||||
|
||||
manifestURLs.forEach(function(manifestURL) {
|
||||
var samples = network.apps[manifestURL].samples;
|
||||
samples.forEach(function(sample) {
|
||||
if (sample.date && sample.date.__date__) {
|
||||
sample.date = new Date(sample.date.__date__);
|
||||
}
|
||||
|
||||
var sampleLocalTime = sample.date.getTime() + offset;
|
||||
var sampleUTCDate = Toolkit.toMidnight(new Date(sampleLocalTime));
|
||||
|
||||
var aggregateSample = samplesByDate[sampleUTCDate.getTime()];
|
||||
if (!aggregateSample) {
|
||||
aggregateSample = samplesByDate[sampleUTCDate.getTime()] = {
|
||||
value: 0,
|
||||
date: sample.date
|
||||
};
|
||||
}
|
||||
|
||||
aggregateSample.value += sample.value;
|
||||
});
|
||||
});
|
||||
|
||||
var dates = Object.keys(samplesByDate);
|
||||
network.samples = dates.map(function(date) {
|
||||
return samplesByDate[date];
|
||||
});
|
||||
|
||||
network.samples.sort(function(a, b) {
|
||||
return a.date.getTime() - b.date.getTime();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle results from requests to the network stats database
|
||||
// for data usage on a specific network
|
||||
function handleResult(request, network) {
|
||||
var result = request.result;
|
||||
var data = adaptData(result);
|
||||
var manifestURL = request.result.appManifestURL;
|
||||
// ignore the 'null' manifestURL
|
||||
if (manifestURL && manifestURL !== 'null') {
|
||||
var isBrowser = request.result.browsingTrafficOnly &&
|
||||
manifestURL === Common.SYSTEM_MANIFEST;
|
||||
if (isBrowser) {
|
||||
manifestURL = Common.BROWSER_APP.manifestURL;
|
||||
}
|
||||
network.apps[manifestURL] = {
|
||||
samples: data[0],
|
||||
total: data[1]
|
||||
};
|
||||
} else {
|
||||
network.samples = network.samples.concat(data[0]);
|
||||
}
|
||||
network.total += data[1];
|
||||
}
|
||||
|
||||
if (simRequests) {
|
||||
simRequests.forEach(function(request) {
|
||||
handleResult(request, lastDataUsage.mobile);
|
||||
});
|
||||
aggregateSamples(lastDataUsage.mobile);
|
||||
}
|
||||
|
||||
if (wifiRequests) {
|
||||
wifiRequests.forEach(function(request) {
|
||||
handleResult(request, lastDataUsage.wifi);
|
||||
});
|
||||
aggregateSamples(lastDataUsage.wifi);
|
||||
}
|
||||
|
||||
var perApp = apps && apps.length > 0;
|
||||
saveLastDataUsage(perApp);
|
||||
|
||||
result.status = 'success';
|
||||
result.data = lastDataUsage;
|
||||
|
||||
// Once bug 1083680 is solved, both caches should contain the same values
|
||||
// so we could use only `costcontrol.lastDataResults`.
|
||||
if (perApp) {
|
||||
costcontrol.lastDataResultsPerApp = lastDataUsage;
|
||||
} else {
|
||||
costcontrol.lastDataResults = lastDataUsage;
|
||||
}
|
||||
debug('Returning up to date statistics.');
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
function requestPerAppUsage(networkId) {
|
||||
function requestSamples(options) {
|
||||
pendingRequests++;
|
||||
var req = statistics.getSamples(networkId, start, end, options);
|
||||
req.onsuccess = checkForCompletion;
|
||||
return req;
|
||||
}
|
||||
|
||||
var requests;
|
||||
if (apps && apps.length > 0) {
|
||||
requests = [];
|
||||
apps.forEach(function(manifestURL) {
|
||||
// Ignoring the system app because of the system traffic is going to
|
||||
// be calculated (Front-end workaround for Bug 1083680)
|
||||
if (manifestURL !== Common.SYSTEM_MANIFEST) {
|
||||
var options = { appManifestURL: manifestURL };
|
||||
// Browser app is included on System app
|
||||
if (manifestURL === Common.BROWSER_APP.manifestURL) {
|
||||
options.appManifestURL = Common.SYSTEM_MANIFEST;
|
||||
options.browsingTrafficOnly = true;
|
||||
}
|
||||
requests.push(requestSamples(options));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
requests = [requestSamples()];
|
||||
}
|
||||
return requests;
|
||||
}
|
||||
|
||||
//Recover current Simcard info
|
||||
if (currentSimcardNetwork) {
|
||||
simRequests = requestPerAppUsage(currentSimcardNetwork);
|
||||
}
|
||||
|
||||
if (wifiInterface) {
|
||||
wifiRequests = requestPerAppUsage(wifiInterface);
|
||||
}
|
||||
|
||||
if (pendingRequests === 0) {
|
||||
updateDataUsage();
|
||||
}
|
||||
}
|
||||
|
||||
// Transform data usage to the model accepted by the render
|
||||
function adaptData(networkStatsResult) {
|
||||
var data = networkStatsResult.data;
|
||||
var output = [];
|
||||
var totalData, accum = 0;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var item = data[i];
|
||||
if (item.txBytes === undefined) {
|
||||
output.push({ date: item.date });
|
||||
continue;
|
||||
}
|
||||
|
||||
totalData = 0;
|
||||
if (item.rxBytes) {
|
||||
totalData += item.rxBytes;
|
||||
}
|
||||
if (item.txBytes) {
|
||||
totalData += item.txBytes;
|
||||
}
|
||||
|
||||
accum += totalData;
|
||||
|
||||
output.push({
|
||||
value: totalData,
|
||||
date: item.date
|
||||
});
|
||||
}
|
||||
return [output, accum];
|
||||
}
|
||||
|
||||
return {
|
||||
getInstance: getInstance,
|
||||
reset: function _onReset() {
|
||||
costcontrol = null;
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
||||
65
gaia/apps/costcontrol/js/desktop-only/async_storage.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const store = new Map();
|
||||
|
||||
function isParentAsyncStorageAvailable() {
|
||||
return !!(window.parent &&
|
||||
window.parent !== window &&
|
||||
window.parent.asyncStorage);
|
||||
}
|
||||
|
||||
function callCallback(value, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
setTimeout(callback.bind(null, value));
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(exports, 'asyncStorage', {
|
||||
value: {
|
||||
getItem: function asm_getItem(key, callback) {
|
||||
if (isParentAsyncStorageAvailable()) {
|
||||
window.parent.asyncStorage.getItem(key, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callCallback(store.get(key) || null, callback);
|
||||
},
|
||||
setItem: function asm_setItem(key, value, callback) {
|
||||
if (isParentAsyncStorageAvailable()) {
|
||||
window.parent.asyncStorage.setItem(key, value, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callCallback(store.set(key, value), callback);
|
||||
},
|
||||
removeItem: function asm_removeItem(key, callback) {
|
||||
if (isParentAsyncStorageAvailable()) {
|
||||
window.parent.asyncStorage.removeItem(key, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callCallback(store.delete(key), callback);
|
||||
},
|
||||
clear: function asm_clear(callback) {
|
||||
if (isParentAsyncStorageAvailable()) {
|
||||
window.parent.asyncStorage.clear(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callCallback(store.clear(), callback);
|
||||
},
|
||||
length: function(callback) {
|
||||
if (isParentAsyncStorageAvailable()) {
|
||||
window.parent.asyncStorage.length(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callCallback(store.size, callback);
|
||||
},
|
||||
key: function() {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
}
|
||||
});
|
||||
})(window);
|
||||
24
gaia/apps/costcontrol/js/desktop-only/navigator_moz_alarms.js
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozAlarms) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.mozAlarms = {
|
||||
add: function() {
|
||||
var returnResult = {};
|
||||
|
||||
setTimeout(() => {
|
||||
returnResult.result = {};
|
||||
if (typeof returnResult.onsuccess === 'function') {
|
||||
returnResult.onsuccess.call(returnResult);
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
},
|
||||
|
||||
remove: function() {}
|
||||
};
|
||||
})();
|
||||
33
gaia/apps/costcontrol/js/desktop-only/navigator_moz_apps_mgmt.js
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozApps.mgmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Apps = [{
|
||||
manifestURL: 'app://system.gaiamobile.org/manifest.webapp',
|
||||
origin: 'app://system.gaiamobile.org',
|
||||
manifest: {
|
||||
name: 'Service'
|
||||
}
|
||||
}];
|
||||
|
||||
navigator.mozApps.mgmt = {
|
||||
getAll: function() {
|
||||
var returnResult = {};
|
||||
|
||||
setTimeout(() => {
|
||||
returnResult.result = Apps;
|
||||
|
||||
if (typeof returnResult.onsuccess === 'function') {
|
||||
returnResult.onsuccess.call(returnResult, {
|
||||
target: returnResult
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
}
|
||||
};
|
||||
})();
|
||||
21
gaia/apps/costcontrol/js/desktop-only/navigator_moz_icc_manager.js
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozIccManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.mozIccManager = {
|
||||
iccIds: ['1234575100210522938'],
|
||||
|
||||
getIccById: function(id) {
|
||||
return {
|
||||
cardState: 'ready',
|
||||
iccInfo: {
|
||||
mcc: id,
|
||||
mnc: id
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})();
|
||||
11
gaia/apps/costcontrol/js/desktop-only/navigator_moz_mobile_connections.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozMobileConnections) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.mozMobileConnections = navigator.mozIccManager.iccIds.map((id) => {
|
||||
return { iccId: id};
|
||||
});
|
||||
})();
|
||||
145
gaia/apps/costcontrol/js/desktop-only/navigator_moz_network_stats.js
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozNetworkStats) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Today = new Date();
|
||||
|
||||
const NetworkInterfaceType = {
|
||||
WIFI: 0,
|
||||
MOBILE: 1
|
||||
};
|
||||
|
||||
const Networks = new Map([
|
||||
['0', {
|
||||
type: NetworkInterfaceType.WIFI,
|
||||
id: '0'
|
||||
}]
|
||||
].concat(navigator.mozIccManager.iccIds.map((id) => [id, {
|
||||
type: NetworkInterfaceType.MOBILE,
|
||||
id: id
|
||||
}])));
|
||||
|
||||
// If we run app in Mulet with the direct URL, then we won't have API for
|
||||
// navigator.mozApps.mgmt available, but if we run entire gaia in Mulet (aka
|
||||
// from HomeScreen) then this API will be provided, so it's better to not have
|
||||
// dependency on the mock navigator.mozApps.mgmt here and just hard code known
|
||||
// app manifest URLs.
|
||||
const appManifestURLs = ['app://system.gaiamobile.org/manifest.webapp'];
|
||||
|
||||
function generateSample(interfaceId, manifestURL, startValueInMb) {
|
||||
return {
|
||||
appManifestURL: manifestURL,
|
||||
network: Networks.get(interfaceId),
|
||||
start: new Date('2014-08-14T05:00:00.000Z'),
|
||||
end: new Date('2014-08-31T05:00:00.000Z'),
|
||||
data: [{
|
||||
rxBytes: startValueInMb * 100000,
|
||||
txBytes: (startValueInMb + 100) * 100000,
|
||||
date: new Date(
|
||||
Today.getFullYear(),
|
||||
Today.getMonth(),
|
||||
Today.getDate() - 15
|
||||
)
|
||||
}, {
|
||||
rxBytes: startValueInMb * 200000,
|
||||
txBytes: (startValueInMb + 100) * 200000,
|
||||
date: Today
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const Samples = new Map([
|
||||
['0', new Map([
|
||||
[null, generateSample('0', null, 457)],
|
||||
[appManifestURLs[0], generateSample('0', appManifestURLs[0], 457)]
|
||||
])],
|
||||
[navigator.mozIccManager.iccIds[0], new Map([
|
||||
[null, generateSample(navigator.mozIccManager.iccIds[0], null, 257)],
|
||||
[
|
||||
appManifestURLs[0],
|
||||
generateSample(
|
||||
navigator.mozIccManager.iccIds[0],
|
||||
appManifestURLs[0],
|
||||
257
|
||||
)
|
||||
]
|
||||
])]
|
||||
]);
|
||||
|
||||
const Alarms = new Map();
|
||||
|
||||
navigator.mozNetworkStats = {
|
||||
WIFI: NetworkInterfaceType.WIFI,
|
||||
|
||||
getAvailableNetworks: function() {
|
||||
var returnResult = {};
|
||||
|
||||
setTimeout(() => {
|
||||
returnResult.result = [];
|
||||
|
||||
Networks.forEach((network) => {
|
||||
returnResult.result.push(network);
|
||||
});
|
||||
|
||||
if (typeof returnResult.onsuccess === 'function') {
|
||||
returnResult.onsuccess.call(returnResult);
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
},
|
||||
|
||||
getSamples: function getSamples(networkInterface, start, end, options) {
|
||||
var networkSamples = Samples.get(networkInterface.id);
|
||||
var returnResult = {};
|
||||
|
||||
setTimeout(() => {
|
||||
if (options && options.appManifestURL) {
|
||||
returnResult.result = networkSamples.get(options.appManifestURL);
|
||||
} else {
|
||||
returnResult.result = networkSamples.get(null);
|
||||
}
|
||||
|
||||
if (typeof returnResult.onsuccess === 'function') {
|
||||
returnResult.onsuccess.call(returnResult);
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
},
|
||||
|
||||
getAllAlarms: function(interfaceId) {
|
||||
var returnResult = {};
|
||||
|
||||
setTimeout(() => {
|
||||
returnResult.result = Alarms.get(interfaceId) || [];
|
||||
|
||||
if (typeof returnResult.onsuccess === 'function') {
|
||||
returnResult.onsuccess.call(returnResult);
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
},
|
||||
|
||||
addAlarm: function(interfaceId, alarm) {
|
||||
var returnResult = {};
|
||||
|
||||
setTimeout(() => {
|
||||
var interfaceAlarms = Alarms.get(interfaceId) || [];
|
||||
|
||||
interfaceAlarms.push(alarm);
|
||||
Alarms.set(interfaceId, interfaceAlarms);
|
||||
|
||||
if (typeof returnResult.onsuccess === 'function') {
|
||||
returnResult.onsuccess.call(returnResult);
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
}
|
||||
};
|
||||
})();
|
||||
35
gaia/apps/costcontrol/js/desktop-only/navigator_moz_settings.js
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Settings = new Map([['locale.hour12', false]]);
|
||||
|
||||
navigator.mozSettings = {
|
||||
createLock: function() {
|
||||
return {
|
||||
get: function(key) {
|
||||
var request = {
|
||||
addEventListener: function(eventName, callback) {
|
||||
request[eventName] = callback;
|
||||
}
|
||||
};
|
||||
|
||||
window.setTimeout(function() {
|
||||
if (typeof request.onsuccess === 'function') {
|
||||
request.result = {};
|
||||
request.result[key] = Settings.get(key);
|
||||
request.onsuccess.call(request);
|
||||
}
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
addObserver: function() {}
|
||||
};
|
||||
})();
|
||||
289
gaia/apps/costcontrol/js/fte.js
Executable file
|
|
@ -0,0 +1,289 @@
|
|||
/* global AutoSettings, BalanceLowLimitView, Common, ConfigManager, SimManager,
|
||||
dataLimitConfigurer, LazyLoader, debug, ViewManager */
|
||||
|
||||
/*
|
||||
* First time experience is in charge of set up the application.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
// Fallback from some values, just in case they are missed from configuration
|
||||
var DEFAULT_LOW_LIMIT_THRESHOLD = 3;
|
||||
var defaultLowLimitThreshold = DEFAULT_LOW_LIMIT_THRESHOLD;
|
||||
window.addEventListener('DOMContentLoaded', function _onDomReady() {
|
||||
initLazyFTE();
|
||||
});
|
||||
|
||||
function initLazyFTE() {
|
||||
var SCRIPTS_NEEDED = [
|
||||
'js/utils/debug.js',
|
||||
'js/utils/toolkit.js',
|
||||
'js/sim_manager.js',
|
||||
'js/common.js',
|
||||
'js/config/config_manager.js',
|
||||
'js/views/BalanceLowLimitView.js',
|
||||
'js/view_manager.js',
|
||||
'js/settings/autosettings.js'
|
||||
];
|
||||
LazyLoader.load(SCRIPTS_NEEDED, function onScriptsLoaded() {
|
||||
setupFTE();
|
||||
parent.postMessage({
|
||||
type: 'fte_ready',
|
||||
data: ''
|
||||
}, Common.COST_CONTROL_APP);
|
||||
|
||||
window.addEventListener('localized', _onLocalize);
|
||||
});
|
||||
}
|
||||
|
||||
var wizard, vmanager;
|
||||
var toStep2, step = 0;
|
||||
function setupFTE() {
|
||||
if (SimManager.isMultiSim()) {
|
||||
window.addEventListener('dataSlotChange', function _onDataSimChange() {
|
||||
window.removeEventListener('dataSlotChange', _onDataSimChange);
|
||||
// Close FTE if change the SimCard for data connections
|
||||
Common.closeFTE();
|
||||
});
|
||||
}
|
||||
ConfigManager.requestAll(function _onSettings(configuration, settings) {
|
||||
wizard = document.getElementById('firsttime-view');
|
||||
vmanager = new ViewManager();
|
||||
|
||||
// Getting some values from config
|
||||
if (configuration && configuration.default_low_limit_threshold) {
|
||||
defaultLowLimitThreshold = configuration.default_low_limit_threshold;
|
||||
}
|
||||
|
||||
// Initialize resetTime and trackingPeriod to default values
|
||||
ConfigManager.setOption({resetTime: 1, trackingPeriod: 'monthly' });
|
||||
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
|
||||
var SCRIPTS_NEEDED = [
|
||||
'js/settings/limitdialog.js',
|
||||
'js/utils/formatting.js'
|
||||
];
|
||||
LazyLoader.load(SCRIPTS_NEEDED, function onScriptsLoaded() {
|
||||
Common.loadNetworkInterfaces(function() {
|
||||
AutoSettings.addType('data-limit', dataLimitConfigurer);
|
||||
if (mode === 'DATA_USAGE_ONLY') {
|
||||
AutoSettings.initialize(ConfigManager, vmanager,
|
||||
'#non-vivo-step-2');
|
||||
}
|
||||
});
|
||||
});
|
||||
// Currency is set by config as well
|
||||
if (configuration && configuration.credit &&
|
||||
configuration.credit.currency) {
|
||||
|
||||
document.getElementById('currency').textContent =
|
||||
configuration.credit.currency;
|
||||
}
|
||||
|
||||
if (mode === 'DATA_USAGE_ONLY') {
|
||||
debug('FTE for non supported SIM');
|
||||
wizard.dataset.steps = '3';
|
||||
reset(['step-1', 'non-vivo-step-1', 'non-vivo-step-2']);
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#non-vivo-step-1');
|
||||
|
||||
} else {
|
||||
wizard.dataset.steps = '4';
|
||||
reset(['step-1', 'step-2']);
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#step-1');
|
||||
|
||||
// Plantype selection
|
||||
toStep2 = document.getElementById('to-step-2');
|
||||
toStep2.disabled = true;
|
||||
|
||||
document.getElementById('prepaid-plan')
|
||||
.addEventListener('click', selectTrack);
|
||||
document.getElementById('postpaid-plan')
|
||||
.addEventListener('click', selectTrack);
|
||||
|
||||
addLowLimitStepConstrains();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
var next = document.querySelectorAll('[data-navigation=next]');
|
||||
[].forEach.call(next, function cc_eachNext(nextButton) {
|
||||
nextButton.addEventListener('click', onNext);
|
||||
});
|
||||
|
||||
var prev = document.querySelectorAll('[data-navigation=back]');
|
||||
[].forEach.call(prev, function cc_eachPrev(prevButton) {
|
||||
prevButton.addEventListener('click', onBack);
|
||||
});
|
||||
|
||||
var finish = document.querySelectorAll('[data-navigation=finish]');
|
||||
[].forEach.call(finish, function cc_eachFinish(finishButton) {
|
||||
finishButton.addEventListener('click', onFinish);
|
||||
});
|
||||
|
||||
// Needed to put the alarms for balance and networkUsage
|
||||
LazyLoader.load(['js/costcontrol.js'], function() {
|
||||
var messageHandlerFrame = document.getElementById('message-handler');
|
||||
messageHandlerFrame.src = 'message_handler.html';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _onLocalize() {
|
||||
Common.localizeWeekdaySelector(
|
||||
document.getElementById('pre3-select-weekday'));
|
||||
Common.localizeWeekdaySelector(
|
||||
document.getElementById('post2-select-weekday'));
|
||||
Common.localizeWeekdaySelector(
|
||||
document.getElementById('non2-select-weekday'));
|
||||
|
||||
function _setResetTimeToDefault(evt) {
|
||||
var today = new Date();
|
||||
var defaultResetTime = (evt.target.value === 'weekly') ?
|
||||
today.getDay() : today.getDate();
|
||||
ConfigManager.setOption({ resetTime: defaultResetTime });
|
||||
}
|
||||
|
||||
// Localized resetTime on trackingPeriod change
|
||||
var trackingPeriodSelector = document
|
||||
.querySelectorAll('[data-option="trackingPeriod"]');
|
||||
[].forEach.call(trackingPeriodSelector, function _reset(tPeriodSel) {
|
||||
tPeriodSel.addEventListener('change', _setResetTimeToDefault);
|
||||
});
|
||||
}
|
||||
|
||||
if (window.location.hash) {
|
||||
wizard = document.getElementById('firsttime-view');
|
||||
|
||||
if (window.location.hash === '#PREPAID' ||
|
||||
window.location.hash === '#POSTPAID') {
|
||||
wizard.querySelector('.authed-sim').hidden = false;
|
||||
} else {
|
||||
wizard.querySelector('.nonauthed-sim').hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TRACK SETUP
|
||||
|
||||
var currentTrack = ['step-1', 'step-2'];
|
||||
function selectTrack(evt) {
|
||||
if (evt.target.value === 'prepaid') {
|
||||
currentTrack = ['step-1', 'step-2', 'prepaid-step-2', 'prepaid-step-3'];
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#prepaid-step-2');
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#prepaid-step-3');
|
||||
balanceLowLimitView.disabled = false;
|
||||
ConfigManager.setOption({
|
||||
dataLimitValue: 40,
|
||||
dataLimitUnit: 'MB',
|
||||
lowLimit: true,
|
||||
lowLimitThreshold: defaultLowLimitThreshold
|
||||
});
|
||||
} else if (evt.target.value === 'postpaid') {
|
||||
currentTrack = ['step-1', 'step-2', 'postpaid-step-2', 'postpaid-step-3'];
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#postpaid-step-2');
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#postpaid-step-3');
|
||||
balanceLowLimitView.disabled = true;
|
||||
ConfigManager.setOption({ dataLimitValue: 2, dataLimitUnit: 'GB' });
|
||||
}
|
||||
|
||||
ConfigManager.setOption({ plantype: evt.target.value }, function _onSet() {
|
||||
toStep2.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
// NAVIGATION
|
||||
|
||||
function reset(track) {
|
||||
// Reposition screens
|
||||
var currentScreen = document.getElementById(currentTrack[step]);
|
||||
var newStartScreen = document.getElementById(track[0]);
|
||||
|
||||
currentScreen.dataset.viewport = 'left';
|
||||
newStartScreen.dataset.viewport = 'right';
|
||||
delete newStartScreen.dataset.viewport;
|
||||
|
||||
for (var i = 1; i < track.length; i += 1) {
|
||||
var id = track[i];
|
||||
if (id) {
|
||||
var screen = document.getElementById(id);
|
||||
screen.dataset.viewport = 'right';
|
||||
}
|
||||
}
|
||||
|
||||
// Reset state
|
||||
step = 0;
|
||||
currentTrack = track;
|
||||
}
|
||||
|
||||
function onNext() {
|
||||
if (step === currentTrack.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentId = currentTrack[step];
|
||||
var currentScreen = document.getElementById(currentId);
|
||||
|
||||
var nextScreenId = currentTrack[step + 1];
|
||||
var nextScreen = document.getElementById(nextScreenId);
|
||||
|
||||
// Enter the next screen
|
||||
delete nextScreen.dataset.viewport;
|
||||
currentScreen.dataset.viewport = 'left';
|
||||
|
||||
step += 1;
|
||||
|
||||
// Validate when in step 2 in order to restore buttons and errors
|
||||
if (step === 2) {
|
||||
balanceLowLimitView && balanceLowLimitView.validate();
|
||||
}
|
||||
}
|
||||
|
||||
function onBack() {
|
||||
if (step === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentId = currentTrack[step];
|
||||
var currentScreen = document.getElementById(currentId);
|
||||
|
||||
var prevScreenId = currentTrack[step - 1];
|
||||
var prevScreen = document.getElementById(prevScreenId);
|
||||
|
||||
// Enter the previous screen
|
||||
delete prevScreen.dataset.viewport;
|
||||
currentScreen.dataset.viewport = 'right';
|
||||
|
||||
step -= 1;
|
||||
}
|
||||
|
||||
function onFinish(evt) {
|
||||
evt.target.disabled = true;
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
ConfigManager.requestSettings(dataSim.iccId,
|
||||
function _onSettings(settings) {
|
||||
ConfigManager.setOption({ fte: false }, function _returnToApp() {
|
||||
Common.updateNextReset(settings.trackingPeriod, settings.resetTime,
|
||||
function _returnToTheApplication() {
|
||||
Common.startApp();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add particular constrains to the page where setting low limit button
|
||||
var balanceLowLimitView;
|
||||
function addLowLimitStepConstrains() {
|
||||
var nextButton = document.getElementById('low-limit-next-button');
|
||||
balanceLowLimitView = new BalanceLowLimitView(
|
||||
document.getElementById('low-limit'),
|
||||
document.getElementById('low-limit-input')
|
||||
);
|
||||
balanceLowLimitView.onvalidation = function(evt) {
|
||||
nextButton.disabled = !evt.isValid;
|
||||
};
|
||||
}
|
||||
|
||||
}());
|
||||
90
gaia/apps/costcontrol/js/iac_manager.js
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
/* global debug, Promise */
|
||||
/* exported IACManager */
|
||||
'use strict';
|
||||
|
||||
var IACManager = (function() {
|
||||
var TIMEOUT = 60 * 1000; // 1 minute
|
||||
var configuration;
|
||||
|
||||
function init(cfg) {
|
||||
configuration = cfg;
|
||||
}
|
||||
|
||||
function broadcastStartOfSMSQuery(type) {
|
||||
return IACManager._sendBroadcastMessage(_generateMsg(type, 'enable'));
|
||||
}
|
||||
|
||||
function broadcastEndOfSMSQuery(type) {
|
||||
return IACManager._sendBroadcastMessage(_generateMsg(type, 'disable'));
|
||||
}
|
||||
|
||||
function _sendBroadcastMessage(msg) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
navigator.mozApps.getSelf().onsuccess = function(evt) {
|
||||
var app = evt.target.result;
|
||||
app.connect('costcontrolSmsQuery').then(function onConnAccepted(ports) {
|
||||
debug('Connection with sms app established...');
|
||||
ports.forEach(function(port) {
|
||||
var waitingForAnswerTimeout;
|
||||
|
||||
port.onmessage = function() {
|
||||
clearTimeout(waitingForAnswerTimeout);
|
||||
resolve();
|
||||
};
|
||||
// if no one has replied the message in one minute, the proposal
|
||||
// will be rejected
|
||||
waitingForAnswerTimeout = window.setTimeout(function() {
|
||||
var errorMsg = 'Rejection by Timeout. No received answer to ' +
|
||||
msg.action + ' ' + msg.type + ' request.';
|
||||
reject(errorMsg);
|
||||
}, TIMEOUT);
|
||||
|
||||
port.postMessage(msg);
|
||||
});
|
||||
}, function onConnRejected(reason) {
|
||||
console.log('Rejected connection attempt due to', reason);
|
||||
reject(reason);
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function _getSmsNumberList(service) {
|
||||
var silentSmsList = [];
|
||||
if (configuration && configuration[service]) {
|
||||
silentSmsList = configuration[service].senders;
|
||||
if (silentSmsList.indexOf(configuration[service].destination) === -1) {
|
||||
silentSmsList.push(configuration[service].destination);
|
||||
}
|
||||
}
|
||||
return silentSmsList;
|
||||
}
|
||||
|
||||
function _reset() {
|
||||
configuration = null;
|
||||
}
|
||||
|
||||
function _generateMsg(service, action) {
|
||||
var outgoingNumber = configuration[service] &&
|
||||
configuration[service].destination;
|
||||
var incomingNumbers = configuration[service] &&
|
||||
configuration[service].senders;
|
||||
return {
|
||||
action: action,
|
||||
type: service,
|
||||
smsNumbers: _getSmsNumberList(service),
|
||||
outgoingNumber: outgoingNumber,
|
||||
incomingNumbers: incomingNumbers
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
broadcastStartOfSMSQuery: broadcastStartOfSMSQuery,
|
||||
broadcastEndOfSMSQuery: broadcastEndOfSMSQuery,
|
||||
_getSmsNumberList: _getSmsNumberList,
|
||||
_generateMsg: _generateMsg,
|
||||
_sendBroadcastMessage: _sendBroadcastMessage,
|
||||
_reset: _reset
|
||||
};
|
||||
}());
|
||||
619
gaia/apps/costcontrol/js/message_handler.js
Executable file
|
|
@ -0,0 +1,619 @@
|
|||
/* global ConfigManager, CostControl, debug, Common, asyncStorage, Formatting,
|
||||
NotificationHelper, _, MozActivity, NetworkUsageAlarm, LazyLoader,
|
||||
SimManager, IACManager, DEBUGGING
|
||||
*/
|
||||
/*jshint -W020 */
|
||||
/* The previous directive,ignore the "Read only" errors, that are produced when
|
||||
redirect global objects (Common and Costcontrol) to parent versions to avoid
|
||||
conflicts.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
function inStandAloneMode() {
|
||||
return window.parent.location.pathname === '/message_handler.html';
|
||||
}
|
||||
|
||||
// Redirect global objects to parent versions to avoid conflicts
|
||||
if (!inStandAloneMode()) {
|
||||
ConfigManager = window.parent.ConfigManager;
|
||||
CostControl = window.parent.CostControl;
|
||||
SimManager = window.parent.SimManager;
|
||||
}
|
||||
|
||||
// XXX: This case implies that message handler triggered by system
|
||||
// (inStandAlone check) has replaced CC application (history's length check).
|
||||
//
|
||||
// This only occurs when the system (window manager) has detected there is
|
||||
// already an iframe for CC application, it is in background and it is
|
||||
// not the message handler. So, as the CC index.html uses the this file
|
||||
// inside an iframe (no standalone mode), all the messages should be attended
|
||||
// so we can conclude **there is nothing to do**.
|
||||
if (inStandAloneMode() && window.history.length > 1) {
|
||||
debug('Nothing to do in message handler, returning to app...');
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function inApplicationMode() {
|
||||
return window.parent.location.pathname === '/index.html';
|
||||
}
|
||||
|
||||
// Close if in standalone mode
|
||||
var closing;
|
||||
|
||||
|
||||
function closeIfProceeds() {
|
||||
debug('Checking for closing...');
|
||||
if (inStandAloneMode()) {
|
||||
closing = Common.closeApplication();
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Remove from here when this is solved
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=800431
|
||||
function addAlarmTimeout(type, delay) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + delay);
|
||||
var request = navigator.mozAlarms.add(date, 'ignoreTimezone', {type: type});
|
||||
return request;
|
||||
}
|
||||
window.addAlarmTimeout = addAlarmTimeout;
|
||||
|
||||
// XXX: Remove from here when this is solved
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=800431
|
||||
function setNextReset(when, callback) {
|
||||
|
||||
// XXX: This is not part of configuration by SIM so we bypass ConfigManager
|
||||
asyncStorage.getItem('nextResetAlarm', function(id) {
|
||||
// There is already an alarm, remove it
|
||||
debug('Current nextResetAlarm', id + '.', id ? 'Removing.' : '');
|
||||
if (id) {
|
||||
navigator.mozAlarms.remove(id);
|
||||
}
|
||||
|
||||
// If no when, disable alarms passing null
|
||||
if (!when) {
|
||||
debug('Automatic reset disabled');
|
||||
updateResetAttributes(null, null, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the alarm is in the past, fake it was launched
|
||||
if (when.getTime() < Date.now()) {
|
||||
debug('Faking a past reset alarm');
|
||||
_onAlarm({ data: { type: 'nextReset' } });
|
||||
if (callback) {
|
||||
setTimeout(callback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If when is provided, request an alarm an set the new values
|
||||
var alarms = navigator.mozAlarms;
|
||||
var request = alarms.add(when, 'ignoreTimezone', {type: 'nextReset' });
|
||||
request.onsuccess = function _onSuccess() {
|
||||
debug('Setting nextResetAlarm', request.result, 'to', when);
|
||||
updateResetAttributes(request.result, when, callback);
|
||||
};
|
||||
});
|
||||
}
|
||||
window.setNextReset = setNextReset;
|
||||
|
||||
function getTopUpTimeout(callback) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
var request = navigator.mozAlarms.getAll();
|
||||
request.onsuccess = function(e) {
|
||||
var alarms = e.target.result;
|
||||
var length = alarms.length;
|
||||
if (!length) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var refId = settings.waitingForTopUp;
|
||||
var index = 0, alarm, found = false;
|
||||
while (index < length && !found) {
|
||||
alarm = alarms[index];
|
||||
found = (alarm.id === refId);
|
||||
index++;
|
||||
}
|
||||
if (found) {
|
||||
debug('TopUp timeout found:', alarm.date);
|
||||
callback(alarm.date);
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
window.getTopUpTimeout = getTopUpTimeout;
|
||||
|
||||
function addNetworkUsageAlarm(dataInterface, dataLimit, onsuccess, onerror) {
|
||||
onsuccess = onsuccess || function() {};
|
||||
onerror = onerror || function() {};
|
||||
NetworkUsageAlarm.updateAlarm(dataInterface, dataLimit, onsuccess, onerror);
|
||||
}
|
||||
window.addNetworkUsageAlarm = addNetworkUsageAlarm;
|
||||
|
||||
// Update the nextResetAlarm and nextReset values and request for
|
||||
// synchronization.
|
||||
function updateResetAttributes(alarmId, date, callback) {
|
||||
asyncStorage.setItem('nextResetAlarm', alarmId, function _updateOption() {
|
||||
ConfigManager.setOption({ nextReset: date }, function _sync() {
|
||||
localStorage.sync = 'nextReset#' + Math.random();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendIncorrectTopUpNotification(callback) {
|
||||
var title = 'topup-incorrectcode-title2';
|
||||
var message = 'topup-incorrectcode-message3';
|
||||
|
||||
sendNotification('topUpError', title, message);
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function sendBalanceThresholdNotification(remaining, settings, callback) {
|
||||
|
||||
debug('Low limit already notified:', settings.lowLimitNotified);
|
||||
debug('Zero balance already notified:', settings.lowLimitNotified);
|
||||
|
||||
// Zero reached notification
|
||||
var type;
|
||||
if (remaining.balance === 0 && !settings.zeroBalanceNotified) {
|
||||
type = 'zeroBalance';
|
||||
|
||||
// There is a limit an we are below that limit and we did not notified yet
|
||||
} else if (settings.lowLimit &&
|
||||
remaining.balance < settings.lowLimitThreshold &&
|
||||
!settings.lowLimitNotified) {
|
||||
type = 'lowBalance';
|
||||
|
||||
// No need for notification
|
||||
} else if (callback) {
|
||||
setTimeout(callback);
|
||||
return;
|
||||
}
|
||||
debug('Notification type:', type);
|
||||
|
||||
// Get l10n for remaining balance
|
||||
var remainingBalance = _('currency', {
|
||||
currency: remaining.currency,
|
||||
value: remaining.balance
|
||||
});
|
||||
|
||||
// Compose notification and send it
|
||||
var title = 'low-balance-notification-title';
|
||||
var message = {
|
||||
id: 'low-balance-notification-text',
|
||||
args: { remaining: remainingBalance }
|
||||
};
|
||||
if (type === 'zeroBalance') {
|
||||
title = 'usage';
|
||||
message = 'zero-balance-message';
|
||||
}
|
||||
|
||||
sendNotification(type, title, message);
|
||||
|
||||
// Finally mark the notification as sent
|
||||
var update = {};
|
||||
var notified = (type === 'lowBalance') ? 'lowLimitNotified' :
|
||||
'zeroBalanceNotified';
|
||||
update[notified] = true;
|
||||
ConfigManager.setOption(update, callback);
|
||||
}
|
||||
window.sendBalanceThresholdNotification = sendBalanceThresholdNotification;
|
||||
|
||||
// When receiving an alarm, differenciate by type and act
|
||||
function _onAlarm(alarm) {
|
||||
clearTimeout(closing);
|
||||
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
function _launchNextReset() {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
Common.resetAll(function updateNextResetAndClose() {
|
||||
Common.updateNextReset(settings.trackingPeriod, settings.resetTime,
|
||||
closeIfProceeds);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
switch (alarm.data.type) {
|
||||
case 'balanceTimeout':
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
settings.errors.BALANCE_TIMEOUT = true;
|
||||
ConfigManager.setOption(
|
||||
{ 'errors': settings.errors, 'waitingForBalance': null },
|
||||
function _onBalanceTimeout() {
|
||||
debug('Timeout for balance');
|
||||
debug('Trying to synchronize!');
|
||||
localStorage.sync = 'errors#' + Math.random();
|
||||
closeIfProceeds();
|
||||
}
|
||||
);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'topupTimeout':
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
settings.errors.TOPUP_TIMEOUT = true;
|
||||
ConfigManager.setOption(
|
||||
{ 'errors': settings.errors, 'waitingForTopUp': null },
|
||||
function _onBalanceTimeout() {
|
||||
debug('Timeout for topup');
|
||||
debug('Trying to synchronize!');
|
||||
localStorage.sync = 'errors#' + Math.random();
|
||||
closeIfProceeds();
|
||||
}
|
||||
);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'nextReset':
|
||||
if (!Common.allNetworkInterfaceLoaded) {
|
||||
Common.loadNetworkInterfaces(_launchNextReset);
|
||||
} else {
|
||||
_launchNextReset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNofificationAction(type, app) {
|
||||
var activityName, action;
|
||||
var noop = function() {};
|
||||
switch (type) {
|
||||
case 'lowBalance':
|
||||
case 'zeroBalance':
|
||||
activityName = 'costcontrol/balance';
|
||||
action = function() {
|
||||
if (window.parent.BalanceTab) {
|
||||
window.parent.CostControlApp.showBalanceTab();
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'topUpError' :
|
||||
activityName = 'costcontrol/balance';
|
||||
action = function() {
|
||||
if (window.parent.BalanceTab) {
|
||||
window.parent.BalanceTab.topUpWithCode(true);
|
||||
}
|
||||
};
|
||||
break;
|
||||
default:
|
||||
activityName = 'costcontrol/data_usage';
|
||||
action = function() {
|
||||
if (window.parent.CostControlApp) {
|
||||
window.parent.CostControlApp.showDataUsageTab();
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (!inStandAloneMode()) {
|
||||
return function() {
|
||||
if (inApplicationMode()) {
|
||||
app.launch();
|
||||
action && action();
|
||||
} else {
|
||||
type = new MozActivity({name: activityName});
|
||||
}
|
||||
};
|
||||
}
|
||||
return noop;
|
||||
}
|
||||
|
||||
function sendNotification(notificationType, title, message) {
|
||||
navigator.mozApps.getSelf().onsuccess = function _onAppReady(evt) {
|
||||
var app = evt.target.result;
|
||||
var iconURL = NotificationHelper.getIconURI(app);
|
||||
var navigate = getNofificationAction(notificationType, app);
|
||||
|
||||
NotificationHelper.send(title, {
|
||||
'bodyL10n': message,
|
||||
'icon': iconURL,
|
||||
'data': notificationType
|
||||
}).then(function(notification) {
|
||||
notification.addEventListener('click', function() {
|
||||
notification.close();
|
||||
navigate();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function _onNetworkAlarm(alarm) {
|
||||
clearTimeout(closing);
|
||||
navigator.mozApps.getSelf().onsuccess = function _onAppReady(evt) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
var limit = Common.getDataLimit(settings);
|
||||
var limitText = Formatting.formatData(Formatting.smartRound(limit));
|
||||
var title = {
|
||||
id: 'data-limit-notification-title2',
|
||||
args: { limit: limitText }
|
||||
};
|
||||
var message = 'data-limit-notification-text2';
|
||||
sendNotification('dataUsage', title, message);
|
||||
ConfigManager.setOption({ 'dataUsageNotified': true },
|
||||
closeIfProceeds);
|
||||
return;
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function disableSilentModeFor(type, configuration) {
|
||||
LazyLoader.load('js/iac_manager.js', function() {
|
||||
IACManager.init(configuration);
|
||||
IACManager.broadcastEndOfSMSQuery(type).then(function(msg) {
|
||||
debug('After broadcasting for ' + type + ' (disabling)');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Register in standalone or for application
|
||||
var costcontrol;
|
||||
function _getCCInstance() {
|
||||
CostControl.getInstance(function _onCostControl(ccontrol) {
|
||||
costcontrol = ccontrol;
|
||||
|
||||
if (inStandAloneMode() || inApplicationMode()) {
|
||||
debug('Installing handlers');
|
||||
|
||||
// When receiving an SMS, recognize and parse
|
||||
navigator.mozSetMessageHandler('sms-received', function _onSMS(sms) {
|
||||
clearTimeout(closing);
|
||||
ConfigManager.requestAll(function _onInfo(configuration, settings) {
|
||||
|
||||
var isBalanceResponse =
|
||||
configuration.balance &&
|
||||
Array.isArray(configuration.balance.senders) &&
|
||||
configuration.balance.senders.indexOf(sms.sender) > -1;
|
||||
|
||||
var isTopupResponse =
|
||||
configuration.topup &&
|
||||
Array.isArray(configuration.topup.senders) &&
|
||||
configuration.topup.senders.indexOf(sms.sender) > -1;
|
||||
|
||||
// Non expected SMS
|
||||
if (!isBalanceResponse && !isTopupResponse) {
|
||||
closeIfProceeds();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the message
|
||||
debug('Parsing received SMS');
|
||||
var isBalance, isConfirmation, isError;
|
||||
isBalance = isConfirmation = isError = false;
|
||||
|
||||
debug('Trying to recognize balance SMS');
|
||||
var description = new RegExp(configuration.balance.regexp);
|
||||
var balanceData = sms.body.match(description);
|
||||
|
||||
if (!balanceData) {
|
||||
debug('Trying to recognize zero balance SMS');
|
||||
// Some carriers use another response messages format
|
||||
// for zero balance
|
||||
var zeroDescription = configuration.balance.zero_regexp ?
|
||||
new RegExp(configuration.balance.zero_regexp) : null;
|
||||
if (zeroDescription && zeroDescription.test(sms.body)) {
|
||||
balanceData = ['0.00', '0', '0'];
|
||||
}
|
||||
}
|
||||
isBalance = !!balanceData;
|
||||
|
||||
if (!isBalance || balanceData.length < 2) {
|
||||
console.warn('Impossible to parse balance message.');
|
||||
|
||||
debug('Trying to recognize TopUp confirmation SMS');
|
||||
description = new RegExp(configuration.topup.confirmation_regexp);
|
||||
isConfirmation = !!sms.body.match(description);
|
||||
if (!isConfirmation) {
|
||||
console.warn('Impossible to parse TopUp confirmation message.');
|
||||
|
||||
debug('Trying to recognize TopUp error SMS');
|
||||
description =
|
||||
new RegExp(configuration.topup.incorrect_code_regexp);
|
||||
isError = !!sms.body.match(description);
|
||||
if (!isError) {
|
||||
console.warn('Impossible to parse TopUp confirmation msg.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!isBalance && !isConfirmation && !isError) {
|
||||
closeIfProceeds();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DEBUGGING &&
|
||||
(isBalance && (settings.waitingForBalance !== null)) ||
|
||||
(isConfirmation && (settings.waitingForTopUp !== null))) {
|
||||
var mobileMessageManager = window.navigator.mozMobileMessage;
|
||||
mobileMessageManager.delete(sms.id);
|
||||
}
|
||||
|
||||
if (isBalance) {
|
||||
// Compose new balance
|
||||
var integer = balanceData[1].replace(/[^0-9]/g, '');
|
||||
var decimal = balanceData[2] || '0';
|
||||
var newBalance = {
|
||||
balance: parseFloat(integer + '.' + decimal),
|
||||
currency: configuration.credit.currency,
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
if (settings.waitingForBalance !== null) {
|
||||
// Remove the timeout
|
||||
navigator.mozAlarms.remove(settings.waitingForBalance);
|
||||
debug('Balance timeout:', settings.waitingForBalance,
|
||||
'removed');
|
||||
|
||||
disableSilentModeFor('balance', configuration);
|
||||
}
|
||||
// Store new balance and sync
|
||||
ConfigManager.setOption(
|
||||
{ 'lastBalance': newBalance, 'waitingForBalance': null },
|
||||
function _onSet() {
|
||||
debug('Balance up to date and stored');
|
||||
debug('Trying to synchronize!');
|
||||
localStorage.sync = 'lastBalance#' + Math.random();
|
||||
sendBalanceThresholdNotification(newBalance, settings,
|
||||
closeIfProceeds);
|
||||
}
|
||||
);
|
||||
} else if (isConfirmation) {
|
||||
if (settings.waitingForTopUp !== null) {
|
||||
// Store SUCCESS for TopIp and sync
|
||||
navigator.mozAlarms.remove(settings.waitingForTopUp);
|
||||
debug('TopUp timeout:', settings.waitingForTopUp, 'removed');
|
||||
disableSilentModeFor('topup', configuration);
|
||||
}
|
||||
ConfigManager.setOption(
|
||||
{
|
||||
'waitingForTopUp': null,
|
||||
'lowLimitNotified': false,
|
||||
'zeroBalanceNotified': false
|
||||
},
|
||||
function _onSet() {
|
||||
debug('TopUp confirmed!');
|
||||
debug('Trying to synchronize!');
|
||||
localStorage.sync = 'waitingForTopUp#' + Math.random();
|
||||
closeIfProceeds();
|
||||
}
|
||||
);
|
||||
} else if (isError) {
|
||||
// Store ERROR for TopUp and sync
|
||||
settings.errors.INCORRECT_TOPUP_CODE = true;
|
||||
navigator.mozAlarms.remove(settings.waitingForTopUp);
|
||||
debug('TopUp timeout: ', settings.waitingForTopUp, 'removed');
|
||||
ConfigManager.setOption(
|
||||
{
|
||||
'errors': settings.errors,
|
||||
'waitingForTopUp': null,
|
||||
'lowLimitNotified': false,
|
||||
'zeroBalanceNotified': false
|
||||
},
|
||||
function _onSet() {
|
||||
debug('Balance up to date and stored');
|
||||
debug('Trying to synchronize!');
|
||||
localStorage.sync = 'errors#' + Math.random();
|
||||
sendIncorrectTopUpNotification(closeIfProceeds);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
navigator.mozSetMessageHandler('networkstats-alarm', _onNetworkAlarm);
|
||||
navigator.mozSetMessageHandler('alarm', _onAlarm);
|
||||
|
||||
// Count a new SMS
|
||||
navigator.mozSetMessageHandler('sms-sent', function _onSent(sms) {
|
||||
clearTimeout(closing);
|
||||
debug('SMS sent!');
|
||||
|
||||
var configuration = ConfigManager.configuration;
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
if (mode === 'PREPAID' &&
|
||||
!costcontrol.isBalanceRequestSMS(sms, configuration)) {
|
||||
costcontrol.request({ type: 'balance' });
|
||||
}
|
||||
if (mode === 'PREPAID' &&
|
||||
costcontrol.isBalanceRequestSMS(sms, configuration)) {
|
||||
closeIfProceeds();
|
||||
} else {
|
||||
var mobileMessageManager = window.navigator.mozMobileMessage;
|
||||
var infoRequest =
|
||||
mobileMessageManager.getSegmentInfoForText(sms.body);
|
||||
infoRequest.onsuccess = function onInfo(evt) {
|
||||
var realCount, smsInfo = evt.target.result;
|
||||
if (!smsInfo || !smsInfo.segments) {
|
||||
console.error(
|
||||
'Invalid getSegmentInfoForText() result. Counting 1 segment');
|
||||
realCount = 1;
|
||||
} else {
|
||||
realCount = smsInfo.segments;
|
||||
}
|
||||
updateSMSCount(realCount);
|
||||
};
|
||||
infoRequest.onerror = function onError() {
|
||||
console.error('Can not retrieve segment info for body ' +
|
||||
sms.body);
|
||||
updateSMSCount(1);
|
||||
};
|
||||
}
|
||||
|
||||
function updateSMSCount(count) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
settings.lastTelephonyActivity.timestamp = new Date();
|
||||
settings.lastTelephonyActivity.smscount += count;
|
||||
ConfigManager.setOption({
|
||||
lastTelephonyActivity: settings.lastTelephonyActivity
|
||||
}, function _sync() {
|
||||
localStorage.sync = 'lastTelephonyActivity#' + Math.random();
|
||||
closeIfProceeds();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// When a call ends
|
||||
navigator.mozSetMessageHandler('telephony-call-ended',
|
||||
function _onCall(tcall) {
|
||||
clearTimeout(closing);
|
||||
if (tcall.direction !== 'outgoing') {
|
||||
closeIfProceeds();
|
||||
return;
|
||||
}
|
||||
debug('Outgoing call finished!');
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
ConfigManager.requestSettings(dataSimIcc.iccId,
|
||||
function _onSettings(settings) {
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
if (mode === 'PREPAID') {
|
||||
costcontrol.request({ type: 'balance' });
|
||||
}
|
||||
|
||||
settings.lastTelephonyActivity.timestamp = new Date();
|
||||
settings.lastTelephonyActivity.calltime += tcall.duration;
|
||||
ConfigManager.setOption({
|
||||
lastTelephonyActivity: settings.lastTelephonyActivity
|
||||
}, function _sync() {
|
||||
localStorage.sync = 'lastTelephonyActivity#' + Math.random();
|
||||
closeIfProceeds();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Notify message handler is ready
|
||||
var readyEvent = new CustomEvent('messagehandlerready');
|
||||
window.parent.dispatchEvent(readyEvent);
|
||||
});
|
||||
}
|
||||
_getCCInstance();
|
||||
}());
|
||||
288
gaia/apps/costcontrol/js/settings/autosettings.js
Executable file
|
|
@ -0,0 +1,288 @@
|
|||
/* global debug */
|
||||
/* exported AutoSettings */
|
||||
|
||||
'use strict';
|
||||
|
||||
var AutoSettings = (function() {
|
||||
|
||||
var OPTION_CONFIGURERS = {
|
||||
'select': function _selectConfigurer(guiWidget) {
|
||||
var optionName = guiWidget.dataset.option;
|
||||
var span, parent = guiWidget.parentElement;
|
||||
if (parent.classList.contains('fake-select')) {
|
||||
var firstChild = parent.firstChild;
|
||||
if (!firstChild || firstChild.tagName !== 'SPAN') {
|
||||
span = document.createElement('span');
|
||||
parent.insertBefore(span, parent.firstChild);
|
||||
}
|
||||
}
|
||||
guiWidget.addEventListener('blur', function _onSelectChange() {
|
||||
debug('Value:', guiWidget.value);
|
||||
settings.option(optionName, guiWidget.value);
|
||||
});
|
||||
settings.observe(optionName, function _onOptionChange(value) {
|
||||
if (value === undefined) {
|
||||
value = settings.defaultValue(optionName);
|
||||
}
|
||||
guiWidget.value = value;
|
||||
if (span) {
|
||||
var selected = guiWidget.options[guiWidget.selectedIndex];
|
||||
var l10nId = selected.getAttribute('data-l10n-id');
|
||||
span.textContent = selected.textContent;
|
||||
if (l10nId) {
|
||||
span.setAttribute('data-l10n-id', l10nId);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// Select is used to simulate custom combo boxes. It displays a selection
|
||||
// dialog with an ok button and possibly a cancel button. The value is only
|
||||
// changed if ok button is pressed. If cancel provided and clicked then
|
||||
// the former value is restored.
|
||||
'customselect': function _customselectConfigurer(guiWidget) {
|
||||
|
||||
var dialog = document.getElementById(guiWidget.dataset.selectdialog);
|
||||
var optionName = guiWidget.dataset.option;
|
||||
|
||||
// Dialog
|
||||
|
||||
var okButton = dialog.querySelector('button.recommend');
|
||||
if (okButton) {
|
||||
okButton.addEventListener('click', function _onDialogOk() {
|
||||
var checked = dialog.querySelector('input[type="radio"]:checked');
|
||||
settings.option(optionName, checked.value);
|
||||
vmanager.closeCurrentView();
|
||||
});
|
||||
}
|
||||
|
||||
var cancelButton = dialog.querySelector('button.cancel');
|
||||
if (cancelButton) {
|
||||
cancelButton.addEventListener('click',
|
||||
function _onDialogCancel() {
|
||||
var currentValue = settings.option(optionName);
|
||||
settings.option(optionName, currentValue);
|
||||
vmanager.closeCurrentView();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
guiWidget.addEventListener('click', function _onWidgetClick() {
|
||||
vmanager.changeViewTo(dialog.id);
|
||||
});
|
||||
|
||||
// Keep the widget and the dialog synchronized
|
||||
settings.observe(optionName,
|
||||
function _onOptionChange(value) {
|
||||
|
||||
// Use default value if no value
|
||||
if (value === null || value === undefined) {
|
||||
value = getDefaultValue(optionName);
|
||||
}
|
||||
|
||||
var radio =
|
||||
dialog.querySelector('input[type="radio"][value="' + value + '"]');
|
||||
|
||||
if (!radio) {
|
||||
value = getDefaultValue(optionName);
|
||||
radio = dialog.querySelector('input[type="radio"][value="' +
|
||||
value + '"]');
|
||||
}
|
||||
radio.checked = true;
|
||||
|
||||
var textSpan = dialog.querySelector('input:checked + span');
|
||||
var tagSpan = guiWidget.querySelector('.tag');
|
||||
tagSpan.textContent = textSpan.textContent;
|
||||
}
|
||||
);
|
||||
|
||||
// Keep the UI localized
|
||||
window.addEventListener('localized', function _onLocalized() {
|
||||
var textSpan = dialog.querySelector('input:checked + span');
|
||||
var tagSpan = guiWidget.querySelector('.tag');
|
||||
tagSpan.textContent = textSpan.textContent;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
// Switch configurer handles an checkbox. When it changes, the checked
|
||||
// state is used as the content for the option.
|
||||
'switch' : function _switchConfigurer(guiWidget) {
|
||||
var optionName = guiWidget.dataset.option;
|
||||
|
||||
// Add an observer to keep synchronization
|
||||
settings.observe(
|
||||
optionName,
|
||||
function _onOptionChange(value) {
|
||||
if (value === null || value === undefined) {
|
||||
value = getDefaultValue(optionName);
|
||||
}
|
||||
guiWidget.checked = value;
|
||||
}
|
||||
);
|
||||
|
||||
// Add an event listener to switch the option
|
||||
guiWidget.addEventListener('change', function ccapp_onSwitchChange() {
|
||||
settings.option(optionName, guiWidget.checked);
|
||||
});
|
||||
},
|
||||
|
||||
// Input configurer handles an input box. When the content changes, the
|
||||
// content is parsed and sent to the proper option.
|
||||
'input' : function _inputConfigurer(guiWidget) {
|
||||
var optionName = guiWidget.dataset.option;
|
||||
|
||||
// Add an observer to keep synchronization
|
||||
settings.observe(
|
||||
optionName,
|
||||
function _onOptionChange(value) {
|
||||
if (value === null || value === undefined) {
|
||||
value = getDefaultValue(optionName);
|
||||
}
|
||||
guiWidget.value = value;
|
||||
}
|
||||
);
|
||||
|
||||
// Add an event listener to switch the option
|
||||
guiWidget.addEventListener('change', function _onContentChange() {
|
||||
var value = guiWidget.value;
|
||||
if (guiWidget.type === 'number') {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
settings.option(optionName, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Add a new type with its configuration function
|
||||
function addType(newType, configFunction) {
|
||||
OPTION_CONFIGURERS[newType] = configFunction;
|
||||
}
|
||||
|
||||
var customRecognizer;
|
||||
// Add a recognizer to detect new types
|
||||
function setCustomRecognizer(recognizer) {
|
||||
customRecognizer = recognizer;
|
||||
}
|
||||
|
||||
// Return the <li> wrapping the option
|
||||
function getEntryParent(item) {
|
||||
while (item && item.tagName !== 'LI') {
|
||||
item = item.parentNode;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
// Return the default value for an option
|
||||
function getDefaultValue(optionName) {
|
||||
return settings.defaultValue(optionName);
|
||||
}
|
||||
|
||||
// Configure the web page
|
||||
var settings, vmanager;
|
||||
function initialize(settingsInterface, viewManager, root) {
|
||||
|
||||
root = root || 'body';
|
||||
settings = settingsInterface;
|
||||
vmanager = viewManager;
|
||||
|
||||
// Return the type of configurer needed based on the properties of
|
||||
// the HTML element marked as the option.
|
||||
function getWidgetType(widget) {
|
||||
if (widget.dataset.widgetType) {
|
||||
return widget.dataset.widgetType;
|
||||
}
|
||||
|
||||
var customType;
|
||||
if (customRecognizer === 'function') {
|
||||
customType = customRecognizer(widget, settings, vmanager);
|
||||
}
|
||||
|
||||
if (customType) {
|
||||
return customType;
|
||||
}
|
||||
|
||||
if (widget.tagName === 'SELECT') {
|
||||
return 'select';
|
||||
}
|
||||
|
||||
if (typeof widget.dataset.selectdialog !== 'undefined') {
|
||||
return 'customselect';
|
||||
}
|
||||
|
||||
if (widget.type === 'checkbox' ||
|
||||
widget.nodeName.toLowerCase() === 'gaia-switch') {
|
||||
return 'switch';
|
||||
}
|
||||
|
||||
if (['text', 'number'].indexOf(widget.type) !== -1) {
|
||||
return 'input';
|
||||
}
|
||||
}
|
||||
|
||||
// Install a dependency based on an expression. When expression is true
|
||||
// call the callback.
|
||||
function installDependency(expression, callback) {
|
||||
var equality = false;
|
||||
var parsed = expression.split('!=');
|
||||
if (parsed.length === 1) {
|
||||
parsed = expression.split('=');
|
||||
equality = true;
|
||||
}
|
||||
|
||||
var dependency = parsed[0];
|
||||
var referenceValue = parsed[1];
|
||||
settings.observe(dependency,
|
||||
function ccapp_dependencyAction(value) {
|
||||
var test = (('' + value) === referenceValue);
|
||||
callback(equality === test); // or is an equality test and values are
|
||||
// equal or is an inequality test and
|
||||
// values are different.
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Widgets
|
||||
var allGUIWidgets = document.querySelectorAll(root + ' .settings-option');
|
||||
[].forEach.call(allGUIWidgets, function _forEachWidget(guiWidget) {
|
||||
|
||||
var type = getWidgetType(guiWidget);
|
||||
var entry = getEntryParent(guiWidget);
|
||||
var configurer = OPTION_CONFIGURERS[type];
|
||||
if (configurer) {
|
||||
configurer(guiWidget, settings, vmanager, root);
|
||||
}
|
||||
|
||||
// Simple dependency resolution:
|
||||
|
||||
// enable / disable some options depending on the values of other
|
||||
var disableOn = guiWidget.dataset.disableOn;
|
||||
if (disableOn) {
|
||||
installDependency(disableOn, function _disable(passed) {
|
||||
guiWidget.disabled = passed;
|
||||
if (entry) {
|
||||
entry.setAttribute('aria-disabled', passed + '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// hide / show some options depending on the values of other
|
||||
var hideOn = guiWidget.dataset.hideOn;
|
||||
if (hideOn) {
|
||||
installDependency(hideOn, function _hide(passed) {
|
||||
guiWidget.hidden = passed;
|
||||
if (entry) {
|
||||
entry.hidden = passed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: initialize,
|
||||
setCustomRecognizer: setCustomRecognizer,
|
||||
addType: addType
|
||||
};
|
||||
|
||||
}());
|
||||
234
gaia/apps/costcontrol/js/settings/limitdialog.js
Executable file
|
|
@ -0,0 +1,234 @@
|
|||
/* global _, addNetworkUsageAlarm, Common, Formatting, SimManager,
|
||||
CostControl */
|
||||
/* exported dataLimitConfigurer */
|
||||
'use strict';
|
||||
|
||||
function dataLimitConfigurer(guiWidget, settings, viewManager, widgetRoot) {
|
||||
var REMOVE_CHAR_CODE = 8,
|
||||
// It Represents any positive real number (up to three digits length),
|
||||
// with optional decimal point, accepting up to 2 decimal places. It is
|
||||
// not necessary adding the significant number when it is zero (eg. '.5'
|
||||
// is a valid entry)
|
||||
DATA_LIMIT_NUMERIC_FORMAT = new RegExp('^[0-9]{0,3}(\\.[0-9]{1,2})?$');
|
||||
|
||||
|
||||
var dialog = document.getElementById('data-limit-dialog');
|
||||
var dataLimitInput = dialog.querySelector('input');
|
||||
var format = function ccal_formatterDataUnit(value) {
|
||||
var unit = settings.option('dataLimitUnit');
|
||||
return Formatting.formatData([value, _(unit)]);
|
||||
};
|
||||
|
||||
window.addEventListener('localized', function _onLocalize() {
|
||||
var tagSpan = guiWidget.querySelector('.tag');
|
||||
tagSpan.textContent = format(dataLimitInput.value);
|
||||
});
|
||||
|
||||
// Configure dialog
|
||||
var okButton = dialog.querySelector('button.recommend');
|
||||
if (okButton) {
|
||||
okButton.addEventListener('click', function ccld_onDialogOk() {
|
||||
var value = parseFloat(dataLimitInput.value);
|
||||
settings.option('dataLimitValue', value);
|
||||
settings.option('dataLimitUnit', currentUnit);
|
||||
var dataLimit = Common.getDataLimit({'dataLimitValue': value,
|
||||
'dataLimitUnit': currentUnit});
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
var simInterface = Common.getDataSIMInterface(dataSim.iccId);
|
||||
addNetworkUsageAlarm(simInterface, dataLimit, null,
|
||||
() => checkDataLimit(dataLimit));
|
||||
});
|
||||
viewManager.closeCurrentView();
|
||||
});
|
||||
}
|
||||
|
||||
var dialogHeader = dialog.querySelector('#limit-dialog-header');
|
||||
if (dialogHeader) {
|
||||
dialogHeader.addEventListener('action',
|
||||
function ccld_onDialogCancel() {
|
||||
var oldValue = settings.option('dataLimitValue');
|
||||
var oldUnit = settings.option('dataLimitUnit');
|
||||
currentUnit = oldUnit;
|
||||
settings.option('dataLimitValue', oldValue);
|
||||
settings.option('dataLimitUnit', oldUnit);
|
||||
dataLimitInput.classList.remove('error');
|
||||
okButton.disabled = false;
|
||||
viewManager.closeCurrentView();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function checkDataLimit(dataLimit) {
|
||||
CostControl.getInstance(function _onCostControl(costcontrol) {
|
||||
var requestObj = {
|
||||
type: 'datausage'
|
||||
};
|
||||
costcontrol.request(requestObj, function _onDataStats(result) {
|
||||
var stats = result.data.mobile.total;
|
||||
if (stats > dataLimit) {
|
||||
var mobileUsage = {
|
||||
limit: Formatting.formatData(Formatting.roundData(dataLimit))
|
||||
};
|
||||
var message = _('data-limit-notification-title2', mobileUsage) +
|
||||
'\n' + _('data-limit-notification-text2');
|
||||
window.alert(message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isNotRemovingChar(newChar) {
|
||||
return REMOVE_CHAR_CODE !== newChar;
|
||||
}
|
||||
|
||||
function isAllowedValue(newValue) {
|
||||
return isValidValue(newValue) || wouldBeValid(newValue);
|
||||
}
|
||||
// Checks if value is numeric and it has a valid format
|
||||
// Not allowed entry characters '-', ',' or more than one '.'
|
||||
function isValidValue(newValue) {
|
||||
var containsNonValidCharacters = [',','-']
|
||||
.some(c => newValue.indexOf(c) > -1);
|
||||
var containsMoreThanOneDot = (newValue.match(/\./g) || []).length > 1;
|
||||
return !Number.isNaN(newValue) &&
|
||||
!containsNonValidCharacters && !containsMoreThanOneDot &&
|
||||
isValidFormat(newValue);
|
||||
}
|
||||
|
||||
function wouldBeValid(lowLimitValue) {
|
||||
var allowedEntry = new RegExp('^0?(\\.0?)?$');
|
||||
return allowedEntry.test(lowLimitValue);
|
||||
}
|
||||
|
||||
function preventBadInput(valid, newChar, currentInput) {
|
||||
// This method returns the string resultant of adding the character of the
|
||||
// keypress event
|
||||
function simulateNewValue(newChar, currentInput) {
|
||||
var value = currentInput.value,
|
||||
idx = currentInput.selectionStart,
|
||||
newCharValue = String.fromCharCode(newChar);
|
||||
return value.substr(0, idx) + newCharValue + value.substr(idx);
|
||||
}
|
||||
var newValue = simulateNewValue(newChar, currentInput);
|
||||
var isInvalid = !valid(newValue);
|
||||
return isInvalid;
|
||||
}
|
||||
|
||||
dataLimitInput.addEventListener('keypress',
|
||||
function cc_onKeypress(event) {
|
||||
var currentInput = event.target;
|
||||
var newChar = event.charCode || event.keyCode;
|
||||
|
||||
// If the entry is not valid or the result of the input is invalid, we
|
||||
// have to prevent the event propagation
|
||||
if (isNotRemovingChar(newChar) &&
|
||||
preventBadInput(isAllowedValue, newChar, currentInput)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function isValidFormat(lowLimitValue) {
|
||||
var numberDataLimit = Number(lowLimitValue);
|
||||
return (numberDataLimit < 1000 &&
|
||||
numberDataLimit > 0 &&
|
||||
DATA_LIMIT_NUMERIC_FORMAT.test(numberDataLimit));
|
||||
}
|
||||
|
||||
// Disable OK button when dataLimitInput not matches any positive real number
|
||||
// (up to three digits length), with optional decimal point, accepting up
|
||||
// to 2 decimal places.
|
||||
dataLimitInput.addEventListener('input',
|
||||
function cc_ondataLimitInputChange(evt) {
|
||||
var lowLimitValue = evt.target.value.trim();
|
||||
var isNumericLowLimit = !Number.isNaN(Number(lowLimitValue));
|
||||
|
||||
if (isNumericLowLimit &&
|
||||
lowLimitValue.indexOf('.0') !== lowLimitValue.length - 2 &&
|
||||
lowLimitValue.indexOf('.') !== lowLimitValue.length - 1) {
|
||||
dataLimitInput.value = Number(lowLimitValue);
|
||||
}
|
||||
|
||||
var isValidValue = (isNumericLowLimit && isValidFormat(lowLimitValue));
|
||||
|
||||
if (isValidValue) {
|
||||
dataLimitInput.classList.remove('error');
|
||||
} else {
|
||||
dataLimitInput.classList.add('error');
|
||||
}
|
||||
okButton.disabled = (!isValidValue);
|
||||
}
|
||||
);
|
||||
|
||||
// Configure the swicth unit button
|
||||
var currentUnit = settings.option('dataLimitUnit');
|
||||
var switchUnitButton = dialog.querySelector('.switch-unit-button');
|
||||
function localizeSwitchUnitButton(unit) {
|
||||
switchUnitButton.setAttribute('data-l10n-id', 'unit-' + unit);
|
||||
switchUnitButton.querySelector('span.tag').setAttribute(
|
||||
'data-l10n-id', unit);
|
||||
}
|
||||
|
||||
localizeSwitchUnitButton(currentUnit);
|
||||
switchUnitButton.addEventListener('click',
|
||||
function ccapp_switchUnit() {
|
||||
currentUnit = (currentUnit === 'MB') ? 'GB' : 'MB';
|
||||
localizeSwitchUnitButton(currentUnit);
|
||||
}
|
||||
);
|
||||
|
||||
// Prevent to loose the focus when tapping on switch unit button
|
||||
// TODO: Replace with 'focusout' on dataLimitInput when fixing bug:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=687787
|
||||
switchUnitButton.addEventListener('mousedown',
|
||||
function _preventFocusLost(evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
);
|
||||
|
||||
// Keep the widget and the dialog synchronized
|
||||
settings.observe('dataLimitValue',
|
||||
function ccld_onValueChange(value) {
|
||||
// Use default value if no value
|
||||
if (value === null || typeof value === 'undefined') {
|
||||
value = settings.defaultValue('dataLimitValue');
|
||||
}
|
||||
|
||||
// Set dialog
|
||||
dataLimitInput.value = value;
|
||||
|
||||
var tagSpan = guiWidget.querySelector('.tag');
|
||||
tagSpan.textContent = format(dataLimitInput.value);
|
||||
}
|
||||
);
|
||||
|
||||
settings.observe('dataLimitUnit',
|
||||
function ccld_onUnitChange(value) {
|
||||
|
||||
// Use default value if no value
|
||||
if (value === null || typeof value === 'undefined') {
|
||||
value = settings.defaultValue('dataLimitUnit');
|
||||
}
|
||||
|
||||
// Set dialog
|
||||
localizeSwitchUnitButton(value);
|
||||
|
||||
var tagSpan = guiWidget.querySelector('.tag');
|
||||
tagSpan.textContent = format(dataLimitInput.value);
|
||||
}
|
||||
);
|
||||
|
||||
// Show the dialog
|
||||
var oldUnitValue;
|
||||
guiWidget.addEventListener('click', function ccld_onWidgetClick() {
|
||||
viewManager.changeViewTo(dialog.id, widgetRoot);
|
||||
dataLimitInput.focus();
|
||||
dataLimitInput.setSelectionRange(dataLimitInput.value.length,
|
||||
dataLimitInput.value.length);
|
||||
oldUnitValue = settings.option('dataLimitUnit');
|
||||
localizeSwitchUnitButton(oldUnitValue);
|
||||
});
|
||||
|
||||
}
|
||||
89
gaia/apps/costcontrol/js/settings/networkUsageAlarm.js
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
/* exported NetworkUsageAlarm */
|
||||
'use strict';
|
||||
var NetworkUsageAlarm = (function() {
|
||||
|
||||
function _addAlarm(networkInterface, value, onsuccess, onerror) {
|
||||
if (!networkInterface) {
|
||||
console.error('Error, the network interface is not defined when trying ' +
|
||||
'to add an alarm');
|
||||
(typeof onerror === 'function') && onerror();
|
||||
return;
|
||||
}
|
||||
if (value === null || typeof value === 'undefined') {
|
||||
console.error('Limit value is undefined, impossible add an alarm');
|
||||
(typeof onerror === 'function') && onerror();
|
||||
return;
|
||||
}
|
||||
|
||||
var request = navigator.mozNetworkStats.addAlarm(networkInterface, value);
|
||||
request.onsuccess = onsuccess;
|
||||
request.onerror = function error(e) {
|
||||
console.error('Error, when trying to addAlarm to the interfaceId: ' +
|
||||
networkInterface.id + ' and limit: ' + value);
|
||||
(typeof onerror === 'function') && onerror();
|
||||
};
|
||||
}
|
||||
|
||||
function _clearAlarms(networkInterface, callback) {
|
||||
if (!networkInterface) {
|
||||
console.error('Error, the network interface is not defined when trying ' +
|
||||
'to remove alarms');
|
||||
return;
|
||||
}
|
||||
var request = navigator.mozNetworkStats.getAllAlarms(networkInterface);
|
||||
request.onsuccess = function success() {
|
||||
var alarmList = request.result;
|
||||
var pendingRequest = alarmList.length;
|
||||
|
||||
function checkForCompletion() {
|
||||
pendingRequest--;
|
||||
if (pendingRequest === 0) {
|
||||
(typeof callback === 'function') && callback();
|
||||
}
|
||||
}
|
||||
|
||||
function _onErrorRemoveAlarm() {
|
||||
console.error('Error when trying to remove one alarm.');
|
||||
checkForCompletion();
|
||||
}
|
||||
|
||||
for (var i = 0; i < alarmList.length; i++) {
|
||||
if (alarmList[i] && alarmList[i].alarmId) {
|
||||
var rA = navigator.mozNetworkStats.removeAlarms(alarmList[i].alarmId);
|
||||
rA.onsuccess = checkForCompletion;
|
||||
rA.onerror = _onErrorRemoveAlarm;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingRequest === 0) {
|
||||
(typeof callback === 'function') && callback();
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function error() {
|
||||
console.error('Error when trying to get alarms from the API to clear it');
|
||||
};
|
||||
}
|
||||
|
||||
function _updateAlarm(dataInterface, value, onsuccess, onerror) {
|
||||
if (dataInterface && value) {
|
||||
var addNewAlarm = _addAlarm.bind(null, dataInterface, value, onsuccess,
|
||||
onerror);
|
||||
_clearAlarms(dataInterface, addNewAlarm);
|
||||
} else {
|
||||
if (!dataInterface) {
|
||||
console.error('Error, the network interface is not defined when ' +
|
||||
'trying to update an alarm');
|
||||
} else {
|
||||
console.error('Error, the data limit value is not defined when ' +
|
||||
'trying to update an alarm');
|
||||
}
|
||||
(typeof onerror === 'function') && onerror();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updateAlarm: _updateAlarm,
|
||||
clearAlarms: _clearAlarms
|
||||
};
|
||||
}());
|
||||
367
gaia/apps/costcontrol/js/settings/settings.js
Executable file
|
|
@ -0,0 +1,367 @@
|
|||
/* global BalanceView, LazyLoader, AutoSettings, BalanceLowLimitView,
|
||||
ResetMenuDialog, ConfirmDialog, ViewManager, dataLimitConfigurer,
|
||||
Formatting
|
||||
*/
|
||||
/* exported debug, sendBalanceThresholdNotification */
|
||||
/*
|
||||
* Settings is in charge of setup the setting section. It uses an AutoSettings
|
||||
* object to automatically bind markup with local settings.
|
||||
*
|
||||
* Settings have three drawing areas with views for current values of balance,
|
||||
* data usage and telephony.
|
||||
*/
|
||||
'use strict';
|
||||
// Import global objects from parent window
|
||||
var ConfigManager = window.parent.ConfigManager;
|
||||
var CostControl = window.parent.CostControl;
|
||||
var SimManager = window.parent.SimManager;
|
||||
var Common = window.parent.Common;
|
||||
var NetworkUsageAlarm = window.parent.NetworkUsageAlarm;
|
||||
|
||||
// Import global functions from parent window
|
||||
var addNetworkUsageAlarm = window.parent.addNetworkUsageAlarm;
|
||||
var resetTelephony = window.parent.resetTelephony;
|
||||
navigator.mozL10n = window.parent.navigator.mozL10n;
|
||||
|
||||
|
||||
// Import debug
|
||||
var DEBUGGING = window.parent.DEBUGGING;
|
||||
var debug = window.parent.debug;
|
||||
|
||||
var Settings = (function() {
|
||||
|
||||
var costcontrol, vmanager, initialized, endLoadSettingsNotified;
|
||||
var plantypeSelector, phoneActivityTitle, phoneActivitySettings;
|
||||
var balanceTitle, balanceSettings, reportsTitle;
|
||||
var balanceView, resetMenuDialog, confirmDialog;
|
||||
|
||||
function configureUI() {
|
||||
CostControl.getInstance(function _onCostControl(instance) {
|
||||
costcontrol = instance;
|
||||
|
||||
// Debug options
|
||||
if (DEBUGGING) {
|
||||
loadDeveloperAids();
|
||||
}
|
||||
|
||||
vmanager = new ViewManager();
|
||||
|
||||
// HTML entities
|
||||
plantypeSelector = document.getElementById('plantype-settings');
|
||||
phoneActivityTitle = document.getElementById('phone-activity-settings');
|
||||
phoneActivitySettings =
|
||||
document.querySelector('#phone-activity-settings + .settings');
|
||||
balanceTitle = document.getElementById('balance-settings');
|
||||
balanceSettings =
|
||||
document.querySelector('#balance-settings + .settings');
|
||||
reportsTitle = document.getElementById('phone-internet-settings');
|
||||
|
||||
// Subviews
|
||||
var balanceConfig = ConfigManager.configuration.balance;
|
||||
balanceView = new BalanceView(
|
||||
document.getElementById('balance'),
|
||||
document.querySelector('#balance + .meta'),
|
||||
balanceConfig ? balanceConfig.minimum_delay : undefined
|
||||
);
|
||||
|
||||
var resetDialog = document.getElementById('reset-dialog');
|
||||
var resetDataDialog = document.getElementById('reset-data-dialog');
|
||||
|
||||
resetMenuDialog = new ResetMenuDialog(resetDataDialog, vmanager);
|
||||
confirmDialog = new ConfirmDialog(resetDialog, vmanager);
|
||||
resetMenuDialog.initializeResetModes(confirmDialog);
|
||||
|
||||
// Autosettings
|
||||
AutoSettings.addType('data-limit', dataLimitConfigurer);
|
||||
AutoSettings.initialize(ConfigManager, vmanager, '#settings-view');
|
||||
configureTelephonyReset();
|
||||
configureDataResets();
|
||||
addDoneConstrains();
|
||||
|
||||
// Add an observer on dataLimit switch to active o deactivate alarms
|
||||
ConfigManager.observe(
|
||||
'dataLimit',
|
||||
function _onDataLimitChange(value, old, key, settings) {
|
||||
SimManager.requestDataSimIcc(function(dataSim) {
|
||||
var iccId = dataSim.iccId;
|
||||
var currentDataInterface = Common.getDataSIMInterface(iccId);
|
||||
if (!value) {
|
||||
NetworkUsageAlarm.clearAlarms(currentDataInterface);
|
||||
} else {
|
||||
addNetworkUsageAlarm(currentDataInterface,
|
||||
Common.getDataLimit(settings));
|
||||
}
|
||||
});
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Update layout when changing plantype
|
||||
ConfigManager.observe('plantype', updateUI, true);
|
||||
|
||||
// Update views
|
||||
ConfigManager.observe('lastTelephonyActivity',
|
||||
function _updateTelephony(activity, old, key, settings) {
|
||||
updateTelephony(activity, settings.lastTelephonyReset);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
ConfigManager.observe('lastDataUsage',
|
||||
function _updateDataUsage(stats, old, key, settings) {
|
||||
updateDataUsage(stats, settings.lastCompleteDataReset);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
ConfigManager.observe('lastBalance',
|
||||
function _updateBalance(balance) {
|
||||
updateBalance(balance, balance.currency);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Close button needs to acquire a reference to the settings view
|
||||
// manager to close itself.
|
||||
var close = document.getElementById('close-settings');
|
||||
close.addEventListener('click', function() {
|
||||
closeSettings();
|
||||
});
|
||||
|
||||
function _setResetTimeToDefault(value, old, key, settings) {
|
||||
var today = new Date();
|
||||
var defaultResetTime = (settings.trackingPeriod === 'weekly') ?
|
||||
today.getDay() : today.getDate();
|
||||
if (settings.resetTime !== defaultResetTime) {
|
||||
ConfigManager.setOption({ resetTime: defaultResetTime });
|
||||
} else {
|
||||
Common.updateNextReset(settings.trackingPeriod, settings.resetTime);
|
||||
}
|
||||
}
|
||||
|
||||
function _updateNextReset(value, old, key, settings) {
|
||||
Common.updateNextReset(settings.trackingPeriod, settings.resetTime);
|
||||
}
|
||||
|
||||
ConfigManager.observe('resetTime', _updateNextReset, true);
|
||||
ConfigManager.observe('trackingPeriod', _setResetTimeToDefault, true);
|
||||
|
||||
initialized = true;
|
||||
|
||||
Settings.updateUI();
|
||||
});
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
window.parent.location.hash = '#';
|
||||
}
|
||||
|
||||
// Loads extra HTML useful for debugging
|
||||
function loadDeveloperAids() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.overrideMimeType('text/plain');
|
||||
xhr.open('GET', '/debug.html', false);
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
var src = document.createElement('DIV');
|
||||
// XXX: We use innerHTML precisely because we need parse the content
|
||||
// without introducing the overhead of DOM methods.
|
||||
src.innerHTML = xhr.responseText;
|
||||
var reference = document.getElementById('plantype-settings');
|
||||
var parent = reference.parentNode;
|
||||
[].forEach.call(src.childNodes, function(node) {
|
||||
reference = parent.insertBefore(node, reference.nextSibling);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Configure reset dialogs for telephony and data usage
|
||||
function configureTelephonyReset() {
|
||||
var resetTelephonyButton = document.getElementById('reset-telephony');
|
||||
resetTelephonyButton.addEventListener('click', function() {
|
||||
confirmDialog.setMessage('reset-telephony-confirmation-warning', {});
|
||||
function resetAction() {
|
||||
resetTelephony();
|
||||
updateUI();
|
||||
confirmDialog.closeConfirmDialog();
|
||||
}
|
||||
confirmDialog.updateAcceptAction(resetAction);
|
||||
confirmDialog.showConfirmDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// Configure reset dialogs for telephony and data usage
|
||||
function configureDataResets() {
|
||||
// Button reset Data Usage Settings send to reset data dialog
|
||||
var resetDataUsage = document.getElementById('reset-data-usage');
|
||||
resetDataUsage.addEventListener('click', function _onDataReset() {
|
||||
var currentTotals = {
|
||||
mobile: costcontrol.lastDataResults.mobile.total,
|
||||
wifi: costcontrol.lastDataResults.wifi.total
|
||||
};
|
||||
resetMenuDialog.showResetMenuDialog(currentTotals);
|
||||
});
|
||||
}
|
||||
|
||||
// Add particular constrains to the "Done" button
|
||||
var balanceLowLimitView;
|
||||
function addDoneConstrains() {
|
||||
var closeButton = document.getElementById('close-settings');
|
||||
balanceLowLimitView = new BalanceLowLimitView(
|
||||
document.getElementById('low-limit'),
|
||||
document.getElementById('low-limit-input')
|
||||
);
|
||||
balanceLowLimitView.onvalidation = function(evt) {
|
||||
closeButton.disabled = !evt.isValid;
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener('localized', function _onLocalize() {
|
||||
if (initialized) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
|
||||
var currentMode;
|
||||
function updateUI() {
|
||||
ConfigManager.requestAll(function _onInfo(configuration, settings) {
|
||||
// L10n
|
||||
Common.localizeWeekdaySelector(
|
||||
document.getElementById('select-weekday'));
|
||||
|
||||
// Layout
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
if (currentMode !== mode) {
|
||||
currentMode = mode;
|
||||
var hidePlantypeSelector = (mode === 'DATA_USAGE_ONLY');
|
||||
var hidePhoneActivity = (mode !== 'POSTPAID');
|
||||
var hideBalance = (mode !== 'PREPAID');
|
||||
var hideReportsTitle = (mode === 'PREPAID');
|
||||
var textReportsTitle = (mode === 'POSTPAID') ?
|
||||
'phone-and-internet-data-report' : 'internet-data-report';
|
||||
|
||||
reportsTitle.querySelector('span').setAttribute(
|
||||
'data-l10n-id', textReportsTitle
|
||||
);
|
||||
|
||||
balanceLowLimitView.disabled = (mode !== 'PREPAID');
|
||||
plantypeSelector.hidden = hidePlantypeSelector;
|
||||
phoneActivityTitle.hidden = hidePhoneActivity;
|
||||
phoneActivitySettings.hidden = hidePhoneActivity;
|
||||
balanceTitle.hidden = hideBalance;
|
||||
balanceSettings.hidden = hideBalance;
|
||||
reportsTitle.hidden = hideReportsTitle;
|
||||
}
|
||||
|
||||
// Views
|
||||
var requestObj = {
|
||||
type: 'datausage'
|
||||
};
|
||||
costcontrol.request(requestObj, function _onDataStats(result) {
|
||||
var stats = result.data;
|
||||
updateDataUsage(stats, settings.lastCompleteDataReset);
|
||||
});
|
||||
|
||||
switch (mode) {
|
||||
case 'PREPAID':
|
||||
updateBalance(settings.lastBalance, configuration.credit.currency);
|
||||
break;
|
||||
case 'POSTPAID':
|
||||
updateTelephony(settings.lastTelephonyActivity,
|
||||
settings.lastTelephonyReset);
|
||||
break;
|
||||
}
|
||||
if (endLoadSettingsNotified) {
|
||||
window.performance.measure('loadSettings', 'loadSettingsStart');
|
||||
endLoadSettingsNotified = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update data usage view on settings
|
||||
function updateDataUsage(datausage, lastCompleteDataReset) {
|
||||
var mobileUsage = document.querySelector('#mobile-data-usage > span');
|
||||
var data = Formatting.roundData(datausage.mobile.total);
|
||||
mobileUsage.textContent = Formatting.formatData(data);
|
||||
|
||||
var wifiUsage = document.querySelector('#wifi-data-usage > span');
|
||||
data = Formatting.roundData(datausage.wifi.total);
|
||||
wifiUsage.textContent = Formatting.formatData(data);
|
||||
|
||||
updateDataUsageTimestamp(lastCompleteDataReset, datausage.timestamp);
|
||||
}
|
||||
|
||||
var dataUsagePeriod = { begin: null, end: null };
|
||||
function updateDataUsageTimestamp(begin, end) {
|
||||
dataUsagePeriod.begin = begin;
|
||||
dataUsagePeriod.end = end;
|
||||
var timestamp = document.querySelector('#wifi-data-usage + .meta');
|
||||
timestamp.innerHTML = '';
|
||||
timestamp.appendChild(Formatting.formatTimeHTML(begin, end));
|
||||
}
|
||||
|
||||
// Update balance view on settings
|
||||
function updateBalance(lastBalance, currency) {
|
||||
var limitCurrency = document.getElementById('settings-low-limit-currency');
|
||||
limitCurrency.textContent = currency;
|
||||
balanceView.update(lastBalance);
|
||||
}
|
||||
|
||||
// Update telephony counters on settings
|
||||
function updateTelephony(activity, lastTelephonyReset) {
|
||||
var calltimeSpan = document.getElementById('calltime');
|
||||
var smscountSpan = document.getElementById('smscount');
|
||||
Common.localize(calltimeSpan, 'magnitude', {
|
||||
value: Formatting.computeTelephonyMinutes(activity),
|
||||
unit: 'min.'
|
||||
});
|
||||
Common.localize(smscountSpan, 'magnitude', {
|
||||
value: activity.smscount,
|
||||
unit: 'SMS'
|
||||
});
|
||||
updateTelephonyTimestamp(lastTelephonyReset, activity.timestamp);
|
||||
}
|
||||
|
||||
var telephonyPeriod = { begin: null, end: null };
|
||||
function updateTelephonyTimestamp(begin, end) {
|
||||
telephonyPeriod.begin = begin;
|
||||
telephonyPeriod.end = end;
|
||||
var timestamp = document.getElementById('telephony-timestamp');
|
||||
timestamp.innerHTML = '';
|
||||
timestamp.appendChild(Formatting.formatTimeHTML(begin, end));
|
||||
}
|
||||
|
||||
window.addEventListener('timeformatchange', function () {
|
||||
updateTelephonyTimestamp(telephonyPeriod.begin, telephonyPeriod.end);
|
||||
updateDataUsageTimestamp(dataUsagePeriod.begin, dataUsagePeriod.end);
|
||||
});
|
||||
|
||||
return {
|
||||
initialize: function() {
|
||||
var SCRIPTS_NEEDED = [
|
||||
'js/utils/toolkit.js',
|
||||
'shared/js/date_time_helper.js',
|
||||
'js/utils/formatting.js',
|
||||
'js/views/BalanceLowLimitView.js',
|
||||
'js/settings/limitdialog.js',
|
||||
'js/settings/autosettings.js',
|
||||
'js/view_manager.js',
|
||||
'js/views/BalanceView.js',
|
||||
'js/views/ResetMenuDialog.js',
|
||||
'js/views/ConfirmDialog.js'
|
||||
];
|
||||
LazyLoader.load(SCRIPTS_NEEDED, function() {
|
||||
if (!Common.allNetworkInterfaceLoaded) {
|
||||
Common.loadNetworkInterfaces(configureUI);
|
||||
} else {
|
||||
configureUI();
|
||||
}
|
||||
});
|
||||
},
|
||||
updateUI: updateUI
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
Settings.initialize();
|
||||
197
gaia/apps/costcontrol/js/sim_manager.js
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
/* global SettingsListener,AirplaneModeHelper, LazyLoader */
|
||||
/* exported SimManager */
|
||||
|
||||
'use strict';
|
||||
|
||||
var SimManager = (function() {
|
||||
/*jshint validthis: true */
|
||||
var serviceIcc = {};
|
||||
|
||||
var serviceSettingMap = {
|
||||
'data' : 'ril.data.defaultServiceId',
|
||||
'telephony' : 'ril.telephony.defaultServiceId',
|
||||
'message' : 'ril.sms.defaultServiceId'
|
||||
};
|
||||
|
||||
function notifyInitializationDone(updatedService) {
|
||||
var simInitializedEvent = new CustomEvent('simManagerReady', {
|
||||
detail: updatedService
|
||||
});
|
||||
window.dispatchEvent(simInitializedEvent);
|
||||
}
|
||||
|
||||
function _init(serviceId) {
|
||||
|
||||
_reset(serviceId);
|
||||
|
||||
LazyLoader.load(['/shared/js/airplane_mode_helper.js'], function() {
|
||||
AirplaneModeHelper.addEventListener('statechange',
|
||||
function _onAirplaneModeChange(state) {
|
||||
if (state === 'disabled') {
|
||||
serviceIcc[serviceId].dirty = true;
|
||||
var eventDetail = { detail: {serviceId: serviceId}};
|
||||
var airplanemodeDisabledEvent =
|
||||
new CustomEvent('airplaneModeDisabled', eventDetail);
|
||||
window.dispatchEvent(airplanemodeDisabledEvent);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function checkForCompletion() {
|
||||
serviceIcc[serviceId].initialized = true;
|
||||
// Putting a listener to mark the dirty value when a change is detected.
|
||||
SettingsListener.observe(serviceSettingMap[serviceId], 0,
|
||||
function(value) {
|
||||
var isDirty = (serviceIcc[serviceId].slotId !== value);
|
||||
serviceIcc[serviceId].dirty = isDirty;
|
||||
serviceIcc[serviceId].slotId = value;
|
||||
if (isDirty) {
|
||||
var serviceSlotChangeEvent =
|
||||
new CustomEvent(serviceId + 'SlotChange', { detail: serviceId });
|
||||
window.dispatchEvent(serviceSlotChangeEvent);
|
||||
}
|
||||
}
|
||||
);
|
||||
notifyInitializationDone(serviceId);
|
||||
}
|
||||
|
||||
if (SimManager.isMultiSim()) {
|
||||
var settings = window.navigator.mozSettings;
|
||||
var req = settings &&
|
||||
settings.createLock().get(serviceSettingMap[serviceId]);
|
||||
|
||||
req.onsuccess = function _onsuccesSlotId() {
|
||||
if (!req.result[serviceSettingMap[serviceId]]) {
|
||||
console.warn('The setting ' + serviceSettingMap[serviceId] +
|
||||
' does not exists, using default Slot (0)');
|
||||
}
|
||||
var slotId = req.result[serviceSettingMap[serviceId]] || 0;
|
||||
serviceIcc[serviceId].slotId = slotId;
|
||||
checkForCompletion();
|
||||
};
|
||||
req.onerror = function _onErrorSlotId() {
|
||||
console.warn('The setting ' + serviceSettingMap[serviceId] +
|
||||
' does not exists');
|
||||
checkForCompletion();
|
||||
};
|
||||
} else {
|
||||
notifyInitializationDone(serviceId);
|
||||
}
|
||||
}
|
||||
|
||||
function _requestDataSIMIcc(onsuccess, onerror) {
|
||||
_requestService('data', onsuccess, onerror);
|
||||
}
|
||||
|
||||
function _requestTelephonySIMIcc(onsuccess, onerror) {
|
||||
_requestService('telephony', onsuccess, onerror);
|
||||
}
|
||||
|
||||
function _requestMessageSIMIcc(onsuccess, onerror) {
|
||||
_requestService('message', onsuccess, onerror);
|
||||
}
|
||||
|
||||
function _requestService(serviceId, onsuccess, onerror) {
|
||||
if (serviceIcc[serviceId] && serviceIcc[serviceId].initialized &&
|
||||
!serviceIcc[serviceId].dirty) {
|
||||
(typeof onsuccess === 'function') && onsuccess(serviceIcc[serviceId]);
|
||||
} else if (!serviceIcc[serviceId] || !serviceIcc[serviceId].initialized) {
|
||||
console.warn('SimManager is not ready, waiting for initialized custom ' +
|
||||
'event');
|
||||
window.addEventListener('simManagerReady', function _onSMInit(evt) {
|
||||
if (evt.detail === 'all' || evt.detail === serviceId) {
|
||||
window.removeEventListener('simManagerReady', _onSMInit);
|
||||
_requestServiceSIMIcc(serviceId, onsuccess, onerror);
|
||||
}
|
||||
});
|
||||
_init(serviceId);
|
||||
} else {
|
||||
_requestServiceSIMIcc(serviceId, onsuccess, onerror);
|
||||
}
|
||||
}
|
||||
|
||||
function _requestServiceSIMIcc(serviceId, onsuccess, onerror) {
|
||||
var iccManager = window.navigator.mozIccManager;
|
||||
var slotId = serviceIcc[serviceId].slotId;
|
||||
var mobileConnection = navigator.mozMobileConnections[slotId];
|
||||
var iccId = mobileConnection.iccId || null;
|
||||
|
||||
if (!iccId) {
|
||||
console.error('The slot ' + slotId + ', configured as the ' + serviceId +
|
||||
' slot, is empty');
|
||||
(typeof onerror === 'function') && onerror();
|
||||
return;
|
||||
}
|
||||
serviceIcc[serviceId].iccId = iccId;
|
||||
serviceIcc[serviceId].icc = iccManager.getIccById(iccId);
|
||||
|
||||
// Icc is not detected although iccId exists
|
||||
if (!serviceIcc[serviceId].icc) {
|
||||
(typeof onerror === 'function') && onerror();
|
||||
return;
|
||||
}
|
||||
|
||||
(typeof onsuccess === 'function') && onsuccess(serviceIcc[serviceId]);
|
||||
}
|
||||
|
||||
function _reset(serviceId) {
|
||||
function _resetService(serviceId) {
|
||||
// If it is not a DSDS device, the slotId is correct
|
||||
var _initialized = !SimManager.isMultiSim();
|
||||
serviceIcc[serviceId] = {
|
||||
dirty: true,
|
||||
icc: null,
|
||||
iccId: null,
|
||||
// The default value for slotId is 0 on the no DSDS device
|
||||
slotId: 0,
|
||||
initialized: _initialized
|
||||
};
|
||||
}
|
||||
if (!serviceId) {
|
||||
Object.keys(serviceSettingMap).forEach(function _initService(serviceId) {
|
||||
_resetService(serviceId);
|
||||
});
|
||||
} else {
|
||||
_resetService(serviceId);
|
||||
}
|
||||
}
|
||||
|
||||
function _requestServiceConnection(serviceId) {
|
||||
var slotId = serviceIcc[serviceId].slotId;
|
||||
return navigator.mozMobileConnections[slotId];
|
||||
}
|
||||
|
||||
function _requestDataConnection(callback) {
|
||||
if (serviceIcc.data && serviceIcc.data.initialized) {
|
||||
var connection = _requestServiceConnection('data');
|
||||
(typeof callback === 'function') && callback(connection);
|
||||
} else {
|
||||
console.warn('SimManager is not ready, waiting for initialized custom ' +
|
||||
'event');
|
||||
window.addEventListener('simManagerReady', function _onSMInit(evt) {
|
||||
if (evt.detail === 'all' || evt.detail === 'data') {
|
||||
window.removeEventListener('simManagerReady', _onSMInit);
|
||||
var connection = _requestServiceConnection('data');
|
||||
(typeof callback === 'function') && callback(connection);
|
||||
}
|
||||
});
|
||||
_init('data');
|
||||
}
|
||||
}
|
||||
return {
|
||||
isMultiSim: function() {
|
||||
// On a multisim device, the mozMobileConnections array always return
|
||||
// the number of slots although these slots are empty.
|
||||
return (navigator.mozMobileConnections.length > 1);
|
||||
},
|
||||
// data info
|
||||
requestDataSimIcc: _requestDataSIMIcc,
|
||||
// telephony info
|
||||
requestTelephonySimIcc: _requestTelephonySIMIcc,
|
||||
requestDataConnection: _requestDataConnection,
|
||||
// message info
|
||||
requestMessageSimIcc: _requestMessageSIMIcc,
|
||||
reset: _reset
|
||||
};
|
||||
})();
|
||||
|
||||
585
gaia/apps/costcontrol/js/utils/chart.js
Executable file
|
|
@ -0,0 +1,585 @@
|
|||
/* global _, debug, Toolkit, Formatting, Common */
|
||||
/* exported ChartUtils */
|
||||
'use strict';
|
||||
|
||||
var ChartUtils = (function() {
|
||||
var DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
|
||||
var DAY = 24 * 60 * 60 * 1000;
|
||||
var NEVER_PERIOD = 30 * DAY;
|
||||
var NEVER_ANCHOR = 21 * DAY;
|
||||
var CHART_BG_RATIO = 0.87;
|
||||
var FONT_SIZE = toDevicePixels(13);
|
||||
var FONT_WEIGHT = '600';
|
||||
var FONT_WEIGHT_AXIS = '400';
|
||||
var WIFI_CHART_STROKE = '#8b9052';
|
||||
var WIFI_CHART_FILL = '#cbd936';
|
||||
var MOBILE_CHART_STROKE = '#762d4a';
|
||||
var MOBILE_CHART_FILL = 'rgba(147, 21, 98, 0.7)';
|
||||
|
||||
function toDevicePixels(value) {
|
||||
return value * DEVICE_PIXEL_RATIO;
|
||||
}
|
||||
|
||||
function makeCSSFontString(fontSize, fontWeight) {
|
||||
return fontWeight + ' ' + fontSize + 'px sans-serif';
|
||||
}
|
||||
|
||||
function formatChartDate(date) {
|
||||
return _('verbose-chart-date-format', {
|
||||
'monthday-number': date.getDate(),
|
||||
'em-month': _('month-' + date.getMonth() + '-short')
|
||||
});
|
||||
}
|
||||
|
||||
function getLimitInBytes(settings) {
|
||||
var multiplier = 1000000; // MB
|
||||
if (settings.dataLimitUnit === 'GB') {
|
||||
multiplier = 1000000000; // GB
|
||||
}
|
||||
|
||||
var value = settings.dataLimitValue;
|
||||
return (value && value !== 0) ? value * multiplier : null;
|
||||
}
|
||||
|
||||
function calculateUpperDate(settings) {
|
||||
var today = new Date();
|
||||
var trackingPeriod = settings.trackingPeriod;
|
||||
var nextReset = settings.nextReset;
|
||||
if (trackingPeriod !== 'never' && nextReset) {
|
||||
return new Date(nextReset.getTime() - DAY);
|
||||
}
|
||||
|
||||
var lastReset = settings.lastCompleteDataReset || today;
|
||||
var offset = today.getTime() - lastReset.getTime();
|
||||
var upperDate = new Date(lastReset.getTime() + NEVER_PERIOD);
|
||||
if (offset >= NEVER_ANCHOR) {
|
||||
upperDate = new Date(today.getTime() + (NEVER_PERIOD - NEVER_ANCHOR));
|
||||
}
|
||||
|
||||
debug('Upper date:', upperDate);
|
||||
return upperDate;
|
||||
}
|
||||
|
||||
function calculateLowerDate(settings) {
|
||||
var today = new Date();
|
||||
var lowerDate = Toolkit.toMidnight(new Date());
|
||||
var nextReset = settings.nextReset || lowerDate;
|
||||
var trackingPeriod = settings.trackingPeriod;
|
||||
|
||||
if (trackingPeriod === 'weekly') {
|
||||
lowerDate.setTime(nextReset.getTime() - (7 * DAY));
|
||||
} else if (trackingPeriod === 'monthly') {
|
||||
var monthDate = settings.resetTime;
|
||||
var isFirstDayOfPeriod = today.getDate() == monthDate;
|
||||
var isAfterFirstDayOfPeriod = today.getDate() > monthDate;
|
||||
if (isAfterFirstDayOfPeriod) {
|
||||
// lowerDate is in the current month
|
||||
lowerDate.setDate(monthDate);
|
||||
} else if (!isFirstDayOfPeriod) {
|
||||
// lowerDate is on the previous month
|
||||
var LAST_DAY_OF_PREVIOUS_MONTH = 0;
|
||||
var newMonth = today.getMonth() - 1;
|
||||
var newYear = today.getFullYear();
|
||||
if (newMonth < 0) {
|
||||
newMonth = 11;
|
||||
newYear--;
|
||||
}
|
||||
|
||||
lowerDate = Toolkit.toMidnight(new Date(newYear, newMonth, monthDate));
|
||||
// The day of the month of lowerDate is different to settings.resetTime
|
||||
// value when the resetTime day doesn't exists this month, (eg. 30th
|
||||
// February), on this case, the lowerDate must be the last day of the
|
||||
// previous month
|
||||
if (lowerDate.getDate() != monthDate) {
|
||||
lowerDate = Toolkit.toMidnight(new Date());
|
||||
lowerDate.setDate(LAST_DAY_OF_PREVIOUS_MONTH);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var lastReset = settings.lastCompleteDataReset || lowerDate;
|
||||
lowerDate = lastReset;
|
||||
var offset = today.getTime() - lastReset.getTime();
|
||||
if (offset >= NEVER_ANCHOR) {
|
||||
lowerDate = new Date(today.getTime() - NEVER_ANCHOR);
|
||||
}
|
||||
}
|
||||
|
||||
debug('Lower date:', lowerDate);
|
||||
return lowerDate;
|
||||
}
|
||||
|
||||
// Expand the model with some computed values
|
||||
function expandModel(base) {
|
||||
|
||||
// Update today
|
||||
var today = Toolkit.toMidnight(new Date());
|
||||
|
||||
// Graphic settings
|
||||
base.originY = Math.floor(base.height * CHART_BG_RATIO);
|
||||
|
||||
// Today value
|
||||
base.axis.X.today = today;
|
||||
|
||||
// Normalize today
|
||||
Toolkit.toMidnight(base.axis.X.today);
|
||||
Toolkit.toMidnight(base.axis.X.lower);
|
||||
Toolkit.toMidnight(base.axis.X.upper);
|
||||
|
||||
// X axis projection function to convert a value into a pixel value
|
||||
var xLowerBound = base.axis.X.lower.getTime();
|
||||
var xSize = base.axis.X.upper.getTime() - xLowerBound;
|
||||
var realWidth = base.endX - base.originX;
|
||||
base.axis.X.len = realWidth;
|
||||
base.axis.X.get = function cc_dataToXPx(value) {
|
||||
var projection = (value.getTime() - xLowerBound) / xSize;
|
||||
return projection * realWidth + base.originX;
|
||||
};
|
||||
|
||||
// Y max value
|
||||
var limitEnabled = true; // XXX: model.limits.enabled;
|
||||
base.axis.Y.maxValue = Math.max(limitEnabled ? base.limits.value : 0,
|
||||
base.data.mobile.total,
|
||||
base.data.wifi.total);
|
||||
|
||||
// Y axis projection function and automatic values
|
||||
base.axis.Y.range = base.axis.Y.upper - base.axis.Y.lower;
|
||||
base.axis.Y.upper = (1 + base.axis.Y.margin) * base.axis.Y.maxValue;
|
||||
var yLowerBound = base.axis.Y.lower;
|
||||
var ySize = base.axis.Y.upper - yLowerBound;
|
||||
base.axis.Y.get = function cc_dataToYPx(value) {
|
||||
var projection = (value - yLowerBound) / ySize;
|
||||
return base.originY * (1 - projection);
|
||||
};
|
||||
base.axis.Y.step = base.axis.Y.get(base.axis.Y.maxValue);
|
||||
|
||||
// Limits
|
||||
base.limits.warning = Common.DATA_USAGE_WARNING;
|
||||
base.limits.warningValue = base.limits.value * Common.DATA_USAGE_WARNING;
|
||||
}
|
||||
|
||||
// Check if the segment of the graph is inside chart area. If so, draw it
|
||||
function clipAndDrawSegment(ctx, model, x0, y0, x1, y1) {
|
||||
if (x0 >= model.originX && x1 <= model.endX) {
|
||||
var x0Fixed = Math.floor(x0) - 0.5;
|
||||
var x1Fixed = Math.floor(x1) + 0.5;
|
||||
|
||||
// Fill
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
ctx.beginPath();
|
||||
ctx.lineTo(x1Fixed, y1);
|
||||
ctx.lineTo(x1Fixed, model.originY);
|
||||
ctx.lineTo(x0Fixed, model.originY);
|
||||
ctx.lineTo(x0Fixed, y0);
|
||||
ctx.moveTo(x1Fixed, y0);
|
||||
ctx.fill();
|
||||
|
||||
// Stroke
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.beginPath();
|
||||
// It's necessary add 1 to the X coordinate to make up for the offset
|
||||
// produced with the drawing of the previous line.
|
||||
ctx.moveTo(x0Fixed + 1, y0);
|
||||
ctx.lineTo(x1Fixed, y1);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawTodayMark(ctx, x, y, color) {
|
||||
ctx.save();
|
||||
var radius = toDevicePixels(4);
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawBackgroundLayer(canvas, model, showMobile) {
|
||||
canvas.height = model.height;
|
||||
var width = canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.save();
|
||||
|
||||
// White bg
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, width, model.originY);
|
||||
|
||||
// Horizontal lines every step
|
||||
var step = model.axis.Y.step;
|
||||
var limitY = model.axis.Y.get(model.limits.value);
|
||||
ctx.strokeStyle = '#e0e0e0';
|
||||
ctx.lineWidth = toDevicePixels(1);
|
||||
var displayLimit = model.limits.enabled && showMobile;
|
||||
for (var y = model.originY - step; y > step; y -= step) {
|
||||
if (displayLimit && same(y, limitY, 0.1)) {
|
||||
continue;
|
||||
}
|
||||
var drawY = Math.floor(y) - 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(model.originX, drawY);
|
||||
ctx.lineTo(model.endX, drawY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Vertical lines every day
|
||||
var days = (model.axis.X.upper - model.axis.X.lower) / DAY;
|
||||
step = model.axis.X.len / days;
|
||||
ctx.strokeStyle = '#eeeeee';
|
||||
ctx.lineWidth = toDevicePixels(1);
|
||||
for (var x = model.originX; x <= model.endX; x += step) {
|
||||
var drawX = Math.floor(x) + 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(drawX, model.originY);
|
||||
ctx.lineTo(drawX, 0);
|
||||
ctx.stroke();
|
||||
// Ensure draw the last vertical line
|
||||
if (((x + step) > model.endX) && (x !== model.endX)) {
|
||||
// the 0.5 offset is needed to avoid drawing a double line.
|
||||
drawX = model.endX - 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(drawX, model.originY);
|
||||
ctx.lineTo(drawX, 0);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// Return true if a and b not differ more than threshold
|
||||
function same(a, b, threshold) {
|
||||
threshold = threshold || 0;
|
||||
return Math.abs(a - b) <= threshold;
|
||||
}
|
||||
}
|
||||
|
||||
function drawTodayLayer(canvas, model) {
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Compute the X offset
|
||||
var offsetX = model.axis.X.get(model.axis.X.today);
|
||||
|
||||
// Configure Centered today text
|
||||
var marginTop = toDevicePixels(10);
|
||||
|
||||
var todayTag = formatChartDate(model.axis.X.today);
|
||||
|
||||
// Render the text
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT);
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
// Update measues of today label
|
||||
var metrics = ctx.measureText(todayTag);
|
||||
model.todayLabel.x0 = offsetX - metrics.width / 2;
|
||||
model.todayLabel.x1 = offsetX + metrics.width / 2;
|
||||
if (model.todayLabel.x1 > model.endX) {
|
||||
ctx.textAlign = 'right';
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(todayTag, offsetX, model.originY + marginTop);
|
||||
|
||||
setAccessibilityAttributes(canvas, 'today-layer', { today: todayTag });
|
||||
}
|
||||
|
||||
function drawAxisLayer(canvas, model, showMobile) {
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Start drawing Y axis
|
||||
var step = model.axis.Y.step;
|
||||
var dataStep = model.axis.Y.upper - model.axis.Y.maxValue;
|
||||
var marginRight = 4;
|
||||
var offsetX = model.originX - marginRight;
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT_AXIS);
|
||||
ctx.textAlign = 'right';
|
||||
var displayLimit = showMobile && model.limits.enabled;
|
||||
var lastUnit;
|
||||
for (var y = 0.5 + model.originY, value = 0;
|
||||
y > step; y -= step, value += dataStep) {
|
||||
|
||||
// First X label for 0 is aligned with the bottom
|
||||
if (value === 0) {
|
||||
lastUnit = Formatting.smartRound(dataStep, 0)[1];
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillStyle = '#6a6a6a';
|
||||
ctx.fillText(Formatting.formatData([0, lastUnit]), offsetX, y - 2.5);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rest of labels are aligned with the middle
|
||||
var rounded = Formatting.smartRound(value, -1);
|
||||
var v = rounded[0];
|
||||
var u = rounded[1];
|
||||
var label = v;
|
||||
if (lastUnit !== u) {
|
||||
label = Formatting.formatData([v, u]);
|
||||
}
|
||||
|
||||
lastUnit = u;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = displayLimit &&
|
||||
(value === model.limits.value) &&
|
||||
model.limits.enabled ?
|
||||
'#b50202' : '#6a6a6a';
|
||||
ctx.fillText(label, offsetX, y);
|
||||
}
|
||||
|
||||
// Now the X axis
|
||||
ctx.fillStyle = '#6a6a6a';
|
||||
var marginTop = toDevicePixels(10);
|
||||
|
||||
// Left tag
|
||||
var leftTag = formatChartDate(model.axis.X.lower);
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT);
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'left';
|
||||
|
||||
var isBelowToday = model.todayLabel.x0 <=
|
||||
model.originX + ctx.measureText(leftTag).width;
|
||||
if (!isBelowToday) {
|
||||
ctx.fillText(leftTag, model.originX, model.originY + marginTop);
|
||||
}
|
||||
|
||||
// Right tag
|
||||
var rightTag = formatChartDate(model.axis.X.upper);
|
||||
ctx.textAlign = 'right';
|
||||
|
||||
isBelowToday = model.todayLabel.x1 >=
|
||||
model.endX - ctx.measureText(rightTag).width;
|
||||
if (!isBelowToday) {
|
||||
ctx.fillText(rightTag, model.endX, model.originY + marginTop);
|
||||
}
|
||||
|
||||
setAccessibilityAttributes(canvas, 'axis-layer',
|
||||
{ from: leftTag, to: rightTag });
|
||||
}
|
||||
|
||||
function setAccessibilityAttributes(element, identifier, data) {
|
||||
if (identifier) {
|
||||
element.setAttribute('data-l10n-id', identifier);
|
||||
if (data) {
|
||||
element.setAttribute('data-l10n-args', JSON.stringify(data));
|
||||
}
|
||||
} else {
|
||||
element.removeAttribute('data-l10n-id');
|
||||
element.removeAttribute('data-l10n-args');
|
||||
element.removeAttribute('aria-label');
|
||||
}
|
||||
}
|
||||
|
||||
function drawLimits(canvas, model, showMobile) {
|
||||
var set = model.limits.value;
|
||||
var color = '#b50202';
|
||||
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var displayLimit = showMobile && model.limits.enabled;
|
||||
if (!displayLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
|
||||
var marginTop = 1;
|
||||
var offsetY = set ? model.axis.Y.get(model.limits.value) :
|
||||
FONT_SIZE + 2 * marginTop;
|
||||
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT);
|
||||
|
||||
// The dashed limit line
|
||||
var lineLength = toDevicePixels(15);
|
||||
var gapLength = toDevicePixels(7);
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = toDevicePixels(1);
|
||||
ctx.beginPath();
|
||||
for (var x = model.originX, drawY = Math.floor(offsetY) - 0.5;
|
||||
x < model.endX; x += gapLength) {
|
||||
ctx.moveTo(x, drawY);
|
||||
ctx.lineTo(Math.min(x += lineLength, model.endX), drawY);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
setAccessibilityAttributes(canvas,
|
||||
'limit-layer-' + model.limits.dataLimitUnit,
|
||||
{ value: model.limits.dataLimitValue });
|
||||
}
|
||||
|
||||
function drawDataLayer(canvas, model, sampleType, style) {
|
||||
var samples = (model.data[sampleType] || {}).samples;
|
||||
if (!samples) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Style
|
||||
ctx.fillStyle = style.fill;
|
||||
ctx.strokeStyle = style.stroke;
|
||||
ctx.lineWidth = toDevicePixels(2);
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.moveTo(model.originX, model.originY);
|
||||
|
||||
var today = Toolkit.toMidnight(new Date());
|
||||
// offset in milliseconds
|
||||
var offset = today.getTimezoneOffset() * 60 * 1000;
|
||||
|
||||
var sum = 0; var x, y = model.originY;
|
||||
var lastX = model.originX, lastY = model.axis.Y.get(sum);
|
||||
// Only dealing with the use cases of negative UTC offset because, due to
|
||||
// the database granularity, the returned samples are given in UTC days, and
|
||||
// we don't know what part of the traffic sample values correspond to the
|
||||
// current localtime day. The query for the samples is generated on the
|
||||
// costcontrol module, and the case into which the returned samples only
|
||||
// have one record with the data for the localtime day of yesterday is not
|
||||
// possible.
|
||||
|
||||
for (var i = 0, len = samples.length; i < len; i++) {
|
||||
var sample = samples[i];
|
||||
var sampleLocalTime = sample.date.getTime() + offset;
|
||||
var sampleUTCDate = Toolkit.toMidnight(new Date(sampleLocalTime));
|
||||
|
||||
var isToday = (today.getTime() === sampleUTCDate.getTime());
|
||||
var isTomorrow = (today.getTime() + DAY === sampleUTCDate.getTime());
|
||||
var thereIsATomorrowSample = (isToday && (i + 2 === len));
|
||||
// Depends on the hour of the day and the offset, it is possible the
|
||||
// networkStats API returns the current data mobile in the tomorrow
|
||||
// sample, because on the UTC hour is another day.
|
||||
if (thereIsATomorrowSample) {
|
||||
// Join the value of the samples for today and tomorrow
|
||||
var tomorrowSample = samples[i+1];
|
||||
if (typeof sample.value === 'undefined') {
|
||||
sample.value = tomorrowSample.value;
|
||||
} else if (typeof tomorrowSample.value !== 'undefined') {
|
||||
sample.value += tomorrowSample.value;
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
lastX = model.axis.X.get(sample.date);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (typeof sample.value === 'undefined') {
|
||||
lastX = x = model.axis.X.get(sample.date);
|
||||
ctx.moveTo(x, y);
|
||||
|
||||
} else {
|
||||
if (i === 0) {
|
||||
lastX = model.axis.X.get(sample.date);
|
||||
}
|
||||
|
||||
sum += sample.value;
|
||||
x = model.axis.X.get(sample.date);
|
||||
y = model.axis.Y.get(sum);
|
||||
|
||||
clipAndDrawSegment(ctx, model, lastX, lastY, x, y);
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
var onlyExistTomorrowSample = (i===0 && isTomorrow);
|
||||
var isXInsideTheGraph = (x >= model.originX);
|
||||
if ((isToday || onlyExistTomorrowSample) && isXInsideTheGraph) {
|
||||
drawTodayMark(ctx, x, y, style.stroke);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!style.pattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pattern = ctx.createPattern(style.pattern, 'repeat');
|
||||
ctx.globalCompositeOperation = 'source-atop';
|
||||
ctx.fillStyle = pattern;
|
||||
ctx.fillRect(0, 0, model.width, model.originY);
|
||||
}
|
||||
|
||||
function drawWarningLayer(canvas, model) {
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
if (!model.limits.enabled || model.limits.value === null) {
|
||||
setAccessibilityAttributes(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
// No problem here
|
||||
var mobileUsage = model.data.mobile.total;
|
||||
var mobileUsageDifference = mobileUsage - model.limits.value;
|
||||
if (mobileUsage <= model.limits.warningValue) {
|
||||
setAccessibilityAttributes(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
// Warning mode
|
||||
if (mobileUsage <= model.limits.value) {
|
||||
var limitValue = Math.floor(model.axis.Y.get(model.limits.value));
|
||||
var warningValue =
|
||||
Math.round(model.axis.Y.get(model.limits.warningValue));
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'rgba(255, 112, 0, 0.5)';
|
||||
ctx.fillRect(
|
||||
model.originX, limitValue,
|
||||
model.axis.X.len + 0.5, warningValue - limitValue
|
||||
);
|
||||
|
||||
setAccessibilityAttributes(canvas, 'warning-layer', {
|
||||
value: Formatting.formatData(Formatting.roundData(
|
||||
-mobileUsageDifference))
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit exceeded
|
||||
var limitValueExceeded = model.axis.Y.get(model.limits.value);
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
|
||||
ctx.fillRect(
|
||||
model.originX, 0,
|
||||
model.axis.X.len + 0.5, limitValueExceeded
|
||||
);
|
||||
setAccessibilityAttributes(canvas, 'limit-exceeded-layer', {
|
||||
value: Formatting.formatData(Formatting.roundData(mobileUsageDifference))
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// Constants
|
||||
WIFI_CHART_STROKE: WIFI_CHART_STROKE,
|
||||
WIFI_CHART_FILL: WIFI_CHART_FILL,
|
||||
MOBILE_CHART_STROKE: MOBILE_CHART_STROKE,
|
||||
MOBILE_CHART_FILL: MOBILE_CHART_FILL,
|
||||
|
||||
// Functions
|
||||
toDevicePixels: toDevicePixels,
|
||||
makeCSSFontString: makeCSSFontString,
|
||||
formatChartDate: formatChartDate,
|
||||
getLimitInBytes: getLimitInBytes,
|
||||
calculateUpperDate: calculateUpperDate,
|
||||
calculateLowerDate: calculateLowerDate,
|
||||
expandModel: expandModel,
|
||||
clipAndDrawSegment: clipAndDrawSegment,
|
||||
drawTodayMark: drawTodayMark,
|
||||
drawBackgroundLayer: drawBackgroundLayer,
|
||||
drawTodayLayer: drawTodayLayer,
|
||||
drawAxisLayer: drawAxisLayer,
|
||||
drawLimits: drawLimits,
|
||||
drawDataLayer: drawDataLayer,
|
||||
drawWarningLayer: drawWarningLayer
|
||||
};
|
||||
}());
|
||||
39
gaia/apps/costcontrol/js/utils/debug.js
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
/* exported debug */
|
||||
'use strict';
|
||||
|
||||
var DEBUGGING = false;
|
||||
|
||||
var debug = (function() {
|
||||
var SEQ_ID = 0;
|
||||
var PROCESS_ID = Date.now();
|
||||
|
||||
var DEBUG_PREFIX = 'CC';
|
||||
|
||||
return function _debug() {
|
||||
if (!DEBUGGING) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentWindow = window;
|
||||
var parents = [];
|
||||
while (currentWindow !== currentWindow.parent) {
|
||||
parents.push(currentWindow.location.pathname);
|
||||
currentWindow = currentWindow.parent;
|
||||
}
|
||||
parents.push(currentWindow.location.pathname);
|
||||
parents = parents.reverse().join('>') + ':';
|
||||
|
||||
var uId = PROCESS_ID;
|
||||
var message = ['(' + uId + '-' + (SEQ_ID++) + ')', DEBUG_PREFIX, parents];
|
||||
for (var i = 0, len = arguments.length, obj; i < len; i++) {
|
||||
obj = arguments[i];
|
||||
message.push(typeof obj === 'object' ? JSON.stringify(obj) : obj);
|
||||
}
|
||||
if (window.dump) {
|
||||
message.push('\n');
|
||||
window.dump(message.join(' '));
|
||||
} else if (console && console.log) {
|
||||
console.log(message.join(' '));
|
||||
}
|
||||
};
|
||||
}());
|
||||
241
gaia/apps/costcontrol/js/utils/formatting.js
Executable file
|
|
@ -0,0 +1,241 @@
|
|||
/* global _ */
|
||||
/* exported Formatting */
|
||||
'use strict';
|
||||
|
||||
var Formatting = (function() {
|
||||
|
||||
var MINUTE = 60 * 1000;
|
||||
var HOUR = 60 * MINUTE;
|
||||
var DAY = 24 * HOUR;
|
||||
|
||||
function getDaysOfDifference(a, b) {
|
||||
return Math.floor((a.getTime() - b.getTime()) / DAY);
|
||||
}
|
||||
|
||||
function getFormattedDate(timestamp, formatter) {
|
||||
return formatter.format(timestamp);
|
||||
}
|
||||
|
||||
function formatTime(timestamp, formatter) {
|
||||
if (!timestamp) {
|
||||
return _('never');
|
||||
}
|
||||
|
||||
var now = new Date(), then = new Date(timestamp);
|
||||
if (formatter) {
|
||||
return getFormattedDate(then, formatter);
|
||||
}
|
||||
|
||||
var date, time;
|
||||
var daysOfDifference = getDaysOfDifference(now, then);
|
||||
if (daysOfDifference === 0) {
|
||||
date = _('today');
|
||||
|
||||
} else if (daysOfDifference === 1) {
|
||||
date = _('yesterday');
|
||||
|
||||
} else {
|
||||
date = getFormattedDate(timestamp, Formatting.formatters.shortWeekday);
|
||||
}
|
||||
|
||||
time = getFormattedDate(timestamp, Formatting.formatters.shortTime);
|
||||
return _('day-hour-format', {
|
||||
day: date,
|
||||
time: time
|
||||
});
|
||||
}
|
||||
|
||||
function formatTimeSinceNow(timestamp) {
|
||||
var now = new Date(), then = new Date(timestamp);
|
||||
|
||||
var formattedTime = formatTime(timestamp);
|
||||
if (getDaysOfDifference(now, then) > 0) {
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
var age = now - then;
|
||||
if (age < MINUTE) {
|
||||
formattedTime = _('minutes-ago-short', { value: 0 });
|
||||
|
||||
} else if (age < HOUR) {
|
||||
var minutes = Math.floor(age / MINUTE);
|
||||
formattedTime = _('minutes-ago-short', { value: minutes });
|
||||
|
||||
} else if (age < DAY) {
|
||||
var hours = Math.floor(age / HOUR);
|
||||
formattedTime = _('hours-ago-short', { value: hours });
|
||||
}
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
function formatData(dataArray) {
|
||||
return isNaN(dataArray[0]) ? '' :
|
||||
_('magnitude', { value: dataArray[0], unit: dataArray[1] });
|
||||
}
|
||||
|
||||
function roundData(value, positions) {
|
||||
positions = (typeof positions === 'undefined') ? 2 : positions;
|
||||
if (value < 1000) {
|
||||
return [value.toFixed(positions), _('B')];
|
||||
}
|
||||
|
||||
if (value < 1000000) {
|
||||
return [(value / 1000).toFixed(positions), _('KB')];
|
||||
}
|
||||
|
||||
if (value < 1000000000) {
|
||||
return [(value / 1000000).toFixed(positions), _('MB')];
|
||||
}
|
||||
|
||||
return [(value / 1000000000).toFixed(positions), _('GB')];
|
||||
}
|
||||
|
||||
function getPositions(value, deltaPositions) {
|
||||
deltaPositions = (typeof deltaPositions === 'undefined') ? 0 :
|
||||
deltaPositions;
|
||||
|
||||
if (parseInt(value) === value) {
|
||||
return 0;
|
||||
}
|
||||
if (value < 10) {
|
||||
return 2 + deltaPositions;
|
||||
}
|
||||
if (value < 100) {
|
||||
return 1 + deltaPositions;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function smartRound(value, deltaPositions) {
|
||||
deltaPositions = (typeof deltaPositions === 'undefined') ? 0 :
|
||||
deltaPositions;
|
||||
if (value < 1000) {
|
||||
var bPositions = getPositions(value, deltaPositions);
|
||||
return [value.toFixed(bPositions), _('B')];
|
||||
}
|
||||
|
||||
if (value < 1000000) {
|
||||
var kbytes = value / 1000;
|
||||
var kbPositions = getPositions(kbytes, deltaPositions);
|
||||
return [kbytes.toFixed(kbPositions), _('KB')];
|
||||
}
|
||||
|
||||
if (value < 1000000000) {
|
||||
var mbytes = value / 1000000;
|
||||
var mPositions = getPositions(mbytes, deltaPositions);
|
||||
return [mbytes.toFixed(mPositions), _('MB')];
|
||||
}
|
||||
|
||||
var gbytes = value / 1000000000;
|
||||
var gPositions = getPositions(gbytes, deltaPositions);
|
||||
return [gbytes.toFixed(gPositions), _('GB')];
|
||||
}
|
||||
|
||||
function formatTimeHTML(timestampA, timestampB) {
|
||||
function timeElement(content) {
|
||||
var time = document.createElement('time');
|
||||
time.textContent = content;
|
||||
return time;
|
||||
}
|
||||
|
||||
var fragment = document.createDocumentFragment();
|
||||
|
||||
// No interval
|
||||
if (typeof timestampB === 'undefined') {
|
||||
fragment.appendChild(timeElement(Formatting.formatTime(timestampA)));
|
||||
return fragment;
|
||||
}
|
||||
|
||||
// Same day case
|
||||
var dateA = new Date(timestampA);
|
||||
var dateB = new Date(timestampB);
|
||||
if (dateA.getFullYear() === dateB.getFullYear() &&
|
||||
dateA.getMonth() === dateB.getMonth() &&
|
||||
dateA.getDay() === dateB.getDay()) {
|
||||
|
||||
return formatTimeHTML(timestampB);
|
||||
}
|
||||
|
||||
// Interval
|
||||
fragment.appendChild(
|
||||
timeElement(
|
||||
Formatting.formatTime(timestampA, Formatting.formatters.shortDate)
|
||||
)
|
||||
);
|
||||
fragment.appendChild(document.createTextNode(' – '));
|
||||
fragment.appendChild(timeElement(Formatting.formatTime(timestampB)));
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function computeTelephonyMinutes(activity) {
|
||||
// Right now the activity for telephony is computed in milliseconds
|
||||
return Math.ceil(activity.calltime / 60000);
|
||||
}
|
||||
|
||||
return {
|
||||
getFormattedDate: getFormattedDate,
|
||||
/*
|
||||
* Used with a Date and a format string, it is the same than using
|
||||
* `l10n.localeFormat()`.
|
||||
*
|
||||
* If format is not provided, default format is assumed to be:
|
||||
* "Today|Yesterday|<WeekDay>, hh:mm"
|
||||
*
|
||||
*/
|
||||
formatTime: formatTime,
|
||||
|
||||
/*
|
||||
* Returns a human friendly string describing time transcurred since the
|
||||
* present moment to the timestamp passed as parameter in minutes or hours.
|
||||
*
|
||||
* If timestamps is yesterday or a day before yesterday, it is shown as
|
||||
* in `Formatting.formatTime()`
|
||||
*/
|
||||
formatTimeSinceNow: formatTimeSinceNow,
|
||||
|
||||
// Format data using magnitude localization
|
||||
// It exepcts a pair with the value and the unit
|
||||
formatData: formatData,
|
||||
|
||||
// Return a fixed point data value in KB/MB/GB
|
||||
roundData: roundData,
|
||||
|
||||
getPositions: getPositions,
|
||||
|
||||
smartRound: smartRound,
|
||||
|
||||
formatTimeHTML: formatTimeHTML,
|
||||
|
||||
// Given the API information compute the human friendly minutes
|
||||
computeTelephonyMinutes: computeTelephonyMinutes,
|
||||
|
||||
formatters: {}
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
|
||||
function setFormatters() {
|
||||
const locales = navigator.languages;
|
||||
Formatting.formatters.longDate = Intl.DateTimeFormat(locales, {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
Formatting.formatters.shortDate = Intl.DateTimeFormat(locales, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
Formatting.formatters.shortTime = Intl.DateTimeFormat(locales, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
Formatting.formatters.shortWeekday = Intl.DateTimeFormat(locales, {
|
||||
weekday: 'short'
|
||||
});
|
||||
}
|
||||
|
||||
setFormatters();
|
||||
window.addEventListener('languagechange', setFormatters, false);
|
||||
window.addEventListener('timeformatchange', setFormatters, false);
|
||||
52
gaia/apps/costcontrol/js/utils/toolkit.js
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
/* global DEBUGGING */
|
||||
/* exported _, deepCopy, Toolkit */
|
||||
'use strict';
|
||||
|
||||
var _ = function cc_fallbackTranslation(keystring) {
|
||||
var r = window.navigator.mozL10n.get.apply(this, arguments);
|
||||
return r || (DEBUGGING ? '!!' : '') + keystring;
|
||||
};
|
||||
|
||||
// Deep copy algorithm with Data support
|
||||
function deepCopy(object) {
|
||||
if (object === null || !(object instanceof Object)) {
|
||||
return object;
|
||||
}
|
||||
|
||||
var clone, constructor = object.constructor;
|
||||
switch (constructor) {
|
||||
case Date:
|
||||
clone = new constructor(object.getTime());
|
||||
break;
|
||||
default:
|
||||
clone = constructor();
|
||||
break;
|
||||
}
|
||||
for (var key in object) {
|
||||
if (object.hasOwnProperty(key)) {
|
||||
clone[key] = deepCopy(object[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
var Toolkit = {
|
||||
checkEnoughDelay: function(threshold, dateA, dateB) {
|
||||
if (!dateA) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var end = dateB || new Date();
|
||||
return (end.getTime() - dateA.getTime()) > threshold;
|
||||
},
|
||||
|
||||
toMidnight: function _toMidnight(date) {
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
return date;
|
||||
}
|
||||
|
||||
};
|
||||
255
gaia/apps/costcontrol/js/view_manager.js
Executable file
|
|
@ -0,0 +1,255 @@
|
|||
/* global AccessibilityHelper, LazyLoader */
|
||||
/* exported ViewManager */
|
||||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
var ViewManager = (function() {
|
||||
|
||||
// The ViewManager is in charge of simply manage the different views of the
|
||||
// applications. ViewManager.changeViewTo() valid values are listed above
|
||||
// these lines.
|
||||
function ViewManager(tabs) {
|
||||
tabs = tabs || [];
|
||||
|
||||
this._tabs = {};
|
||||
tabs.forEach(function _registerTab(tabItem) {
|
||||
if (typeof tabItem !== 'object') {
|
||||
tabItem = { id: tabItem };
|
||||
}
|
||||
this._tabs[tabItem.id] = tabItem.tab || 'left';
|
||||
}, this);
|
||||
|
||||
this._currentView = null;
|
||||
this._currentTab = null;
|
||||
|
||||
}
|
||||
|
||||
// Return true if the passed view is a tab
|
||||
ViewManager.prototype._isTab = function _isTab(view) {
|
||||
return this._tabs.hasOwnProperty(view);
|
||||
};
|
||||
|
||||
// Make target enter screen's main area and call callback after, passing as
|
||||
// arguments if the new view is a tab, the new view id and a third parameter
|
||||
// depending on if the view was a tab or not:
|
||||
// If it is a tab: it returns the current overlay view id or null
|
||||
// If it is not a tab: it returns the previous ovrlay view or null
|
||||
ViewManager.prototype.changeViewTo = function _changeViewTo(viewHash,
|
||||
obstructed,
|
||||
callback) {
|
||||
var hashParts = viewHash.split('?');
|
||||
var viewId = hashParts[0];
|
||||
if (this.isCurrentView(viewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note here how we set the same value with different semantincs.
|
||||
// This is used at the end of the function and the names are the correct
|
||||
// because, depending on if the view is a tab or not, semantics may change.
|
||||
var previousViewId, currentViewId;
|
||||
previousViewId = currentViewId = this._currentView ?
|
||||
this._currentView.id : null;
|
||||
|
||||
var view = document.getElementById(viewId);
|
||||
var params = parseQueryParams(hashParts[1]);
|
||||
var self = this;
|
||||
|
||||
// lazy load HTML of the panel
|
||||
this.loadPanel(view, function() {
|
||||
// Tabs are treated in a different way than overlay views
|
||||
var isTab = self._isTab(viewId);
|
||||
if (isTab) {
|
||||
|
||||
// Disposing the current view
|
||||
var disposingTab = null;
|
||||
if (self._currentTab) {
|
||||
disposingTab = document.getElementById(self._currentTab);
|
||||
}
|
||||
if (disposingTab) {
|
||||
disposingTab.dataset.viewport = self._tabs[disposingTab.id];
|
||||
document.getElementById(disposingTab.id + '-filter').classList.remove(
|
||||
'.selected');
|
||||
}
|
||||
// Showing the new one
|
||||
view.dataset.viewport = '';
|
||||
document.getElementById(view.id + '-filter').classList.add('.selected');
|
||||
AccessibilityHelper.setAriaSelected(
|
||||
document.getElementById(view.id + '-control'),
|
||||
document.querySelectorAll('[role="tab"]'));
|
||||
|
||||
self._currentTab = viewId;
|
||||
|
||||
// Overlay view
|
||||
} else {
|
||||
self.closeCurrentView();
|
||||
previousViewId = self._currentView ? self._currentView.id : '';
|
||||
self._currentView = {
|
||||
id: viewId,
|
||||
defaultViewport: view.dataset.viewport,
|
||||
obstructed: obstructed
|
||||
};
|
||||
|
||||
// With a combination of CSS, we actually animate and display the view
|
||||
delete view.dataset.viewport;
|
||||
if (obstructed) {
|
||||
document.querySelector(obstructed).classList.add('behind');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(isTab, viewId, isTab ? currentViewId : previousViewId);
|
||||
}
|
||||
notifyViewChange(isTab, viewId, params);
|
||||
});
|
||||
};
|
||||
|
||||
function notifyViewChange(isTab, current, params) {
|
||||
var type = isTab ? 'tabchanged' : 'viewchanged';
|
||||
var event = new CustomEvent(type, {
|
||||
detail: {
|
||||
id: current,
|
||||
params: params
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
ViewManager.prototype.loadPanel = function _loadPanel(panel, callback) {
|
||||
if (!panel || panel.hidden === false) {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
LazyLoader.load(panel, function() {
|
||||
var resourceRemaining = 0;
|
||||
|
||||
function loadResource(node) {
|
||||
node.onload = onResourceLoaded;
|
||||
document.head.appendChild(node);
|
||||
resourceRemaining++;
|
||||
}
|
||||
|
||||
// activate all styles
|
||||
Array.from(panel.querySelectorAll('link')).forEach((node) => {
|
||||
if (!document.querySelector('head > link[href="' + node.href + '"]')) {
|
||||
var styleLink = document.createElement('link');
|
||||
styleLink.href = node.href;
|
||||
styleLink.media = node.media;
|
||||
styleLink.rel = node.rel;
|
||||
styleLink.type = node.type;
|
||||
|
||||
loadResource(styleLink);
|
||||
}
|
||||
node.remove();
|
||||
});
|
||||
|
||||
// activate all scripts
|
||||
Array.from(panel.querySelectorAll('script')).forEach((node) => {
|
||||
if (!document.querySelector('head > script[src="' + node.src + '"]')) {
|
||||
var scriptLink = document.createElement('script');
|
||||
scriptLink.src = node.src;
|
||||
scriptLink.type = node.type;
|
||||
|
||||
loadResource(scriptLink);
|
||||
}
|
||||
node.remove();
|
||||
});
|
||||
|
||||
// add listeners
|
||||
var headers = Array.from(
|
||||
panel.querySelectorAll('gaia-header[action="close"]')
|
||||
);
|
||||
headers.forEach((headerWithClose) => {
|
||||
headerWithClose.addEventListener('action', function() {
|
||||
window.parent.location.hash = '#';
|
||||
});
|
||||
});
|
||||
|
||||
panel.hidden = false;
|
||||
|
||||
checkCallback();
|
||||
|
||||
function checkCallback() {
|
||||
if (resourceRemaining === 0 && typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function onResourceLoaded() {
|
||||
resourceRemaining--;
|
||||
checkCallback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Close the current view returning to the previous one
|
||||
ViewManager.prototype.closeCurrentView = function _closeCurrentView() {
|
||||
if (!this._currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = document.getElementById(this._currentView.id);
|
||||
|
||||
// With a combination of CSS, Restoring the last viewport we actually
|
||||
// animate and hide the current view
|
||||
view.dataset.viewport = this._currentView.defaultViewport;
|
||||
|
||||
if (this._currentView.obstructed) {
|
||||
var obstructed = document.querySelector(this._currentView.obstructed);
|
||||
obstructed.classList.remove('behind');
|
||||
}
|
||||
this._currentView = null;
|
||||
};
|
||||
|
||||
// Test if the current view is the one passed as parameter
|
||||
ViewManager.prototype.isCurrentView = function _isCurrentView(view) {
|
||||
return this._currentView && this._currentView.id === view;
|
||||
};
|
||||
|
||||
// Return the current view id or null if not current view
|
||||
ViewManager.prototype.getCurrentView = function _getCurrentView() {
|
||||
return this._currentView ? this._currentView.id : null;
|
||||
};
|
||||
|
||||
// Return true if the tab id passed is the current tab
|
||||
ViewManager.prototype.isCurrentTab = function _isCurrentTab(tab) {
|
||||
return this._currentTab && this._currentTab === tab;
|
||||
};
|
||||
|
||||
// Return the current tab
|
||||
ViewManager.prototype.getCurrentTab = function _getCurrentTab() {
|
||||
return this._currentTab;
|
||||
};
|
||||
|
||||
function parseQueryParams(queryParams) {
|
||||
var params = {};
|
||||
|
||||
queryParams = (queryParams || '').split('&');
|
||||
queryParams.forEach(function(param) {
|
||||
if (!param) { return; }
|
||||
|
||||
var pair = param.split('=');
|
||||
var key = pair[0];
|
||||
var value = pair[1];
|
||||
|
||||
if (params[key]) {
|
||||
if (!Array.isArray(params[key])) {
|
||||
params[key] = [params[key]];
|
||||
}
|
||||
|
||||
params[key].push(value);
|
||||
return;
|
||||
}
|
||||
|
||||
params[key] = value;
|
||||
});
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
return ViewManager;
|
||||
}());
|
||||
44
gaia/apps/costcontrol/js/views/BalanceLowLimitView.js
Executable file
|
|
@ -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});
|
||||
}
|
||||
};
|
||||
83
gaia/apps/costcontrol/js/views/BalanceView.js
Executable file
|
|
@ -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;
|
||||
}
|
||||
31
gaia/apps/costcontrol/js/views/ConfirmDialog.js
Executable file
|
|
@ -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();
|
||||
};
|
||||
80
gaia/apps/costcontrol/js/views/NonReadyScreen.js
Executable file
|
|
@ -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;
|
||||
};
|
||||
57
gaia/apps/costcontrol/js/views/ResetMenuDialog.js
Executable file
|
|
@ -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();
|
||||
};
|
||||
16
gaia/apps/costcontrol/js/views/TopUpLayoutView.js
Executable file
|
|
@ -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
gaia/apps/costcontrol/js/views/appdetail.js
Executable 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
gaia/apps/costcontrol/js/views/balance.js
Executable 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
gaia/apps/costcontrol/js/views/datausage.js
Executable 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
gaia/apps/costcontrol/js/views/telephony.js
Executable 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();
|
||||
563
gaia/apps/costcontrol/js/widget.js
Executable file
|
|
@ -0,0 +1,563 @@
|
|||
/* global _, debug, BalanceView, AirplaneModeHelper, setNextReset, SimManager,
|
||||
ConfigManager, Common, CostControl, MozActivity, LazyLoader,
|
||||
Formatting
|
||||
*/
|
||||
/* exported activity */
|
||||
|
||||
'use strict';
|
||||
/*
|
||||
* The widget is in charge of show balance, telephony data and data usage
|
||||
* statistics depending on the SIM inserted.
|
||||
*
|
||||
* It has three areas of drawing: balance, telephony and data usage.
|
||||
* The update is done by demand when the widget is shown or in reaction of
|
||||
* a balance update or timeout.
|
||||
*/
|
||||
|
||||
var Widget = (function() {
|
||||
var costcontrol, activity;
|
||||
function checkSIMStatus(dataSim) {
|
||||
var dataSimIcc = dataSim.icc;
|
||||
var cardState = checkCardState(dataSimIcc);
|
||||
|
||||
if (cardState !== 'ready') {
|
||||
debug('SIM not ready:', dataSimIcc.cardState);
|
||||
initialized = false;
|
||||
dataSimIcc.oncardstatechange = checkSIMStatus.bind(null, dataSim);
|
||||
// SIM ready
|
||||
} else {
|
||||
debug('SIM ready. ICCID:', dataSim.iccId);
|
||||
dataSimIcc.oncardstatechange = undefined;
|
||||
var SCRIPTS_NEEDED_TO_START = [
|
||||
'js/costcontrol.js',
|
||||
'js/config/config_manager.js'
|
||||
];
|
||||
LazyLoader.load(SCRIPTS_NEEDED_TO_START, startWidget);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Check the card status. Return 'ready' if all OK or take actions for
|
||||
// special situations such as 'pin/puk locked' or 'absent'.
|
||||
function checkCardState(dataSimIcc) {
|
||||
var state, cardState;
|
||||
state = cardState = dataSimIcc.cardState;
|
||||
|
||||
// SIM is absent
|
||||
if (!cardState || cardState === 'absent') {
|
||||
debug('There is no SIM');
|
||||
Widget.showSimError('no-sim3');
|
||||
|
||||
// SIM is not initialized correctly
|
||||
} else if (cardState === 'unknown') {
|
||||
debug('Unknow state fo the sim');
|
||||
Widget.showSimError('unknown');
|
||||
|
||||
// SIM is locked
|
||||
} else if (
|
||||
cardState === 'pinRequired' ||
|
||||
cardState === 'pukRequired'
|
||||
) {
|
||||
Widget.showSimError('sim2-locked');
|
||||
state = 'locked';
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function loadMessageHandler() {
|
||||
if (ConfigManager.option('nextReset')) {
|
||||
window.addEventListener('messagehandlerready', function _setNextReset() {
|
||||
window.removeEventListener('messagehandlerready', _setNextReset);
|
||||
LazyLoader.load('js/settings/networkUsageAlarm.js', function() {
|
||||
Common.loadNetworkInterfaces(function() {
|
||||
setNextReset(ConfigManager.option('nextReset'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
document.getElementById('message-handler').src = 'message_handler.html';
|
||||
}
|
||||
|
||||
function startWidget() {
|
||||
CostControl.getInstance(function _onCostControlReady(instance) {
|
||||
costcontrol = instance;
|
||||
loadMessageHandler();
|
||||
setupWidget();
|
||||
});
|
||||
}
|
||||
|
||||
var initialized, widget, leftPanel, rightPanel, fte, views = {};
|
||||
var balanceView;
|
||||
|
||||
function _openBalanceTab() {
|
||||
activity = new MozActivity({ name: 'costcontrol/balance' });
|
||||
}
|
||||
|
||||
function _openTelephonyTab() {
|
||||
activity = new MozActivity({ name: 'costcontrol/telephony' });
|
||||
}
|
||||
|
||||
function _openDataUsageTab() {
|
||||
activity = new MozActivity({ name: 'costcontrol/data_usage' });
|
||||
}
|
||||
|
||||
function _onVisibilityChange(evt) {
|
||||
AirplaneModeHelper.ready(function() {
|
||||
if (!document.hidden && initialized &&
|
||||
(AirplaneModeHelper.getStatus() === 'disabled')) {
|
||||
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
||||
checkCardState(dataSimIcc.icc);
|
||||
updateUI();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _onHashChange() {
|
||||
if (window.location.hash.split('#')[1] === 'update') {
|
||||
updateUI(true); // update only data usage
|
||||
}
|
||||
}
|
||||
|
||||
function _onLocalize() {
|
||||
if (initialized) {
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
function setupWidget() {
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
var isDataUsageOnly = (mode === 'DATA_USAGE_ONLY');
|
||||
var isBalanceEnabled = !isDataUsageOnly;
|
||||
|
||||
// HTML entities
|
||||
widget = document.getElementById('cost-control');
|
||||
leftPanel = document.getElementById('left-panel');
|
||||
rightPanel = document.getElementById('right-panel');
|
||||
fte = document.getElementById('fte-view');
|
||||
views.dataUsage = document.getElementById('datausage-view');
|
||||
views.limitedDataUsage = document.getElementById('datausage-limit-view');
|
||||
views.telephony = document.getElementById('telephony-view');
|
||||
views.balance = document.getElementById('balance-view');
|
||||
|
||||
if (isBalanceEnabled) {
|
||||
// Use observers to handle not on-demand updates
|
||||
ConfigManager.observe('lastBalance', onBalance, true);
|
||||
ConfigManager.observe('waitingForBalance', onErrors, true);
|
||||
ConfigManager.observe('errors', onErrors, true);
|
||||
|
||||
LazyLoader.load('js/views/BalanceView.js', function() {
|
||||
// Subviews
|
||||
var balanceConfig = ConfigManager.configuration.balance;
|
||||
balanceView = new BalanceView(
|
||||
document.getElementById('balance-credit'),
|
||||
document.querySelector('#balance-credit + .meta'),
|
||||
balanceConfig ? balanceConfig.minimum_delay : undefined
|
||||
);
|
||||
});
|
||||
|
||||
// Open application with the proper view
|
||||
views.balance.addEventListener('click', _openBalanceTab);
|
||||
views.telephony.addEventListener('click', _openTelephonyTab);
|
||||
}
|
||||
|
||||
// Use observers to handle not on-demand updates
|
||||
ConfigManager.observe('lastCompleteDataReset', onReset, true);
|
||||
ConfigManager.observe('lastTelephonyReset', onReset, true);
|
||||
|
||||
// Update UI when visible
|
||||
document.addEventListener('visibilitychange', _onVisibilityChange);
|
||||
|
||||
// Update data usage on network activity
|
||||
window.addEventListener('hashchange', _onHashChange);
|
||||
|
||||
window.addEventListener('localized', _onLocalize);
|
||||
|
||||
// Open application with the proper view
|
||||
leftPanel.addEventListener('click', _openDataUsageTab);
|
||||
|
||||
LazyLoader.load([
|
||||
'shared/js/date_time_helper.js',
|
||||
'js/utils/formatting.js'
|
||||
], function() {
|
||||
updateUI();
|
||||
});
|
||||
|
||||
if (SimManager.isMultiSim()) {
|
||||
window.addEventListener('dataSlotChange', function _onDataSimChange() {
|
||||
// Before updating the widget, it's necessary remove the cached values
|
||||
// of costcontrol and config to force an update
|
||||
CostControl.reset();
|
||||
Widget.reset();
|
||||
ConfigManager.setConfig(null);
|
||||
SimManager.requestDataSimIcc(checkSIMStatus);
|
||||
});
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// BALANCE ACTIONS
|
||||
|
||||
// On balance update received
|
||||
function onBalance(balance, old, key, settings) {
|
||||
debug('Balance received:', balance);
|
||||
setBalanceMode('default');
|
||||
updateBalance(balance, settings.lowLimit && settings.lowLimitThreshold);
|
||||
debug('Balance updated!');
|
||||
}
|
||||
|
||||
// On balance update fail
|
||||
function onErrors(errors, old, key, settings) {
|
||||
if (!errors || !errors.BALANCE_TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
debug('Balance timeout!');
|
||||
|
||||
setBalanceMode('warning');
|
||||
errors.BALANCE_TIMEOUT = false;
|
||||
ConfigManager.setOption({errors: errors});
|
||||
}
|
||||
|
||||
// On reset telephony or data usage
|
||||
function onReset(value, old, key, settings) {
|
||||
updateUI();
|
||||
}
|
||||
|
||||
// USER INTERFACE
|
||||
// Reuse fte panel to display errors
|
||||
function _showSimError(status, updateTextOnly) {
|
||||
// Wait to l10n resources are ready
|
||||
navigator.mozL10n.ready(function showErrorStatus() {
|
||||
var widget = document.getElementById('cost-control');
|
||||
var fte = document.getElementById('fte-view');
|
||||
var leftPanel = document.getElementById('left-panel');
|
||||
var rightPanel = document.getElementById('right-panel');
|
||||
|
||||
if (!updateTextOnly) {
|
||||
widget.hidden = false;
|
||||
fte.hidden = false;
|
||||
leftPanel.hidden = true;
|
||||
rightPanel.hidden = true;
|
||||
}
|
||||
var className = 'widget-' + status;
|
||||
Common.localize(fte.querySelector('p:first-child'), className +
|
||||
'-heading');
|
||||
Common.localize(fte.querySelector('p:last-child'), className +
|
||||
'-meta');
|
||||
});
|
||||
}
|
||||
|
||||
function setupFte(provider, mode) {
|
||||
|
||||
widget.hidden = false;
|
||||
fte.hidden = false;
|
||||
leftPanel.hidden = true;
|
||||
rightPanel.hidden = true;
|
||||
|
||||
fte.addEventListener('click', function launchFte(evt) {
|
||||
fte.removeEventListener('click', launchFte);
|
||||
evt.stopImmediatePropagation();
|
||||
|
||||
activity = new MozActivity({ name: 'costcontrol/balance' });
|
||||
});
|
||||
|
||||
var keyLookup = {
|
||||
PREPAID: 'widget-authed-sim2',
|
||||
POSTPAID: 'widget-authed-sim2',
|
||||
DATA_USAGE_ONLY: 'widget-nonauthed-sim2'
|
||||
};
|
||||
var simKey = keyLookup[mode];
|
||||
|
||||
Common.localize(
|
||||
fte.querySelector('p:first-child'),
|
||||
simKey + '-heading',
|
||||
{provider: provider}
|
||||
);
|
||||
Common.localize(fte.querySelector('p:last-child'), simKey +
|
||||
'-meta');
|
||||
}
|
||||
|
||||
var hashMark = 0;
|
||||
function updateUI(updateOnlyDataUsage) {
|
||||
|
||||
ConfigManager.requestAll(function _onInfo(configuration, settings) {
|
||||
var mode = ConfigManager.getApplicationMode();
|
||||
debug('Widget UI mode:', mode);
|
||||
|
||||
var isPrepaid = (mode === 'PREPAID');
|
||||
var isDataUsageOnly = (mode === 'DATA_USAGE_ONLY');
|
||||
|
||||
// Show fte mode widget
|
||||
if (settings.fte) {
|
||||
setupFte(configuration.provider, mode);
|
||||
debug('Widget in FTE mode');
|
||||
return;
|
||||
}
|
||||
|
||||
// Layout
|
||||
widget.hidden = true;
|
||||
fte.hidden = true;
|
||||
leftPanel.hidden = false;
|
||||
rightPanel.hidden = false;
|
||||
|
||||
var isLimited = settings.dataLimit;
|
||||
views.dataUsage.hidden = isLimited;
|
||||
views.limitedDataUsage.hidden = !isLimited;
|
||||
|
||||
// Always data usage
|
||||
rightPanel.hidden = isDataUsageOnly;
|
||||
|
||||
// And the other view if applies...
|
||||
if (isDataUsageOnly) {
|
||||
widget.classList.add('full');
|
||||
widget.hidden = false;
|
||||
|
||||
} else {
|
||||
widget.classList.remove('full');
|
||||
views.balance.hidden = !isPrepaid;
|
||||
views.telephony.hidden = isPrepaid;
|
||||
widget.hidden = false;
|
||||
}
|
||||
|
||||
// Content for data statistics
|
||||
var requestObj = { type: 'datausage' };
|
||||
costcontrol.request(requestObj, function _onDataStatistics(result) {
|
||||
debug(result);
|
||||
var stats = result.data;
|
||||
var data = Formatting.roundData(stats.mobile.total);
|
||||
if (isLimited) {
|
||||
|
||||
// UI elements
|
||||
var dataLimit = views.limitedDataUsage.querySelector('#data-limit');
|
||||
var dataAvailable =
|
||||
views.limitedDataUsage.querySelector('#data-available');
|
||||
|
||||
var current = stats.mobile.total;
|
||||
var limit = Common.getDataLimit(settings);
|
||||
debug(limit);
|
||||
|
||||
// State
|
||||
views.limitedDataUsage.classList.remove('nearby-limit');
|
||||
views.limitedDataUsage.classList.remove('reached-limit');
|
||||
|
||||
// Limit trespassed
|
||||
var limitTrespassed = (current > limit);
|
||||
if (limitTrespassed) {
|
||||
views.limitedDataUsage.classList.add('reached-limit');
|
||||
|
||||
// Warning percentage of the limit reached
|
||||
} else if (current >= limit * Common.DATA_USAGE_WARNING) {
|
||||
views.limitedDataUsage.classList.add('nearby-limit');
|
||||
}
|
||||
|
||||
// Texts
|
||||
var limitText = Formatting.roundData(limit);
|
||||
limitText = _('magnitude', {
|
||||
value: limitText[0],
|
||||
unit: limitText[1]
|
||||
});
|
||||
|
||||
navigator.mozL10n.setAttributes(dataLimit, 'data-limit',
|
||||
{ value: limitText });
|
||||
|
||||
var rawValue = Math.abs(limit - current);
|
||||
var text = Formatting.roundData(rawValue);
|
||||
|
||||
if (!limitTrespassed) {
|
||||
navigator.mozL10n.setAttributes(dataAvailable, 'data-available2',
|
||||
{ value: parseFloat(text[0]), unit: text[1] });
|
||||
} else {
|
||||
navigator.mozL10n.setAttributes(dataAvailable, 'over-limit',
|
||||
{ value: parseFloat(text[0]), unit: text[1] });
|
||||
}
|
||||
} else {
|
||||
// Texts
|
||||
document.getElementById('mobile-usage-value').textContent =
|
||||
_('magnitude', { value: data[0], unit: data[1] });
|
||||
var meta = views.dataUsage.querySelector('.meta');
|
||||
meta.innerHTML = '';
|
||||
meta.appendChild(Formatting.formatTimeHTML(stats.timestamp));
|
||||
}
|
||||
// inform driver in system we are finished to update the widget
|
||||
hashMark = 1 - hashMark; // toogle between 0 and 1
|
||||
window.location.hash = '#updateDone#' + hashMark;
|
||||
});
|
||||
|
||||
// Content for balance or telephony
|
||||
if (!isDataUsageOnly && !updateOnlyDataUsage) {
|
||||
if (mode === 'PREPAID') {
|
||||
updateBalance(settings.lastBalance,
|
||||
settings.lowLimit && settings.lowLimitThreshold);
|
||||
|
||||
requestObj = { type: 'balance' };
|
||||
costcontrol.request(requestObj, function _onRequest(result) {
|
||||
debug(result);
|
||||
var status = result.status;
|
||||
var balance = result.data;
|
||||
setBalanceMode(status === 'error' ? 'warning' : 'updating');
|
||||
updateBalance(balance,
|
||||
settings.lowLimit && settings.lowLimitThreshold);
|
||||
});
|
||||
|
||||
} else if (mode === 'POSTPAID') {
|
||||
requestObj = { type: 'telephony' };
|
||||
costcontrol.request(requestObj, function _onRequest(result) {
|
||||
var dataActivity = result.data;
|
||||
document.getElementById('telephony-calltime').textContent =
|
||||
_('magnitude', {
|
||||
value: Formatting.computeTelephonyMinutes(dataActivity),
|
||||
unit: 'min'
|
||||
}
|
||||
);
|
||||
document.getElementById('telephony-smscount').textContent =
|
||||
_('magnitude', {
|
||||
value: dataActivity.smscount,
|
||||
unit: 'SMS'
|
||||
}
|
||||
);
|
||||
var meta = views.telephony.querySelector('.meta');
|
||||
meta.innerHTML = '';
|
||||
meta.appendChild(Formatting.formatTimeHTML(dataActivity.timestamp));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update the balance in balance view
|
||||
function updateBalance(balance, limit) {
|
||||
|
||||
if (!balance) {
|
||||
debug('Balance not available');
|
||||
balanceView.update();
|
||||
return;
|
||||
}
|
||||
|
||||
var isUpdating = views.balance.classList.contains('updating');
|
||||
balanceView.update(balance, isUpdating);
|
||||
|
||||
// Limits: reaching zero / low limit
|
||||
if (balance.balance === 0) {
|
||||
views.balance.classList.add('no-credit');
|
||||
|
||||
} else {
|
||||
views.balance.classList.remove('no-credit');
|
||||
|
||||
if (limit && balance.balance < limit) {
|
||||
views.balance.classList.add('low-credit');
|
||||
} else {
|
||||
views.balance.classList.remove('low-credit');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set warning / updating modes
|
||||
var lastBalanceMode;
|
||||
function setBalanceMode(mode) {
|
||||
if (mode === lastBalanceMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastBalanceMode = mode;
|
||||
views.balance.classList.remove('updating');
|
||||
views.balance.classList.remove('warning');
|
||||
|
||||
if (mode === 'warning') {
|
||||
views.balance.classList.add('warning');
|
||||
}
|
||||
|
||||
if (mode === 'updating') {
|
||||
views.balance.classList.add('updating');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('airplaneModeDisabled',
|
||||
function _onAirplanemodeDisabled(evt) {
|
||||
if (evt.detail && evt.detail.serviceId === 'data') {
|
||||
SimManager.requestDataSimIcc(checkSIMStatus);
|
||||
}
|
||||
});
|
||||
|
||||
function initWidget() {
|
||||
var isWaitingForIcc = false;
|
||||
function waitForIccAndCheckSim() {
|
||||
if (!isWaitingForIcc) {
|
||||
var iccManager = window.navigator.mozIccManager;
|
||||
iccManager.addEventListener('iccdetected',
|
||||
function _oniccdetected() {
|
||||
isWaitingForIcc = false;
|
||||
iccManager.removeEventListener('iccdetected', _oniccdetected);
|
||||
if (AirplaneModeHelper.getStatus() === 'disabled') {
|
||||
SimManager.requestDataSimIcc(checkSIMStatus);
|
||||
}
|
||||
}
|
||||
);
|
||||
isWaitingForIcc = true;
|
||||
}
|
||||
}
|
||||
SimManager.requestDataSimIcc(checkSIMStatus, function _errorNoSim() {
|
||||
AirplaneModeHelper.ready(function() {
|
||||
waitForIccAndCheckSim();
|
||||
var errorMessageId = (AirplaneModeHelper.getStatus() === 'enabled') ?
|
||||
'airplane-mode' : 'no-sim3';
|
||||
console.warn('Error when trying to get the ICC ID');
|
||||
Widget.showSimError(errorMessageId);
|
||||
});
|
||||
});
|
||||
AirplaneModeHelper.addEventListener('statechange',
|
||||
function _onAirplaneModeChange(state) {
|
||||
if (state === 'enabled') {
|
||||
Widget.showSimError('airplane-mode');
|
||||
} else if (isWaitingForIcc) {
|
||||
var updateTextOnly = true;
|
||||
Widget.showSimError('no-sim3', updateTextOnly);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
var SCRIPTS_NEEDED = [
|
||||
'js/common.js',
|
||||
'js/utils/toolkit.js'
|
||||
];
|
||||
// Check if the mandatory APIs to work exist.
|
||||
if (!window.navigator.mozMobileConnections ||
|
||||
!window.navigator.mozIccManager ||
|
||||
!window.navigator.mozNetworkStats) {
|
||||
LazyLoader.load(SCRIPTS_NEEDED, function _showError() {
|
||||
Widget.showSimError('no-sim3');
|
||||
});
|
||||
} else {
|
||||
SCRIPTS_NEEDED = [
|
||||
'js/sim_manager.js',
|
||||
'js/common.js',
|
||||
'js/utils/toolkit.js',
|
||||
'js/utils/debug.js'
|
||||
];
|
||||
LazyLoader.load(SCRIPTS_NEEDED, initWidget);
|
||||
}
|
||||
},
|
||||
|
||||
showSimError: _showSimError,
|
||||
|
||||
reset: function() {
|
||||
document.removeEventListener('visibilitychange', _onVisibilityChange);
|
||||
window.removeEventListener('hashchange', _onHashChange);
|
||||
window.removeEventListener('localized', _onLocalize);
|
||||
leftPanel.removeEventListener('click', _openDataUsageTab);
|
||||
if (views.balance) {
|
||||
views.balance.removeEventListener('click', _openBalanceTab);
|
||||
}
|
||||
if (views.telephony) {
|
||||
views.telephony.removeEventListener('click', _openTelephonyTab);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
Widget.init();
|
||||
18
gaia/apps/costcontrol/jsdoc.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "costcontrol",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["apps/costcontrol/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/costcontrol"
|
||||
}
|
||||
}
|
||||
229
gaia/apps/costcontrol/locales/costcontrol.en-US.properties
Executable file
|
|
@ -0,0 +1,229 @@
|
|||
# Usage
|
||||
# Note: "Usage" is the application name
|
||||
topup-confirmation-title2 = Usage
|
||||
topup-confirmation-message = Top-up confirmed
|
||||
topup-incorrectcode-title2 = Usage
|
||||
topup-incorrectcode-message3 = Incorrect top-up code entered.
|
||||
|
||||
updating = Updating
|
||||
updating-ellipsis = Updating…
|
||||
yesterday = Yesterday
|
||||
today = Today
|
||||
never = Never
|
||||
|
||||
magnitude = {{value}} {{unit}}
|
||||
currency = {{currency}}{{value}}
|
||||
|
||||
# FTE Widget
|
||||
widget = Usage Widget
|
||||
widget-authed-sim2-heading = New {{provider}} SIM card inserted
|
||||
widget-authed-sim2-meta = Tap to track your phone and internet usage.
|
||||
widget-nonauthed-sim2-heading = New SIM card inserted
|
||||
widget-nonauthed-sim2-meta = Tap to track your internet usage.
|
||||
widget-airplane-mode-heading = Airplane Mode
|
||||
widget-airplane-mode-meta = Please, turn off airplane mode to enable Usage.
|
||||
widget-no-sim3-heading = No SIM card
|
||||
widget-no-sim3-meta = Insert a SIM card to enable Usage.
|
||||
widget-sim2-locked-heading = Locked SIM card
|
||||
widget-sim2-locked-meta = Unlock your SIM card to enable Usage.
|
||||
widget-unknown-heading = Error
|
||||
widget-unknown-meta = Please, make sure airplane mode is off.
|
||||
|
||||
# FTE Welcome
|
||||
fte = Usage First Time Experience
|
||||
fte-authed-sim = Save money by keeping track of your phone and internet usage.
|
||||
fte-nonauthed-sim = Save money by keeping track of your internet usage.
|
||||
fte-no-sim = Get clear information about how much internet you’re using.
|
||||
# Here Usage is the name of the application
|
||||
fte-sim-info2 = Information shown on Usage comes directly from this device. Check with your mobile service provider for detailed account information.
|
||||
|
||||
# FTE steps
|
||||
fte-plantype-title = Type of Contract
|
||||
fte-plantype-hint = Select the type of contract you have with your mobile service provider.
|
||||
|
||||
fte-prepaid2-title = Low Balance Alert
|
||||
fte-prepaid2-hint = Set an alert to avoid running out of credit.
|
||||
|
||||
fte-prepaid3-title = Data Report and Alert
|
||||
fte-prepaid3-hint = Receive reports about your Internet usage and set an alert to avoid using too much.
|
||||
|
||||
fte-postpaid2-title = Phone and Data Report
|
||||
fte-postpaid2-hint = Receive reports about your phone and Internet usage.
|
||||
|
||||
fte-postpaid3-title = Data Alert
|
||||
fte-postpaid3-hint = Set an alert to avoid using too much data.
|
||||
|
||||
fte-onlydata2-title = Data Report
|
||||
fte-onlydata2-hint = Receive reports about your internet usage.
|
||||
|
||||
fte-onlydata3-title = Data Alert
|
||||
fte-onlydata3-hint = Set an alert to avoid using too much data.
|
||||
|
||||
# In the HTML
|
||||
phone-activity = Phone Activity
|
||||
|
||||
balance = Balance
|
||||
on-roaming-message2 = Roaming: no automatic balance update. You might be charged for updating manually.
|
||||
balance-error-message2 = Balance didn’t update correctly. Try updating manually.
|
||||
airplane-mode-error-message2 = Turn off airplane mode to update your balance or top up.
|
||||
|
||||
disable-airplane-mode = Do you want to disable Airplane mode to update or top up?
|
||||
no-coverage-error-message2 = You need phone coverage to update your balance or top up.
|
||||
|
||||
top-up = Top Up
|
||||
enter-top-up-code = Enter the top-up code
|
||||
top-up-code-explanation2 = Find it on the scratch card or directly in your receipt.
|
||||
wait-for-confirmation = You will receive a confirmation message
|
||||
incorrect-code2 = Make sure the code is correct and try again.
|
||||
topup-error2 = There was a problem sending the code. Please, try again.
|
||||
|
||||
application-error-title = Service unavailable
|
||||
application-error-message = Contact your company representative
|
||||
service-unavailable-title = Service unavailable
|
||||
service-unavailable-message = Neither update nor top-up is possible at the moment. Please try later.
|
||||
|
||||
data-usage = Data usage
|
||||
update-button = Update
|
||||
topup-button2 = Use top-up code
|
||||
topup-ussd-button2 = Open top-up menu
|
||||
ok = OK
|
||||
|
||||
top-up-in-progress2 = Your top-up is being processed.
|
||||
top-up-timed-out2 = Update your balance to confirm. If balance is unchanged, try topping up again.
|
||||
on-topup-not-confirmed-title = Top-up not confirmed
|
||||
on-topup-not-confirmed-message = Try updating your balance or retry top-up.
|
||||
send = send
|
||||
plantype-setup-title = My phone and Internet plan is
|
||||
prepaid = Prepaid
|
||||
postpaid = Postpaid
|
||||
phoneactivity = Phone activity
|
||||
sms-sent = SMS sent
|
||||
call-made = Calls made
|
||||
settings = Settings
|
||||
# LOCALIZATION NOTE (settings-button): label for settings button. The following
|
||||
# string is spoken by screen readers and not shown on the screen.
|
||||
settings-button.ariaLabel = Settings
|
||||
low-balance-alert = Low balance alert
|
||||
low-balance-explanation2 = When balance is below
|
||||
phone-and-internet-data-report = Phone and Internet data report
|
||||
internet-data-report = Internet data report
|
||||
starting-on = Starting on
|
||||
cancel = Cancel
|
||||
monthly = Monthly
|
||||
weekly = Weekly
|
||||
monday = Monday
|
||||
tuesday = Tuesday
|
||||
wednesday = Wednesday
|
||||
thursday = Thursday
|
||||
friday = Friday
|
||||
saturday = Saturday
|
||||
sunday = Sunday
|
||||
billing-cycle-ends = Billing cycle ends
|
||||
tracking-will-reset = Tracking will reset on this date
|
||||
yes = Yes
|
||||
done = Done
|
||||
confirmation = Confirmation
|
||||
reset-wifi-confirmation-warning = Your current Wi-Fi data usage is {{wifiData}}. Clear it all?
|
||||
reset-mobile-confirmation-warning = Your current mobile data usage is {{mobileData}}. Clear it all?
|
||||
reset-all-confirmation-warning = Your current data usages are {{mobileData}} on mobile, {{wifiData}} on Wi-Fi. Clear it all?
|
||||
reset-telephony-confirmation-warning = All current data will be deleted. Are you sure you want to reset?
|
||||
reset = Reset
|
||||
reset-wifi = Reset Wi-Fi usage
|
||||
reset-mobile = Reset mobile usage
|
||||
reset-all = Reset mobile & Wi-Fi data
|
||||
not-set = Not set
|
||||
used = Used
|
||||
limit-passed = Limit passed
|
||||
limit = Limit
|
||||
data-limit-header = Data usage limit
|
||||
data-limit-alert = Data use alert
|
||||
data-limit-value2 = When use is above
|
||||
data-limit-dialog-title2 = Enter a mobile data limit alert
|
||||
data-limit-dialog-explanation2 = Connection speeds reduce after the limit is reached. Check with your data provider to set an appropriate alert level.
|
||||
|
||||
# LOCALIZATION NOTE (data-limit-input-label): A label for an input field for
|
||||
# data limit. The following string is spoken by screen readers and not shown on
|
||||
# the screen.
|
||||
data-limit-input-label.ariaLabel=Data limit
|
||||
|
||||
# LOCALIZATION NOTE (wifi-usage, mobile-usage): these strings are displayed in
|
||||
# the main window
|
||||
wifi-usage = Wi-Fi usage
|
||||
mobile-usage = Mobile usage
|
||||
|
||||
# LOCALIZATION NOTE (wifi-usage-settings, mobile-usage-settings): these strings
|
||||
# are displayed in the Settings section
|
||||
wifi-usage-settings = Wi-Fi usage
|
||||
mobile-usage-settings = Mobile usage
|
||||
|
||||
im-prepaid = I’m Pre-paid
|
||||
im-postpaid = I’m Post-paid
|
||||
reset-data-usage = Reset data usage
|
||||
reset-report = Reset report
|
||||
next = Next
|
||||
back = Back
|
||||
lets-go = Let’s go!
|
||||
alert = Alert
|
||||
alert-limit = Alert Limit
|
||||
fte-welcome-title = Welcome
|
||||
data-limit-notification-title2 = {{limit}} of data used
|
||||
data-limit-notification-text2 = You’ve reached the limit you selected
|
||||
low-balance-notification-title = Low balance
|
||||
low-balance-notification-text = {{remaining}} remaining phone credit
|
||||
zero-balance-message = Balance has reached zero, no phone credit remaining.
|
||||
not-available = Not available
|
||||
usage = Usage
|
||||
internet = Internet
|
||||
phone = Phone
|
||||
loading-interface-data = Loading interface data. Please retry in a while.
|
||||
|
||||
# value in the following strings is an amount of data (100 MB, 2 GB...)
|
||||
data-limit = Data Limit ({{value}})
|
||||
data-available2={[ plural(value) ]}
|
||||
data-available2[zero] = {{value}} {{unit}} available
|
||||
data-available2[one] = {{value}} {{unit}} available
|
||||
data-available2[two] = {{value}} {{unit}} available
|
||||
data-available2[few] = {{value}} {{unit}} available
|
||||
data-available2[many] = {{value}} {{unit}} available
|
||||
data-available2[other] = {{value}} {{unit}} available
|
||||
over-limit={[ plural(value) ]}
|
||||
over-limit[one] = {{value}} {{unit}} over limit
|
||||
over-limit[two] = {{value}} {{unit}} over limit
|
||||
over-limit[few] = {{value}} {{unit}} over limit
|
||||
over-limit[many] = {{value}} {{unit}} over limit
|
||||
over-limit[other] = {{value}} {{unit}} over limit
|
||||
|
||||
verbose-chart-date-format = {{em-month}} {{monthday-number}}
|
||||
|
||||
# Specif format for CC, string in format Today|Yesterday|<WeekDay>, hh:mm
|
||||
day-hour-format = {{day}}, {{time}}
|
||||
|
||||
# Localized units
|
||||
B = B
|
||||
KB = KB
|
||||
MB = MB
|
||||
GB = GB
|
||||
# LOCALIZATION NOTE (unit-*): For switch unit button a label describing the
|
||||
# unit. The following strings are spoken by screen readers and not shown on the
|
||||
# screen.
|
||||
unit-MB.ariaLabel = MB
|
||||
unit-GB.ariaLabel = GB
|
||||
|
||||
# App Usage
|
||||
app-usage-list-header = Mobile usage per app
|
||||
app-usage-no-data = You have not used any mobile data yet. Usage for each application will show up here when you do.
|
||||
data-used-since = {{amount}} of data used since {{start-date}}
|
||||
data-used-this-week = {{amount}} of data used this week
|
||||
data-used-this-month = {{amount}} of data used this month
|
||||
data-usage-other-apps = Others
|
||||
data-usage-browser-app = Browser
|
||||
|
||||
# LOCALIZATION NOTE: For charts the following labels are used to describe
|
||||
# various layers drawn. The following strings are spoken by screen readers and
|
||||
# not shown on the screen.
|
||||
limit-layer-MB.ariaLabel = Limit of {{value}} MB
|
||||
limit-layer-GB.ariaLabel = Limit of {{value}} GB
|
||||
warning-layer.ariaLabel = {{value}} left
|
||||
limit-exceeded-layer.ariaLabel = Exceeded by {{value}}
|
||||
today-layer.ariaLabel = Today is {{today}}
|
||||
axis-layer.ariaLabel = From {{from}} to {{to}}
|
||||
90
gaia/apps/costcontrol/manifest.webapp
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
"name": "Usage",
|
||||
"description": "Usage application to see credit and data usage statistics",
|
||||
"type": "certified",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"permissions": {
|
||||
"themeable": {},
|
||||
"sms": {},
|
||||
"mobileconnection": {},
|
||||
"desktop-notification": {},
|
||||
"settings": {
|
||||
"access": "readonly"
|
||||
},
|
||||
"networkstats-manage": {},
|
||||
"alarms": {},
|
||||
"telephony": {},
|
||||
"audio-channel-notification": {},
|
||||
"webapps-manage": {},
|
||||
"device-storage:apps": {
|
||||
"access": "readonly"
|
||||
},
|
||||
"systemXHR": {},
|
||||
"moz-extremely-unstable-and-will-change-webcomponents": {}
|
||||
},
|
||||
"activities": {
|
||||
"costcontrol/balance": {
|
||||
"filters": {},
|
||||
"disposition": "window"
|
||||
},
|
||||
"costcontrol/telephony": {
|
||||
"filters": {},
|
||||
"disposition": "window"
|
||||
},
|
||||
"costcontrol/data_usage": {
|
||||
"filters": {},
|
||||
"disposition": "window"
|
||||
}
|
||||
},
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "Usage",
|
||||
"description": "Usage application to see credit and data usage statistics"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Usage",
|
||||
"description": "Usage application to see credit and data usage statistics"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Suivi conso",
|
||||
"description": "L’application Suivi conso affiche le crédit et les statistiques d’utilisation des données"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "Usage",
|
||||
"description": "Usage application to see credit and data usage statistics"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"84": "/style/icons/costcontrol_84.png",
|
||||
"126": "/style/icons/costcontrol_126.png",
|
||||
"142": "/style/icons/costcontrol_142.png",
|
||||
"189": "/style/icons/costcontrol_189.png",
|
||||
"284": "/style/icons/costcontrol_284.png"
|
||||
},
|
||||
"orientation": "default",
|
||||
"messages": [
|
||||
{
|
||||
"sms-received": "/message_handler.html"
|
||||
},
|
||||
{
|
||||
"alarm": "/message_handler.html"
|
||||
},
|
||||
{
|
||||
"sms-sent": "/message_handler.html"
|
||||
},
|
||||
{
|
||||
"networkstats-alarm": "/message_handler.html"
|
||||
},
|
||||
{
|
||||
"telephony-call-ended": "/message_handler.html"
|
||||
},
|
||||
{
|
||||
"notification": "/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
32
gaia/apps/costcontrol/message_handler.html
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>Message handler</title>
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/costcontrol.{locale}.properties">
|
||||
<!-- Firefox Desktop shims -->
|
||||
<script defer src="js/desktop-only/navigator_moz_icc_manager.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_network_stats.js"></script>
|
||||
<script defer src="js/desktop-only/navigator_moz_alarms.js"></script>
|
||||
|
||||
<script type="text/javascript" src="shared/js/l10n.js"></script>
|
||||
<script type="text/javascript" src="shared/js/async_storage.js"></script>
|
||||
<script type="text/javascript" src="shared/js/notification_helper.js"></script>
|
||||
<script type="text/javascript" src="shared/js/settings_listener.js"></script>
|
||||
<script type="text/javascript" src="shared/js/lazy_loader.js"></script>
|
||||
<script type="text/javascript" src="js/utils/debug.js"></script>
|
||||
<script type="text/javascript" src="js/utils/toolkit.js"></script>
|
||||
<script type="text/javascript" src="js/utils/formatting.js"></script>
|
||||
<script type="text/javascript" src="js/sim_manager.js"></script>
|
||||
<script type="text/javascript" src="js/common.js"></script>
|
||||
<script type="text/javascript" defer src="js/settings/networkUsageAlarm.js"></script>
|
||||
<script type="text/javascript" src="js/config/config_manager.js"></script>
|
||||
<script type="text/javascript" src="js/costcontrol.js"></script>
|
||||
<script type="text/javascript" src="js/message_handler.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
10
gaia/apps/costcontrol/package.json
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"webide": {
|
||||
"prepackage": {
|
||||
"command": "make",
|
||||
"env": ["APP=costcontrol"],
|
||||
"cwd": "../.."
|
||||
},
|
||||
"packageDir": "../../build_stage/costcontrol/"
|
||||
}
|
||||
}
|
||||
259
gaia/apps/costcontrol/settings.html
Executable file
|
|
@ -0,0 +1,259 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-color" content="#eeeeee">
|
||||
<title>Usage Settings</title>
|
||||
|
||||
<!-- Building blocks -->
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/buttons.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/input_areas.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/confirm.css">
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/layout.css">
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<link rel="stylesheet" type="text/css" href="/shared/elements/gaia-icons/gaia-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css">
|
||||
|
||||
|
||||
<!-- App and Views -->
|
||||
<link rel="stylesheet" type="text/css" href="style/app.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/settings.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/datalimits.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/views/timestamp-highlights.css">
|
||||
|
||||
<!-- Firefox Desktop shims -->
|
||||
<script defer src="js/desktop-only/navigator_moz_settings.js"></script>
|
||||
|
||||
<!-- Localization -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/costcontrol.{locale}.properties">
|
||||
<link rel="localization" href="/shared/locales/date/date.{locale}.properties">
|
||||
<link rel="localization" href="/shared/elements/gaia_menu/locales/gaia_menu.{locale}.properties">
|
||||
<script type="text/javascript" defer src="shared/js/l10n.js"></script>
|
||||
<script type="text/javascript" defer src="shared/js/lazy_loader.js"></script>
|
||||
<!-- TODO: Lazyly loaded, remove when mozHour12 is part of the platform
|
||||
<script type="text/javascript" defer src="shared/js/date_time_helper.js"></script>
|
||||
-->
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<script defer src="/shared/elements/config.js"></script>
|
||||
<script defer src="/shared/js/component_utils.js"></script>
|
||||
<script defer src="/shared/elements/gaia-header/dist/gaia-header.js"></script>
|
||||
<script defer src="/shared/elements/gaia_menu/script.js"></script>
|
||||
<script defer src="shared/elements/gaia_subheader/script.js"></script>
|
||||
<script defer src="/shared/elements/gaia_select/script.js"></script>
|
||||
<script defer src="/shared/elements/gaia_switch/script.js"></script>
|
||||
|
||||
<!-- Specific scripts -->
|
||||
<script type="text/javascript" defer src="js/settings/settings.js"></script>
|
||||
</head>
|
||||
<body role="application" class="theme-settings">
|
||||
<section role="region" class="window skin-organic">
|
||||
<!-- Settings view -->
|
||||
<section role="region" id="settings-view" class="view">
|
||||
<gaia-header>
|
||||
<h1 data-l10n-id="settings"></h1>
|
||||
<button id="close-settings" data-l10n-id="done">done</button>
|
||||
</gaia-header>
|
||||
|
||||
<!-- Plantype -->
|
||||
<section class="view-scroll gaia-list" role="region">
|
||||
<ul class="settings" id="plantype-settings" hidden>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<select id="widget-plantype" class="settings-option" data-option="plantype">
|
||||
<option value="prepaid" data-l10n-id="prepaid">Prepaid</option>
|
||||
<option value="postpaid" data-l10n-id="postpaid">Postpaid</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Phone activity -->
|
||||
<gaia-subheader id="phone-activity-settings" hidden aria-level="2" role="heading" skin="organic">
|
||||
<span data-l10n-id="phone-activity">Phone Activity</span>
|
||||
</gaia-subheader>
|
||||
<ul class="settings" hidden>
|
||||
<li class="vbox">
|
||||
<p>
|
||||
<span id="calltime"></span> <span id="smscount"></span>
|
||||
</p>
|
||||
<p id="telephony-timestamp"></p>
|
||||
<button data-l10n-id="reset" id="reset-telephony">Reset</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Balance alerts -->
|
||||
<gaia-subheader id="balance-settings" hidden aria-level="2" role="heading" skin="organic">
|
||||
<span data-l10n-id="balance">balance</span>
|
||||
</gaia-subheader>
|
||||
<ul class="settings" hidden>
|
||||
<li class="vbox">
|
||||
<p id="balance"></p>
|
||||
<p class="meta"></p>
|
||||
</li>
|
||||
<li class="active">
|
||||
<gaia-switch class="settings-option" data-option="lowLimit" id="low-limit">
|
||||
<label data-l10n-id="low-balance-alert"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<label class="end" for="low-limit-input" data-l10n-id="low-balance-explanation2">When balance is below</label>
|
||||
<div class="low-limit-container hbox middle">
|
||||
<input class="settings-option" data-option="lowLimitThreshold" data-disable-on="lowLimit=false" id="low-limit-input" type="number" step="any" />
|
||||
<span id="settings-low-limit-currency" dir="auto"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Data usage limits -->
|
||||
<gaia-subheader id="data-usage-settings" aria-level="2" role="heading" skin="organic">
|
||||
<span data-l10n-id="data-usage">Data Usage</span>
|
||||
</gaia-subheader>
|
||||
<ul class="settings">
|
||||
<li class="vbox" role="presentation">
|
||||
<p id="mobile-data-usage" role="listitem">
|
||||
<span class="usage"></span> <span data-l10n-id="mobile-usage-settings">Mobile usage</span>
|
||||
</p>
|
||||
<p id="wifi-data-usage" role="listitem">
|
||||
<span class="usage"></span> <span data-l10n-id="wifi-usage-settings">Wi-Fi usage</span>
|
||||
</p>
|
||||
<p class="meta" role="listitem"></p>
|
||||
<button data-l10n-id="reset" id="reset-data-usage">Reset</button>
|
||||
</li>
|
||||
<li class="active">
|
||||
<gaia-switch class="settings-option" data-option="dataLimit">
|
||||
<label data-l10n-id="data-limit-alert"></label>
|
||||
</gaia-switch>
|
||||
</li>
|
||||
<li class="vbox not-last">
|
||||
<p data-l10n-id="data-limit-value2">When use is above</p>
|
||||
<button class="settings-option icon icon-dialog" data-widget-type="data-limit" data-disable-on="dataLimit=false">
|
||||
<span class="tag"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tracking period -->
|
||||
<gaia-subheader id="phone-internet-settings" hidden aria-level="2" role="heading" skin="organic">
|
||||
<span data-l10n-id="internet-data-report">Internet data report</span>
|
||||
</gaia-subheader>
|
||||
<ul class="settings">
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="reset-report">Reset report</label>
|
||||
<select class="settings-option" data-option="trackingPeriod">
|
||||
<option value="monthly" data-l10n-id="monthly">Monthly</option>
|
||||
<option value="weekly" data-l10n-id="weekly">Weekly</option>
|
||||
<option value="never" data-l10n-id="never">Never</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select id="select-weekday" class="settings-option" data-option="resetTime" data-hide-on="trackingPeriod!=weekly" data-disable-on="trackingPeriod!=weekly">
|
||||
<option class="monday" value="1" data-l10n-id="monday">Monday</option>
|
||||
<option value="2" data-l10n-id="tuesday">Tuesday</option>
|
||||
<option value="3" data-l10n-id="wednesday">Wednesday</option>
|
||||
<option value="4" data-l10n-id="thursday">Thursday</option>
|
||||
<option value="5" data-l10n-id="friday">Friday</option>
|
||||
<option value="6" data-l10n-id="saturday">Saturday</option>
|
||||
<option class="sunday" value="0" data-l10n-id="sunday">Sunday</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
<li class="vbox">
|
||||
<gaia-select>
|
||||
<label data-l10n-id="starting-on">Starting on</label>
|
||||
<select class="settings-option icon icon-dialog" data-option="resetTime" data-hide-on="trackingPeriod!=monthly" data-disable-on="trackingPeriod!=monthly">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
<option value="16">16</option>
|
||||
<option value="17">17</option>
|
||||
<option value="18">18</option>
|
||||
<option value="19">19</option>
|
||||
<option value="20">20</option>
|
||||
<option value="21">21</option>
|
||||
<option value="22">22</option>
|
||||
<option value="23">23</option>
|
||||
<option value="24">24</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
<option value="28">28</option>
|
||||
<option value="29">29</option>
|
||||
<option value="30">30</option>
|
||||
<option value="31">31</option>
|
||||
</select>
|
||||
</gaia-select>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- Data usage limits -->
|
||||
<section aria-labelledby="limit-dialog-heading" role="dialog" id="data-limit-dialog" class="view" data-viewport="bottom">
|
||||
<gaia-header id="limit-dialog-header" action="close">
|
||||
<h1 id="limit-dialog-heading" data-l10n-id="data-limit-header"></h1>
|
||||
<button id="data-usage-done-button" data-l10n-id="done" class="recommend">Done</button>
|
||||
</gaia-header>
|
||||
<section class="content">
|
||||
<div class="info">
|
||||
<h2 data-l10n-id="data-limit-dialog-title2">Enter a mobile data limit alert</h2>
|
||||
<p data-l10n-id="data-limit-dialog-explanation2">
|
||||
Connection speeds reduce after the limit is reached. Check with your data provider to set an appropriate alert level.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<form>
|
||||
<ul class="buttons-list" role="presentation">
|
||||
<li role="presentation">
|
||||
<label for="data-limit-input" data-l10n-id="data-limit-input-label">
|
||||
<input id="data-limit-input" type="text" x-inputmode="numeric" maxlength="6" />
|
||||
</label>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button type="button" class="icon icon-dialog switch-unit-button">
|
||||
<span class="tag"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Reset confirmation -->
|
||||
<gaia-menu class="view" data-viewport="ethereal" id="reset-data-dialog">
|
||||
<header data-l10n-id="reset-data-usage">Reset Data usage</header>
|
||||
</gaia-menu>
|
||||
|
||||
<!-- Reset confirmation -->
|
||||
<form role="dialog" class="view" data-type="confirm" data-viewport="ethereal" id="reset-dialog">
|
||||
<section>
|
||||
<h1 data-l10n-id="confirmation">Confirmation</h1>
|
||||
<p data-l10n-id="reset-telephony-confirmation-warning">All current data will be deleted. Are you sure you want to reset?</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button data-l10n-id="cancel" class="close-reset-dialog">Cancel</button>
|
||||
<button data-l10n-id="yes" class="danger">Yes</button>
|
||||
</menu>
|
||||
</form>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
243
gaia/apps/costcontrol/style/app.css
Executable file
|
|
@ -0,0 +1,243 @@
|
|||
/* GENERAL */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a::-moz-focus-inner {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10px;
|
||||
color: #000;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex; /* Fix Bug 976665 */
|
||||
}
|
||||
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
input.error {
|
||||
background-color: rgba(219, 113, 95, 0.2) !important;
|
||||
border-color: #db715f !important;
|
||||
color: #c3503c;
|
||||
}
|
||||
|
||||
/* WINDOW & VIEWS */
|
||||
.window, .view {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.window {
|
||||
opacity: 1.0;
|
||||
background: #f4f4f4;
|
||||
transition: opacity ease 0.2s;
|
||||
}
|
||||
|
||||
.splash {
|
||||
background-color: black;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-image: url("/style/icons/cost-control.png");
|
||||
background-size: 6rem;
|
||||
}
|
||||
|
||||
|
||||
.window.non-ready {
|
||||
opacity: 0.0;
|
||||
}
|
||||
|
||||
/* Hide/show tab panel when other views show up */
|
||||
|
||||
#tabpanel {
|
||||
transition: visibility var(--transition-duration);
|
||||
}
|
||||
|
||||
#tabpanel.behind {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. GaiaHeader will vertically align
|
||||
* display block elements
|
||||
*/
|
||||
|
||||
gaia-header [data-icon]:before {
|
||||
display: block; /* 1 */
|
||||
color: var(--header-button-color, #000);
|
||||
}
|
||||
|
||||
#fte_view.non-ready {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.view {
|
||||
top: 9.3rem;
|
||||
background: #f4f4f4;
|
||||
transform: translate(0, 0);
|
||||
transition: transform 0.5s ease,
|
||||
opacity 0.2s ease,
|
||||
visibility 0.5s;
|
||||
}
|
||||
|
||||
#balance-tab,
|
||||
#telephony-tab {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.view.standalone {
|
||||
top: 5rem;
|
||||
}
|
||||
|
||||
.view-scroll {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
-moz-user-select: none;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* VIEWPORTS */
|
||||
.view.behind {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.view[data-viewport="ethereal"] {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.view[data-viewport="bottom"] {
|
||||
transform: translateY(100%);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.view[data-viewport="top"] {
|
||||
transform: translateY(-100%);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.view[data-viewport="left"] {
|
||||
transform: translateX(-100%);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.view[data-viewport="right"] {
|
||||
transform: translateX(100%);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.view[data-viewport="inside"] {
|
||||
transform: scale(0.3);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#settings-view.view {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* BB HEADER ICONS */
|
||||
[icon="config"]:before {
|
||||
background-image: url(images/app/icons/config.png);
|
||||
}
|
||||
|
||||
/* BB BUTTONS ICONS */
|
||||
.buttons-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.fake-select select {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
-moz-user-select: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
li button.icon-reload:before {
|
||||
background-image: url(images/app/icons/reload.png);
|
||||
background-size: 3rem;
|
||||
}
|
||||
|
||||
li button.icon-topup:before {
|
||||
background-image: url(images/app/icons/topup.png);
|
||||
background-size: 3rem;
|
||||
}
|
||||
|
||||
li button.icon-topupcode:before {
|
||||
background-image: url(images/app/icons/topupcode.png);
|
||||
background-size: 3rem;
|
||||
}
|
||||
|
||||
/* CONTENT */
|
||||
section.content {
|
||||
padding: 1.5rem 2rem;
|
||||
}
|
||||
|
||||
#message-handler {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#topup-dialog,
|
||||
#settings-view {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
#reset-data-usage {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* GRAPHS */
|
||||
section.graph {
|
||||
position: relative;
|
||||
height: 28.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
section.graph canvas {
|
||||
position: absolute;
|
||||
-moz-user-select: none;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* FADE */
|
||||
.fade {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade.in {
|
||||
opacity: 1;
|
||||
transition: opacity var(--transition-duration) linear;
|
||||
}
|
||||
|
||||
.fade.out {
|
||||
transition: opacity var(--transition-duration) linear;
|
||||
}
|
||||
BIN
gaia/apps/costcontrol/style/icons/costcontrol_126.png
Executable file
|
After Width: | Height: | Size: 3 KiB |
BIN
gaia/apps/costcontrol/style/icons/costcontrol_142.png
Executable file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
gaia/apps/costcontrol/style/icons/costcontrol_189.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
gaia/apps/costcontrol/style/icons/costcontrol_284.png
Executable file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
gaia/apps/costcontrol/style/icons/costcontrol_84.png
Executable file
|
After Width: | Height: | Size: 2 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/config.png
Executable file
|
After Width: | Height: | Size: 373 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/config@1.5x.png
Executable file
|
After Width: | Height: | Size: 615 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/config@2.25x.png
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/config@2x.png
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/default.png
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/default@1.5x.png
Executable file
|
After Width: | Height: | Size: 2 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/default@2.25x.png
Executable file
|
After Width: | Height: | Size: 3 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/default@2x.png
Executable file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/reload.png
Executable file
|
After Width: | Height: | Size: 344 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/reload@1.5x.png
Executable file
|
After Width: | Height: | Size: 530 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/reload@2.25x.png
Executable file
|
After Width: | Height: | Size: 912 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/reload@2x.png
Executable file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/topup.png
Executable file
|
After Width: | Height: | Size: 215 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topup@1.5x.png
Executable file
|
After Width: | Height: | Size: 281 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topup@2.25x.png
Executable file
|
After Width: | Height: | Size: 427 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topup@2x.png
Executable file
|
After Width: | Height: | Size: 667 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topupcode.png
Executable file
|
After Width: | Height: | Size: 322 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topupcode@1.5x.png
Executable file
|
After Width: | Height: | Size: 466 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topupcode@2.25x.png
Executable file
|
After Width: | Height: | Size: 998 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/topupcode@2x.png
Executable file
|
After Width: | Height: | Size: 905 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/warning.png
Executable file
|
After Width: | Height: | Size: 353 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/warning@1.5x.png
Executable file
|
After Width: | Height: | Size: 647 B |
BIN
gaia/apps/costcontrol/style/images/app/icons/warning@2.25x.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
gaia/apps/costcontrol/style/images/app/icons/warning@2x.png
Executable file
|
After Width: | Height: | Size: 605 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern-3g.png
Executable file
|
After Width: | Height: | Size: 207 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern-3g@1.5x.png
Executable file
|
After Width: | Height: | Size: 394 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern-3g@2.25x.png
Executable file
|
After Width: | Height: | Size: 341 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern-3g@2x.png
Executable file
|
After Width: | Height: | Size: 306 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern.png
Executable file
|
After Width: | Height: | Size: 115 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern@1.5x.png
Executable file
|
After Width: | Height: | Size: 102 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern@2.25x.png
Executable file
|
After Width: | Height: | Size: 124 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/datausage/pattern@2x.png
Executable file
|
After Width: | Height: | Size: 117 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/pattern.png
Executable file
|
After Width: | Height: | Size: 763 B |
BIN
gaia/apps/costcontrol/style/images/app/ui/pattern@1.5x.png
Executable file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
gaia/apps/costcontrol/style/images/app/ui/pattern@2.25x.png
Executable file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
gaia/apps/costcontrol/style/images/app/ui/pattern@2x.png
Executable file
|
After Width: | Height: | Size: 7 KiB |
16
gaia/apps/costcontrol/style/views/appdetail.css
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#appdetail-view.view {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#appdetail-view > .view-scroll {
|
||||
top: 5rem;
|
||||
}
|
||||
|
||||
#appdetail-view .info {
|
||||
font-size: 1.7rem;
|
||||
padding: 0 3rem 1rem;
|
||||
}
|
||||
|
||||
#appdetail-view .settings > li:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||