Maybe finished with license stuff

This commit is contained in:
wheaney 2024-05-22 14:49:47 -07:00
parent 17fb4fee61
commit 016d9b0c4a
13 changed files with 190 additions and 265 deletions

@ -1 +1 @@
Subproject commit f379c976e003823ed6a61fe2d054c802b3bca80d
Subproject commit 8e5513c89df30ca2cfdcd03947dcb8d9562db6b3

@ -1 +1 @@
Subproject commit 024f9724f9b93176fdd91f089da78d15c8c91f40
Subproject commit 2ab04f36ac4cf1c1f33d9729a5566a1cf744736b

View File

@ -3,8 +3,6 @@
<gresource prefix="/com/xronlinux/BreezyDesktop">
<file preprocess="xml-stripblanks">gtk/connected-device.ui</file>
<file preprocess="xml-stripblanks">gtk/license-dialog.ui</file>
<file preprocess="xml-stripblanks">gtk/license-feature-row.ui</file>
<file preprocess="xml-stripblanks">gtk/license-tier-row.ui</file>
<file preprocess="xml-stripblanks">gtk/no-device.ui</file>
<file preprocess="xml-stripblanks">gtk/no-extension.ui</file>
<file preprocess="xml-stripblanks">gtk/shortcut-dialog.ui</file>

View File

@ -6,61 +6,62 @@
<property name="modal">1</property>
<property name="default_width">440</property>
<property name="default_height">200</property>
<property name="use-header-bar">1</property>
<child type="action">
<object class="GtkButton" id="refresh_license_button">
<property name="icon-name">view-refresh-symbolic</property>
</object>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<child>
<object class="AdwBanner" id="license_action_needed_banner">
<property name="revealed">0</property>
<property name="title" translatable="yes">Some features expire soon</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">1</property>
<property name="margin-top">5</property>
<property name="label" translatable="yes">Tier Status</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="1.2"/>
</attributes>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<object class="AdwPreferencesGroup">
<property name="margin-top">10</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="true">Donate</property>
<property name="subtitle" translatable="true">ko-fi.com/wheaney</property>
<property name="subtitle-selectable">True</property>
<child type="suffix">
<object class="GtkLinkButton">
<property name="icon-name">go-next-symbolic</property>
<property name="uri">https://ko-fi.com/wheaney</property>
</object>
</child>
<style>
<class name="property"/>
</style>
</object>
</child>
<child>
<object class="AdwEntryRow" id="request_token">
<property name="visible">0</property>
<property name="title" translatable="true">Request a token</property>
<property name="input-purpose">6</property>
<property name="show-apply-button">1</property>
</object>
</child>
<child>
<object class="AdwEntryRow" id="verify_token">
<property name="visible">0</property>
<property name="title" translatable="true">Verify token</property>
<property name="input-hints">16</property>
<property name="show-apply-button">1</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="tiers">
<property name="orientation">vertical</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">1</property>
<property name="margin-top">25</property>
<property name="label" translatable="yes">Feature Availability</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="1.2"/>
</attributes>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
</object>
</child>
<child>
<object class="GtkBox" id="features">
<property name="orientation">vertical</property>
</object>
</child>
</object>

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="LicenseFeatureRow" parent="GtkGrid">
<property name="column-spacing">10</property>
<property name="column-homogeneous">true</property>
<style>
<class name="LicenseFeatureRow">
.tier_name {
text-align: right;
}
.tier_status {
text-align: left;
}
</class>
</style>
<child>
<object class="GtkLabel" id="feature_name">
<property name="xalign">1.0</property>
<property name="hexpand">true</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="feature_status">
<property name="xalign">0.0</property>
<property name="hexpand">true</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
</template>
</interface>

