Skip to content
This repository has been archived by the owner on Feb 4, 2023. It is now read-only.

Add drafts #339

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/main_common.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -11,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';

Expand All @@ -19,6 +21,8 @@ Future<void> mainCommon(AppConfig appConfig) async {

final logConsoleStore = LogConsolePageStore();
final sharedPrefs = await SharedPreferences.getInstance();
await Hive.initFlutter();
await CommentDraftStore.open();

_setupLogger(appConfig, logConsoleStore);
_setupTimeago();
Expand Down
140 changes: 140 additions & 0 deletions lib/pages/settings/drafts_page.dart
Original file line number Diff line number Diff line change
@@ -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<LazyBox<String>>(
valueListenable: CommentDraftStore.allDraftsListenable(),
builder: (context, box, widget) {
if (box.isEmpty) {
return const Center(child: Text('no drafts yet'));
}

Future<void> removeAllDrafts() async {
final removeAll = await showDialog<bool>(
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<String?>(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'));
}
}
8 changes: 8 additions & 0 deletions lib/pages/settings/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
],
),
Expand Down
37 changes: 37 additions & 0 deletions lib/stores/comment_drafts_store.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/adapters.dart';

class CommentDraftStore {
static const _boxKey = 'comment_drafts';
static late LazyBox<String> _box;

static Future<void> open() async {
_box = await Hive.openLazyBox<String>(_boxKey);
}

static Future<void> close() async {
await _box.compact();
await _box.close();
}

static Future<void> compact() async {
await _box.compact();
}

static String? keyAt(int index) => _box.keyAt(index);

static ValueListenable<LazyBox<String>> allDraftsListenable() =>
_box.listenable();

static Future<String?> loadDraft(String apId) => _box.get(apId);

static Future<void> saveDraft(String apId, String text) =>
_box.put(apId, text);

static Future<void> removeDraft(String apId) => _box.delete(apId);

static Future<void> removeAllDrafts() async {
await _box.deleteFromDisk();
await open();
}
}
36 changes: 35 additions & 1 deletion lib/widgets/write_comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -94,7 +113,22 @@ class WriteComment extends HookWidget {

return Scaffold(
appBar: AppBar(
leading: const CloseButton(),
leading: CloseButton(
onPressed: () async {
// save draft before closing
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();
},
),
actions: [
IconButton(
icon: markdownModeIcon(fancy: showFancy.value),
Expand Down
21 changes: 21 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down