[gnome-shell/T29763: 173/249] js/ui/payg.js: PayGo UI utilities



commit b1e871703e6d69d71b3ccc93aa768a71c7b8ae13
Author: Travis Reitter <travis reitter endlessm com>
Date:   Thu Nov 29 10:22:50 2018 -0800

    js/ui/payg.js: PayGo UI utilities
    
    This factors out some PayGo UI code so it can be re-used for the
    upcoming PayGo status applet and its notifications.
    
    https://phabricator.endlessm.com/T24125
    
     * 2020-04-02: Squash with 1358d284d
    
     * 2019-10-21:
        - Replace Tweener by ClutterActor.ease()
        - Replace Mainloop by GLib
        - Squash with "ed6d7c628 payg: make widgets insensitive […]"

 js/js-resources.gresource.xml |   1 +
 js/ui/payg.js                 | 214 ++++++++++++++++++++++++++++++++++++++++++
 js/ui/paygUnlockDialog.js     | 149 +++++++----------------------
 po/POTFILES.in                |   1 +
 4 files changed, 251 insertions(+), 114 deletions(-)
---
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 4fca81d025..9e09648234 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -154,6 +154,7 @@
     <file>ui/hotCorner.js</file>
     <file>ui/iconGridLayout.js</file>
     <file>ui/internetSearch.js</file>
+    <file>ui/payg.js</file>
     <file>ui/paygUnlockDialog.js</file>
     <file>ui/sideComponent.js</file>
     <file>ui/status/automaticUpdates.js</file>
diff --git a/js/ui/payg.js b/js/ui/payg.js
new file mode 100644
index 0000000000..04b1efb2cf
--- /dev/null
+++ b/js/ui/payg.js
@@ -0,0 +1,214 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+//
+// Copyright (C) 2018 Endless Mobile, Inc.
+//
+// Licensed under the GNU General Public License Version 2
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+/* exported PaygUnlockUi, SPINNER_ICON_SIZE_PIXELS, SUCCESS_DELAY_SECONDS */
+
+const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
+
+const PaygManager = imports.misc.paygManager;
+
+const Gettext = imports.gettext;
+const Main = imports.ui.main;
+
+var SUCCESS_DELAY_SECONDS = 3;
+
+var SPINNER_ICON_SIZE_PIXELS = 16;
+var SPINNER_ANIMATION_DELAY_MSECS = 1000;
+var SPINNER_ANIMATION_TIME_MSECS = 300;
+
+var UnlockStatus = {
+    NOT_VERIFYING: 0,
+    VERIFYING: 1,
+    FAILED: 2,
+    TOO_MANY_ATTEMPTS: 3,
+    SUCCEEDED: 4,
+};
+
+var PaygUnlockUi = GObject.registerClass(
+class PaygUnlockUi extends St.Widget {
+
+    // the following properties and functions are required for any subclasses of
+    // this class
+
+    // properties
+    // -----------
+    // applyButton
+    // entryCode
+    // spinner
+    // verificationStatus
+
+    // functions
+    // ----------
+    // entryReset
+    // entrySetEnabled
+    // onCodeAdded
+    // reset
+
+    _init(params = {}) {
+        super._init(params);
+        this._clearTooManyAttemptsId = 0;
+        this.connect('destroy', this._onDestroy.bind(this));
+    }
+
+    updateApplyButtonSensitivity() {
+        let sensitive = this.validateCurrentCode() &&
+            this.verificationStatus !== UnlockStatus.VERIFYING &&
+            this.verificationStatus !== UnlockStatus.SUCCEEDED &&
+            this.verificationStatus !== UnlockStatus.TOO_MANY_ATTEMPTS;
+
+        this.applyButton.reactive = sensitive;
+        this.applyButton.can_focus = sensitive;
+    }
+
+    updateSensitivity() {
+        let shouldEnableEntry =
+            this.verificationStatus !== UnlockStatus.VERIFYING &&
+            this.verificationStatus !== UnlockStatus.SUCCEEDED &&
+            this.verificationStatus !== UnlockStatus.TOO_MANY_ATTEMPTS;
+
+        this.updateApplyButtonSensitivity();
+        this.entrySetEnabled(shouldEnableEntry);
+    }
+
+    processError(error) {
+        logError(error, 'Error adding PAYG code');
+
+        // The 'too many errors' case is a bit special, and sets a different state.
+        if (error.matches(PaygManager.PaygErrorDomain, PaygManager.PaygError.TOO_MANY_ATTEMPTS)) {
+            let currentTime = Shell.util_get_boottime() / GLib.USEC_PER_SEC;
+            let secondsLeft = Main.paygManager.rateLimitEndTime - currentTime;
+            if (secondsLeft > 30) {
+                let minutesLeft = Math.max(0, Math.ceil(secondsLeft / 60));
+                this.setErrorMessage(
+                    Gettext.ngettext(
+                        'Too many attempts. Try again in %s minute.',
+                        'Too many attempts. Try again in %s minutes.', minutesLeft)
+                        .format(minutesLeft));
+            } else {
+                this.setErrorMessage(_('Too many attempts. Try again in a few seconds.'));
+            }
+
+            // Make sure to clean the status once the time is up (if this dialog is still alive)
+            // and make sure that we install this callback at some point in the future (+1 sec).
+            this._clearTooManyAttemptsId = GLib.timeout_add_seconds(
+                GLib.PRIORITY_DEFAULT,
+                Math.max(1, secondsLeft),
+                () => {
+                    this._verificationStatus = UnlockStatus.NOT_VERIFYING;
+                    this._clearError();
+                    this._updateSensitivity();
+                    return GLib.SOURCE_REMOVE;
+                });
+
+            this.verificationStatus = UnlockStatus.TOO_MANY_ATTEMPTS;
+            return;
+        }
+
+        // Common errors after this point.
+        if (error.matches(PaygManager.PaygErrorDomain, PaygManager.PaygError.INVALID_CODE)) {
+            this.setErrorMessage(_('Invalid code. Please try again.'));
+        } else if (error.matches(PaygManager.PaygErrorDomain, PaygManager.PaygError.CODE_ALREADY_USED)) {
+            this.setErrorMessage(_('Code already used. Please enter a new code.'));
+        } else if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.TIMED_OUT)) {
+            this.setErrorMessage(_('Time exceeded while verifying the code'));
+        } else {
+            // We don't consider any other error here (and we don't consider DISABLED explicitly,
+            // since that should not happen), but still we need to show something to the user.
+            this.setErrorMessage(_('Unknown error'));
+        }
+
+        this.verificationStatus = UnlockStatus.FAILED;
+    }
+
+    _onDestroy() {
+        if (this._clearTooManyAttemptsId > 0) {
+            GLib.source_remove(this._clearTooManyAttemptsId);
+            this._clearTooManyAttemptsId = 0;
+        }
+    }
+
+    setErrorMessage(message) {
+        if (message) {
+            this.errorLabel.text = message;
+            this.errorLabel.opacity = 255;
+        } else {
+            this.errorLabel.text = '';
+            this.errorLabel.opacity = 0;
+        }
+    }
+
+    clearError() {
+        this.setErrorMessage(null);
+    }
+
+    startSpinning() {
+        this.spinner.play();
+        this.spinner.actor.show();
+        this.spinner.actor.ease({
+            opacity: 255,
+            delay: SPINNER_ANIMATION_DELAY_MSECS,
+            duration: SPINNER_ANIMATION_TIME_MSECS,
+            mode: Clutter.AnimationMode.LINEAR,
+        });
+    }
+
+    stopSpinning() {
+        this.spinner.actor.hide();
+        this.spinner.actor.opacity = 0;
+        this.spinner.stop();
+    }
+
+    reset() {
+        this.stopSpinning();
+        this.entryReset();
+        this.updateSensitivity();
+    }
+
+    validateCurrentCode() {
+        return Main.paygManager.validateCode(this.entryCode);
+    }
+
+    startVerifyingCode() {
+        if (!this.validateCurrentCode())
+            return;
+
+        this.verificationStatus = UnlockStatus.VERIFYING;
+        this.startSpinning();
+        this.updateSensitivity();
+        this.cancelled = false;
+
+        Main.paygManager.addCode(this.entryCode, error => {
+            // We don't care about the result if we're closing the dialog.
+            if (this.cancelled) {
+                this.verificationStatus = UnlockStatus.NOT_VERIFYING;
+                return;
+            }
+
+            if (error) {
+                this.processError(error);
+            } else {
+                this.verificationStatus = UnlockStatus.SUCCEEDED;
+                this.onCodeAdded();
+            }
+
+            this.reset();
+        });
+    }
+});
diff --git a/js/ui/paygUnlockDialog.js b/js/ui/paygUnlockDialog.js
index 5c52db8153..70e3a8da22 100644
--- a/js/ui/paygUnlockDialog.js
+++ b/js/ui/paygUnlockDialog.js
@@ -25,6 +25,7 @@ const { Atk, Clutter, Gio, GLib,
 const Gettext = imports.gettext;
 
 const PaygManager = imports.misc.paygManager;
+const Payg = imports.ui.payg;
 
 const Animation = imports.ui.animation;
 const Main = imports.ui.main;
@@ -181,7 +182,7 @@ var PaygUnlockDialog = GObject.registerClass({
         'failed': {},
         'wake-up-screen': {},
     },
-}, class PaygUnlockDialog extends St.Widget {
+}, class PaygUnlockDialog extends Payg.PaygUnlockUi {
 
     _init(parentActor) {
         super._init({
@@ -192,7 +193,6 @@ var PaygUnlockDialog = GObject.registerClass({
         });
 
         this._parentActor = parentActor;
-
         this._entry = null;
         this._errorMessage = null;
         this._cancelButton = null;
@@ -200,7 +200,7 @@ var PaygUnlockDialog = GObject.registerClass({
         this._spinner = null;
         this._cancelled = false;
 
-        this._verificationStatus = UnlockStatus.NOT_VERIFYING;
+        this._verificationStatus = Payg.UnlockStatus.NOT_VERIFYING;
 
         // Clear the clipboard to make sure nothing can be copied into the entry.
         St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, '');
@@ -312,24 +312,21 @@ var PaygUnlockDialog = GObject.registerClass({
             this._onCancelled();
         });
         this._nextButton.connect('clicked', () => {
-            this._startVerifyingCode();
+            this.startVerifyingCode();
         });
 
         this._entry.connect('code-changed', () => {
-            this._updateNextButtonSensitivity();
+            this.updateApplyButtonSensitivity();
         });
 
         this._entry.clutter_text.connect('activate', () => {
-            this._startVerifyingCode();
+            this.startVerifyingCode();
         });
 
-        this._clearTooManyAttemptsId = 0;
-        this.connect('destroy', this._onDestroy.bind(this));
-
         this._idleMonitor = Meta.IdleMonitor.get_core();
         this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT_SECS * MSEC_PER_SEC, 
this._onCancelled.bind(this));
 
-        this._updateSensitivity();
+        this.updateSensitivity();
         this._entry.grab_key_focus();
     }
 
@@ -376,7 +373,7 @@ var PaygUnlockDialog = GObject.registerClass({
         buttonsBox.add_child(buttonSpacer);
 
         // We make the most of the spacer to show the spinner while verifying the code.
-        this._spinner = new Animation.Spinner(SPINNER_ICON_SIZE_PIXELS, {
+        this._spinner = new Animation.Spinner(Payg.SPINNER_ICON_SIZE_PIXELS, {
             animate: true,
             hideOnStop: true,
         });
@@ -399,132 +396,56 @@ var PaygUnlockDialog = GObject.registerClass({
 
     _onCancelled() {
         this._cancelled = true;
-        this._reset();
+        this.reset();
 
         // The ScreenShield will connect to the 'failed' signal
         // to know when to cancel the unlock dialog.
-        if (this._verificationStatus !== UnlockStatus.SUCCEEDED)
+        if (this._verificationStatus !== Payg.UnlockStatus.SUCCEEDED)
             this.emit('failed');
     }
 
-    _validateCurrentCode() {
-        return Main.paygManager.validateCode(this._entry.code);
+    entrySetEnabled(enabled) {
+        this._entry.setEnabled(enabled);
     }
 
-    _updateNextButtonSensitivity() {
-        let sensitive = this._validateCurrentCode() &&
-            this._verificationStatus !== UnlockStatus.VERIFYING &&
-            this._verificationStatus !== UnlockStatus.TOO_MANY_ATTEMPTS;
-
-        this._nextButton.reactive = sensitive;
-        this._nextButton.can_focus = sensitive;
+    entryReset() {
+        this._entry.reset();
     }
 
-    _updateSensitivity() {
-        let shouldEnableEntry = this._verificationStatus !== UnlockStatus.VERIFYING &&
-            this._verificationStatus !== UnlockStatus.TOO_MANY_ATTEMPTS;
-
-        this._updateNextButtonSensitivity();
-        this._entry.setEnabled(shouldEnableEntry);
+    onCodeAdded() {
+        this.clearError();
     }
 
-    _setErrorMessage(message) {
-        if (message) {
-            this._errorMessage.text = message;
-            this._errorMessage.opacity = 255;
-        } else {
-            this._errorMessage.text = '';
-            this._errorMessage.opacity = 0;
-        }
+    get entryCode() {
+        return this._entry.code;
     }
 
-    _reset() {
-        this._spinner.stop();
-        this._entry.reset();
-        this._updateSensitivity();
+    get verificationStatus() {
+        return this._verificationStatus;
     }
 
-    _processError(error) {
-        logError(error, 'Error adding PAYG code');
-
-        // The 'too many errors' case is a bit special, and sets a different state.
-        if (error.matches(PaygManager.PaygErrorDomain, PaygManager.PaygError.TOO_MANY_ATTEMPTS)) {
-            let currentTime = GLib.get_real_time() / GLib.USEC_PER_SEC;
-            let secondsLeft = Main.paygManager.rateLimitEndTime - currentTime;
-            if (secondsLeft > 30) {
-                let minutesLeft = Math.max(0, Math.ceil(secondsLeft / 60));
-                this._setErrorMessage(
-                    Gettext.ngettext(
-                        'Too many attempts. Try again in %s minute.',
-                        'Too many attempts. Try again in %s minutes.', minutesLeft).format(minutesLeft));
-            } else {
-                this._setErrorMessage(_('Too many attempts. Try again in a few seconds.'));
-            }
-
-            // Make sure to clean the status once the time is up (if this dialog is still alive)
-            // and make sure that we install this callback at some point in the future (+1 sec).
-            this._clearTooManyAttemptsId = GLib.timeout_add_seconds(
-                GLib.PRIORITY_DEFAULT,
-                Math.max(1, secondsLeft),
-                () => {
-                    this._verificationStatus = UnlockStatus.NOT_VERIFYING;
-                    this._clearError();
-                    this._updateSensitivity();
-                    this._entry.grab_key_focus();
-                    return GLib.SOURCE_REMOVE;
-                });
-
-            this._verificationStatus = UnlockStatus.TOO_MANY_ATTEMPTS;
-            return;
-        }
-
-        // Common errors after this point.
-        if (error.matches(PaygManager.PaygErrorDomain, PaygManager.PaygError.INVALID_CODE)) {
-            this._setErrorMessage(_('Invalid code. Please try again.'));
-        } else if (error.matches(PaygManager.PaygErrorDomain, PaygManager.PaygError.CODE_ALREADY_USED)) {
-            this._setErrorMessage(_('Code already used. Please enter a new code.'));
-        } else if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.TIMED_OUT)) {
-            this._setErrorMessage(_('Time exceeded while verifying the code'));
-        } else {
-            // We don't consider any other error here (and we don't consider DISABLED explicitly,
-            // since that should not happen), but still we need to show something to the user.
-            this._setErrorMessage(_('Unknown error'));
-        }
-
-        this._verificationStatus = UnlockStatus.FAILED;
+    set verificationStatus(value) {
+        this._verificationStatus = value;
     }
 
-    _clearError() {
-        this._setErrorMessage(null);
+    get cancelled() {
+        return this._cancelled;
     }
 
-    _addCodeCallback(error) {
-        // We don't care about the result if we're closing the dialog.
-        if (this._cancelled) {
-            this._verificationStatus = UnlockStatus.NOT_VERIFYING;
-            return;
-        }
-
-        if (error) {
-            this._processError(error);
-        } else {
-            this._verificationStatus = UnlockStatus.SUCCEEDED;
-            this._clearError();
-        }
-
-        this._reset();
+    set cancelled(value) {
+        this._cancelled = value;
     }
 
-    _startVerifyingCode() {
-        if (!this._validateCurrentCode())
-            return;
+    get errorLabel() {
+        return this._errorMessage;
+    }
 
-        this._verificationStatus = UnlockStatus.VERIFYING;
-        this._spinner.play();
-        this._updateSensitivity();
-        this._cancelled = false;
+    get spinner() {
+        return this._spinner;
+    }
 
-        Main.paygManager.addCode(this._entry.code, this._addCodeCallback.bind(this));
+    get applyButton() {
+        return this._nextButton;
     }
 
     addCharacter(unichar) {
@@ -532,7 +453,7 @@ var PaygUnlockDialog = GObject.registerClass({
     }
 
     cancel() {
-        this._reset();
+        this.entryReset();
         this.destroy();
     }
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 05cffc4611..d8e6e52348 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -51,6 +51,7 @@ js/ui/overview.js
 js/ui/padOsd.js
 js/ui/panel.js
 js/ui/paygUnlockDialog.js
+js/ui/payg.js
 js/ui/popupMenu.js
 js/ui/runDialog.js
 js/ui/screenShield.js


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]