[gnome-shell/eos3.8: 174/255] payg: factor out reminder notifications
- From: Matthew Leeds <mwleeds src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/eos3.8: 174/255] payg: factor out reminder notifications
- Date: Wed, 10 Jun 2020 19:12:37 +0000 (UTC)
commit c4830ceaf2df04ad4eb3ca7c427dacfc5afbe795
Author: Travis Reitter <travis reitter endlessm com>
Date: Mon Dec 3 22:51:57 2018 -0800
payg: factor out reminder notifications
This will allow re-use by the upcoming PayGo status applet
https://phabricator.endlessm.com/T24125
js/misc/paygManager.js | 90 +----------------
js/ui/payg.js | 255 ++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 258 insertions(+), 87 deletions(-)
---
diff --git a/js/misc/paygManager.js b/js/misc/paygManager.js
index 49d236c247..d252bf0e5a 100644
--- a/js/misc/paygManager.js
+++ b/js/misc/paygManager.js
@@ -27,9 +27,7 @@ const { Gio, GLib, GnomeDesktop, GObject, Shell } = imports.gi;
const { loadInterfaceXML } = imports.misc.fileUtils;
-const Gettext = imports.gettext;
-const Main = imports.ui.main;
-const MessageTray = imports.ui.messageTray;
+const Payg = imports.ui.payg;
const EOS_PAYG_NAME = 'com.endlessm.Payg1';
const EOS_PAYG_PATH = '/com/endlessm/Payg1';
@@ -52,10 +50,6 @@ const DBusErrorsMapping = {
DISABLED: 'com.endlessm.Payg1.Error.Disabled',
};
-// Title and description text to be shown in the periodic reminders.
-const NOTIFICATION_TITLE_TEXT = _('Pay as You Go');
-const NOTIFICATION_DETAILED_FORMAT_STRING = _('Subscription runs out in %s.');
-
// This list defines the different instants in time where we would
// want to show notifications to the user reminding that the payg
// subscription will be expiring soon, up to a max GLib.MAXUINT32.
@@ -73,57 +67,6 @@ const notificationAlertTimesSecs = [
30, // 30 seconds
];
-// Takes an UNIX timestamp (in seconds) and returns a string
-// with a precision level appropriate to show to the user.
-//
-// The returned string will be formatted just in seconds for times
-// under 1 minute, in minutes for times under 2 hours, in hours and
-// minutes (if applicable) for times under 1 day, and then in days
-// and hours (if applicable) for anything longer than that in days.
-//
-// Some examples:
-// - 45 seconds => "45 seconds"
-// - 60 seconds => "1 minute"
-// - 95 seconds => "1 minute"
-// - 120 seconds => "2 minutes"
-// - 3600 seconds => "60 minutes"
-// - 4500 seconds => "75 minutes"
-// - 7200 seconds => "2 hours"
-// - 8640 seconds => "2 hours 24 minutes"
-// - 86400 seconds => "1 day"
-// - 115200 seconds => "1 day 8 hours"
-// - 172800 seconds => "2 days"
-function timeToString(seconds) {
- if (seconds < 60)
- return Gettext.ngettext('%s second', '%s seconds', seconds).format(Math.floor(seconds));
-
- let minutes = Math.floor(seconds / 60);
- if (minutes < 120)
- return Gettext.ngettext('%s minute', '%s minutes', minutes).format(minutes);
-
- let hours = Math.floor(minutes / 60);
- if (hours < 24) {
- let hoursStr = Gettext.ngettext('%s hour', '%s hours', hours).format(hours);
-
- let minutesPast = minutes % 60;
- if (minutesPast == 0)
- return hoursStr;
-
- let minutesStr = Gettext.ngettext('%s minute', '%s minutes', minutesPast).format(minutesPast);
- return ("%s %s").format(hoursStr, minutesStr);
- }
-
- let days = Math.floor(hours / 24);
- let daysStr = Gettext.ngettext('%s day', '%s days', days).format(days);
-
- let hoursPast = hours % 24;
- if (hoursPast == 0)
- return daysStr;
-
- let hoursStr = Gettext.ngettext('%s hour', '%s hours', hoursPast).format(hoursPast);
- return ("%s %s").format(daysStr, hoursStr);
-}
-
var PaygManager = GObject.registerClass({
Signals: {
'code-expired': {},
@@ -145,7 +88,7 @@ var PaygManager = GObject.registerClass({
this._rateLimitEndTime = 0;
this._codeFormat = '';
this._codeFormatRegex = null;
- this._notification = null;
+ this._paygNotifier = new Payg.PaygNotifier();
// Keep track of clock changes to update notifications.
this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
@@ -269,31 +212,6 @@ var PaygManager = GObject.registerClass({
this._updateExpirationReminders();
}
- _notifyPaygReminder(secondsLeft) {
- // Only notify when in an regular session, not in GDM or initial-setup.
- if (Main.sessionMode.currentMode != 'user' &&
- Main.sessionMode.currentMode != 'endless' &&
- Main.sessionMode.currentMode != 'user-coding')
- return;
-
- if (this._notification)
- this._notification.destroy();
-
- let source = new MessageTray.SystemNotificationSource();
- Main.messageTray.add(source);
-
- let timeLeft = timeToString(secondsLeft);
- this._notification = new MessageTray.Notification(
- source,
- NOTIFICATION_TITLE_TEXT,
- NOTIFICATION_DETAILED_FORMAT_STRING.format(timeLeft));
- this._notification.setUrgency(MessageTray.Urgency.HIGH);
- this._notification.setTransient(false);
- source.notify(this._notification);
-
- this._notification.connect('destroy', () => this._notification = null);
- }
-
_maybeNotifyUser() {
// Sanity check.
if (notificationAlertTimesSecs.length === 0)
@@ -301,7 +219,7 @@ var PaygManager = GObject.registerClass({
let secondsLeft = this.timeRemainingSecs();
if (secondsLeft > 0 && secondsLeft <= notificationAlertTimesSecs[0])
- this._notifyPaygReminder(secondsLeft);
+ this._paygNotifier.notify(secondsLeft);
}
_updateExpirationReminders() {
@@ -336,7 +254,7 @@ var PaygManager = GObject.registerClass({
() => {
// We want to show "round" numbers in the notification, matching
// whatever is specified in the notificationAlertTimeSecs array.
- this._notifyPaygReminder(targetAlertTime);
+ this._paygNotifier.notify(targetAlertTime);
// Reset _expirationReminderId before _updateExpirationReminders()
// to prevent an attempt to remove the same GSourceFunc twice.
diff --git a/js/ui/payg.js b/js/ui/payg.js
index 04b1efb2cf..cb2d59b54b 100644
--- a/js/ui/payg.js
+++ b/js/ui/payg.js
@@ -18,14 +18,17 @@
// 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 */
+/* exported PaygUnlockUi, SPINNER_ICON_SIZE_PIXELS, SUCCESS_DELAY_SECONDS,
+ ApplyCodeNotification, timeToString */
const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
const PaygManager = imports.misc.paygManager;
const Gettext = imports.gettext;
+const Animation = imports.ui.animation;
const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
var SUCCESS_DELAY_SECONDS = 3;
@@ -33,6 +36,10 @@ var SPINNER_ICON_SIZE_PIXELS = 16;
var SPINNER_ANIMATION_DELAY_MSECS = 1000;
var SPINNER_ANIMATION_TIME_MSECS = 300;
+const NOTIFICATION_TITLE_TEXT = _('Pay as You Go');
+const NOTIFICATION_EARLY_CODE_ENTRY_TEXT = _('Enter an unlock code to extend PayGo time before expiration.');
+const NOTIFICATION_DETAILED_FORMAT_STRING = _('Subscription runs out in %s.');
+
var UnlockStatus = {
NOT_VERIFYING: 0,
VERIFYING: 1,
@@ -212,3 +219,249 @@ class PaygUnlockUi extends St.Widget {
});
}
});
+
+var PaygUnlockWidget = GObject.registerClass({
+ Signals: {
+ 'code-added': {},
+ 'code-rejected': { param_types: [GObject.TYPE_STRING] },
+ },
+}, class PaygUnlockWidget extends PaygUnlockUi {
+
+ _init() {
+ super._init();
+
+ this._verificationStatus = UnlockStatus.NOT_VERIFYING;
+ this._codeEntry = this._createCodeEntry();
+ this._spinner = this._createSpinner();
+ let entrySpinnerBox = new St.BoxLayout({
+ style_class: 'notification-actions',
+ x_expand: false,
+ });
+ entrySpinnerBox.add_child(this._codeEntry);
+ entrySpinnerBox.add_child(this._spinner.actor);
+
+ this._buttonBox = new St.BoxLayout({
+ style_class: 'notification-actions',
+ x_expand: true,
+ vertical: true,
+ });
+ global.focus_manager.add_group(this._buttonBox);
+ this._buttonBox.add_child(entrySpinnerBox);
+
+ this._applyButton = this._createApplyButton();
+ this._applyButton.connect('clicked', this.startVerifyingCode.bind(this));
+ this._buttonBox.add_child(this._applyButton);
+
+ this.updateSensitivity();
+ }
+
+ _createCodeEntry() {
+ let codeEntry = new St.Entry({
+ style_class: 'notification-payg-entry',
+ x_expand: true,
+ can_focus: true,
+ });
+ codeEntry.clutter_text.connect('activate', this.startVerifyingCode.bind(this));
+ codeEntry.clutter_text.connect('text-changed', this.updateApplyButtonSensitivity.bind(this));
+ codeEntry._enabled = true;
+
+ return codeEntry;
+ }
+
+ _createSpinner() {
+ // We make the most of the spacer to show the spinner while verifying the code.
+ let spinnerIcon = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
+ let spinner = new Animation.AnimatedIcon(spinnerIcon, SPINNER_ICON_SIZE_PIXELS);
+ spinner.actor.opacity = 0;
+ spinner.actor.hide();
+
+ return spinner;
+ }
+
+ _createApplyButton() {
+ let box = new St.BoxLayout();
+
+ let label = new St.Bin({
+ x_expand: true,
+ child: new St.Label({
+ x_expand: true,
+ x_align: Clutter.ActorAlign.CENTER,
+ text: _('Apply Code'),
+ }),
+ });
+ box.add_child(label);
+
+ let button = new St.Button({
+ child: box,
+ x_fill: true,
+ x_expand: true,
+ button_mask: St.ButtonMask.ONE,
+ style_class: 'hotplug-notification-item button',
+ });
+
+ return button;
+ }
+
+ setErrorMessage(message) {
+ this.emit('code-rejected', message);
+ }
+
+ _onEntryChanged() {
+ this.updateApplyButtonSensitivity();
+ }
+
+ onCodeAdded() {
+ this.emit('code-added');
+ }
+
+ entryReset() {
+ this._codeEntry.set_text('');
+ }
+
+ entrySetEnabled(enabled) {
+ if (this._codeEntry._enabled === enabled)
+ return;
+
+ this._codeEntry._enabled = enabled;
+ this._codeEntry.reactive = enabled;
+ this._codeEntry.can_focus = enabled;
+ this._codeEntry.clutter_text.reactive = enabled;
+ this._codeEntry.clutter_text.editable = enabled;
+ this._codeEntry.clutter_text.cursor_visible = enabled;
+ }
+
+ get entryCode() {
+ return this._codeEntry.get_text();
+ }
+
+ get verificationStatus() {
+ return this._verificationStatus;
+ }
+
+ set verificationStatus(value) {
+ this._verificationStatus = value;
+ }
+
+ get spinner() {
+ return this._spinner;
+ }
+
+ get applyButton() {
+ return this._applyButton;
+ }
+
+ get buttonBox() {
+ return this._buttonBox;
+ }
+
+});
+
+var ApplyCodeNotification = GObject.registerClass(
+class ApplyCodeNotification extends MessageTray.Notification {
+ _init(source, title, banner) {
+ super._init(source, title, banner);
+
+ this._titleOrig = title;
+
+ // Note: "banner" is actually the string displayed in the banner, not a
+ // banner object. This variable name simply follows the convention of
+ // the parent class.
+ this._bannerOrig = banner;
+ this._verificationStatus = UnlockStatus.NOT_VERIFYING;
+ }
+
+ createBanner() {
+ this._banner = new MessageTray.NotificationBanner(this);
+ this._unlockWidget = new PaygUnlockWidget();
+ this._unlockWidget.connect('code-added', this._onCodeAdded.bind(this));
+ this._unlockWidget.connect('code-rejected', this._onCodeRejected.bind(this));
+ this._banner.setActionArea(this._unlockWidget.buttonBox);
+
+ return this._banner;
+ }
+
+ _onCodeAdded() {
+ this._setMessage(_('Code applied successfully!'));
+
+ GLib.timeout_add_seconds(
+ GLib.PRIORITY_DEFAULT,
+ SUCCESS_DELAY_SECONDS,
+ () => {
+ this.emit('done-displaying');
+ this.destroy();
+
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+
+ // if errorMessage is unspecified, a default message will be populated based
+ // on whether time remains
+ _onCodeRejected(unlockWidget, errorMessage) {
+ this._setMessage(errorMessage ? errorMessage : this._bannerOrig);
+ }
+
+ _setMessage(message) {
+ this.update(this._titleOrig, message);
+ }
+
+ activate() {
+ // We get here if the Apply button is inactive when we try to click it.
+ // Unless we're already done, exit early so we don't destroy the
+ // notification)
+ if (this._verificationStatus !== UnlockStatus.SUCCEEDED)
+ return;
+
+ super.activate();
+ }
+});
+
+// Takes an UNIX timestamp (in seconds) and returns a string
+// with a precision level appropriate to show to the user.
+//
+// The returned string will be formatted just in seconds for times
+// under 1 minute, in minutes for times under 2 hours, in hours and
+// minutes (if applicable) for times under 1 day, and then in days
+// and hours (if applicable) for anything longer than that in days.
+//
+// Some examples:
+// - 45 seconds => "45 seconds"
+// - 60 seconds => "1 minute"
+// - 95 seconds => "1 minute"
+// - 120 seconds => "2 minutes"
+// - 3600 seconds => "60 minutes"
+// - 4500 seconds => "75 minutes"
+// - 7200 seconds => "2 hours"
+// - 8640 seconds => "2 hours 24 minutes"
+// - 86400 seconds => "1 day"
+// - 115200 seconds => "1 day 8 hours"
+// - 172800 seconds => "2 days"
+function timeToString(seconds) {
+ if (seconds < 60)
+ return Gettext.ngettext('%s second', '%s seconds', seconds).format(Math.floor(seconds));
+
+ let minutes = Math.floor(seconds / 60);
+ if (minutes < 120)
+ return Gettext.ngettext('%s minute', '%s minutes', minutes).format(minutes);
+
+ let hours = Math.floor(minutes / 60);
+ if (hours < 24) {
+ let hoursStr = Gettext.ngettext('%s hour', '%s hours', hours).format(hours);
+
+ let minutesPast = minutes % 60;
+ if (minutesPast === 0)
+ return hoursStr;
+
+ let minutesStr = Gettext.ngettext('%s minute', '%s minutes', minutesPast).format(minutesPast);
+ return '%s %s'.format(hoursStr, minutesStr);
+ }
+
+ let days = Math.floor(hours / 24);
+ let daysStr = Gettext.ngettext('%s day', '%s days', days).format(days);
+
+ let hoursPast = hours % 24;
+ if (hoursPast === 0)
+ return daysStr;
+
+ let hoursStr = Gettext.ngettext('%s hour', '%s hours', hoursPast).format(hoursPast);
+ return '%s %s'.format(daysStr, hoursStr);
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]