Implement whole ass "backend"
This commit is contained in:
86
lib/db.dart
Normal file
86
lib/db.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
64
lib/notes.dart
Normal 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});
|
||||||
|
}
|
24
pubspec.lock
24
pubspec.lock
@@ -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:
|
||||||
|
@@ -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:
|
||||||
|
Reference in New Issue
Block a user