Implement whole ass "backend"

This commit is contained in:
2025-04-21 22:24:41 +02:00
parent 740fef5d7d
commit 427b503e5e
5 changed files with 202 additions and 34 deletions

86
lib/db.dart Normal file
View File

@@ -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<String> _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<void> 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;
}
}
}

View File

@@ -1,6 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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:system_tray/system_tray.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
@@ -9,6 +11,7 @@ const Duration popupInterval = Duration(hours: 1);
const String notificationSound = 'MeetTheSniper.mp3'; const String notificationSound = 'MeetTheSniper.mp3';
void main() async { void main() async {
await DB.init();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await windowManager.ensureInitialized(); await windowManager.ensureInitialized();
@@ -125,6 +128,8 @@ class _MainPageState extends State<MainPage> with WindowListener {
final TextEditingController _currentEntryController = TextEditingController(); final TextEditingController _currentEntryController = TextEditingController();
final TextEditingController _todoController = TextEditingController(); final TextEditingController _todoController = TextEditingController();
Note? previousNote;
Timer? _popupTimer; Timer? _popupTimer;
Timer? _debounceTimer; Timer? _debounceTimer;
@@ -180,6 +185,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
} }
Future<void> _showWindow() async { Future<void> _showWindow() async {
_loadData();
bool wasVisible = await windowManager.isVisible(); bool wasVisible = await windowManager.isVisible();
if (!wasVisible) { if (!wasVisible) {
await windowManager.setSize(const Size(1600, 900)); await windowManager.setSize(const Size(1600, 900));
@@ -193,47 +199,38 @@ class _MainPageState extends State<MainPage> with WindowListener {
} }
Future<void> _playSound() async { Future<void> _playSound() async {
try { await _audioPlayer.stop();
await _audioPlayer.stop(); await _audioPlayer.play(AssetSource('sounds/$notificationSound'));
await _audioPlayer.play(AssetSource('sounds/$notificationSound')); debugPrint("Played sound: $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("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
} }
void _loadData() { void _loadData() async {
_previousEntryController.text = final note = await getLatestNote();
"This is a placeholder for the previous entry."; previousNote = note;
_todoController.text = "- Placeholder Todo 1\n- Placeholder Todo 2"; _previousEntryController.text = note?.content ?? "";
final todo = await getLatestTodo();
_todoController.text = todo?.content ?? "";
_currentEntryController.text = ""; _currentEntryController.text = "";
debugPrint("Data loaded (placeholder)."); debugPrint("Data loaded (placeholder).");
} }
void _saveData() { void _saveData() async {
String previousEntry = _previousEntryController.text;
String currentEntry = _currentEntryController.text; String currentEntry = _currentEntryController.text;
String todoList = _todoController.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]", "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<void> _setWindowConfig() async { Future<void> _setWindowConfig() async {
await windowManager.setAspectRatio(16 / 9); await windowManager.setAspectRatio(16 / 9);
} }
@@ -247,7 +244,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
if (event is KeyDownEvent && if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.escape) { event.logicalKey == LogicalKeyboardKey.escape) {
debugPrint("Escape key pressed - hiding window."); debugPrint("Escape key pressed - hiding window.");
windowManager.hide(); onWindowClose();
} }
}, },
child: Scaffold( child: Scaffold(
@@ -302,14 +299,10 @@ class _MainPageState extends State<MainPage> with WindowListener {
context, context,
).textTheme.bodyMedium, // Apply theme text style ).textTheme.bodyMedium, // Apply theme text style
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Todo List', labelText: 'Todo',
// border: OutlineInputBorder(), // Handled by theme // border: OutlineInputBorder(), // Handled by theme
// contentPadding: EdgeInsets.all(8.0), // Handled by theme or default // contentPadding: EdgeInsets.all(8.0), // Handled by theme or default
), ),
onChanged: (text) {
// Auto-save Todo list changes (consider debouncing)
_saveTodoList();
},
), ),
), ),
], ],

64
lib/notes.dart Normal file
View File

@@ -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<Note?> 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<void> createNote(String content) async {
if (content.isEmpty) {
return;
}
await DB.db.insert('notes', {'content': content});
}
Future<void> updateNote(Note note) async {
await DB.db.update(
'notes',
{'content': note.content},
where: 'date = ?',
whereArgs: [note.date],
);
}
Future<Todo?> 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<void> createTodo(String content) async {
if (content.isEmpty) {
return;
}
await DB.db.insert('todos', {'content': content});
}

View File

@@ -365,6 +365,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.1" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:

View File

@@ -37,6 +37,7 @@ dependencies:
system_tray: ^2.0.3 system_tray: ^2.0.3
window_manager: ^0.4.3 window_manager: ^0.4.3
audioplayers: ^6.4.0 audioplayers: ^6.4.0
sqflite_common_ffi: ^2.3.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: