diff --git a/lib/db.dart b/lib/db.dart index 1aff4d8..8392f6f 100644 --- a/lib/db.dart +++ b/lib/db.dart @@ -28,6 +28,11 @@ CREATE TABLE IF NOT EXISTS scratches ( ); CREATE INDEX IF NOT EXISTS idx_scratches_date ON scratches (date); CREATE UNIQUE INDEX IF NOT EXISTS idx_scratches_date_unique ON scratches (date); + +CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY NOT NULL, + value TEXT NOT NULL +); '''; static Future _getDatabasePath() async { @@ -83,4 +88,27 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_scratches_date_unique ON scratches (date); rethrow; } } + + // Settings Management + static Future getSetting(String key) async { + final List> maps = await db.query( + 'settings', + columns: ['value'], + where: 'key = ?', + whereArgs: [key], + ); + if (maps.isNotEmpty) { + return maps.first['value'] as String?; + } + return null; + } + + static Future setSetting(String key, String value) async { + await db.insert( + 'settings', + {'key': key, 'value': value}, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + debugPrint("Setting updated: $key = $value"); + } } diff --git a/lib/main.dart b/lib/main.dart index 462592b..1c04f39 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,13 +8,11 @@ import 'package:window_manager/window_manager.dart'; import 'package:audioplayers/audioplayers.dart'; // TODO: Add an icon to the executable, simply use the existing tray icon -// TODO: Add an entry field for the duration ie. the interval of apperance -// TODO: Also the sound file, if possible... -// TODO: Cram the above into the database // TODO: Implement some sort of scroll through notes -const Duration popupInterval = Duration(minutes: 20); -const String notificationSound = 'MeetTheSniper.mp3'; +// Default values - will be replaced by DB values if they exist +const Duration _defaultPopupInterval = Duration(minutes: 20); +const String _defaultNotificationSound = 'MeetTheSniper.mp3'; void main() async { await DB.init(); @@ -135,8 +133,12 @@ class MainPageState extends State with WindowListener { final TextEditingController _currentEntryController = TextEditingController(); final FocusNode _currentEntryFocusNode = FocusNode(); final TextEditingController _scratchController = TextEditingController(); + final TextEditingController _intervalController = TextEditingController(); + final TextEditingController _soundController = TextEditingController(); Note? previousNote; + Duration _currentPopupInterval = _defaultPopupInterval; + String _currentNotificationSound = _defaultNotificationSound; Timer? _popupTimer; Timer? _debounceTimer; @@ -147,7 +149,6 @@ class MainPageState extends State with WindowListener { windowManager.addListener(this); _initSystemTray(); _loadData(); - _startPopupTimer(); windowManager.setPreventClose(true); _setWindowConfig(); } @@ -161,6 +162,8 @@ class MainPageState extends State with WindowListener { _currentEntryController.dispose(); _currentEntryFocusNode.dispose(); _scratchController.dispose(); + _intervalController.dispose(); + _soundController.dispose(); _audioPlayer.dispose(); super.dispose(); } @@ -201,9 +204,11 @@ class MainPageState extends State with WindowListener { } void _startPopupTimer() { - _popupTimer = Timer.periodic(popupInterval, (timer) { + _popupTimer?.cancel(); + _popupTimer = Timer.periodic(_currentPopupInterval, (timer) { _showWindow(); }); + debugPrint("Popup timer started with interval: ${_currentPopupInterval.inMinutes} minutes"); } Future _showWindow() async { @@ -224,11 +229,27 @@ class MainPageState extends State with WindowListener { Future _playSound() async { await _audioPlayer.stop(); - await _audioPlayer.play(AssetSource('sounds/$notificationSound')); - debugPrint("Played sound: $notificationSound"); + try { + await _audioPlayer.play(AssetSource('sounds/$_currentNotificationSound')); + debugPrint("Played sound: $_currentNotificationSound"); + } catch (e) { + debugPrint("Error playing sound $_currentNotificationSound: e"); + } } void _loadData() async { + String? intervalMinutesStr = await DB.getSetting('popupIntervalMinutes'); + String? soundFileStr = await DB.getSetting('notificationSound'); + + int intervalMinutes = int.tryParse(intervalMinutesStr ?? '') ?? _defaultPopupInterval.inMinutes; + _currentPopupInterval = Duration(minutes: intervalMinutes); + _currentNotificationSound = soundFileStr ?? _defaultNotificationSound; + + _intervalController.text = intervalMinutes.toString(); + _soundController.text = _currentNotificationSound; + + _startPopupTimer(); + final note = await getLatestNote(); previousNote = note; _previousEntryController.text = note?.content ?? ""; @@ -244,6 +265,8 @@ class MainPageState extends State with WindowListener { String previousEntry = _previousEntryController.text; String currentEntry = _currentEntryController.text; String scratchContent = _scratchController.text; + String intervalStr = _intervalController.text; + String soundStr = _soundController.text; await createNote(currentEntry); await createScratch(scratchContent); @@ -252,6 +275,23 @@ class MainPageState extends State with WindowListener { await updateNote(previousNote!); } + int newIntervalMinutes = int.tryParse(intervalStr) ?? _currentPopupInterval.inMinutes; + Duration newInterval = Duration(minutes: newIntervalMinutes); + if (newInterval != _currentPopupInterval) { + _currentPopupInterval = newInterval; + DB.setSetting('popupIntervalMinutes', newIntervalMinutes.toString()); + _startPopupTimer(); + } else { + DB.setSetting('popupIntervalMinutes', newIntervalMinutes.toString()); + } + + if (soundStr != _currentNotificationSound) { + _currentNotificationSound = soundStr; + DB.setSetting('notificationSound', soundStr); + } else { + DB.setSetting('notificationSound', soundStr); + } + debugPrint( "Saving data... Current Entry: [${currentEntry.length} chars], Scratch: [${scratchContent.length} chars]", ); @@ -278,7 +318,72 @@ class MainPageState extends State with WindowListener { } }, child: Scaffold( - appBar: AppBar(title: const Text('Journaler'), actions: const []), + appBar: AppBar( + title: const Text('Journaler'), + actions: [ + // Group Label and Input for Interval + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0).copyWith(left: 8.0), // Add padding + child: Row( + mainAxisSize: MainAxisSize.min, // Use minimum space + children: [ + const Text("Interval (m):"), + const SizedBox(width: 4), // Space between label and input + SizedBox( + width: 60, // Constrain width + child: TextField( + controller: _intervalController, + // textAlignVertical: TextAlignVertical.center, // Let default alignment handle it + decoration: const InputDecoration( + border: OutlineInputBorder(), + isDense: true, + contentPadding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + ), + ), + ], + ), + ), + // Group Label and Input for Sound + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Sound:"), + const SizedBox(width: 4), + SizedBox( + width: 150, // Constrain width + child: TextField( + controller: _soundController, + // textAlignVertical: TextAlignVertical.center, + decoration: const InputDecoration( + border: OutlineInputBorder(), + isDense: true, + contentPadding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + hintText: 'sound.mp3', + ), + ), + ), + ], + ), + ), + // Test Sound Button + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: IconButton( + icon: const Icon(Icons.volume_up), + tooltip: 'Test Sound', + onPressed: _playSound, + ), + ), + const SizedBox(width: 10), + ], + ), body: Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -312,7 +417,6 @@ class MainPageState extends State with WindowListener { decoration: const InputDecoration( labelText: 'Current Entry (What\'s on your mind?)', ), - onChanged: (text) {}, ), ), ], @@ -320,19 +424,14 @@ class MainPageState extends State with WindowListener { ), const SizedBox(width: 8), Expanded( - flex: 3, + flex: 4, // Adjust flex factor as needed child: TextField( controller: _scratchController, maxLines: null, expands: true, - style: - Theme.of( - context, - ).textTheme.bodyMedium, // Apply theme text style + style: Theme.of(context).textTheme.bodyMedium, // Apply theme text style decoration: const InputDecoration( labelText: 'Scratch', - // border: OutlineInputBorder(), // Handled by theme - // contentPadding: EdgeInsets.all(8.0), // Handled by theme or default ), ), ),