diff --git a/lib/db.dart b/lib/db.dart new file mode 100644 index 0000000..7a86873 --- /dev/null +++ b/lib/db.dart @@ -0,0 +1,86 @@ +import 'dart:io' show Platform, Directory; +import 'package:flutter/material.dart'; +import 'package:path/path.dart' as path; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + +const settingsDir = '.journaler'; +const dbFileName = 'journaler.db'; + +class DB { + static late Database db; + + static const String _schema = ''' +CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT DEFAULT CURRENT_TIMESTAMP, + content TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_notes_date ON notes (date); +CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_date_unique ON notes (date); +-- Todos are "static", we are only interested in the latest entry +-- ie. they're not really a list but instead a string +-- But we will also keep a history of all todos +-- Because we might as well +CREATE TABLE IF NOT EXISTS todos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT DEFAULT CURRENT_TIMESTAMP, + content TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_todos_date ON todos (date); +CREATE UNIQUE INDEX IF NOT EXISTS idx_todos_date_unique ON todos (date); +'''; + + static Future _getDatabasePath() async { + debugPrint('Attempting to get database path...'); + if (Platform.isWindows || Platform.isLinux) { + // Get user's home directory + final home = + Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; + if (home == null) { + throw Exception('Could not find home directory'); + } + debugPrint('Home directory found: home'); + + final dbDir = Directory(path.join(home, settingsDir)); + if (!await dbDir.exists()) { + await dbDir.create(recursive: true); + debugPrint('$settingsDir directory created'); + } else { + debugPrint('$settingsDir directory already exists'); + } + + return path.join(dbDir.path, dbFileName); + } else { + // Default path for other platforms + final databasesPath = await databaseFactoryFfi.getDatabasesPath(); + debugPrint('Using default databases path: databasesPath'); + return path.join(databasesPath, dbFileName); + } + } + + static Future init() async { + debugPrint('Starting database initialization...'); + sqfliteFfiInit(); + + final dbPath = await _getDatabasePath(); + debugPrint('Database path: dbPath'); + + try { + db = await databaseFactoryFfi.openDatabase( + dbPath, + options: OpenDatabaseOptions( + version: 1, + onCreate: (db, version) async { + debugPrint('Creating database schema...'); + await db.execute(_schema); + debugPrint('Database schema created successfully'); + }, + ), + ); + debugPrint('Database opened and initialized'); + } catch (e) { + debugPrint('Failed to initialize database: e'); + rethrow; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index bdd27d8..655e226 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:journaler/db.dart'; +import 'package:journaler/notes.dart'; import 'package:system_tray/system_tray.dart'; import 'package:window_manager/window_manager.dart'; import 'package:audioplayers/audioplayers.dart'; @@ -9,6 +11,7 @@ const Duration popupInterval = Duration(hours: 1); const String notificationSound = 'MeetTheSniper.mp3'; void main() async { + await DB.init(); WidgetsFlutterBinding.ensureInitialized(); await windowManager.ensureInitialized(); @@ -125,6 +128,8 @@ class _MainPageState extends State with WindowListener { final TextEditingController _currentEntryController = TextEditingController(); final TextEditingController _todoController = TextEditingController(); + Note? previousNote; + Timer? _popupTimer; Timer? _debounceTimer; @@ -180,6 +185,7 @@ class _MainPageState extends State with WindowListener { } Future _showWindow() async { + _loadData(); bool wasVisible = await windowManager.isVisible(); if (!wasVisible) { await windowManager.setSize(const Size(1600, 900)); @@ -193,47 +199,38 @@ class _MainPageState extends State with WindowListener { } Future _playSound() async { - try { - await _audioPlayer.stop(); - await _audioPlayer.play(AssetSource('sounds/$notificationSound')); - debugPrint("Played sound: $notificationSound"); - } catch (e, stackTrace) { - debugPrint("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - debugPrint("Error playing sound '$notificationSound': $e"); - debugPrint("Stack trace: $stackTrace"); - debugPrint( - "Ensure file exists, is valid audio, and assets/sounds/ is in pubspec.yaml", - ); - debugPrint("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - } + await _audioPlayer.stop(); + await _audioPlayer.play(AssetSource('sounds/$notificationSound')); + debugPrint("Played sound: $notificationSound"); } - void _loadData() { - _previousEntryController.text = - "This is a placeholder for the previous entry."; - _todoController.text = "- Placeholder Todo 1\n- Placeholder Todo 2"; + void _loadData() async { + final note = await getLatestNote(); + previousNote = note; + _previousEntryController.text = note?.content ?? ""; + + final todo = await getLatestTodo(); + _todoController.text = todo?.content ?? ""; _currentEntryController.text = ""; + debugPrint("Data loaded (placeholder)."); } - void _saveData() { + void _saveData() async { + String previousEntry = _previousEntryController.text; String currentEntry = _currentEntryController.text; String todoList = _todoController.text; - print( + await createNote(currentEntry); + await createTodo(todoList); + previousNote!.content = previousEntry; + await updateNote(previousNote!); + + debugPrint( "Saving data (placeholder)... Current Entry: [${currentEntry.length} chars], Todo: [${todoList.length} chars]", ); } - void _saveTodoList() { - if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel(); - - _debounceTimer = Timer(const Duration(milliseconds: 500), () { - String todoList = _todoController.text; - print("Debounced Save: Saving Todo list... [${todoList.length} chars]"); - }); - } - Future _setWindowConfig() async { await windowManager.setAspectRatio(16 / 9); } @@ -247,7 +244,7 @@ class _MainPageState extends State with WindowListener { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) { debugPrint("Escape key pressed - hiding window."); - windowManager.hide(); + onWindowClose(); } }, child: Scaffold( @@ -302,14 +299,10 @@ class _MainPageState extends State with WindowListener { context, ).textTheme.bodyMedium, // Apply theme text style decoration: const InputDecoration( - labelText: 'Todo List', + labelText: 'Todo', // border: OutlineInputBorder(), // Handled by theme // contentPadding: EdgeInsets.all(8.0), // Handled by theme or default ), - onChanged: (text) { - // Auto-save Todo list changes (consider debouncing) - _saveTodoList(); - }, ), ), ], diff --git a/lib/notes.dart b/lib/notes.dart new file mode 100644 index 0000000..72f523a --- /dev/null +++ b/lib/notes.dart @@ -0,0 +1,64 @@ +import 'package:journaler/db.dart'; + +class Note { + final String date; + String content; + + Note({required this.date, required this.content}); +} + +class Todo { + final String date; + final String content; + + Todo({required this.date, required this.content}); +} + +Future getLatestNote() async { + final note = await DB.db.rawQuery( + 'SELECT content, date FROM notes ORDER BY date DESC LIMIT 1', + ); + if (note.isEmpty) { + return null; + } + return Note( + date: note[0]['date'] as String, + content: note[0]['content'] as String, + ); +} + +Future createNote(String content) async { + if (content.isEmpty) { + return; + } + await DB.db.insert('notes', {'content': content}); +} + +Future updateNote(Note note) async { + await DB.db.update( + 'notes', + {'content': note.content}, + where: 'date = ?', + whereArgs: [note.date], + ); +} + +Future getLatestTodo() async { + final todo = await DB.db.rawQuery( + 'SELECT content, date FROM todos ORDER BY date DESC LIMIT 1', + ); + if (todo.isEmpty) { + return null; + } + return Todo( + date: todo[0]['date'] as String, + content: todo[0]['content'] as String, + ); +} + +Future createTodo(String content) async { + if (content.isEmpty) { + return; + } + await DB.db.insert('todos', {'content': content}); +} diff --git a/pubspec.lock b/pubspec.lock index 282f9da..68a595e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -365,6 +365,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_common_ffi: + dependency: "direct main" + description: + name: sqflite_common_ffi + sha256: "1f3ef3888d3bfbb47785cc1dda0dc7dd7ebd8c1955d32a9e8e9dae1e38d1c4c1" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e" + url: "https://pub.dev" + source: hosted + version: "2.7.5" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1ec1b6d..850f345 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: system_tray: ^2.0.3 window_manager: ^0.4.3 audioplayers: ^6.4.0 + sqflite_common_ffi: ^2.3.5 dev_dependencies: flutter_test: