Skip to content

Commit

Permalink
Use hive for persisting data
Browse files Browse the repository at this point in the history
Replace shared_preferences for saving settings
Threadstate saving proof of concept with scrolled-to post
  • Loading branch information
moffatman committed Mar 31, 2021
1 parent d13328a commit d65d0f4
Show file tree
Hide file tree
Showing 17 changed files with 471 additions and 140 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,7 @@ app.*.symbols
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!/dev/ci/**/Gemfile.lock

# Generated model adapter classes (Hive)
*.g.dart
10 changes: 7 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:chan/services/persistence.dart';
import 'package:chan/services/settings.dart';
import 'package:cupertino_back_gesture/cupertino_back_gesture.dart';
import 'package:flutter/cupertino.dart';
Expand All @@ -7,7 +8,10 @@ import 'sites/4chan.dart';
import 'pages/tab.dart';
import 'package:provider/provider.dart';

void main() => runApp(ChanApp());
void main() async {
await Persistence.initialize();
runApp(ChanApp());
}

class ChanApp extends StatelessWidget {
@override
Expand All @@ -16,7 +20,7 @@ class ChanApp extends StatelessWidget {
backGestureWidth: BackGestureWidth.fraction(1),
child: MultiProvider(
providers: [
ChangeNotifierProvider<Settings>(create: (_) => Settings()),
ChangeNotifierProvider<EffectiveSettings>(create: (_) => EffectiveSettings()),
Provider<ImageboardSite>(create: (_) => Site4Chan(
baseUrl: 'boards.4chan.org',
sysUrl: 'sys.4chan.org',
Expand All @@ -29,7 +33,7 @@ class ChanApp extends StatelessWidget {
child: SettingsSystemListener(
child: Builder(
builder: (BuildContext context) {
final brightness = context.watch<Settings>().theme;
final brightness = context.watch<EffectiveSettings>().theme;
CupertinoThemeData theme = CupertinoThemeData(brightness: Brightness.light, primaryColor: Colors.black);
if (brightness == Brightness.dark) {
theme = CupertinoThemeData(brightness: Brightness.dark, scaffoldBackgroundColor: Color.fromRGBO(20, 20, 20, 1), primaryColor: Colors.white);
Expand Down
4 changes: 3 additions & 1 deletion lib/main_desktop.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// @dart=2.9
import 'package:chan/services/persistence.dart';
import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride;
import 'package:flutter/material.dart';

import './main.dart';

void main() {
void main() async {
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
await Persistence.initialize();
runApp(ChanApp());
}
2 changes: 1 addition & 1 deletion lib/models/post_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class PostQuoteLinkSpan extends PostSpan {
final sameAsParent = zone?.parentIds.contains(id) ?? false;
final postList = context.watch<List<Post>>();
final post = postList.firstWhere((p) => p.id == this.id);
final settings = context.watch<Settings>();
final settings = context.watch<EffectiveSettings>();
final newParentIds = (zone?.parentIds ?? []).followedBy([post.id]).toList();
return WidgetSpan(
child: HoverPopup(
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class BoardPage extends StatelessWidget {
),
child: RefreshableList<Thread>(
listUpdater: () => site.getCatalog(board.name).then((list) {
if (context.read<Settings>().hideStickiedThreads) {
if (context.read<EffectiveSettings>().hideStickiedThreads) {
return list.where((thread) => !thread.isSticky).toList();
}
else {
Expand Down
6 changes: 3 additions & 3 deletions lib/pages/gallery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class _GalleryPageState extends State<GalleryPage> {
}
});
});
if (context.read<Settings>().autoloadAttachments) {
if (context.read<EffectiveSettings>().autoloadAttachments) {
requestRealViewer(widget.attachments[currentIndex]);
}
}
Expand Down Expand Up @@ -213,7 +213,7 @@ class _GalleryPageState extends State<GalleryPage> {
void _animateToPage(int index, {int milliseconds = 200}) {
final attachment = widget.attachments[index];
widget.onChange?.call(attachment);
if (context.read<Settings>().autoloadAttachments && statuses[attachment]!.value is AttachmentUnloadedStatus) {
if (context.read<EffectiveSettings>().autoloadAttachments && statuses[attachment]!.value is AttachmentUnloadedStatus) {
requestRealViewer(widget.attachments[index]);
}
setState(() {
Expand All @@ -233,7 +233,7 @@ class _GalleryPageState extends State<GalleryPage> {
void _onPageChanged(int index) {
final attachment = widget.attachments[index];
widget.onChange?.call(attachment);
if (context.read<Settings>().autoloadAttachments && statuses[attachment]!.value is AttachmentUnloadedStatus) {
if (context.read<EffectiveSettings>().autoloadAttachments && statuses[attachment]!.value is AttachmentUnloadedStatus) {
requestRealViewer(widget.attachments[index]);
}
currentIndex = index;
Expand Down
30 changes: 15 additions & 15 deletions lib/pages/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:extended_image_library/extended_image_library.dart';
class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final settings = context.read<Settings>();
final settings = context.read<EffectiveSettings>();
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Settings')
Expand All @@ -23,30 +23,30 @@ class SettingsPage extends StatelessWidget {
padding: EdgeInsets.all(16),
child: Text('Theme'),
),
CupertinoSegmentedControl<Setting_Theme>(
CupertinoSegmentedControl<ThemeSetting>(
children: {
Setting_Theme.Light: Text('Light'),
Setting_Theme.System: Text('Follow System'),
Setting_Theme.Dark: Text('Dark')
ThemeSetting.Light: Text('Light'),
ThemeSetting.System: Text('Follow System'),
ThemeSetting.Dark: Text('Dark')
},
groupValue: settings.themePreference,
groupValue: settings.themeSetting,
onValueChanged: (newValue) {
settings.themePreference = newValue;
settings.themeSetting = newValue;
}
),
Container(
padding: EdgeInsets.all(16),
child: Text('Automatically load attachments'),
),
CupertinoSegmentedControl<Setting_AutoloadAttachments>(
CupertinoSegmentedControl<AutoloadAttachmentsSetting>(
children: {
Setting_AutoloadAttachments.Always: Text('Always'),
Setting_AutoloadAttachments.WiFi: Text('When on Wi-Fi'),
Setting_AutoloadAttachments.Never: Text('Never')
AutoloadAttachmentsSetting.Always: Text('Always'),
AutoloadAttachmentsSetting.WiFi: Text('When on Wi-Fi'),
AutoloadAttachmentsSetting.Never: Text('Never')
},
groupValue: settings.autoloadAttachmentsPreference,
groupValue: settings.autoloadAttachmentsSetting,
onValueChanged: (newValue) {
settings.autoloadAttachmentsPreference = newValue;
settings.autoloadAttachmentsSetting = newValue;
}
),
Container(
Expand Down Expand Up @@ -91,15 +91,15 @@ class _SettingsCachePanelState extends State<SettingsCachePanel> {
Future<void> _readFilesystemInfo() async {
folderSizes = {};
final systemTempDirectory = await getTemporaryDirectory();
await for (final directory in systemTempDirectory.list()) {
/*await for (final directory in systemTempDirectory.list()) {
if (directory is Directory) {
int size = 0;
await for (final subentry in directory.list(recursive: true)) {
size += (await subentry.stat()).size;
}
folderSizes![directory.path.split('/').last] = size;
}
}
}*/
setState(() {});
}

Expand Down
31 changes: 28 additions & 3 deletions lib/pages/thread.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:chan/models/attachment.dart';
import 'package:chan/services/persistence.dart';
import 'package:chan/sites/imageboard_site.dart';
import 'package:chan/pages/gallery.dart';
import 'package:chan/widgets/post_row.dart';
Expand Down Expand Up @@ -31,6 +32,7 @@ class ThreadPage extends StatefulWidget {
}

class _ThreadPageState extends State<ThreadPage> with TickerProviderStateMixin {
PersistentThreadState? persistentState;
Thread? thread;
bool showReplyBox = false;

Expand All @@ -40,6 +42,28 @@ class _ThreadPageState extends State<ThreadPage> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
_getThreadState();
_listController.slowScrollUpdates.listen((_) {
persistentState?.lastSeenPostId = _listController.findNextMatch((_) => true)?.id;
persistentState?.save();
});
}

@override
void didUpdateWidget(ThreadPage old) {
super.didUpdateWidget(old);
if (widget.board != old.board || widget.id != old.id) {
setState(() {
thread = null;
persistentState = null;
});
_getThreadState();
}
}

Future<void> _getThreadState() async {
persistentState = await Persistence.getThreadState(widget.board.name, widget.id);
setState(() {});
}

void _showGallery({bool initiallyShowChrome = true, Attachment? initialAttachment}) {
Expand Down Expand Up @@ -105,8 +129,9 @@ class _ThreadPageState extends State<ThreadPage> with TickerProviderStateMixin {
autoUpdateDuration: const Duration(seconds: 60),
listUpdater: () async {
final _thread = await context.read<ImageboardSite>().getThread(widget.board.name, widget.id);
if (thread == null && widget.initialPostId != null) {
Future.delayed(Duration(milliseconds: 50), () => _listController.scrollToFirstMatching((post) => post.id == widget.initialPostId));
final int? scrollToId = widget.initialPostId ?? persistentState?.lastSeenPostId;
if (thread == null && scrollToId != null) {
Future.delayed(Duration(milliseconds: 50), () => _listController.scrollToFirstMatching((post) => post.id == scrollToId));
}
setState(() {
thread = _thread;
Expand Down Expand Up @@ -153,7 +178,7 @@ class _ThreadPageState extends State<ThreadPage> with TickerProviderStateMixin {
key: replyBoxKey,
board: widget.board,
threadId: widget.id,
onReplyPosted: (receipt) {
onReplyPosted: () {
setState(() {
showReplyBox = false;
});
Expand Down
73 changes: 73 additions & 0 deletions lib/services/persistence.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:chan/services/settings.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
part 'persistence.g.dart';

class Persistence {
static final threadStateBox = Hive.lazyBox<PersistentThreadState>('threadStates');

static Future<void> initialize() async {
await Hive.initFlutter();
Hive.registerAdapter(ThemeSettingAdapter());
Hive.registerAdapter(AutoloadAttachmentsSettingAdapter());
Hive.registerAdapter(SavedSettingsAdapter());
await Hive.openBox<SavedSettings>('settings');
Hive.registerAdapter(PostReceiptAdapter());
Hive.registerAdapter(PersistentThreadStateAdapter());
await Hive.openLazyBox<PersistentThreadState>('threadStates');
}

static Future<PersistentThreadState> getThreadState(String board, int id, {bool updateOpenedTime = false}) async {
final existingState = await threadStateBox.get('$board/$id');
if (existingState != null) {
if (updateOpenedTime) {
existingState.lastOpenedTime = DateTime.now();
await existingState.save();
}
return existingState;
}
else {
final newState = PersistentThreadState(
lastOpenedTime: updateOpenedTime ? DateTime.now() : null
);
threadStateBox.put('$board/$id', newState);
return newState;
}
}
}

@HiveType(typeId: 3)
class PersistentThreadState extends HiveObject {
@HiveField(0)
int? lastSeenPostId;
@HiveField(1)
DateTime lastOpenedTime;
@HiveField(2)
bool watched;
@HiveField(3)
final List<PostReceipt> receipts;

PersistentThreadState({
this.lastSeenPostId,
DateTime? lastOpenedTime,
this.watched = false,
this.receipts = const []
}) : this.lastOpenedTime = lastOpenedTime ?? DateTime.now();

@override
String toString() => 'PersistentThreadState(lastSeenPostId: $lastSeenPostId, receipts: $receipts';
}

@HiveType(typeId: 4)
class PostReceipt extends HiveObject {
@HiveField(0)
final String password;
@HiveField(1)
final int id;
PostReceipt({
required this.password,
required this.id
});
@override
String toString() => 'PostReceipt(id: $id, password: $password)';
}
Loading

0 comments on commit d65d0f4

Please sign in to comment.