import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:system_tray/system_tray.dart'; import 'package:window_manager/window_manager.dart'; import 'package:audioplayers/audioplayers.dart'; const Duration popupInterval = Duration(hours: 1); const String notificationSound = 'MeetTheSniper.mp3'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await windowManager.ensureInitialized(); WindowOptions windowOptions = const WindowOptions( size: Size(1600, 900), center: true, backgroundColor: Colors.transparent, skipTaskbar: false, titleBarStyle: TitleBarStyle.normal, ); windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.hide(); }); runApp(const JournalerApp()); } class JournalerApp extends StatelessWidget { const JournalerApp({super.key}); static final TextTheme _baseTextTheme = const TextTheme( bodyMedium: TextStyle(fontSize: 24), ); static final TextStyle _baseTitleTextStyle = const TextStyle( fontSize: 20, fontWeight: FontWeight.w500, ); static final InputDecorationTheme _baseInputDecorationTheme = const InputDecorationTheme( border: OutlineInputBorder(), labelStyle: TextStyle(fontSize: 20), ); static final ThemeData lightTheme = ThemeData.light().copyWith( visualDensity: VisualDensity.adaptivePlatformDensity, colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.light, ), scaffoldBackgroundColor: Colors.grey[100], appBarTheme: AppBarTheme( backgroundColor: Colors.blue, foregroundColor: Colors.white, titleTextStyle: _baseTitleTextStyle.copyWith(color: Colors.white), ), inputDecorationTheme: _baseInputDecorationTheme.copyWith( focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.blue.shade600, width: 2.0), ), labelStyle: _baseInputDecorationTheme.labelStyle?.copyWith( color: Colors.grey[700], ), ), textTheme: _baseTextTheme, ); static final ThemeData darkTheme = ThemeData.dark().copyWith( visualDensity: VisualDensity.adaptivePlatformDensity, colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), scaffoldBackgroundColor: Colors.grey[900], appBarTheme: AppBarTheme( backgroundColor: Colors.grey[850], foregroundColor: Colors.white, titleTextStyle: _baseTitleTextStyle.copyWith(color: Colors.white), ), inputDecorationTheme: _baseInputDecorationTheme.copyWith( border: const OutlineInputBorder( borderSide: BorderSide(color: Colors.grey), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.blue.shade300, width: 2.0), ), labelStyle: _baseInputDecorationTheme.labelStyle?.copyWith( color: Colors.grey[400], ), ), textTheme: _baseTextTheme.copyWith( bodyMedium: _baseTextTheme.bodyMedium?.copyWith(color: Colors.white), ), ); @override Widget build(BuildContext context) { return MaterialApp( title: 'Journaler', theme: lightTheme, darkTheme: darkTheme, themeMode: ThemeMode.system, home: const MainPage(), debugShowCheckedModeBanner: false, ); } } class MainPage extends StatefulWidget { const MainPage({super.key}); @override State createState() => _MainPageState(); } class _MainPageState extends State with WindowListener { final SystemTray _systemTray = SystemTray(); final AudioPlayer _audioPlayer = AudioPlayer(); final TextEditingController _previousEntryController = TextEditingController(); final TextEditingController _currentEntryController = TextEditingController(); final TextEditingController _todoController = TextEditingController(); Timer? _popupTimer; Timer? _debounceTimer; @override void initState() { super.initState(); windowManager.addListener(this); _initSystemTray(); _loadData(); _startPopupTimer(); windowManager.setPreventClose(true); _setWindowConfig(); } @override void dispose() { windowManager.removeListener(this); _popupTimer?.cancel(); _debounceTimer?.cancel(); _previousEntryController.dispose(); _currentEntryController.dispose(); _todoController.dispose(); _audioPlayer.dispose(); super.dispose(); } @override void onWindowClose() { _saveData(); windowManager.hide(); } @override void onWindowFocus() { setState(() {}); } Future _initSystemTray() async { String iconPath = 'assets/app_icon.ico'; await _systemTray.initSystemTray(iconPath: iconPath, toolTip: "Journaler"); _systemTray.registerSystemTrayEventHandler((eventName) { debugPrint("System Tray Event: $eventName"); _showWindow(); }); } void _startPopupTimer() { _popupTimer = Timer.periodic(popupInterval, (timer) { _showWindow(); }); } Future _showWindow() async { bool wasVisible = await windowManager.isVisible(); if (!wasVisible) { await windowManager.setSize(const Size(1600, 900)); await windowManager.center(); await windowManager.show(); await windowManager.focus(); await _playSound(); } else { await windowManager.focus(); } } 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("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } } void _loadData() { _previousEntryController.text = "This is a placeholder for the previous entry."; _todoController.text = "- Placeholder Todo 1\n- Placeholder Todo 2"; _currentEntryController.text = ""; debugPrint("Data loaded (placeholder)."); } void _saveData() { String currentEntry = _currentEntryController.text; String todoList = _todoController.text; print( "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); } @override Widget build(BuildContext context) { return KeyboardListener( focusNode: FocusNode(), autofocus: true, onKeyEvent: (event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) { debugPrint("Escape key pressed - hiding window."); windowManager.hide(); } }, child: Scaffold( appBar: AppBar(title: const Text('Journaler'), actions: const []), body: Padding( padding: const EdgeInsets.all(8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( flex: 9, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: TextField( controller: _previousEntryController, maxLines: null, expands: true, style: Theme.of(context).textTheme.bodyMedium, decoration: const InputDecoration( labelText: 'Previous Entry', ), ), ), const SizedBox(height: 8), Expanded( child: TextField( controller: _currentEntryController, maxLines: null, expands: true, autofocus: true, style: Theme.of(context).textTheme.bodyMedium, decoration: const InputDecoration( labelText: 'Current Entry (What\'s on your mind?)', ), onChanged: (text) {}, ), ), ], ), ), const SizedBox(width: 8), Expanded( flex: 3, child: TextField( controller: _todoController, maxLines: null, expands: true, style: Theme.of( context, ).textTheme.bodyMedium, // Apply theme text style decoration: const InputDecoration( labelText: 'Todo List', // 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(); }, ), ), ], ), ), ), ); } }