From 8cf3859ed851e9f8e73720ce4485354ab2443538 Mon Sep 17 00:00:00 2001 From: shilangyu Date: Wed, 24 Feb 2021 21:54:15 +0100 Subject: [PATCH 1/7] make stores easily serializable --- lib/main.dart | 15 +- lib/stores/accounts_store.dart | 118 +++++++------- lib/stores/accounts_store.g.dart | 32 ++++ lib/stores/config_store.dart | 33 ++-- lib/stores/config_store.g.dart | 58 +++++++ lib/stores/shared_pref_keys.dart | 8 - pubspec.lock | 254 ++++++++++++++++++++++++++++++- pubspec.yaml | 3 + 8 files changed, 419 insertions(+), 102 deletions(-) create mode 100644 lib/stores/accounts_store.g.dart create mode 100644 lib/stores/config_store.g.dart delete mode 100644 lib/stores/shared_pref_keys.dart diff --git a/lib/main.dart b/lib/main.dart index 89cfe17..cb6b231 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,21 +19,14 @@ import 'util/extensions/brightness.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - final configStore = ConfigStore(); - await configStore.load(); - - final accountsStore = AccountsStore(); - await accountsStore.load(); + final configStore = await ConfigStore.load(); + final accountsStore = await AccountsStore.load(); runApp( MultiProvider( providers: [ - ChangeNotifierProvider.value( - value: configStore, - ), - ChangeNotifierProvider.value( - value: accountsStore, - ), + ChangeNotifierProvider.value(value: configStore), + ChangeNotifierProvider.value(value: accountsStore), ], child: const MyApp(), ), diff --git a/lib/stores/accounts_store.dart b/lib/stores/accounts_store.dart index 61c7c3d..44d59d7 100644 --- a/lib/stores/accounts_store.dart +++ b/lib/stores/accounts_store.dart @@ -2,73 +2,57 @@ import 'dart:collection'; import 'dart:convert'; import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:lemmy_api_client/v2.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../util/unawaited.dart'; -import 'shared_pref_keys.dart'; + +part 'accounts_store.g.dart'; /// Store that manages all accounts +@JsonSerializable() class AccountsStore extends ChangeNotifier { + static const prefsKey = 'v1:AccountsStore'; + static final _prefs = SharedPreferences.getInstance(); + /// Map containing JWT tokens of specific users. /// If a token is in this map, the user is considered logged in /// for that account. /// `tokens['instanceHost']['username']` - HashMap> _tokens; + @protected + @JsonKey(defaultValue: {'lemmy.ml': {}}) + Map> tokens; /// default account for a given instance /// map where keys are instanceHosts and values are usernames - HashMap _defaultAccounts; + @protected + @JsonKey(defaultValue: {}) + Map defaultAccounts; /// default account for the app /// It is in a form of `username@instanceHost` - String _defaultAccount; + @protected + String defaultAccount; - Future load() async { - final prefs = await SharedPreferences.getInstance(); + static Future load() async { + final prefs = await _prefs; - // I barely understand what I did. Long story short it casts a - // raw json into a nested ObservableMap - nestedMapsCast(T f(String jwt)) => HashMap.of( - (jsonDecode(prefs.getString(SharedPrefKeys.tokens) ?? - '{"lemmy.ml":{}}') as Map) - ?.map( - (k, e) => MapEntry( - k, - HashMap.of( - (e as Map)?.map( - (k, e) => MapEntry(k, e == null ? null : f(e as String)), - ), - ), - ), - ), - ); - - // set saved settings or create defaults - _tokens = nestedMapsCast((json) => Jwt(json)); - _defaultAccount = prefs.getString(SharedPrefKeys.defaultAccount); - _defaultAccounts = HashMap.of(Map.castFrom( - jsonDecode(prefs.getString(SharedPrefKeys.defaultAccounts) ?? 'null') - as Map ?? - {}, - )); - - notifyListeners(); + return _$AccountsStoreFromJson( + jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map, + ); } Future save() async { - final prefs = await SharedPreferences.getInstance(); + final prefs = await _prefs; - await prefs.setString(SharedPrefKeys.defaultAccount, _defaultAccount); - await prefs.setString( - SharedPrefKeys.defaultAccounts, jsonEncode(_defaultAccounts)); - await prefs.setString(SharedPrefKeys.tokens, jsonEncode(_tokens)); + await prefs.setString(prefsKey, jsonEncode(_$AccountsStoreToJson(this))); } /// automatically sets default accounts void _assignDefaultAccounts() { // remove dangling defaults - _defaultAccounts.entries + defaultAccounts.entries .map((dft) { final instance = dft.key; final username = dft.value; @@ -79,21 +63,21 @@ class AccountsStore extends ChangeNotifier { } }) .toList() - .forEach(_defaultAccounts.remove); - if (_defaultAccount != null) { - final instance = _defaultAccount.split('@')[1]; - final username = _defaultAccount.split('@')[0]; + .forEach(defaultAccounts.remove); + if (defaultAccount != null) { + final instance = defaultAccount.split('@')[1]; + final username = defaultAccount.split('@')[0]; // if instance or username doesn't exist, remove if (!instances.contains(instance) || !usernamesFor(instance).contains(username)) { - _defaultAccount = null; + defaultAccount = null; } } // set local defaults for (final instanceHost in instances) { // if this instance is not in defaults - if (!_defaultAccounts.containsKey(instanceHost)) { + if (!defaultAccounts.containsKey(instanceHost)) { // select first account in this instance, if any if (!isAnonymousFor(instanceHost)) { setDefaultAccountFor(instanceHost, usernamesFor(instanceHost).first); @@ -102,7 +86,7 @@ class AccountsStore extends ChangeNotifier { } // set global default - if (_defaultAccount == null) { + if (defaultAccount == null) { // select first account of first instance for (final instanceHost in instances) { // select first account in this instance, if any @@ -114,19 +98,19 @@ class AccountsStore extends ChangeNotifier { } String get defaultUsername { - if (_defaultAccount == null) { + if (defaultAccount == null) { return null; } - return _defaultAccount.split('@')[0]; + return defaultAccount.split('@')[0]; } String get defaultInstanceHost { - if (_defaultAccount == null) { + if (defaultAccount == null) { return null; } - return _defaultAccount.split('@')[1]; + return defaultAccount.split('@')[1]; } String defaultUsernameFor(String instanceHost) { @@ -134,16 +118,16 @@ class AccountsStore extends ChangeNotifier { return null; } - return _defaultAccounts[instanceHost]; + return defaultAccounts[instanceHost]; } Jwt get defaultToken { - if (_defaultAccount == null) { + if (defaultAccount == null) { return null; } - final userTag = _defaultAccount.split('@'); - return _tokens[userTag[1]][userTag[0]]; + final userTag = defaultAccount.split('@'); + return tokens[userTag[1]][userTag[0]]; } Jwt defaultTokenFor(String instanceHost) { @@ -151,13 +135,13 @@ class AccountsStore extends ChangeNotifier { return null; } - return _tokens[instanceHost][_defaultAccounts[instanceHost]]; + return tokens[instanceHost][defaultAccounts[instanceHost]]; } /// returns token for user of a certain id Jwt tokenForId(String instanceHost, int userId) => - _tokens.containsKey(instanceHost) - ? _tokens[instanceHost] + tokens.containsKey(instanceHost) + ? tokens[instanceHost] .values .firstWhere((val) => val.payload.id == userId, orElse: () => null) : null; @@ -167,12 +151,12 @@ class AccountsStore extends ChangeNotifier { return null; } - return _tokens[instanceHost][username]; + return tokens[instanceHost][username]; } /// sets globally default account void setDefaultAccount(String instanceHost, String username) { - _defaultAccount = '$username@$instanceHost'; + defaultAccount = '$username@$instanceHost'; notifyListeners(); save(); @@ -180,7 +164,7 @@ class AccountsStore extends ChangeNotifier { /// sets default account for given instance void setDefaultAccountFor(String instanceHost, String username) { - _defaultAccounts[instanceHost] = username; + defaultAccounts[instanceHost] = username; notifyListeners(); save(); @@ -193,20 +177,20 @@ class AccountsStore extends ChangeNotifier { return true; } - return _tokens[instanceHost].isEmpty; + return tokens[instanceHost].isEmpty; } /// `true` if no added instance has an account assigned to it bool get hasNoAccount => loggedInInstances.isEmpty; - Iterable get instances => _tokens.keys; + Iterable get instances => tokens.keys; Iterable get loggedInInstances => instances.where((e) => !isAnonymousFor(e)); /// Usernames that are assigned to a given instance Iterable usernamesFor(String instanceHost) => - _tokens[instanceHost].keys; + tokens[instanceHost].keys; /// adds a new account /// if it's the first account ever the account is @@ -230,7 +214,7 @@ class AccountsStore extends ChangeNotifier { final userData = await lemmy.run(GetSite(auth: token.raw)).then((value) => value.myUser); - _tokens[instanceHost][userData.name] = token; + tokens[instanceHost][userData.name] = token; _assignDefaultAccounts(); notifyListeners(); @@ -257,7 +241,7 @@ class AccountsStore extends ChangeNotifier { } } - _tokens[instanceHost] = HashMap(); + tokens[instanceHost] = HashMap(); _assignDefaultAccounts(); notifyListeners(); @@ -266,7 +250,7 @@ class AccountsStore extends ChangeNotifier { /// This also removes all accounts assigned to this instance void removeInstance(String instanceHost) { - _tokens.remove(instanceHost); + tokens.remove(instanceHost); _assignDefaultAccounts(); notifyListeners(); @@ -274,7 +258,7 @@ class AccountsStore extends ChangeNotifier { } void removeAccount(String instanceHost, String username) { - _tokens[instanceHost].remove(username); + tokens[instanceHost].remove(username); _assignDefaultAccounts(); notifyListeners(); diff --git a/lib/stores/accounts_store.g.dart b/lib/stores/accounts_store.g.dart new file mode 100644 index 0000000..2b909ae --- /dev/null +++ b/lib/stores/accounts_store.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'accounts_store.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AccountsStore _$AccountsStoreFromJson(Map json) { + return AccountsStore() + ..tokens = (json['tokens'] as Map)?.map( + (k, e) => MapEntry( + k, + (e as Map)?.map( + (k, e) => + MapEntry(k, e == null ? null : Jwt.fromJson(e as String)), + )), + ) ?? + {'lemmy.ml': {}} + ..defaultAccounts = (json['defaultAccounts'] as Map)?.map( + (k, e) => MapEntry(k, e as String), + ) ?? + {} + ..defaultAccount = json['defaultAccount'] as String; +} + +Map _$AccountsStoreToJson(AccountsStore instance) => + { + 'tokens': instance.tokens, + 'defaultAccounts': instance.defaultAccounts, + 'defaultAccount': instance.defaultAccount, + }; diff --git a/lib/stores/config_store.dart b/lib/stores/config_store.dart index 27f49f4..e1ee670 100644 --- a/lib/stores/config_store.dart +++ b/lib/stores/config_store.dart @@ -1,12 +1,20 @@ +import 'dart:convert'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'shared_pref_keys.dart'; +part 'config_store.g.dart'; /// Store managing user-level configuration such as theme or language +@JsonSerializable() class ConfigStore extends ChangeNotifier { + static const prefsKey = 'v1:ConfigStore'; + static final _prefs = SharedPreferences.getInstance(); + ThemeMode _theme; + @JsonKey(defaultValue: ThemeMode.system) ThemeMode get theme => _theme; set theme(ThemeMode theme) { _theme = theme; @@ -15,6 +23,7 @@ class ConfigStore extends ChangeNotifier { } bool _amoledDarkMode; + @JsonKey(defaultValue: false) bool get amoledDarkMode => _amoledDarkMode; set amoledDarkMode(bool amoledDarkMode) { _amoledDarkMode = amoledDarkMode; @@ -22,23 +31,17 @@ class ConfigStore extends ChangeNotifier { save(); } - Future load() async { - final prefs = await SharedPreferences.getInstance(); - // load saved settings or create defaults - theme = - _themeModeFromString(prefs.getString(SharedPrefKeys.theme) ?? 'system'); - amoledDarkMode = prefs.getBool(SharedPrefKeys.amoledDarkMode) ?? false; - notifyListeners(); + static Future load() async { + final prefs = await _prefs; + + return _$ConfigStoreFromJson( + jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map, + ); } Future save() async { - final prefs = await SharedPreferences.getInstance(); + final prefs = await _prefs; - await prefs.setString(SharedPrefKeys.theme, describeEnum(theme)); - await prefs.setBool(SharedPrefKeys.amoledDarkMode, amoledDarkMode); + await prefs.setString(prefsKey, jsonEncode(_$ConfigStoreToJson(this))); } } - -/// converts string to ThemeMode -ThemeMode _themeModeFromString(String theme) => - ThemeMode.values.firstWhere((e) => describeEnum(e) == theme); diff --git a/lib/stores/config_store.g.dart b/lib/stores/config_store.g.dart new file mode 100644 index 0000000..1257853 --- /dev/null +++ b/lib/stores/config_store.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'config_store.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ConfigStore _$ConfigStoreFromJson(Map json) { + return ConfigStore() + ..theme = _$enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? + ThemeMode.system + ..amoledDarkMode = json['amoledDarkMode'] as bool ?? false; +} + +Map _$ConfigStoreToJson(ConfigStore instance) => + { + 'theme': _$ThemeModeEnumMap[instance.theme], + 'amoledDarkMode': instance.amoledDarkMode, + }; + +T _$enumDecode( + Map enumValues, + dynamic source, { + T unknownValue, +}) { + if (source == null) { + throw ArgumentError('A value must be provided. Supported values: ' + '${enumValues.values.join(', ')}'); + } + + final value = enumValues.entries + .singleWhere((e) => e.value == source, orElse: () => null) + ?.key; + + if (value == null && unknownValue == null) { + throw ArgumentError('`$source` is not one of the supported values: ' + '${enumValues.values.join(', ')}'); + } + return value ?? unknownValue; +} + +T _$enumDecodeNullable( + Map enumValues, + dynamic source, { + T unknownValue, +}) { + if (source == null) { + return null; + } + return _$enumDecode(enumValues, source, unknownValue: unknownValue); +} + +const _$ThemeModeEnumMap = { + ThemeMode.system: 'system', + ThemeMode.light: 'light', + ThemeMode.dark: 'dark', +}; diff --git a/lib/stores/shared_pref_keys.dart b/lib/stores/shared_pref_keys.dart deleted file mode 100644 index 4c2497e..0000000 --- a/lib/stores/shared_pref_keys.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// Collection of string constants that are keys to SharedPreferences -class SharedPrefKeys { - static const tokens = 'tokens'; - static const defaultAccount = 'defaultAccount'; - static const defaultAccounts = 'defaultAccounts'; - static const theme = 'theme'; - static const amoledDarkMode = 'amoledDarkMode'; -} diff --git a/pubspec.lock b/pubspec.lock index 9080205..c63d8b1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "14.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.41.2" archive: dependency: transitive description: @@ -29,6 +43,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0-nullsafety.1" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.5" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.3" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.7" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.0" cached_network_image: dependency: "direct main" description: @@ -50,6 +120,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0-nullsafety.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -57,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0-nullsafety.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.0" collection: dependency: transitive description: @@ -85,6 +176,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.12" effective_dart: dependency: "direct dev" description: @@ -120,6 +218,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.2.1" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11" flutter: dependency: "direct main" description: flutter @@ -205,6 +310,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" http: dependency: transitive description: @@ -212,6 +331,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" http_parser: dependency: transitive description: @@ -247,13 +373,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.1" - json_annotation: + io: dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2" + json_annotation: + dependency: "direct main" description: name: json_annotation url: "https://pub.dartlang.org" source: hosted version: "3.1.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.1" latinize: dependency: transitive description: @@ -268,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.0" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" markdown: dependency: "direct main" description: @@ -296,6 +450,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.3" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" modal_bottom_sheet: dependency: "direct main" description: @@ -310,6 +471,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.4" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" octo_image: dependency: transitive description: @@ -317,6 +492,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.0" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" package_info: dependency: "direct main" description: @@ -401,6 +583,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" process: dependency: transitive description: @@ -415,6 +604,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.3.3" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.8" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" rxdart: dependency: transitive description: @@ -464,11 +674,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.2+3" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.4+1" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10+3" source_span: dependency: transitive description: @@ -504,6 +735,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0-nullsafety.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" string_scanner: dependency: transitive description: @@ -539,6 +777,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.29" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+3" typed_data: dependency: transitive description: @@ -602,6 +847,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0-nullsafety.3" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2e3c420..97c9248 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: fuzzy: <1.0.0 lemmy_api_client: ^0.12.0 matrix4_transform: ^1.1.7 + json_annotation: ^3.1.1 flutter: sdk: flutter @@ -59,6 +60,8 @@ dev_dependencies: sdk: flutter effective_dart: ^1.0.0 flutter_launcher_icons: ^0.8.1 + json_serializable: ^3.5.1 + build_runner: ^1.11.1 flutter_icons: android: true From e474cebcdb1d2cdaf9e718981991a360c3b6f7ac Mon Sep 17 00:00:00 2001 From: shilangyu Date: Wed, 24 Feb 2021 21:59:01 +0100 Subject: [PATCH 2/7] Add changelog --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47513f5..73c7cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,22 @@ ## Unreleased +WARNING: due to some internal changes your local settings will be reset (logged out of accounts, removed instances, theme back to default) + ### Added -- Added page with saved posts/comments. It can be accessed from the profile tab under the bookmark icon -- Added modlog page. Can be visited in the context of an instance or community from the about tab - Added inbox page, that can be accessed by tapping bell in the home tab -- Added abillity to send private messages +- Added page with saved posts/comments. It can be accessed from the profile tab under the bookmark icon +- Added ability to send private messages +- Added modlog page. Can be visited in the context of an instance or community from the about tab ### Changed - Titles on some pages, have an appear affect when scrolling down -- Long pressing comments now have a ripple effect - +- Long pressing comments now has a ripple effect ### Fixed -- Time of posts is now displayed properly. Unless you live in UTC zone, then you won't notice a difference. +- Time of posts is now displayed properly. Unless you live in UTC zone, then you won't notice a difference - Fixed a bug where links would not work on Android 11 ## v0.2.3 - 2021-02-09 From bc7779158cd92ed8f6d8bfcc4c63e05b9246eaf1 Mon Sep 17 00:00:00 2001 From: shilangyu Date: Wed, 24 Feb 2021 22:02:36 +0100 Subject: [PATCH 3/7] Simplify tokenForId --- lib/stores/accounts_store.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/stores/accounts_store.dart b/lib/stores/accounts_store.dart index 44d59d7..186ea80 100644 --- a/lib/stores/accounts_store.dart +++ b/lib/stores/accounts_store.dart @@ -139,12 +139,9 @@ class AccountsStore extends ChangeNotifier { } /// returns token for user of a certain id - Jwt tokenForId(String instanceHost, int userId) => - tokens.containsKey(instanceHost) - ? tokens[instanceHost] - .values - .firstWhere((val) => val.payload.id == userId, orElse: () => null) - : null; + Jwt tokenForId(String instanceHost, int userId) => tokens[instanceHost] + ?.values + ?.firstWhere((val) => val.payload.id == userId, orElse: () => null); Jwt tokenFor(String instanceHost, String username) { if (!usernamesFor(instanceHost).contains(username)) { From 213c9b7b0d117bda6346ad592ba608370d6ec821 Mon Sep 17 00:00:00 2001 From: shilangyu Date: Wed, 24 Feb 2021 22:08:11 +0100 Subject: [PATCH 4/7] Mention nerd stuff in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c7cfd..17b51a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ WARNING: due to some internal changes your local settings will be reset (logged - Titles on some pages, have an appear affect when scrolling down - Long pressing comments now has a ripple effect +- Nerd stuff now contains more nerd stuff ### Fixed From a9dc676ca808677896f0d5913729b20dda5a96c6 Mon Sep 17 00:00:00 2001 From: shilangyu Date: Wed, 24 Feb 2021 22:33:21 +0100 Subject: [PATCH 5/7] Simplify comment.dart --- lib/stores/accounts_store.dart | 5 ----- lib/widgets/comment.dart | 27 ++++++++++----------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/lib/stores/accounts_store.dart b/lib/stores/accounts_store.dart index 186ea80..0247126 100644 --- a/lib/stores/accounts_store.dart +++ b/lib/stores/accounts_store.dart @@ -138,11 +138,6 @@ class AccountsStore extends ChangeNotifier { return tokens[instanceHost][defaultAccounts[instanceHost]]; } - /// returns token for user of a certain id - Jwt tokenForId(String instanceHost, int userId) => tokens[instanceHost] - ?.values - ?.firstWhere((val) => val.payload.id == userId, orElse: () => null); - Jwt tokenFor(String instanceHost, String username) { if (!usernamesFor(instanceHost).contains(username)) { return null; diff --git a/lib/widgets/comment.dart b/lib/widgets/comment.dart index 9ac3036..4b3da13 100644 --- a/lib/widgets/comment.dart +++ b/lib/widgets/comment.dart @@ -29,7 +29,6 @@ class CommentWidget extends HookWidget { final int depth; final CommentTree commentTree; final bool detached; - final UserMentionView userMentionView; final bool wasVoted; final bool canBeMarkedAsRead; final bool hideOnRead; @@ -48,9 +47,8 @@ class CommentWidget extends HookWidget { this.detached = false, this.canBeMarkedAsRead = false, this.hideOnRead = false, - }) : wasVoted = - (commentTree.comment.myVote ?? VoteType.none) != VoteType.none, - userMentionView = null; + }) : wasVoted = + (commentTree.comment.myVote ?? VoteType.none) != VoteType.none; CommentWidget.fromCommentView( CommentView cv, { @@ -64,14 +62,13 @@ class CommentWidget extends HookWidget { ); CommentWidget.fromUserMentionView( - this.userMentionView, { - this.hideOnRead = false, - }) : commentTree = - CommentTree(CommentView.fromJson(userMentionView.toJson())), - depth = 0, - wasVoted = (userMentionView.myVote ?? VoteType.none) != VoteType.none, - detached = true, - canBeMarkedAsRead = true; + UserMentionView userMentionView, { + bool hideOnRead = false, + }) : this.fromCommentView( + CommentView.fromJson(userMentionView.toJson()), + hideOnRead: hideOnRead, + canBeMarkedAsRead: true, + ); _showCommentInfo(BuildContext context) { final com = commentTree.comment; @@ -417,9 +414,7 @@ class _MarkAsRead extends HookWidget { final accStore = useAccountsStore(); final comment = commentView.comment; - final recipient = commentView.recipient; final instanceHost = commentView.instanceHost; - final post = commentView.post; final isRead = useState(comment.read); final delayedRead = useDelayedLoading(); @@ -431,9 +426,7 @@ class _MarkAsRead extends HookWidget { query: MarkCommentAsRead( commentId: comment.id, read: !isRead.value, - auth: recipient != null - ? accStore.tokenFor(instanceHost, recipient.name)?.raw - : accStore.tokenForId(instanceHost, post.creatorId)?.raw, + auth: accStore.defaultTokenFor(instanceHost)?.raw, ), onSuccess: (val) { isRead.value = val.commentView.comment.read; From 7cc928764bd8f85c224a165e5e858baf3504da0d Mon Sep 17 00:00:00 2001 From: shilangyu Date: Wed, 24 Feb 2021 22:48:19 +0100 Subject: [PATCH 6/7] Propagate userMentionId --- lib/widgets/comment.dart | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/widgets/comment.dart b/lib/widgets/comment.dart index 4b3da13..f648d40 100644 --- a/lib/widgets/comment.dart +++ b/lib/widgets/comment.dart @@ -32,6 +32,7 @@ class CommentWidget extends HookWidget { final bool wasVoted; final bool canBeMarkedAsRead; final bool hideOnRead; + final int userMentionId; static const colors = [ Colors.pink, @@ -47,6 +48,7 @@ class CommentWidget extends HookWidget { this.detached = false, this.canBeMarkedAsRead = false, this.hideOnRead = false, + this.userMentionId, }) : wasVoted = (commentTree.comment.myVote ?? VoteType.none) != VoteType.none; @@ -64,10 +66,12 @@ class CommentWidget extends HookWidget { CommentWidget.fromUserMentionView( UserMentionView userMentionView, { bool hideOnRead = false, - }) : this.fromCommentView( - CommentView.fromJson(userMentionView.toJson()), + }) : this( + CommentTree(CommentView.fromJson(userMentionView.toJson())), hideOnRead: hideOnRead, canBeMarkedAsRead: true, + detached: true, + userMentionId: userMentionView.userMention.id, ); _showCommentInfo(BuildContext context) { @@ -275,6 +279,7 @@ class CommentWidget extends HookWidget { _MarkAsRead( commentTree.comment, onChanged: (val) => isRead.value = val, + userMentionId: userMentionId, ), if (detached) TileAction( @@ -406,8 +411,13 @@ class CommentWidget extends HookWidget { class _MarkAsRead extends HookWidget { final CommentView commentView; final ValueChanged onChanged; + final int userMentionId; - const _MarkAsRead(this.commentView, {this.onChanged}); + const _MarkAsRead( + this.commentView, { + @required this.onChanged, + @required this.userMentionId, + }) : assert(commentView != null); @override Widget build(BuildContext context) { @@ -434,10 +444,26 @@ class _MarkAsRead extends HookWidget { }, ); + Future handleMarkMentionAsSeen() => delayedAction( + context: context, + delayedLoading: delayedRead, + instanceHost: instanceHost, + query: MarkUserMentionAsRead( + userMentionId: userMentionId, + read: !isRead.value, + auth: accStore.defaultTokenFor(instanceHost)?.raw, + ), + onSuccess: (val) { + isRead.value = val.userMention.read; + onChanged?.call(isRead.value); + }, + ); + return TileAction( icon: Icons.check, delayedLoading: delayedRead, - onPressed: handleMarkAsSeen, + onPressed: + userMentionId != null ? handleMarkMentionAsSeen : handleMarkAsSeen, iconColor: isRead.value ? Theme.of(context).accentColor : null, tooltip: 'mark as ${isRead.value ? 'un' : ''}read', ); From fcf39be00fde7f19e1bcf96aa2f9790999f04a39 Mon Sep 17 00:00:00 2001 From: shilangyu Date: Thu, 25 Feb 2021 21:23:54 +0100 Subject: [PATCH 7/7] Fix null check typo --- lib/pages/inbox.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/inbox.dart b/lib/pages/inbox.dart index a259088..de9ac62 100644 --- a/lib/pages/inbox.dart +++ b/lib/pages/inbox.dart @@ -373,7 +373,7 @@ class PrivateMessageTile extends HookWidget { final val = await showCupertinoModalPopup( context: context, builder: (_) => WriteMessagePage.edit(pmv.value)); - if (pmv != null) pmv.value = val; + if (val != null) pmv.value = val; }, ), TileAction(