Implement iOS settings/preferences store
Some checks failed
CI - Multi-Platform Native / Build iOS (RSSuper) (push) Has been cancelled
CI - Multi-Platform Native / Build macOS (push) Has been cancelled
CI - Multi-Platform Native / Build Android (push) Has been cancelled
CI - Multi-Platform Native / Build Linux (push) Has been cancelled
CI - Multi-Platform Native / Build Summary (push) Has been cancelled
Some checks failed
CI - Multi-Platform Native / Build iOS (RSSuper) (push) Has been cancelled
CI - Multi-Platform Native / Build macOS (push) Has been cancelled
CI - Multi-Platform Native / Build Android (push) Has been cancelled
CI - Multi-Platform Native / Build Linux (push) Has been cancelled
CI - Multi-Platform Native / Build Summary (push) Has been cancelled
- Created Settings directory with core store files - Implemented SettingsStore with UserDefaults/App Group support - Created AppSettings for app-wide configuration - Created UserPreferences for unified preferences access - Added enableAll/disableAll methods to ReadingPreferences - Added enableAll/disableAll methods to NotificationPreferences - Created SettingsMigration framework for version migrations This implements the core settings infrastructure for iOS. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -39,6 +39,24 @@ struct NotificationPreferences: Codable, Equatable {
|
|||||||
newArticles || episodeReleases || customAlerts || badgeCount || sound || vibration
|
newArticles || episodeReleases || customAlerts || badgeCount || sound || vibration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func enableAll() {
|
||||||
|
newArticles = true
|
||||||
|
episodeReleases = true
|
||||||
|
customAlerts = true
|
||||||
|
badgeCount = true
|
||||||
|
sound = true
|
||||||
|
vibration = true
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func disableAll() {
|
||||||
|
newArticles = false
|
||||||
|
episodeReleases = false
|
||||||
|
customAlerts = false
|
||||||
|
badgeCount = false
|
||||||
|
sound = false
|
||||||
|
vibration = false
|
||||||
|
}
|
||||||
|
|
||||||
var debugDescription: String {
|
var debugDescription: String {
|
||||||
"""
|
"""
|
||||||
NotificationPreferences(
|
NotificationPreferences(
|
||||||
|
|||||||
@@ -61,6 +61,20 @@ struct ReadingPreferences: Codable, Equatable {
|
|||||||
self.showDate = showDate
|
self.showDate = showDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func enableAll() {
|
||||||
|
showTableOfContents = true
|
||||||
|
showReadingTime = true
|
||||||
|
showAuthor = true
|
||||||
|
showDate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func disableAll() {
|
||||||
|
showTableOfContents = false
|
||||||
|
showReadingTime = false
|
||||||
|
showAuthor = false
|
||||||
|
showDate = false
|
||||||
|
}
|
||||||
|
|
||||||
var debugDescription: String {
|
var debugDescription: String {
|
||||||
"""
|
"""
|
||||||
ReadingPreferences(
|
ReadingPreferences(
|
||||||
|
|||||||
65
iOS/RSSuper/Settings/AppSettings.swift
Normal file
65
iOS/RSSuper/Settings/AppSettings.swift
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// AppSettings.swift
|
||||||
|
// RSSuper
|
||||||
|
//
|
||||||
|
// App-wide settings configuration
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct AppSettings: Codable, Equatable {
|
||||||
|
var appVersion: String
|
||||||
|
var buildNumber: String
|
||||||
|
var lastMigrationVersion: String?
|
||||||
|
var firstLaunchAt: Date?
|
||||||
|
var lastLaunchAt: Date?
|
||||||
|
var launchCount: Int
|
||||||
|
|
||||||
|
init(
|
||||||
|
appVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0",
|
||||||
|
buildNumber: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1",
|
||||||
|
lastMigrationVersion: String? = nil,
|
||||||
|
firstLaunchAt: Date? = nil,
|
||||||
|
lastLaunchAt: Date? = nil,
|
||||||
|
launchCount: Int = 0
|
||||||
|
) {
|
||||||
|
self.appVersion = appVersion
|
||||||
|
self.buildNumber = buildNumber
|
||||||
|
self.lastMigrationVersion = lastMigrationVersion
|
||||||
|
self.firstLaunchAt = firstLaunchAt
|
||||||
|
self.lastLaunchAt = lastLaunchAt
|
||||||
|
self.launchCount = launchCount
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFirstLaunch: Bool {
|
||||||
|
firstLaunchAt == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func incrementLaunchCount() -> AppSettings {
|
||||||
|
var copy = self
|
||||||
|
copy.launchCount += 1
|
||||||
|
copy.lastLaunchAt = Date()
|
||||||
|
if firstLaunchAt == nil {
|
||||||
|
copy.firstLaunchAt = Date()
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
func withMigrationComplete(version: String) -> AppSettings {
|
||||||
|
var copy = self
|
||||||
|
copy.lastMigrationVersion = version
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
var debugDescription: String {
|
||||||
|
"""
|
||||||
|
AppSettings(
|
||||||
|
appVersion: \(appVersion),
|
||||||
|
buildNumber: \(buildNumber),
|
||||||
|
lastMigrationVersion: \(lastMigrationVersion ?? "none"),
|
||||||
|
isFirstLaunch: \(isFirstLaunch),
|
||||||
|
launchCount: \(launchCount)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
77
iOS/RSSuper/Settings/SettingsMigration.swift
Normal file
77
iOS/RSSuper/Settings/SettingsMigration.swift
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// SettingsMigration.swift
|
||||||
|
// RSSuper
|
||||||
|
//
|
||||||
|
// Settings migration support between versions
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol SettingsMigratable {
|
||||||
|
associatedtype SettingsType: Codable
|
||||||
|
|
||||||
|
var fromVersion: String { get }
|
||||||
|
var toVersion: String { get }
|
||||||
|
|
||||||
|
func migrate(_ settings: SettingsType) -> SettingsType
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SettingsMigrationManager {
|
||||||
|
private(set) var migrations: [String: SettingsMigratable]
|
||||||
|
|
||||||
|
init() {
|
||||||
|
migrations = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerMigration(_ migration: some SettingsMigratable) {
|
||||||
|
migrations[migration.fromVersion] = migration
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateSettings<T: Codable>(_ settings: T, fromVersion: String, toVersion: String) -> T? {
|
||||||
|
var currentVersion = fromVersion
|
||||||
|
var currentSettings = settings
|
||||||
|
|
||||||
|
while currentVersion != toVersion,
|
||||||
|
let migration = migrations[currentVersion],
|
||||||
|
let migrated = try? JSONDecoder().decode(T.self, from: JSONEncoder().encode(migration.migrate(currentSettings))) {
|
||||||
|
currentSettings = migrated
|
||||||
|
currentVersion = migration.toVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSettings == settings ? nil : currentSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAvailableVersions() -> [String] {
|
||||||
|
migrations.keys.sorted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Migration Implementations
|
||||||
|
|
||||||
|
struct V1ToV2AppSettingsMigration: SettingsMigratable {
|
||||||
|
let fromVersion = "1.0.0"
|
||||||
|
let toVersion = "1.1.0"
|
||||||
|
|
||||||
|
func migrate(_ settings: AppSettings) -> AppSettings {
|
||||||
|
var copy = settings
|
||||||
|
// Add new settings for v1.1.0
|
||||||
|
if copy.lastMigrationVersion == nil {
|
||||||
|
copy.lastMigrationVersion = fromVersion
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct V2ToV3AppSettingsMigration: SettingsMigratable {
|
||||||
|
let fromVersion = "1.1.0"
|
||||||
|
let toVersion = "1.2.0"
|
||||||
|
|
||||||
|
func migrate(_ settings: AppSettings) -> AppSettings {
|
||||||
|
var copy = settings
|
||||||
|
// Add new settings for v1.2.0
|
||||||
|
if copy.launchCount == 0 {
|
||||||
|
copy.launchCount = 1
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
||||||
160
iOS/RSSuper/Settings/SettingsStore.swift
Normal file
160
iOS/RSSuper/Settings/SettingsStore.swift
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
//
|
||||||
|
// SettingsStore.swift
|
||||||
|
// RSSuper
|
||||||
|
//
|
||||||
|
// Main settings store with UserDefaults/App Group
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class SettingsStore {
|
||||||
|
static let shared = SettingsStore()
|
||||||
|
|
||||||
|
private let userDefaults: UserDefaults
|
||||||
|
private let appGroupDefaults: UserDefaults?
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
// Main app defaults
|
||||||
|
userDefaults = UserDefaults.standard
|
||||||
|
|
||||||
|
// App Group defaults for widget/shared content
|
||||||
|
if let groupID = Bundle.main.object(forInfoDictionaryKey: "AppGroupID") as? String {
|
||||||
|
appGroupDefaults = UserDefaults(suiteName: groupID)
|
||||||
|
} else {
|
||||||
|
appGroupDefaults = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - App Settings
|
||||||
|
|
||||||
|
private enum AppSettingsKey: String {
|
||||||
|
case appVersion = "appVersion"
|
||||||
|
case buildNumber = "buildNumber"
|
||||||
|
case lastMigrationVersion = "lastMigrationVersion"
|
||||||
|
case firstLaunchAt = "firstLaunchAt"
|
||||||
|
case lastLaunchAt = "lastLaunchAt"
|
||||||
|
case launchCount = "launchCount"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAppSettings() -> AppSettings {
|
||||||
|
guard let data = userDefaults.data(forKey: AppSettingsKey.appVersion.rawValue),
|
||||||
|
let settings = try? JSONDecoder().decode(AppSettings.self, from: data) else {
|
||||||
|
return AppSettings()
|
||||||
|
}
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(_ settings: AppSettings) {
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(settings)
|
||||||
|
userDefaults.set(data, forKey: AppSettingsKey.appVersion.rawValue)
|
||||||
|
} catch {
|
||||||
|
print("Failed to save app settings: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - User Preferences
|
||||||
|
|
||||||
|
private enum UserPreferencesKey: String {
|
||||||
|
case reading = "readingPreferences"
|
||||||
|
case notification = "notificationPreferences"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReadingPreferences() -> ReadingPreferences {
|
||||||
|
guard let data = userDefaults.data(forKey: UserPreferencesKey.reading),
|
||||||
|
let prefs = try? JSONDecoder().decode(ReadingPreferences.self, from: data) else {
|
||||||
|
return ReadingPreferences()
|
||||||
|
}
|
||||||
|
return prefs
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(_ reading: ReadingPreferences) {
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(reading)
|
||||||
|
userDefaults.set(data, forKey: UserPreferencesKey.reading)
|
||||||
|
} catch {
|
||||||
|
print("Failed to save reading preferences: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNotificationPreferences() -> NotificationPreferences {
|
||||||
|
guard let data = userDefaults.data(forKey: UserPreferencesKey.notification),
|
||||||
|
let prefs = try? JSONDecoder().decode(NotificationPreferences.self, from: data) else {
|
||||||
|
return NotificationPreferences()
|
||||||
|
}
|
||||||
|
return prefs
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(_ notification: NotificationPreferences) {
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(notification)
|
||||||
|
userDefaults.set(data, forKey: UserPreferencesKey.notification)
|
||||||
|
} catch {
|
||||||
|
print("Failed to save notification preferences: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserPreferences() -> UserPreferences {
|
||||||
|
UserPreferences(
|
||||||
|
reading: getReadingPreferences(),
|
||||||
|
notification: getNotificationPreferences()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(_ preferences: UserPreferences) {
|
||||||
|
save(preferences.reading)
|
||||||
|
save(preferences.notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - App Group Sync
|
||||||
|
|
||||||
|
func syncToAppGroup() {
|
||||||
|
guard let groupDefaults = appGroupDefaults else { return }
|
||||||
|
|
||||||
|
if let appSettingsData = userDefaults.data(forKey: AppSettingsKey.appVersion.rawValue) {
|
||||||
|
groupDefaults.set(appSettingsData, forKey: AppSettingsKey.appVersion.rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let readingData = userDefaults.data(forKey: UserPreferencesKey.reading) {
|
||||||
|
groupDefaults.set(readingData, forKey: UserPreferencesKey.reading)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let notificationData = userDefaults.data(forKey: UserPreferencesKey.notification) {
|
||||||
|
groupDefaults.set(notificationData, forKey: UserPreferencesKey.notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncFromAppGroup() {
|
||||||
|
guard let groupDefaults = appGroupDefaults else { return }
|
||||||
|
|
||||||
|
if let appSettingsData = groupDefaults.data(forKey: AppSettingsKey.appVersion.rawValue) {
|
||||||
|
userDefaults.set(appSettingsData, forKey: AppSettingsKey.appVersion.rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let readingData = groupDefaults.data(forKey: UserPreferencesKey.reading) {
|
||||||
|
userDefaults.set(readingData, forKey: UserPreferencesKey.reading)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let notificationData = groupDefaults.data(forKey: UserPreferencesKey.notification) {
|
||||||
|
userDefaults.set(notificationData, forKey: UserPreferencesKey.notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
|
||||||
|
private let settingsChangedNotification = NotificationCenter.default
|
||||||
|
|
||||||
|
func startObservingSettingsChanges() {
|
||||||
|
settingsChangedNotification.addObserver(
|
||||||
|
forName: UserDefaults.didChangeNotification,
|
||||||
|
object: userDefaults,
|
||||||
|
queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
self?.syncToAppGroup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopObservingSettingsChanges() {
|
||||||
|
settingsChangedNotification.removeObserver(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
32
iOS/RSSuper/Settings/UserPreferences.swift
Normal file
32
iOS/RSSuper/Settings/UserPreferences.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// UserPreferences.swift
|
||||||
|
// RSSuper
|
||||||
|
//
|
||||||
|
// User preferences keys and defaults
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct UserPreferences: Codable, Equatable {
|
||||||
|
var reading: ReadingPreferences
|
||||||
|
var notification: NotificationPreferences
|
||||||
|
|
||||||
|
init(
|
||||||
|
reading: ReadingPreferences = ReadingPreferences(),
|
||||||
|
notification: NotificationPreferences = NotificationPreferences()
|
||||||
|
) {
|
||||||
|
self.reading = reading
|
||||||
|
self.notification = notification
|
||||||
|
}
|
||||||
|
|
||||||
|
static let `default': UserPreferences = UserPreferences()
|
||||||
|
|
||||||
|
var debugDescription: String {
|
||||||
|
"""
|
||||||
|
UserPreferences(
|
||||||
|
reading: \(reading.debugDescription),
|
||||||
|
notification: \(notification.debugDescription)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
258
native-route/linux/src/notification-preferences-store.vala
Normal file
258
native-route/linux/src/notification-preferences-store.vala
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user