From 5f23176e6eb8047b07332830d8395d9419b0063c Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Sat, 27 Aug 2022 02:16:53 +0200 Subject: [PATCH 1/2] add comment drafts --- lib/main_common.dart | 2 ++ lib/stores/comment_drafts_store.dart | 24 +++++++++++++++++++++ lib/widgets/write_comment.dart | 31 +++++++++++++++++++++++++++- pubspec.lock | 21 +++++++++++++++++++ pubspec.yaml | 4 +++- 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 lib/stores/comment_drafts_store.dart diff --git a/lib/main_common.dart b/lib/main_common.dart index 0dec2f12..f8d1d69c 100644 --- a/lib/main_common.dart +++ b/lib/main_common.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:hive_flutter/adapters.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -19,6 +20,7 @@ Future mainCommon(AppConfig appConfig) async { final logConsoleStore = LogConsolePageStore(); final sharedPrefs = await SharedPreferences.getInstance(); + await Hive.initFlutter(); _setupLogger(appConfig, logConsoleStore); _setupTimeago(); diff --git a/lib/stores/comment_drafts_store.dart b/lib/stores/comment_drafts_store.dart new file mode 100644 index 00000000..45d1027f --- /dev/null +++ b/lib/stores/comment_drafts_store.dart @@ -0,0 +1,24 @@ +import 'package:hive/hive.dart'; + +class CommentDraftStore { + static const _boxKey = 'comment_drafts'; + + static Future loadDraft(String apId) async { + final box = await Hive.openBox(_boxKey); + final text = box.get(apId); + await box.close(); + return text; + } + + static Future saveDraft(String apId, String text) async { + final box = await Hive.openBox(_boxKey); + await box.put(apId, text); + await box.close(); + } + + static Future removeDraft(String apId) async { + final box = await Hive.openBox(_boxKey); + await box.delete(apId); + await box.close(); + } +} diff --git a/lib/widgets/write_comment.dart b/lib/widgets/write_comment.dart index 960a95b7..8bc36eb4 100644 --- a/lib/widgets/write_comment.dart +++ b/lib/widgets/write_comment.dart @@ -5,6 +5,7 @@ import 'package:lemmy_api_client/v3.dart'; import '../hooks/delayed_loading.dart'; import '../hooks/logged_in_action.dart'; import '../l10n/l10n.dart'; +import '../stores/comment_drafts_store.dart'; import 'editor/editor.dart'; import 'markdown_mode_icon.dart'; import 'markdown_text.dart'; @@ -40,6 +41,20 @@ class WriteComment extends HookWidget { text: _isEdit ? comment?.content : null, ); + // load draft if exists + useEffect(() { + () async { + if (_isEdit) return; + + final previousDraft = + await CommentDraftStore.loadDraft(comment?.apId ?? post.apId); + if (previousDraft != null) { + editorController.textEditingController.text = previousDraft; + } + }(); + return null; + }, []); + final preview = () { final body = () { final text = comment?.content ?? post.body; @@ -84,6 +99,10 @@ class WriteComment extends HookWidget { )); } }(); + + // remove draft because it's not needed anymore + await CommentDraftStore.removeDraft(comment?.apId ?? post.apId); + Navigator.of(context).pop(res.commentView); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( @@ -94,7 +113,17 @@ class WriteComment extends HookWidget { return Scaffold( appBar: AppBar( - leading: const CloseButton(), + leading: CloseButton( + onPressed: () async { + // save draft before closing + if (!_isEdit && + editorController.textEditingController.text.trim().isNotEmpty) { + await CommentDraftStore.saveDraft(comment?.apId ?? post.apId, + editorController.textEditingController.text); + } + Navigator.of(context).pop(); + }, + ), actions: [ IconButton( icon: markdownModeIcon(fancy: showFancy.value), diff --git a/pubspec.lock b/pubspec.lock index 22571acf..55a65c45 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -350,6 +350,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.3" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d997b902..8ea31e24 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,8 @@ dependencies: provider: ^6.0.0 mobx: ^2.0.4 flutter_mobx: ^2.0.2 - + hive: ^2.2.3 + hive_flutter: ^1.1.0 # utils timeago: ^3.0.2 fuzzy: ^0.4.0-nullsafety.0 @@ -71,6 +72,7 @@ dev_dependencies: build_runner: ^2.1.2 mobx_codegen: ^2.0.2 freezed: ^2.0.2 + hive_generator: ^1.1.3 flutter_icons: android: true From 89df32f4a948ce772564b4089985576203447627 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Mon, 29 Aug 2022 01:50:09 +0200 Subject: [PATCH 2/2] add drafts section to settings --- lib/main_common.dart | 2 + lib/pages/settings/drafts_page.dart | 140 +++++++++++++++++++++++++++ lib/pages/settings/settings.dart | 8 ++ lib/stores/comment_drafts_store.dart | 41 +++++--- lib/widgets/write_comment.dart | 13 ++- 5 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 lib/pages/settings/drafts_page.dart diff --git a/lib/main_common.dart b/lib/main_common.dart index f8d1d69c..6ed901fd 100644 --- a/lib/main_common.dart +++ b/lib/main_common.dart @@ -12,6 +12,7 @@ import 'app_config.dart'; import 'l10n/timeago/pl.dart'; import 'pages/log_console/log_console_page_store.dart'; import 'stores/accounts_store.dart'; +import 'stores/comment_drafts_store.dart'; import 'stores/config_store.dart'; import 'util/mobx_provider.dart'; @@ -21,6 +22,7 @@ Future mainCommon(AppConfig appConfig) async { final logConsoleStore = LogConsolePageStore(); final sharedPrefs = await SharedPreferences.getInstance(); await Hive.initFlutter(); + await CommentDraftStore.open(); _setupLogger(appConfig, logConsoleStore); _setupTimeago(); diff --git a/lib/pages/settings/drafts_page.dart b/lib/pages/settings/drafts_page.dart new file mode 100644 index 00000000..1c8d7582 --- /dev/null +++ b/lib/pages/settings/drafts_page.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hive/hive.dart'; + +import '../../stores/comment_drafts_store.dart'; + +class DraftsPage extends HookWidget { + const DraftsPage._(); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + leading: const BackButton(), + title: const Text('Drafts'), + bottom: const TabBar( + isScrollable: true, + tabs: [ + Tab(child: Text('Comments')), + Tab(child: Text('Posts')), + ], + ), + ), + body: const TabBarView(children: [ + _CommentsTab(), + _PostsTab(), + ])), + ); + } + + static Route route() => MaterialPageRoute( + builder: (context) => const DraftsPage._(), + ); +} + +class _CommentsTab extends HookWidget { + const _CommentsTab(); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder>( + valueListenable: CommentDraftStore.allDraftsListenable(), + builder: (context, box, widget) { + if (box.isEmpty) { + return const Center(child: Text('no drafts yet')); + } + + Future removeAllDrafts() async { + final removeAll = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text( + 'Do you want to remove ALL comment drafts?'), + actions: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + style: ElevatedButton.styleFrom( + primary: Colors.red, + ), + child: const Text('Yes'), + ), + OutlinedButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text('No'), + ), + ], + )) ?? + false; + if (removeAll) { + await CommentDraftStore.removeAllDrafts(); + } + } + + return ListView.builder( + itemCount: box.length + 1, + itemBuilder: (context, index) { + if (index == box.length) { + return Padding( + padding: const EdgeInsets.all(16), + child: ElevatedButton( + onPressed: removeAllDrafts, + style: ElevatedButton.styleFrom( + primary: Colors.red, + ), + child: const Text('Remove all drafts'), + ), + ); + } + return _CommentDraftTile(CommentDraftStore.keyAt(index)!); + }, + ); + }, + ); + } +} + +class _CommentDraftTile extends HookWidget { + final String databaseKey; + + const _CommentDraftTile(this.databaseKey); + + @override + Widget build(BuildContext context) { + final body = useState(null); + useEffect(() { + CommentDraftStore.loadDraft(databaseKey) + .then((value) => body.value = value); + return null; + }); + + return ListTile( + key: ValueKey(key), + title: body.value == null + ? const CircularProgressIndicator.adaptive() + : Text(body.value!), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + CommentDraftStore.removeDraft(databaseKey); + }, + ), + subtitle: Text(databaseKey), + ); + } +} + +class _PostsTab extends StatelessWidget { + const _PostsTab(); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('TBD')); + } +} diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 8cbc4eb2..af22052b 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -16,6 +16,7 @@ import '../manage_account.dart'; import 'add_account_page.dart'; import 'add_instance_page.dart'; import 'blocks/blocks.dart'; +import 'drafts_page.dart'; /// Page with a list of different settings sections class SettingsPage extends HookWidget { @@ -60,6 +61,13 @@ class SettingsPage extends HookWidget { goTo(context, (_) => const AppearanceConfigPage()); }, ), + ListTile( + leading: const Icon(Icons.drive_file_rename_outline_outlined), + title: const Text('Drafts'), + onTap: () { + Navigator.of(context).push(DraftsPage.route()); + }, + ), const AboutTile() ], ), diff --git a/lib/stores/comment_drafts_store.dart b/lib/stores/comment_drafts_store.dart index 45d1027f..9e77e9d5 100644 --- a/lib/stores/comment_drafts_store.dart +++ b/lib/stores/comment_drafts_store.dart @@ -1,24 +1,37 @@ -import 'package:hive/hive.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hive_flutter/adapters.dart'; class CommentDraftStore { static const _boxKey = 'comment_drafts'; + static late LazyBox _box; - static Future loadDraft(String apId) async { - final box = await Hive.openBox(_boxKey); - final text = box.get(apId); - await box.close(); - return text; + static Future open() async { + _box = await Hive.openLazyBox(_boxKey); } - static Future saveDraft(String apId, String text) async { - final box = await Hive.openBox(_boxKey); - await box.put(apId, text); - await box.close(); + static Future close() async { + await _box.compact(); + await _box.close(); } - static Future removeDraft(String apId) async { - final box = await Hive.openBox(_boxKey); - await box.delete(apId); - await box.close(); + static Future compact() async { + await _box.compact(); + } + + static String? keyAt(int index) => _box.keyAt(index); + + static ValueListenable> allDraftsListenable() => + _box.listenable(); + + static Future loadDraft(String apId) => _box.get(apId); + + static Future saveDraft(String apId, String text) => + _box.put(apId, text); + + static Future removeDraft(String apId) => _box.delete(apId); + + static Future removeAllDrafts() async { + await _box.deleteFromDisk(); + await open(); } } diff --git a/lib/widgets/write_comment.dart b/lib/widgets/write_comment.dart index 8bc36eb4..2d563673 100644 --- a/lib/widgets/write_comment.dart +++ b/lib/widgets/write_comment.dart @@ -116,10 +116,15 @@ class WriteComment extends HookWidget { leading: CloseButton( onPressed: () async { // save draft before closing - if (!_isEdit && - editorController.textEditingController.text.trim().isNotEmpty) { - await CommentDraftStore.saveDraft(comment?.apId ?? post.apId, - editorController.textEditingController.text); + if (!_isEdit) { + if (editorController.textEditingController.text + .trim() + .isNotEmpty) { + await CommentDraftStore.saveDraft(comment?.apId ?? post.apId, + editorController.textEditingController.text); + } else { + await CommentDraftStore.removeDraft(comment?.apId ?? post.apId); + } } Navigator.of(context).pop(); },