Auto-commit 2026-03-30 16:30
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gsettings schema="org.rssuper.notification.preferences">
|
||||
<prefix>rssuper</prefix>
|
||||
<binding>
|
||||
<property name="newArticles" type="boolean"/>
|
||||
</binding>
|
||||
<binding>
|
||||
<property name="episodeReleases" type="boolean"/>
|
||||
</binding>
|
||||
<binding>
|
||||
<property name="customAlerts" type="boolean"/>
|
||||
</binding>
|
||||
<binding>
|
||||
<property name="badgeCount" type="boolean"/>
|
||||
</binding>
|
||||
<binding>
|
||||
<property name="sound" type="boolean"/>
|
||||
</binding>
|
||||
<binding>
|
||||
<property name="vibration" type="boolean"/>
|
||||
</binding>
|
||||
<binding>
|
||||
<property name="preferences" type="json"/>
|
||||
</binding>
|
||||
|
||||
<keyvalue>
|
||||
<key name="newArticles">New Article Notifications</key>
|
||||
<default>true</default>
|
||||
<description>Enable notifications for new articles</description>
|
||||
</keyvalue>
|
||||
|
||||
<keyvalue>
|
||||
<key name="episodeReleases">Episode Release Notifications</key>
|
||||
<default>true</default>
|
||||
<description>Enable notifications for episode releases</description>
|
||||
</keyvalue>
|
||||
|
||||
<keyvalue>
|
||||
<key name="customAlerts">Custom Alert Notifications</key>
|
||||
<default>true</default>
|
||||
<description>Enable notifications for custom alerts</description>
|
||||
</keyvalue>
|
||||
|
||||
<keyvalue>
|
||||
<key name="badgeCount">Badge Count</key>
|
||||
<default>true</default>
|
||||
<description>Show badge count in app header</description>
|
||||
</keyvalue>
|
||||
|
||||
<keyvalue>
|
||||
<key name="sound">Sound</key>
|
||||
<default>true</default>
|
||||
<description>Play sound on notification</description>
|
||||
</keyvalue>
|
||||
|
||||
<keyvalue>
|
||||
<key name="vibration">Vibration</key>
|
||||
<default>true</default>
|
||||
<description>Vibrate device on notification</description>
|
||||
</keyvalue>
|
||||
|
||||
<keyvalue>
|
||||
<key name="preferences">All Preferences</key>
|
||||
<default>{
|
||||
"newArticles": true,
|
||||
"episodeReleases": true,
|
||||
"customAlerts": true,
|
||||
"badgeCount": true,
|
||||
"sound": true,
|
||||
"vibration": true
|
||||
}</default>
|
||||
<description>All notification preferences as JSON</description>
|
||||
</keyvalue>
|
||||
</gsettings>
|
||||
373
native-route/linux/src/notification-manager.vala
Normal file
373
native-route/linux/src/notification-manager.vala
Normal file
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* notification-manager.vala
|
||||
*
|
||||
* Notification manager for RSSuper on Linux.
|
||||
* Coordinates notifications, badge management, and tray integration.
|
||||
*/
|
||||
|
||||
using Gio;
|
||||
using GLib;
|
||||
using Gtk;
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* NotificationManager - Manager for coordinating notifications
|
||||
*/
|
||||
public class NotificationManager : Object {
|
||||
|
||||
// Singleton instance
|
||||
private static NotificationManager? _instance;
|
||||
|
||||
// Notification service
|
||||
private NotificationService? _notification_service;
|
||||
|
||||
// Badge reference
|
||||
private Gtk.Badge? _badge;
|
||||
|
||||
// Tray icon reference
|
||||
private Gtk.TrayIcon? _tray_icon;
|
||||
|
||||
// App reference
|
||||
private Gtk.App? _app;
|
||||
|
||||
// Current unread count
|
||||
private int _unread_count = 0;
|
||||
|
||||
// Badge visibility
|
||||
private bool _badge_visible = true;
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
public static NotificationManager? get_instance() {
|
||||
if (_instance == null) {
|
||||
_instance = new NotificationManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance
|
||||
*/
|
||||
private NotificationManager() {
|
||||
_notification_service = NotificationService.get_instance();
|
||||
_app = Gtk.App.get_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the notification manager
|
||||
*/
|
||||
public void initialize() {
|
||||
// Set up badge
|
||||
_badge = Gtk.Badge.new();
|
||||
_badge.set_visible(_badge_visible);
|
||||
_badge.set_halign(Gtk.Align.START);
|
||||
|
||||
// Connect badge changed signal
|
||||
_badge.changed.connect(_on_badge_changed);
|
||||
|
||||
// Set up tray icon
|
||||
_tray_icon = Gtk.TrayIcon.new();
|
||||
_tray_icon.set_icon_name("rssuper");
|
||||
_tray_icon.set_tooltip_text("RSSuper - Press for notifications");
|
||||
|
||||
// Connect tray icon clicked signal
|
||||
_tray_icon.clicked.connect(_on_tray_clicked);
|
||||
|
||||
// Set up tray icon popup menu
|
||||
var popup = new PopupMenu();
|
||||
popup.add_item(new Gtk.Label("Notifications: " + _unread_count.toString()));
|
||||
popup.add_item(new Gtk.Separator());
|
||||
popup.add_item(new Gtk.Label("Mark all as read"));
|
||||
popup.add_item(new Gtk.Separator());
|
||||
popup.add_item(new Gtk.Label("Settings"));
|
||||
popup.add_item(new Gtk.Label("Exit"));
|
||||
popup.connect_closed(_on_tray_closed);
|
||||
|
||||
_tray_icon.set_popup(popup);
|
||||
|
||||
// Connect tray icon popup menu signal
|
||||
popup.menu_closed.connect(_on_tray_popup_closed);
|
||||
|
||||
// Set up tray icon popup handler
|
||||
_tray_icon.set_popup_handler(_on_tray_popup);
|
||||
|
||||
// Set up tray icon tooltip
|
||||
_tray_icon.set_tooltip_text("RSSuper - Press for notifications");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the badge in the app header
|
||||
*/
|
||||
public void set_up_badge() {
|
||||
_badge.set_visible(_badge_visible);
|
||||
_badge.set_halign(Gtk.Align.START);
|
||||
|
||||
// Set up badge changed signal
|
||||
_badge.changed.connect(_on_badge_changed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the tray icon
|
||||
*/
|
||||
public void set_up_tray_icon() {
|
||||
_tray_icon.set_icon_name("rssuper");
|
||||
_tray_icon.set_tooltip_text("RSSuper - Press for notifications");
|
||||
|
||||
// Connect tray icon clicked signal
|
||||
_tray_icon.clicked.connect(_on_tray_clicked);
|
||||
|
||||
// Set up tray icon popup menu
|
||||
var popup = new PopupMenu();
|
||||
popup.add_item(new Gtk.Label("Notifications: " + _unread_count.toString()));
|
||||
popup.add_item(new Gtk.Separator());
|
||||
popup.add_item(new Gtk.Label("Mark all as read"));
|
||||
popup.add_item(new Gtk.Separator());
|
||||
popup.add_item(new Gtk.Label("Settings"));
|
||||
popup.add_item(new Gtk.Label("Exit"));
|
||||
popup.connect_closed(_on_tray_closed);
|
||||
|
||||
_tray_icon.set_popup(popup);
|
||||
|
||||
// Connect tray icon popup menu signal
|
||||
popup.menu_closed.connect(_on_tray_popup_closed);
|
||||
|
||||
// Set up tray icon popup handler
|
||||
_tray_icon.set_popup_handler(_on_tray_popup);
|
||||
|
||||
// Set up tray icon tooltip
|
||||
_tray_icon.set_tooltip_text("RSSuper - Press for notifications");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show badge
|
||||
*/
|
||||
public void show_badge() {
|
||||
_badge.set_visible(_badge_visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide badge
|
||||
*/
|
||||
public void hide_badge() {
|
||||
_badge.set_visible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show badge with count
|
||||
*/
|
||||
public void show_badge_with_count(int count) {
|
||||
_badge.set_visible(_badge_visible);
|
||||
_badge.set_label(count.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set unread count
|
||||
*/
|
||||
public void set_unread_count(int count) {
|
||||
_unread_count = count;
|
||||
|
||||
// Update badge
|
||||
if (_badge != null) {
|
||||
_badge.set_label(count.toString());
|
||||
}
|
||||
|
||||
// Update tray icon popup
|
||||
if (_tray_icon != null) {
|
||||
var popup = _tray_icon.get_popup();
|
||||
if (popup != null) {
|
||||
popup.set_label("Notifications: " + count.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Show badge if count > 0
|
||||
if (count > 0) {
|
||||
show_badge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear unread count
|
||||
*/
|
||||
public void clear_unread_count() {
|
||||
_unread_count = 0;
|
||||
hide_badge();
|
||||
|
||||
// Update tray icon popup
|
||||
if (_tray_icon != null) {
|
||||
var popup = _tray_icon.get_popup();
|
||||
if (popup != null) {
|
||||
popup.set_label("Notifications: 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unread count
|
||||
*/
|
||||
public int get_unread_count() {
|
||||
return _unread_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get badge reference
|
||||
*/
|
||||
public Gtk.Badge? get_badge() {
|
||||
return _badge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tray icon reference
|
||||
*/
|
||||
public Gtk.TrayIcon? get_tray_icon() {
|
||||
return _tray_icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app reference
|
||||
*/
|
||||
public Gtk.App? get_app() {
|
||||
return _app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if badge should be visible
|
||||
*/
|
||||
public bool should_show_badge() {
|
||||
return _unread_count > 0 && _badge_visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set badge visibility
|
||||
*/
|
||||
public void set_badge_visibility(bool visible) {
|
||||
_badge_visible = visible;
|
||||
|
||||
if (_badge != null) {
|
||||
_badge.set_visible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification with badge
|
||||
*/
|
||||
public void show_with_badge(string title, string body,
|
||||
string icon = null,
|
||||
Urgency urgency = Urgency.NORMAL) {
|
||||
|
||||
var notification = _notification_service.create(title, body, icon, urgency);
|
||||
notification.show_with_timeout(5000);
|
||||
|
||||
// Show badge
|
||||
if (_unread_count == 0) {
|
||||
show_badge_with_count(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification without badge
|
||||
*/
|
||||
public void show_without_badge(string title, string body,
|
||||
string icon = null,
|
||||
Urgency urgency = Urgency.NORMAL) {
|
||||
|
||||
var notification = _notification_service.create(title, body, icon, urgency);
|
||||
notification.show_with_timeout(5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show critical notification
|
||||
*/
|
||||
public void show_critical(string title, string body,
|
||||
string icon = null) {
|
||||
show_with_badge(title, body, icon, Urgency.CRITICAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show low priority notification
|
||||
*/
|
||||
public void show_low(string title, string body,
|
||||
string icon = null) {
|
||||
show_with_badge(title, body, icon, Urgency.LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show normal notification
|
||||
*/
|
||||
public void show_normal(string title, string body,
|
||||
string icon = null) {
|
||||
show_with_badge(title, body, icon, Urgency.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle badge changed signal
|
||||
*/
|
||||
private void _on_badge_changed(Gtk.Badge badge) {
|
||||
var count = badge.get_label();
|
||||
if (!string.IsNullOrEmpty(count)) {
|
||||
_unread_count = int.Parse(count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tray icon clicked signal
|
||||
*/
|
||||
private void _on_tray_clicked(Gtk.TrayIcon tray) {
|
||||
show_notifications_panel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tray icon popup closed signal
|
||||
*/
|
||||
private void _on_tray_popup_closed(Gtk.Popup popup) {
|
||||
// Popup closed, hide icon
|
||||
if (_tray_icon != null) {
|
||||
_tray_icon.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tray icon popup open signal
|
||||
*/
|
||||
private void _on_tray_popup(Gtk.TrayIcon tray, Gtk.MenuItem menu) {
|
||||
// Show icon when popup is opened
|
||||
if (_tray_icon != null) {
|
||||
_tray_icon.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tray icon closed signal
|
||||
*/
|
||||
private void _on_tray_closed(Gtk.App app) {
|
||||
// App closed, hide tray icon
|
||||
if (_tray_icon != null) {
|
||||
_tray_icon.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notifications panel
|
||||
*/
|
||||
private void show_notifications_panel() {
|
||||
// TODO: Show notifications panel
|
||||
print("Notifications panel requested");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification service
|
||||
*/
|
||||
public NotificationService? get_notification_service() {
|
||||
return _notification_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if notification manager is available
|
||||
*/
|
||||
public bool is_available() {
|
||||
return _notification_service != null && _notification_service.is_available();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
285
native-route/linux/src/notification-preferences-store.vala
Normal file
285
native-route/linux/src/notification-preferences-store.vala
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* notification-preferences-store.vala
|
||||
*
|
||||
* Store for notification preferences.
|
||||
* Provides persistent storage for user notification settings.
|
||||
*/
|
||||
|
||||
using GLib;
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* NotificationPreferencesStore - Persistent storage for notification preferences
|
||||
*
|
||||
* Uses GSettings for persistent storage following freedesktop.org conventions.
|
||||
*/
|
||||
public class NotificationPreferencesStore : Object {
|
||||
|
||||
// Singleton instance
|
||||
private static NotificationPreferencesStore? _instance;
|
||||
|
||||
// GSettings schema key
|
||||
private const string SCHEMA_KEY = "org.rssuper.notification.preferences";
|
||||
|
||||
// GSettings schema description
|
||||
private const string SCHEMA_DESCRIPTION = "RSSuper notification preferences";
|
||||
|
||||
// GSettings schema source URI
|
||||
private const string SCHEMA_SOURCE = "file:///app/gsettings/org.rssuper.notification.preferences.gschema.xml";
|
||||
|
||||
// Preferences schema
|
||||
private GSettings? _settings;
|
||||
|
||||
// Preferences object
|
||||
private NotificationPreferences? _preferences;
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
public static NotificationPreferencesStore? get_instance() {
|
||||
if (_instance == null) {
|
||||
_instance = new NotificationPreferencesStore();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance
|
||||
*/
|
||||
private NotificationPreferencesStore() {
|
||||
_settings = GSettings.new(SCHEMA_KEY, SCHEMA_DESCRIPTION);
|
||||
|
||||
// Load initial preferences
|
||||
_preferences = NotificationPreferences.from_json_string(_settings.get_string("preferences"));
|
||||
|
||||
if (_preferences == null) {
|
||||
// Set default preferences if none exist
|
||||
_preferences = new NotificationPreferences();
|
||||
_settings.set_string("preferences", _preferences.to_json_string());
|
||||
}
|
||||
|
||||
// Listen for settings changes
|
||||
_settings.changed.connect(_on_settings_changed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification preferences
|
||||
*/
|
||||
public NotificationPreferences? get_preferences() {
|
||||
return _preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set notification preferences
|
||||
*/
|
||||
public void set_preferences(NotificationPreferences prefs) {
|
||||
_preferences = prefs;
|
||||
|
||||
// Save to GSettings
|
||||
_settings.set_string("preferences", prefs.to_json_string());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get new articles preference
|
||||
*/
|
||||
public bool get_new_articles() {
|
||||
return _preferences != null ? _preferences.new_articles : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new articles preference
|
||||
*/
|
||||
public void set_new_articles(bool enabled) {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.new_articles = enabled;
|
||||
_settings.set_boolean("newArticles", enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get episode releases preference
|
||||
*/
|
||||
public bool get_episode_releases() {
|
||||
return _preferences != null ? _preferences.episode_releases : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set episode releases preference
|
||||
*/
|
||||
public void set_episode_releases(bool enabled) {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.episode_releases = enabled;
|
||||
_settings.set_boolean("episodeReleases", enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom alerts preference
|
||||
*/
|
||||
public bool get_custom_alerts() {
|
||||
return _preferences != null ? _preferences.custom_alerts : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom alerts preference
|
||||
*/
|
||||
public void set_custom_alerts(bool enabled) {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.custom_alerts = enabled;
|
||||
_settings.set_boolean("customAlerts", enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get badge count preference
|
||||
*/
|
||||
public bool get_badge_count() {
|
||||
return _preferences != null ? _preferences.badge_count : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set badge count preference
|
||||
*/
|
||||
public void set_badge_count(bool enabled) {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.badge_count = enabled;
|
||||
_settings.set_boolean("badgeCount", enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sound preference
|
||||
*/
|
||||
public bool get_sound() {
|
||||
return _preferences != null ? _preferences.sound : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sound preference
|
||||
*/
|
||||
public void set_sound(bool enabled) {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.sound = enabled;
|
||||
_settings.set_boolean("sound", enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get vibration preference
|
||||
*/
|
||||
public bool get_vibration() {
|
||||
return _preferences != null ? _preferences.vibration : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set vibration preference
|
||||
*/
|
||||
public void set_vibration(bool enabled) {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.vibration = enabled;
|
||||
_settings.set_boolean("vibration", enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all notifications
|
||||
*/
|
||||
public void enable_all() {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.enable_all();
|
||||
|
||||
// Save to GSettings
|
||||
_settings.set_string("preferences", _preferences.to_json_string());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all notifications
|
||||
*/
|
||||
public void disable_all() {
|
||||
_preferences = _preferences ?? new NotificationPreferences();
|
||||
_preferences.disable_all();
|
||||
|
||||
// Save to GSettings
|
||||
_settings.set_string("preferences", _preferences.to_json_string());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all preferences as dictionary
|
||||
*/
|
||||
public Dictionary<string, object> get_all_preferences() {
|
||||
if (_preferences == null) {
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
var prefs = new Dictionary<string, object>();
|
||||
prefs["new_articles"] = _preferences.new_articles;
|
||||
prefs["episode_releases"] = _preferences.episode_releases;
|
||||
prefs["custom_alerts"] = _preferences.custom_alerts;
|
||||
prefs["badge_count"] = _preferences.badge_count;
|
||||
prefs["sound"] = _preferences.sound;
|
||||
prefs["vibration"] = _preferences.vibration;
|
||||
|
||||
return prefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all preferences from dictionary
|
||||
*/
|
||||
public void set_all_preferences(Dictionary<string, object> prefs) {
|
||||
_preferences = new NotificationPreferences();
|
||||
|
||||
if (prefs.containsKey("new_articles")) {
|
||||
_preferences.new_articles = prefs["new_articles"] as bool;
|
||||
}
|
||||
if (prefs.containsKey("episode_releases")) {
|
||||
_preferences.episode_releases = prefs["episode_releases"] as bool;
|
||||
}
|
||||
if (prefs.containsKey("custom_alerts")) {
|
||||
_preferences.custom_alerts = prefs["custom_alerts"] as bool;
|
||||
}
|
||||
if (prefs.containsKey("badge_count")) {
|
||||
_preferences.badge_count = prefs["badge_count"] as bool;
|
||||
}
|
||||
if (prefs.containsKey("sound")) {
|
||||
_preferences.sound = prefs["sound"] as bool;
|
||||
}
|
||||
if (prefs.containsKey("vibration")) {
|
||||
_preferences.vibration = prefs["vibration"] as bool;
|
||||
}
|
||||
|
||||
// Save to GSettings
|
||||
_settings.set_string("preferences", _preferences.to_json_string());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema key
|
||||
*/
|
||||
public string get_schema_key() {
|
||||
return SCHEMA_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema description
|
||||
*/
|
||||
public string get_schema_description() {
|
||||
return SCHEMA_DESCRIPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema source
|
||||
*/
|
||||
public string get_schema_source() {
|
||||
return SCHEMA_SOURCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle settings changed signal
|
||||
*/
|
||||
private void _on_settings_changed(GSettings settings) {
|
||||
// Settings changed, reload preferences
|
||||
_preferences = NotificationPreferences.from_json_string(settings.get_string("preferences"));
|
||||
|
||||
if (_preferences == null) {
|
||||
// Set defaults on error
|
||||
_preferences = new NotificationPreferences();
|
||||
settings.set_string("preferences", _preferences.to_json_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
232
native-route/linux/src/notification-service.vala
Normal file
232
native-route/linux/src/notification-service.vala
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* notification-service.vala
|
||||
*
|
||||
* Main notification service for RSSuper on Linux.
|
||||
* Implements Gio.Notification API following freedesktop.org spec.
|
||||
*/
|
||||
|
||||
using Gio;
|
||||
using GLib;
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* NotificationService - Main notification service for Linux
|
||||
*
|
||||
* Handles desktop notifications using Gio.Notification.
|
||||
* Follows freedesktop.org notify-send specification.
|
||||
*/
|
||||
public class NotificationService : Object {
|
||||
|
||||
// Singleton instance
|
||||
private static NotificationService? _instance;
|
||||
|
||||
// Gio.Notification instance
|
||||
private Gio.Notification? _notification;
|
||||
|
||||
// Tray icon reference
|
||||
private Gtk.App? _app;
|
||||
|
||||
// Default title
|
||||
private string _default_title = "RSSuper";
|
||||
|
||||
// Default urgency
|
||||
private Urgency _default_urgency = Urgency.NORMAL;
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
public static NotificationService? get_instance() {
|
||||
if (_instance == null) {
|
||||
_instance = new NotificationService();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance (for singleton pattern)
|
||||
*/
|
||||
private NotificationService() {
|
||||
_app = Gtk.App.get_active();
|
||||
_default_title = _app != null ? _app.get_name() : "RSSuper";
|
||||
_default_urgency = Urgency.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if notification service is available
|
||||
*/
|
||||
public bool is_available() {
|
||||
return Gio.Notification.is_available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new notification
|
||||
*
|
||||
* @param title The notification title
|
||||
* @param body The notification body
|
||||
* @param urgency Urgency level (NORMAL, CRITICAL, LOW)
|
||||
* @param timestamp Optional timestamp (defaults to now)
|
||||
*/
|
||||
public Notification create(string title, string body,
|
||||
Urgency urgency = Urgency.NORMAL,
|
||||
DateTime timestamp = null) {
|
||||
|
||||
_notification = Gio.Notification.new(_default_title);
|
||||
_notification.set_body(body);
|
||||
_notification.set_urgency(urgency);
|
||||
|
||||
if (timestamp == null) {
|
||||
_notification.set_time_now();
|
||||
} else {
|
||||
_notification.set_time(timestamp);
|
||||
}
|
||||
|
||||
return _notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a notification with summary and icon
|
||||
*/
|
||||
public Notification create(string title, string body, string icon,
|
||||
Urgency urgency = Urgency.NORMAL,
|
||||
DateTime timestamp = null) {
|
||||
|
||||
_notification = Gio.Notification.new(title);
|
||||
_notification.set_body(body);
|
||||
_notification.set_urgency(urgency);
|
||||
|
||||
if (timestamp == null) {
|
||||
_notification.set_time_now();
|
||||
} else {
|
||||
_notification.set_time(timestamp);
|
||||
}
|
||||
|
||||
// Set icon
|
||||
try {
|
||||
_notification.set_icon(icon);
|
||||
} catch (Error e) {
|
||||
warning("Failed to set icon: %s", e.message);
|
||||
}
|
||||
|
||||
return _notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a notification with summary, body, and icon
|
||||
*/
|
||||
public Notification create(string summary, string body, string icon,
|
||||
Urgency urgency = Urgency.NORMAL,
|
||||
DateTime timestamp = null) {
|
||||
|
||||
_notification = Gio.Notification.new(summary);
|
||||
_notification.set_body(body);
|
||||
_notification.set_urgency(urgency);
|
||||
|
||||
if (timestamp == null) {
|
||||
_notification.set_time_now();
|
||||
} else {
|
||||
_notification.set_time(timestamp);
|
||||
}
|
||||
|
||||
// Set icon
|
||||
try {
|
||||
_notification.set_icon(icon);
|
||||
} catch (Error e) {
|
||||
warning("Failed to set icon: %s", e.message);
|
||||
}
|
||||
|
||||
return _notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the notification
|
||||
*/
|
||||
public void show() {
|
||||
if (_notification == null) {
|
||||
warning("Cannot show null notification");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_notification.show();
|
||||
} catch (Error e) {
|
||||
warning("Failed to show notification: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the notification with timeout
|
||||
*
|
||||
* @param timeout_seconds Timeout in seconds (default: 5)
|
||||
*/
|
||||
public void show_with_timeout(int timeout_seconds = 5) {
|
||||
if (_notification == null) {
|
||||
warning("Cannot show null notification");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_notification.show_with_timeout(timeout_seconds * 1000);
|
||||
} catch (Error e) {
|
||||
warning("Failed to show notification with timeout: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification instance
|
||||
*/
|
||||
public Gio.Notification? get_notification() {
|
||||
return _notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default title
|
||||
*/
|
||||
public void set_default_title(string title) {
|
||||
_default_title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default urgency
|
||||
*/
|
||||
public void set_default_urgency(Urgency urgency) {
|
||||
_default_urgency = urgency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default title
|
||||
*/
|
||||
public string get_default_title() {
|
||||
return _default_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default urgency
|
||||
*/
|
||||
public Urgency get_default_urgency() {
|
||||
return _default_urgency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app reference
|
||||
*/
|
||||
public Gtk.App? get_app() {
|
||||
return _app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the notification can be shown
|
||||
*/
|
||||
public bool can_show() {
|
||||
return _notification != null && _notification.can_show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available urgency levels
|
||||
*/
|
||||
public static List<Urgency> get_available_urgencies() {
|
||||
return Urgency.get_available();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
native-route/linux/src/repository/Repositories.vala
Normal file
41
native-route/linux/src/repository/Repositories.vala
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Repositories.vala
|
||||
*
|
||||
* Repository interfaces for Linux state management
|
||||
*/
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* FeedRepository - Interface for feed repository operations
|
||||
*/
|
||||
public interface FeedRepository : Object {
|
||||
public abstract void get_feed_items(string? subscription_id, State<FeedItem[]> callback);
|
||||
public abstract FeedItem? get_feed_item_by_id(string id) throws Error;
|
||||
public abstract void insert_feed_item(FeedItem item) throws Error;
|
||||
public abstract void insert_feed_items(FeedItem[] items) throws Error;
|
||||
public abstract void update_feed_item(FeedItem item) throws Error;
|
||||
public abstract void mark_as_read(string id, bool is_read) throws Error;
|
||||
public abstract void mark_as_starred(string id, bool is_starred) throws Error;
|
||||
public abstract void delete_feed_item(string id) throws Error;
|
||||
public abstract int get_unread_count(string? subscription_id) throws Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* SubscriptionRepository - Interface for subscription repository operations
|
||||
*/
|
||||
public interface SubscriptionRepository : Object {
|
||||
public abstract void get_all_subscriptions(State<FeedSubscription[]> callback);
|
||||
public abstract void get_enabled_subscriptions(State<FeedSubscription[]> callback);
|
||||
public abstract void get_subscriptions_by_category(string category, State<FeedSubscription[]> callback);
|
||||
public abstract FeedSubscription? get_subscription_by_id(string id) throws Error;
|
||||
public abstract FeedSubscription? get_subscription_by_url(string url) throws Error;
|
||||
public abstract void insert_subscription(FeedSubscription subscription) throws Error;
|
||||
public abstract void update_subscription(FeedSubscription subscription) throws Error;
|
||||
public abstract void delete_subscription(string id) throws Error;
|
||||
public abstract void set_enabled(string id, bool enabled) throws Error;
|
||||
public abstract void set_error(string id, string? error) throws Error;
|
||||
public abstract void update_last_fetched_at(string id, ulong last_fetched_at) throws Error;
|
||||
public abstract void update_next_fetch_at(string id, ulong next_fetch_at) throws Error;
|
||||
}
|
||||
}
|
||||
136
native-route/linux/src/repository/RepositoriesImpl.vala
Normal file
136
native-route/linux/src/repository/RepositoriesImpl.vala
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* RepositoriesImpl.vala
|
||||
*
|
||||
* Repository implementations for Linux state management
|
||||
*/
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* FeedRepositoryImpl - Implementation of FeedRepository
|
||||
*/
|
||||
public class FeedRepositoryImpl : Object, FeedRepository {
|
||||
private Database db;
|
||||
|
||||
public FeedRepositoryImpl(Database db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public override void get_feed_items(string? subscription_id, State<FeedItem[]> callback) {
|
||||
try {
|
||||
var feedItems = db.getFeedItems(subscription_id);
|
||||
callback.set_success(feedItems);
|
||||
} catch (Error e) {
|
||||
callback.set_error("Failed to get feed items", e);
|
||||
}
|
||||
}
|
||||
|
||||
public override FeedItem? get_feed_item_by_id(string id) throws Error {
|
||||
return db.getFeedItemById(id);
|
||||
}
|
||||
|
||||
public override void insert_feed_item(FeedItem item) throws Error {
|
||||
db.insertFeedItem(item);
|
||||
}
|
||||
|
||||
public override void insert_feed_items(FeedItem[] items) throws Error {
|
||||
foreach (var item in items) {
|
||||
db.insertFeedItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
public override void update_feed_item(FeedItem item) throws Error {
|
||||
db.updateFeedItem(item);
|
||||
}
|
||||
|
||||
public override void mark_as_read(string id, bool is_read) throws Error {
|
||||
db.markFeedItemAsRead(id, is_read);
|
||||
}
|
||||
|
||||
public override void mark_as_starred(string id, bool is_starred) throws Error {
|
||||
db.markFeedItemAsStarred(id, is_starred);
|
||||
}
|
||||
|
||||
public override void delete_feed_item(string id) throws Error {
|
||||
db.deleteFeedItem(id);
|
||||
}
|
||||
|
||||
public override int get_unread_count(string? subscription_id) throws Error {
|
||||
return db.getUnreadCount(subscription_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SubscriptionRepositoryImpl - Implementation of SubscriptionRepository
|
||||
*/
|
||||
public class SubscriptionRepositoryImpl : Object, SubscriptionRepository {
|
||||
private Database db;
|
||||
|
||||
public SubscriptionRepositoryImpl(Database db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public override void get_all_subscriptions(State<FeedSubscription[]> callback) {
|
||||
try {
|
||||
var subscriptions = db.getAllSubscriptions();
|
||||
callback.set_success(subscriptions);
|
||||
} catch (Error e) {
|
||||
callback.set_error("Failed to get subscriptions", e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void get_enabled_subscriptions(State<FeedSubscription[]> callback) {
|
||||
try {
|
||||
var subscriptions = db.getEnabledSubscriptions();
|
||||
callback.set_success(subscriptions);
|
||||
} catch (Error e) {
|
||||
callback.set_error("Failed to get enabled subscriptions", e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void get_subscriptions_by_category(string category, State<FeedSubscription[]> callback) {
|
||||
try {
|
||||
var subscriptions = db.getSubscriptionsByCategory(category);
|
||||
callback.set_success(subscriptions);
|
||||
} catch (Error e) {
|
||||
callback.set_error("Failed to get subscriptions by category", e);
|
||||
}
|
||||
}
|
||||
|
||||
public override FeedSubscription? get_subscription_by_id(string id) throws Error {
|
||||
return db.getSubscriptionById(id);
|
||||
}
|
||||
|
||||
public override FeedSubscription? get_subscription_by_url(string url) throws Error {
|
||||
return db.getSubscriptionByUrl(url);
|
||||
}
|
||||
|
||||
public override void insert_subscription(FeedSubscription subscription) throws Error {
|
||||
db.insertSubscription(subscription);
|
||||
}
|
||||
|
||||
public override void update_subscription(FeedSubscription subscription) throws Error {
|
||||
db.updateSubscription(subscription);
|
||||
}
|
||||
|
||||
public override void delete_subscription(string id) throws Error {
|
||||
db.deleteSubscription(id);
|
||||
}
|
||||
|
||||
public override void set_enabled(string id, bool enabled) throws Error {
|
||||
db.setSubscriptionEnabled(id, enabled);
|
||||
}
|
||||
|
||||
public override void set_error(string id, string? error) throws Error {
|
||||
db.setSubscriptionError(id, error);
|
||||
}
|
||||
|
||||
public override void update_last_fetched_at(string id, ulong last_fetched_at) throws Error {
|
||||
db.setSubscriptionLastFetchedAt(id, last_fetched_at);
|
||||
}
|
||||
|
||||
public override void update_next_fetch_at(string id, ulong next_fetch_at) throws Error {
|
||||
db.setSubscriptionNextFetchAt(id, next_fetch_at);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
native-route/linux/src/state/ErrorType.vala
Normal file
34
native-route/linux/src/state/ErrorType.vala
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* ErrorType.vala
|
||||
*
|
||||
* Error types for state management
|
||||
*/
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* ErrorType - Category of errors
|
||||
*/
|
||||
public enum ErrorType {
|
||||
NETWORK,
|
||||
DATABASE,
|
||||
PARSING,
|
||||
AUTH,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrorDetails - Detailed error information
|
||||
*/
|
||||
public class ErrorDetails : Object {
|
||||
public ErrorType type { get; set; }
|
||||
public string message { get; set; }
|
||||
public bool retryable { get; set; }
|
||||
|
||||
public ErrorDetails(ErrorType type, string message, bool retryable = false) {
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.retryable = retryable;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
native-route/linux/src/state/State.vala
Normal file
110
native-route/linux/src/state/State.vala
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* State.vala
|
||||
*
|
||||
* Reactive state management using GObject signals
|
||||
*/
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* State - Enumerated state for reactive state management
|
||||
*/
|
||||
public enum State {
|
||||
IDLE,
|
||||
LOADING,
|
||||
SUCCESS,
|
||||
ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* State<T> - Generic state container with signals
|
||||
*/
|
||||
public class State<T> : Object {
|
||||
private State _state;
|
||||
private T? _data;
|
||||
private string? _message;
|
||||
private Error? _error;
|
||||
|
||||
public State() {
|
||||
_state = State.IDLE;
|
||||
}
|
||||
|
||||
public State.idle() {
|
||||
_state = State.IDLE;
|
||||
}
|
||||
|
||||
public State.loading() {
|
||||
_state = State.LOADING;
|
||||
}
|
||||
|
||||
public State.success(T data) {
|
||||
_state = State.SUCCESS;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public State.error(string message, Error? error = null) {
|
||||
_state = State.ERROR;
|
||||
_message = message;
|
||||
_error = error;
|
||||
}
|
||||
|
||||
public State get_state() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
public T? get_data() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
public string? get_message() {
|
||||
return _message;
|
||||
}
|
||||
|
||||
public Error? get_error() {
|
||||
return _error;
|
||||
}
|
||||
|
||||
public bool is_idle() {
|
||||
return _state == State.IDLE;
|
||||
}
|
||||
|
||||
public bool is_loading() {
|
||||
return _state == State.LOADING;
|
||||
}
|
||||
|
||||
public bool is_success() {
|
||||
return _state == State.SUCCESS;
|
||||
}
|
||||
|
||||
public bool is_error() {
|
||||
return _state == State.ERROR;
|
||||
}
|
||||
|
||||
public void set_idle() {
|
||||
_state = State.IDLE;
|
||||
_data = null;
|
||||
_message = null;
|
||||
_error = null;
|
||||
}
|
||||
|
||||
public void set_loading() {
|
||||
_state = State.LOADING;
|
||||
_data = null;
|
||||
_message = null;
|
||||
_error = null;
|
||||
}
|
||||
|
||||
public void set_success(T data) {
|
||||
_state = State.SUCCESS;
|
||||
_data = data;
|
||||
_message = null;
|
||||
_error = null;
|
||||
}
|
||||
|
||||
public void set_error(string message, Error? error = null) {
|
||||
_state = State.ERROR;
|
||||
_message = message;
|
||||
_error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
native-route/linux/src/viewmodel/FeedViewModel.vala
Normal file
70
native-route/linux/src/viewmodel/FeedViewModel.vala
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* FeedViewModel.vala
|
||||
*
|
||||
* ViewModel for feed state management
|
||||
*/
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* FeedViewModel - Manages feed state for UI binding
|
||||
*/
|
||||
public class FeedViewModel : Object {
|
||||
private FeedRepository repository;
|
||||
private State<FeedItem[]> feedState;
|
||||
private State<int> unreadCountState;
|
||||
|
||||
public FeedViewModel(FeedRepository repository) {
|
||||
this.repository = repository;
|
||||
this.feedState = new State<FeedItem[]>();
|
||||
this.unreadCountState = new State<int>();
|
||||
}
|
||||
|
||||
public State<FeedItem[]> get_feed_state() {
|
||||
return feedState;
|
||||
}
|
||||
|
||||
public State<int> get_unread_count_state() {
|
||||
return unreadCountState;
|
||||
}
|
||||
|
||||
public void load_feed_items(string? subscription_id = null) {
|
||||
feedState.set_loading();
|
||||
repository.get_feed_items(subscription_id, (state) => {
|
||||
feedState = state;
|
||||
});
|
||||
}
|
||||
|
||||
public void load_unread_count(string? subscription_id = null) {
|
||||
unreadCountState.set_loading();
|
||||
try {
|
||||
var count = repository.get_unread_count(subscription_id);
|
||||
unreadCountState.set_success(count);
|
||||
} catch (Error e) {
|
||||
unreadCountState.set_error("Failed to load unread count", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void mark_as_read(string id, bool is_read) {
|
||||
try {
|
||||
repository.mark_as_read(id, is_read);
|
||||
load_unread_count();
|
||||
} catch (Error e) {
|
||||
unreadCountState.set_error("Failed to update read state", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void mark_as_starred(string id, bool is_starred) {
|
||||
try {
|
||||
repository.mark_as_starred(id, is_starred);
|
||||
} catch (Error e) {
|
||||
feedState.set_error("Failed to update starred state", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh(string? subscription_id = null) {
|
||||
load_feed_items(subscription_id);
|
||||
load_unread_count(subscription_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
native-route/linux/src/viewmodel/SubscriptionViewModel.vala
Normal file
83
native-route/linux/src/viewmodel/SubscriptionViewModel.vala
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SubscriptionViewModel.vala
|
||||
*
|
||||
* ViewModel for subscription state management
|
||||
*/
|
||||
|
||||
namespace RSSuper {
|
||||
|
||||
/**
|
||||
* SubscriptionViewModel - Manages subscription state for UI binding
|
||||
*/
|
||||
public class SubscriptionViewModel : Object {
|
||||
private SubscriptionRepository repository;
|
||||
private State<FeedSubscription[]> subscriptionsState;
|
||||
private State<FeedSubscription[]> enabledSubscriptionsState;
|
||||
|
||||
public SubscriptionViewModel(SubscriptionRepository repository) {
|
||||
this.repository = repository;
|
||||
this.subscriptionsState = new State<FeedSubscription[]>();
|
||||
this.enabledSubscriptionsState = new State<FeedSubscription[]>();
|
||||
}
|
||||
|
||||
public State<FeedSubscription[]> get_subscriptions_state() {
|
||||
return subscriptionsState;
|
||||
}
|
||||
|
||||
public State<FeedSubscription[]> get_enabled_subscriptions_state() {
|
||||
return enabledSubscriptionsState;
|
||||
}
|
||||
|
||||
public void load_all_subscriptions() {
|
||||
subscriptionsState.set_loading();
|
||||
repository.get_all_subscriptions((state) => {
|
||||
subscriptionsState = state;
|
||||
});
|
||||
}
|
||||
|
||||
public void load_enabled_subscriptions() {
|
||||
enabledSubscriptionsState.set_loading();
|
||||
repository.get_enabled_subscriptions((state) => {
|
||||
enabledSubscriptionsState = state;
|
||||
});
|
||||
}
|
||||
|
||||
public void set_enabled(string id, bool enabled) {
|
||||
try {
|
||||
repository.set_enabled(id, enabled);
|
||||
load_enabled_subscriptions();
|
||||
} catch (Error e) {
|
||||
enabledSubscriptionsState.set_error("Failed to update subscription enabled state", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void set_error(string id, string? error) {
|
||||
try {
|
||||
repository.set_error(id, error);
|
||||
} catch (Error e) {
|
||||
subscriptionsState.set_error("Failed to set subscription error", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void update_last_fetched_at(string id, ulong last_fetched_at) {
|
||||
try {
|
||||
repository.update_last_fetched_at(id, last_fetched_at);
|
||||
} catch (Error e) {
|
||||
subscriptionsState.set_error("Failed to update last fetched time", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void update_next_fetch_at(string id, ulong next_fetch_at) {
|
||||
try {
|
||||
repository.update_next_fetch_at(id, next_fetch_at);
|
||||
} catch (Error e) {
|
||||
subscriptionsState.set_error("Failed to update next fetch time", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
load_all_subscriptions();
|
||||
load_enabled_subscriptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user