View File

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="LicenseTierRow" parent="GtkGrid">
<property name="column-spacing">10</property>
<property name="column-homogeneous">true</property>
<style>
<class name="LicenseTierRow">
.tier_name {
text-align: right;
}
.tier_status {
text-align: left;
}
</class>
</style>
<child>
<object class="GtkLabel" id="tier_name">
<property name="xalign">1.0</property>
<property name="hexpand">true</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="tier_status">
<property name="xalign">0.0</property>
<property name="hexpand">true</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkGrid" id="tier_funds_needed">
<child>
<object class="GtkLabel" id="tier_funds_needed_monthly_label">
<property name="visible">0</property>
<property name="label">Monthly</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="tier_funds_needed_monthly_amount">
<property name="visible">0</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="tier_funds_needed_yearly_label">
<property name="visible">0</property>
<property name="label">Yearly</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="tier_funds_needed_yearly_amount">
<property name="visible">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="tier_funds_needed_lifetime_label">
<property name="visible">0</property>
<property name="label">Lifetime</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="tier_funds_needed_lifetime_amount">
<property name="visible">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<layout>
<property name="column">0</property>
<property name="row">1</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
</template>
</interface>

View File

@ -27,14 +27,18 @@
</object>
</property>
<property name="child">
<object class="GtkBox" id="main_content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="AdwBanner" id="license_action_needed_banner">
<property name="revealed">0</property>
<property name="title" translatable="yes">Some features expire soon</property>
<property name="button-label" translatable="yes">Update your license</property>
<property name="button-label" translatable="yes">View details</property>
</object>
</child>
<child>
<object class="GtkBox" id="main_content" />
</child>
</object>
</property>
</template>

View File

@ -1,7 +1,8 @@
from gi.repository import Gtk
from gi.repository import Adw, Gtk, GLib
from .statemanager import StateManager
from .licensetierrow import LicenseTierRow
from .licensefeaturerow import LicenseFeatureRow
from .xrdriveripc import XRDriverIPC
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/license-dialog.ui')
class LicenseDialog(Gtk.Dialog):
@ -9,18 +10,67 @@ class LicenseDialog(Gtk.Dialog):
tiers = Gtk.Template.Child()
features = Gtk.Template.Child()
request_token = Gtk.Template.Child()
verify_token = Gtk.Template.Child()
refresh_license_button = Gtk.Template.Child()
def __init__(self):
super(Gtk.Dialog, self).__init__()
self.init_template()
self.state_manager = StateManager.get_instance()
self._handle_license();
self.ipc = XRDriverIPC.get_instance()
StateManager.get_instance().connect('license-action-needed', self._handle_license)
self._handle_license(StateManager.get_instance())
def _handle_license(self):
license_view = self.state_manager.state['ui_view']['license']
self.request_token.connect('apply', self._on_request_token)
self.verify_token.connect('apply', self._on_verify_token)
self.refresh_license_button.connect('clicked', self._refresh_license)
def _refresh_license(self, widget):
self.refresh_license_button.set_sensitive(False)
self.ipc.write_control_flags({'refresh_device_license': True})
GLib.timeout_add_seconds(3, self._handle_license)
def _handle_license(self, state_manager = None, val = None):
GLib.idle_add(self._handle_license_idle, state_manager or StateManager.get_instance())
def _handle_license_idle(self, state_manager):
self.refresh_license_button.set_sensitive(False)
license_view = state_manager.state['ui_view']['license']
self.request_token.set_visible(not state_manager.confirmed_token)
self.verify_token.set_visible(not state_manager.confirmed_token)
for child in self.tiers:
self.tiers.remove(child)
tiers_group = Adw.PreferencesGroup(title="Paid Tier Status", margin_top=20)
self.tiers.append(tiers_group)
for tier_name, tier_details in license_view['tiers'].items():
self.tiers.add(LicenseTierRow(tier_name, tier_details))
tiers_group.add(LicenseTierRow(tier_name, tier_details))
for child in self.features:
self.features.remove(child)
features_group = Adw.PreferencesGroup(title="Feature Availability", margin_top=20)
self.features.append(features_group)
for feature_name, feature_details in license_view['features'].items():
self.features.add(LicenseFeatureRow(feature_name, feature_details))
features_group.add(LicenseFeatureRow(feature_name, feature_details))
self.refresh_license_button.set_sensitive(True)
def _on_request_token(self, widget):
email_address = self.request_token.get_text()
self.request_token.set_editable(False)
if not self.ipc.request_token(email_address):
self.request_token.set_editable(True)
def _on_verify_token(self, widget):
token = self.verify_token.get_text()
self.request_token.set_editable(False)
self.verify_token.set_editable(False)
if self.ipc.verify_token(token):
self.ipc.write_control_flags({'refresh_device_license': True})
else:
self.request_token.set_editable(True)
self.verify_token.set_editable(True)

