diff --git a/docs/changelog.md b/docs/changelog.md index effac63..b484440 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -9,6 +9,10 @@ SPDX-License-Identifier: CC-BY-4.0 **Release Date:** TBD +#### Enhancements + +- When opening the preferences, a dialog is now shown occasionally to kindly ask for a donation. The dialog can be disabled once it has been shown for the first time. + #### Bug Fixes - Fixed an issue on GNOME 44.2 which caused the effects to look broken. It was caused by [this change](https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2650). diff --git a/prefs.js b/prefs.js index 82d7614..7c0ad56 100644 --- a/prefs.js +++ b/prefs.js @@ -394,6 +394,55 @@ var PreferencesDialog = class PreferencesDialog { }); } + // Count the number of times the user has opened the preferences window. Every now + // and then, we show a dialog asking the user to support the extension. We connect + // to the notify::visible signal to ensure that the dialog can be a modal dialog. + window.connect('notify::visible', (window) => { + // Do not show the dialog when the window is hidden. + if (!window.get_visible()) { + return; + } + + // Do not show the dialog when the user has disabled it. + if (!this._settings.get_boolean('show-support-dialog')) { + return; + } + + const count = this._settings.get_int('prefs-open-count') + 1; + this._settings.set_int('prefs-open-count', count); + + // Show the dialog every 10th time. + if (count % 10 == 0) { + const dialog = this._createMessageDialog( + '❤️ Do you love Burn-My-Windows?', + `Even the smallest donation can have a big impact! If just one of ten Burn-My-Windows users donated $1 per month, I could dedicate myself to creating awesome open-source projects full-time! + +Ko-fi: https://ko-fi.com/schneegans +GitHub: https://github.com/sponsors/schneegans`, + window, [ + { + label: 'Do not show this again!', + destructive: true, + default: false, + action: () => { + this._settings.set_boolean('show-support-dialog', false); + } + }, + { + label: 'Remind me later.', + destructive: false, + default: true, + } + ]); + + if (utils.isGTK4()) { + dialog.show(); + } else { + dialog.show_all(); + } + } + }); + // Populate the menu with actions. const group = Gio.SimpleActionGroup.new(); window.insert_action_group('prefs', group); @@ -592,56 +641,38 @@ var PreferencesDialog = class PreferencesDialog { // This action shows a confirmation dialog. Upon user approval, the currently // active profile is deleted. We use the more beautiful Adw.MessageDialog if it is // available (usually on GNOME 43 and beyond). - let deleteProfileDialog; - const dialogTitle = _('Delete this Profile?'); - const dialogSubtitle = _( - 'The current effect profile with all its effect settings will be permanently lost.'); + const deleteProfileDialog = this._createMessageDialog( + _('Delete this Profile?'), + _('The current effect profile with all its effect settings will be permanently lost.'), + window, [ + { + label: _('Cancel'), + destructive: false, + default: true, + }, + { + label: _('Delete'), + destructive: true, + default: false, + action: () => { + // Stop editing the current profile. + this._builder.get_object('edit-profile-button').active = false; - if (utils.isADW() && Adw.MessageDialog) { - deleteProfileDialog = - new Adw.MessageDialog({heading: dialogTitle, body: dialogSubtitle}); - deleteProfileDialog.add_response('cancel', _('Cancel')); - deleteProfileDialog.add_response('delete', _('Delete')); - deleteProfileDialog.set_response_appearance('delete', - Adw.ResponseAppearance.DESTRUCTIVE); - deleteProfileDialog.set_default_response('cancel'); - deleteProfileDialog.set_close_response('cancel'); - } else { - deleteProfileDialog = new Gtk.MessageDialog({ - text: dialogTitle, - secondary_text: dialogSubtitle, - modal: true, - title: '', - buttons: Gtk.ButtonsType.OK_CANCEL - }); - } + // Delete the currently active profile. + this._profileManager.deleteProfile(this._activeProfile.path); - if (utils.isGTK4()) { - deleteProfileDialog.set_hide_on_close(true); - } + // Select another one. + this._settings.set_string('active-profile', + this._profileManager.getProfiles()[0].path); - deleteProfileDialog.set_transient_for(window); - - deleteProfileDialog.connect('response', (dialog, response) => { - if (response == 'delete' || response == Gtk.ResponseType.OK) { - // Stop editing the current profile. - this._builder.get_object('edit-profile-button').active = false; - - // Delete the currently active profile. - this._profileManager.deleteProfile(this._activeProfile.path); - - // Select another one. - this._settings.set_string('active-profile', - this._profileManager.getProfiles()[0].path); - - // The number of profiles has changed, so we have to update the profile - // selection menu. - this._updateProfileMenu(); - } - - dialog.hide(); - }); + // The number of profiles has changed, so we have to update the profile + // selection menu. + this._updateProfileMenu(); + } + } + ]); + // This action deletes the currently active profile. const deleteProfileAction = Gio.SimpleAction.new('profile-delete', null); deleteProfileAction.connect('activate', () => { deleteProfileDialog.show(); @@ -974,6 +1005,77 @@ var PreferencesDialog = class PreferencesDialog { window.show_all(); } } + + // Helper function to show a message dialog. The dialog is modal and has a title, a + // message and a list of buttons. Each button is an object with a label, a default flag + // and a destructive flag. Each button object can also have an "action" callback that is + // called when the button is clicked. + // This method works on GTK3, GTK4, and libadwaita. + _createMessageDialog(title, message, window, buttons) { + let dialog; + + if (utils.isADW() && Adw.MessageDialog) { + dialog = new Adw.MessageDialog({ + heading: title, + body: message, + body_use_markup: true, + modal: true, + }); + + buttons.forEach((button, i) => { + const response = i.toString(); + dialog.add_response(response, button.label); + + if (button.default) { + dialog.set_default_response(response); + dialog.set_close_response(response); + } + + if (button.destructive) { + dialog.set_response_appearance(response, Adw.ResponseAppearance.DESTRUCTIVE); + } + }); + + } else { + + dialog = new Gtk.MessageDialog({ + text: title, + secondary_text: message, + secondary_use_markup: true, + modal: true, + title: '', + }); + + buttons.forEach((button, i) => { + dialog.add_button(button.label, i); + + if (button.default) { + dialog.set_default_response(i); + } + }); + } + + if (utils.isGTK4()) { + dialog.set_hide_on_close(true); + } + + dialog.set_transient_for(window); + + // If the dialog is closed or a button is clicked, we hide the dialog and call the + // button's action callback if it exists. + dialog.connect('response', (dialog, response) => { + const i = parseInt(response); + + const button = buttons[i]; + if (button && button.action) { + button.action(); + } + + dialog.hide(); + }); + + return dialog; + } } // This is used for setting up the translations. diff --git a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml index 50fd50e..844fc0e 100644 --- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml +++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml @@ -43,6 +43,18 @@ SPDX-License-Identifier: CC0-1.0 Used to check whether the extension got updated from the extension side. + + 0 + Prefs Open Count + The number of times the settings dialog was openend. + + + + true + Show Support Dialog + If set to false, the ask-for-support dialog will never be shown. + + \ No newline at end of file