diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-08 00:05:39 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-08 00:05:39 +0300 |
| commit | 3e4bacb7b939b1de98a2fb07115f638750637357 (patch) | |
| tree | 0897a6854d1c3ca3b1d0252657e992abbba061ba /lib/screens/home_screen.dart | |
| parent | 665b150727ee7259e398d38d1f6bc51b4c2d4a1c (diff) | |
Rewrite as Flutter app and rename to Quicklogmain
Drop the Go/Fyne implementation and replace it with a native Flutter app
targeting Android (primary) and Linux desktop (development). Rename
quicklogger -> quicklog throughout (project, applicationId, MethodChannel,
cache filename, AppBar title, Linux binary).
Storage on Android moves to /Android/data/org.buetow.quicklog/files/ so
MANAGE_EXTERNAL_STORAGE is no longer required. Share-intent receive is
preserved via a Kotlin ShareActivity that writes shared text to the app
cache, which Dart reads through a MethodChannel on app resume; MainActivity
also accepts SEND directly so launchers that prefer the main target work.
New since the Fyne version: an entry browser screen with read-only viewer
and long-press delete.
Cross-compiling to Android ARM from amd64 Linux no longer needs Docker /
fyne-cross / NDK -- flutter build apk emits ARM binaries directly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'lib/screens/home_screen.dart')
| -rw-r--r-- | lib/screens/home_screen.dart | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart new file mode 100644 index 0000000..86dd4d0 --- /dev/null +++ b/lib/screens/home_screen.dart @@ -0,0 +1,230 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import '../services/log_service.dart'; +import '../services/preferences.dart'; +import '../services/share_service.dart'; +import '../services/shared_text_handler.dart'; +import 'entry_browser_screen.dart'; +import 'preferences_screen.dart'; + +const int kMaxTextLength = 5000; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State<HomeScreen> createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver { + final TextEditingController _controller = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + final PreferencesService _prefs = PreferencesService(); + bool _warnShown = false; + bool _loadingShared = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _controller.addListener(_onTextChanged); + if (Platform.isAndroid) { + WidgetsBinding.instance.addPostFrameCallback((_) => _loadSharedText()); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _controller.removeListener(_onTextChanged); + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed && Platform.isAndroid) { + _loadSharedText(); + } + } + + void _onTextChanged() { + final length = _controller.text.length; + if (_loadingShared) { + _warnShown = false; + setState(() {}); + return; + } + if (length > kMaxTextLength && !_warnShown) { + _warnShown = true; + _showLengthWarning(length); + } else if (length <= kMaxTextLength) { + _warnShown = false; + } + setState(() {}); + } + + void _showLengthWarning(int length) { + showDialog<void>( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Text Limit'), + content: Text( + 'Text is getting long ($length chars). Consider logging to avoid ' + 'performance issues.', + ), + actions: [ + TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('OK')), + ], + ), + ); + } + + void _resetInput() { + _controller.clear(); + _warnShown = false; + setState(() {}); + } + + Future<void> _logText() async { + final dir = await _prefs.directory(); + try { + await logEntry(dir, _controller.text); + _resetInput(); + } catch (e) { + _showError(e); + } + } + + void _showError(Object error) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $error'), backgroundColor: Colors.red), + ); + } + + void _showInfo(String title, String message) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); + } + + Future<void> _loadSharedText() async { + final txt = await ShareService.readSharedTextFromCache(); + if (txt == null || txt.isEmpty) return; + _loadingShared = true; + final dir = await _prefs.directory(); + final autoLog = await _prefs.autoLogSharedText(); + await handleSharedTextLoad( + text: txt, + autoLog: autoLog, + dir: dir, + prefill: (s) { + _controller.text = s; + _controller.selection = TextSelection.collapsed(offset: s.length); + }, + focus: () => _focusNode.requestFocus(), + resetInput: _resetInput, + clearCache: ShareService.clearSharedTextCache, + logFn: (d, t) async { + await logEntry(d, t); + }, + showInfo: _showInfo, + showError: _showError, + ); + _loadingShared = false; + if (mounted) setState(() {}); + } + + Future<void> _openPreferences() async { + await Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const PreferencesScreen()), + ); + } + + Future<void> _openEntryBrowser() async { + await Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const EntryBrowserScreen()), + ); + } + + void _showAbout() { + showAboutDialog( + context: context, + applicationName: 'Quicklog', + applicationVersion: '0.1.2', + applicationIcon: Image.asset('logo-small.png', width: 48, height: 48), + applicationLegalese: 'Jot timestamped markdown notes.', + ); + } + + @override + Widget build(BuildContext context) { + final length = _controller.text.length; + return Scaffold( + appBar: AppBar( + title: const Text('Quicklog'), + actions: [ + IconButton( + tooltip: 'Browse entries', + icon: const Icon(Icons.list), + onPressed: _openEntryBrowser, + ), + IconButton( + tooltip: 'Preferences', + icon: const Icon(Icons.settings), + onPressed: _openPreferences, + ), + IconButton( + tooltip: 'About', + icon: const Icon(Icons.info_outline), + onPressed: _showAbout, + ), + ], + ), + body: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TextField( + controller: _controller, + focusNode: _focusNode, + maxLines: null, + expands: true, + textAlignVertical: TextAlignVertical.top, + decoration: const InputDecoration( + hintText: 'Enter text here...', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(height: 8), + Row( + children: [ + FilledButton.icon( + onPressed: _logText, + icon: const Icon(Icons.save), + label: const Text('Log text'), + ), + const SizedBox(width: 8), + OutlinedButton( + onPressed: () { + _resetInput(); + _focusNode.requestFocus(); + }, + child: const Text('Clear'), + ), + const Spacer(), + Text('$length chars'), + ], + ), + ], + ), + ), + ); + } +} |