View File

@ -1,27 +1,20 @@
from gi.repository import Gtk
from gi.repository import Adw, Gtk
from .time import time_remaining_text
feature_names = {
'sbs': 'Side-by-side mode (gaming)',
'smooth_follow': 'Smooth Follow (gaming)',
FEATURE_NAMES = {
'sbs': 'Side-by-side mode (for gaming)',
'smooth_follow': 'Smooth Follow',
'productivity_basic': 'Breezy Desktop',
'productivity_pro': 'Breezy Desktop w/ multiple monitors',
}
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/license-feature-row.ui')
class LicenseFeatureRow(Gtk.Grid):
__gtype_name__ = 'LicenseFeatureRow'
feature_name = Gtk.Template.Child()
feature_status = Gtk.Template.Child()
class LicenseFeatureRow(Adw.ActionRow):
def __init__(self, feature, feature_details):
super(Gtk.Grid, self).__init__()
self.init_template()
self.feature_name.set_markup(f"<b>{feature_names[feature]}</b>")
super().__init__()
self.set_title(FEATURE_NAMES[feature])
status = 'Disabled'
if feature_details.get('is_enabled') == True:
@ -33,4 +26,4 @@ class LicenseFeatureRow(Gtk.Grid):
time_remaining = time_remaining_text(funds_needed_in_seconds)
if time_remaining: details = f" ({time_remaining} remaining)"
self.feature_status.set_markup(f"{status}{details}")
self.set_subtitle(f"{status}{details}")

View File

@ -1,4 +1,4 @@
from gi.repository import Gtk
from gi.repository import Adw, Gtk
from .time import time_remaining_text
@ -14,31 +14,18 @@ PERIOD_DESCRIPTIONS = {
'lifetime': 'with lifetime access',
}
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/license-tier-row.ui')
class LicenseTierRow(Gtk.Grid):
__gtype_name__ = 'LicenseTierRow'
PERIOD_RANKS = {
'monthly': 1,
'yearly': 2,
'lifetime': 3,
}
tier_name = Gtk.Template.Child()
tier_status = Gtk.Template.Child()
tier_funds_needed = Gtk.Template.Child()
tier_funds_needed_monthly_label = Gtk.Template.Child()
tier_funds_needed_monthly_amount = Gtk.Template.Child()
tier_funds_needed_yearly_label = Gtk.Template.Child()
tier_funds_needed_yearly_amount = Gtk.Template.Child()
tier_funds_needed_lifetime_label = Gtk.Template.Child()
tier_funds_needed_lifetime_amount = Gtk.Template.Child()
class LicenseTierRow(Adw.ExpanderRow):
def __init__(self, tier, tier_details):
super(Gtk.Grid, self).__init__()
self.init_template()
super().__init__()
self.funds_needed_elements = {
'monthly': [self.tier_funds_needed_monthly_label, self.tier_funds_needed_monthly_amount],
'yearly': [self.tier_funds_needed_yearly_label, self.tier_funds_needed_yearly_amount],
'lifetime': [self.tier_funds_needed_lifetime_label, self.tier_funds_needed_lifetime_amount],
}
self.tier_name.set_markup(f"<b>{TIER_NAMES[tier]}</b>")
self.set_title(TIER_NAMES[tier])
active_period = tier_details.get('active_period')
funds_needed_in_seconds = tier_details.get('funds_needed_in_seconds')
@ -50,21 +37,27 @@ class LicenseTierRow(Gtk.Grid):
if funds_needed_in_seconds is not None and funds_needed_in_seconds > 0:
time_remaining = time_remaining_text(funds_needed_in_seconds)
if time_remaining: details += f" ({time_remaining} remaining)"
if active_period == 'lifetime' or funds_needed_in_seconds is None:
self.tier_funds_needed.set_visible(False)
if active_period == 'lifetime':
self.set_enable_expansion(False)
self.set_icon_name(None)
self.tier_status.set_markup(f"{status}{details}")
self.set_expanded(False)
self.set_subtitle(f"{status}{details}")
for period, amount in tier_details['funds_needed_by_period'].items():
amount_text = None
if amount > 0:
label_widget, amount_widget = self.funds_needed_elements[period]
amount_text = f"US${amount}"
amount_text = f"<b>${amount}</b> USD"
if active_period == period:
amount_text += " to renew"
else:
elif active_period is not None:
amount_text += " to upgrade"
amount_widget.set_markup(amount_text)
label_widget.set_visible(True)
amount_widget.set_visible(True)
elif active_period is not None and PERIOD_RANKS[period] >= PERIOD_RANKS[active_period]:
amount_text = "Ready to auto-renew"
if amount_text is not None:
row_widget = Adw.ActionRow(title=period.capitalize())
row_widget.add_suffix(Gtk.Label(label=amount_text, use_markup=True))
self.add_row(row_widget)

View File

@ -1,7 +1,12 @@
import sys
import threading
from gi.repository import GObject
from .time import LICENSE_WARN_SECONDS
from .xrdriveripc import XRDriverIPC
# shouldn't need a number larger than a year
LICENSE_ACTION_NEEDED_MAX = 60 * 60 * 24 * 366
class Logger:
def info(self, message):
print(message)
@ -12,12 +17,12 @@ class Logger:
class StateManager(GObject.GObject):
__gsignals__ = {
'device-update': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
'license-action-needed': (GObject.SIGNAL_RUN_FIRST, None, (int,)),
'license-action-needed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)),
}
__gproperties__ = {
'follow-mode': (bool, 'Follow Mode', 'Whether the follow mode is enabled', False, GObject.ParamFlags.READWRITE),
'license-action-needed-date': (int, 'License Action Needed Date', 'The date, in seconds, when the license action was needed', 0, 1024000, 0, GObject.ParamFlags.READWRITE),
'license-action-needed-seconds': (int, 'License Action Needed Seconds', 'The remaining time until the license action is needed', 0, LICENSE_ACTION_NEEDED_MAX, 0, GObject.ParamFlags.READWRITE),
}
_instance = None
@ -48,6 +53,7 @@ class StateManager(GObject.GObject):
self.connected_device_name = None
self.license_action_needed = False
self.license_action_needed_seconds = 0
self.confirmed_token = False
self.start()
@ -65,23 +71,32 @@ class StateManager(GObject.GObject):
self.connected_device_name = new_device_name
self.emit('device-update', self.connected_device_name)
license_view = self.state['ui_view']['license']
action_needed_seconds = license_view.get('action_needed_seconds')
action_needed = action_needed_seconds is not None
if (action_needed != self.license_action_needed):
self.license_action_needed = action_needed
self.license_action_needed_seconds = action_needed_seconds
self.emit('license-action-needed', action_needed_seconds)
license_view = self.state['ui_view'].get('license')
if license_view:
confirmed_token = license_view.get('confirmed_token') == True
action_needed_details = license_view.get('action_needed')
action_needed_seconds = action_needed_details.get('seconds') if action_needed_details else None
action_needed = action_needed_seconds is not None and action_needed_seconds < LICENSE_WARN_SECONDS
if (action_needed != self.license_action_needed or self.confirmed_token != confirmed_token):
self.license_action_needed = action_needed
self.license_action_needed_seconds = action_needed_seconds
self.confirmed_token = confirmed_token
self.emit('license-action-needed', action_needed or not confirmed_token)
self.set_property('follow-mode', self.state.get('breezy_desktop_smooth_follow_enabled'))
self.set_property('license-action-needed-date', self.license_action_needed_seconds)
self.set_property('license-action-needed-seconds', self.license_action_needed_seconds)
if self.running: threading.Timer(1.0, self._refresh_state).start()
def do_set_property(self, prop, value):
if prop.name == 'follow-mode':
self.follow_mode = value
if prop.name == 'license-action-needed-seconds':
self.license_action_needed_seconds = value
def do_get_property(self, prop):
if prop.name == 'follow-mode':
return self.follow_mode
return self.follow_mode
if prop.name == 'license-action-needed-seconds':
return self.license_action_needed_seconds

View File

@ -1,14 +1,19 @@
from math import floor
# we'll begin to alert the user when there's less than a week left
LICENSE_WARN_SECONDS = 60 * 60 * 24 * 7
def time_remaining_text(seconds):
if not seconds:
return
if seconds < 60 * 60:
if seconds / 60 < 60:
return 'less than an hour'
elif seconds / 60 * 60 < 24:
time_remaining = seconds / 60 * 60
elif seconds / (60 * 60) < 24:
time_remaining = floor(seconds / (60 * 60))
return '1 hour' if time_remaining == 1 else f'{time_remaining} hours'
elif seconds / 24 * 60 * 60 < 30:
time_remaining = seconds / 24 * 60 * 60
elif seconds / (24 * 60 * 60) < 30:
time_remaining = floor(seconds / (24 * 60 * 60))
return '1 day' if time_remaining == 1 else f'{time_remaining} days'
else:
return

View File

@ -17,12 +17,14 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import Gtk
from gi.repository import Gtk, GLib
from .extensionsmanager import ExtensionsManager
from .licensedialog import LicenseDialog
from .statemanager import StateManager
from .connecteddevice import ConnectedDevice
from .nodevice import NoDevice
from .noextension import NoExtension
from .time import LICENSE_WARN_SECONDS
@Gtk.Template(resource_path='/com/xronlinux/BreezyDesktop/gtk/window.ui')
class BreezydesktopWindow(Gtk.ApplicationWindow):
@ -49,12 +51,16 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
self.connect("destroy", self._on_window_destroy)
def _handle_device_update(self, state_manager, val):
GLib.idle_add(self._handle_device_update_gui, state_manager)
def _handle_device_update_gui(self, state_manager):
license_action_needed_seconds = state_manager.get_property('license-action-needed-seconds')
show_banner = license_action_needed_seconds is not None and license_action_needed_seconds < LICENSE_WARN_SECONDS
self.license_action_needed_banner.set_revealed(show_banner)
for child in self.main_content:
self.main_content.remove(child)
if state_manager.license_action_needed:
self.main_content.append(self.no_license)
if not ExtensionsManager.get_instance().is_installed():
self.main_content.append(self.no_extension)
elif state_manager.connected_device_name:
@ -64,7 +70,9 @@ class BreezydesktopWindow(Gtk.ApplicationWindow):
self.main_content.append(self.no_device)
def _on_license_action_needed_button_clicked(self, widget):
self.state_manager.ipc.show_license()
dialog = LicenseDialog()
dialog.set_transient_for(widget.get_ancestor(Gtk.Window))
dialog.present()
def _on_window_destroy(self, widget):
self.state_manager.disconnect_by_func(self._handle_device_update)