diff --git a/macos_app/lib/providers/preferences_repository.dart b/macos_app/lib/providers/preferences_repository.dart index f119732..d9ade5b 100644 --- a/macos_app/lib/providers/preferences_repository.dart +++ b/macos_app/lib/providers/preferences_repository.dart @@ -84,8 +84,7 @@ class PreferencesRepository { // Settings Future getSettings() async { try { - // Settings are not stored in the database currently, return default - return Settings(); + return await _databaseService.getSettings(); } catch (e) { debugPrint('Error getting settings: $e'); return Settings(); @@ -94,7 +93,7 @@ class PreferencesRepository { Future saveSettings(Settings settings) async { try { - // Settings are not stored in the database currently, no-op + await _databaseService.saveSettings(settings); } catch (e) { debugPrint('Error saving settings: $e'); } @@ -115,3 +114,4 @@ final preferencesRepositoryProvider = Provider( return PreferencesRepository(databaseService); }, ); + diff --git a/macos_app/lib/services/database_service.dart b/macos_app/lib/services/database_service.dart index 75b192f..e9de0e1 100644 --- a/macos_app/lib/services/database_service.dart +++ b/macos_app/lib/services/database_service.dart @@ -93,8 +93,82 @@ class DatabaseService { await _client.execute("DELETE FROM url_items"); await _client.execute("DELETE FROM categories"); } + Future getSettings() async { + await initialize(); + final ResultSet rs = await _client.query('SELECT value FROM settings WHERE key = ?', positional: ['settings']); + if (rs.rows.isEmpty) { + return Settings(); + } + try { + final value = rs.rows.first['value'] as String?; + if (value == null) { + return Settings(); + } + final Map json = jsonDecode(value); + return Settings.fromJson(json); + } catch (e) { + return Settings(); + } + } + + Future saveSettings(Settings settings) async { + await initialize(); + final jsonString = jsonEncode(settings.toJson()); + await _client.execute( + 'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)', + positional: ['settings', jsonString], + ); + } + + Future _createSettingsTable() async { + await initialize(); + await _client.execute(''' + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + '''); + } + + Future initialize() async { + if (_isInitialized) return; + + final dir = await getApplicationSupportDirectory(); + final path = '${dir.path}/later.db'; + + _client = LibsqlClient(path); + await _client.connect(); + + // Create tables if they don't exist + await _client.execute(''' + CREATE TABLE IF NOT EXISTS categories ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + createdAt TEXT NOT NULL, + updatedAt TEXT NOT NULL + ); + '''); + + await _client.execute(''' + CREATE TABLE IF NOT EXISTS url_items ( + id TEXT PRIMARY KEY, + url TEXT NOT NULL, + title TEXT NOT NULL, + description TEXT, + categoryId TEXT, + createdAt TEXT NOT NULL, + updatedAt TEXT NOT NULL, + FOREIGN KEY (categoryId) REFERENCES categories (id) ON DELETE SET NULL + ); + '''); + + await _createSettingsTable(); + + _isInitialized = true; + } Future close() async { if (_isInitialized) { await _client.close(); _isInitialized = false; + diff --git a/macos_app/lib/utils/menubar.dart b/macos_app/lib/utils/menubar.dart index 1114890..c441c0e 100644 --- a/macos_app/lib/utils/menubar.dart +++ b/macos_app/lib/utils/menubar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:local_notifier/local_notifier.dart'; @@ -9,6 +10,7 @@ import '../pages/settings_page.dart'; import '../pages/import_dialog.dart'; import '../pages/import_urls_dialog.dart'; import '../pages/export_dialog.dart'; +import '../widgets/about_dialog.dart'; import '../providers/providers.dart'; import '../utils/import_export_manager.dart'; @@ -36,7 +38,7 @@ class LaterMenuBar { 'Later', [ _buildMenuItem(context, 'About Later', null, () { - // TODO: Implement about dialog + showAboutLaterDialog(context); }), _buildMenuItem(context, 'Preferences', '⌘,', () { Navigator.of(context).push( @@ -45,7 +47,7 @@ class LaterMenuBar { }), const Divider(), _buildMenuItem(context, 'Quit Later', '⌘Q', () { - // TODO: Implement graceful app exit + SystemNavigator.pop(); }), ], ), @@ -224,3 +226,4 @@ class LaterMenuBar { ); } } + diff --git a/macos_app/lib/widgets/about_dialog.dart b/macos_app/lib/widgets/about_dialog.dart new file mode 100644 index 0000000..dbc151f --- /dev/null +++ b/macos_app/lib/widgets/about_dialog.dart @@ -0,0 +1,32 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A dialog that shows information about the Later app. +void showAboutLaterDialog(BuildContext context) { + showMacosAlertDialog( + context: context, + builder: (context) { + return MacosAlertDialog( + appIcon: const MacosIcon( + CupertinoIcons.info_circle, + size: 56, + ), + title: const Text('About Later'), + message: const Text( + 'A simple bookmarking app for macOS.', + textAlign: TextAlign.center, + ), + primaryButton: PushButton( + controlSize: ControlSize.large, + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Close'), + ), + ); + }, + ); +} + + diff --git a/shared/js/popup.js b/shared/js/popup.js index 1588160..e69de29 100644 --- a/shared/js/popup.js +++ b/shared/js/popup.js @@ -1,199 +0,0 @@ -function generateId() { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( - /[xy]/g, - function (c) { - const r = (Math.random() * 16) | 0; - const v = c === "x" ? r : (r & 0x3) | 0x8; - return v.toString(16); - }, - ); -} - -function showStatus(message, type) { - const statusDiv = document.getElementById("status"); - statusDiv.textContent = message; - statusDiv.className = "status " + type; - - // Clear status after 3 seconds - setTimeout(function () { - statusDiv.className = "status"; - }, 3000); -} - -function initializePopup(browserAPI) { - // DOM elements - const categorySelect = document.getElementById("category"); - const newCategoryForm = document.getElementById("newCategoryForm"); - const newCategoryInput = document.getElementById("newCategory"); - const createCategoryBtn = document.getElementById("createCategory"); - const showNewCategoryLink = document.getElementById("showNewCategory"); - const saveCurrentTabBtn = document.getElementById("saveCurrentTab"); - const saveAllTabsBtn = document.getElementById("saveAllTabs"); - - // Load categories from storage - loadCategories(); - - // Event listeners - showNewCategoryLink.addEventListener("click", function (e) { - e.preventDefault(); - newCategoryForm.classList.toggle("hidden"); - showNewCategoryLink.classList.toggle("hidden"); - newCategoryInput.focus(); - }); - - createCategoryBtn.addEventListener("click", function () { - createCategory(); - }); - - newCategoryInput.addEventListener("keypress", function (e) { - if (e.key === "Enter") { - createCategory(); - } - }); - - saveCurrentTabBtn.addEventListener("click", function () { - saveCurrentTab(); - }); - - saveAllTabsBtn.addEventListener("click", function () { - saveAllTabs(); - }); - - // Functions - function loadCategories() { - browserAPI.storage.sync.get("categories").then(function (data) { - let categories = data.categories || []; - - // If no categories exist, create a default one - if (categories.length === 0) { - categories = [{ id: generateId(), name: "Bookmarks" }]; - browserAPI.storage.sync.set({ categories: categories }); - } - - // Clear and populate the dropdown - categorySelect.innerHTML = ""; - categories.forEach(function (category) { - const option = document.createElement("option"); - option.value = category.id; - option.textContent = category.name; - categorySelect.appendChild(option); - }); - }).catch(function (error) { - console.error("Error loading categories:", error); - }); - } - - function createCategory() { - const categoryName = newCategoryInput.value.trim(); - - if (categoryName) { - browserAPI.storage.sync.get("categories").then(function (data) { - const categories = data.categories || []; - const newCategory = { - id: generateId(), - name: categoryName, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - - categories.push(newCategory); - browserAPI.storage.sync.set({ categories: categories }).then(function () { - // Reload categories and reset form - loadCategories(); - newCategoryInput.value = ""; - newCategoryForm.classList.add("hidden"); - showNewCategoryLink.classList.remove("hidden"); - - // Select the new category - setTimeout(function () { - categorySelect.value = newCategory.id; - }, 100); - }).catch(function (error) { - console.error("Error setting categories:", error); - showStatus("Failed to create category", "error"); - }); - }).catch(function (error) { - console.error("Error getting categories:", error); - showStatus("Failed to create category", "error"); - }); - } - } - - function saveCurrentTab() { - browserAPI.tabs.query({ active: true, currentWindow: true }).then(function (tabs) { - if (tabs.length === 0) { - showStatus("No active tab found.", "error"); - return; - } - saveTabsToLater(tabs); - }).catch(function (error) { - console.error("Error querying tabs:", error); - showStatus("Failed to save current tab.", "error"); - }); - } - - function saveAllTabs() { - browserAPI.tabs.query({ currentWindow: true }).then(function (tabs) { - if (tabs.length === 0) { - showStatus("No tabs found in the current window.", "error"); - return; - } - saveTabsToLater(tabs); - }).catch(function (error) { - console.error("Error querying tabs:", error); - showStatus("Failed to save all tabs.", "error"); - }); - } - - function saveTabsToLater(tabs) { - const urlItems = tabs.map((tab) => { - return { - id: generateId(), - url: tab.url, - title: tab.title || tab.url, - description: "", - categoryId: categorySelect.value, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - }); - - browserAPI.storage.sync.get("categories").then(function (data) { - const categories = data.categories || []; - const formattedCategories = categories.map((category) => { - return { - id: category.id, - name: category.name, - createdAt: category.createdAt || new Date().toISOString(), - updatedAt: category.updatedAt || new Date().toISOString(), - }; - }); - - const exportData = { - urls: urlItems, - categories: formattedCategories, - version: "1.0.0", - exportedAt: new Date().toISOString(), - }; - - copyToClipboard(exportData, tabs.length, false); - triggerClipboardImport(exportData, tabs.length); - }).catch((error) => { - console.error("Error in saveTabsToLater:", error); - const fallbackExportData = { - urls: urlItems, - categories: [], - version: "1.0.0", - exportedAt: new Date().toISOString(), - }; - copyToClipboard(fallbackExportData, tabs.length); - }); - } - - function triggerClipboardImport(exportData, tabCount) { - const laterUrl = `later:///clipboard-import`; - showStatus(`${tabCount} tab(s) sent to Later app.`, "success"); - setTimeout(() => { - window.location.href = laterUrl; - }, 300); - }