Migrate ConfigStore to mobx (#270)
* Migrate ConfigStore to mobx * Add tests * Remove provider imports * Mock shared_preferences * Reorganize saving in ConfigStore
This commit is contained in:
parent
8f34591111
commit
760565384f
|
@ -19,7 +19,6 @@ linter:
|
||||||
- avoid_setters_without_getters
|
- avoid_setters_without_getters
|
||||||
- avoid_single_cascade_in_expression_statements
|
- avoid_single_cascade_in_expression_statements
|
||||||
- avoid_type_to_string
|
- avoid_type_to_string
|
||||||
- avoid_types_on_closure_parameters
|
|
||||||
- avoid_unnecessary_containers
|
- avoid_unnecessary_containers
|
||||||
- avoid_unused_constructor_parameters
|
- avoid_unused_constructor_parameters
|
||||||
- avoid_void_async
|
- avoid_void_async
|
||||||
|
@ -105,13 +104,13 @@ linter:
|
||||||
- unrelated_type_equality_checks
|
- unrelated_type_equality_checks
|
||||||
- use_full_hex_values_for_flutter_colors
|
- use_full_hex_values_for_flutter_colors
|
||||||
- use_is_even_rather_than_modulo
|
- use_is_even_rather_than_modulo
|
||||||
- use_test_throws_matchers
|
- use_named_constants
|
||||||
- use_raw_strings
|
- use_raw_strings
|
||||||
- use_rethrow_when_possible
|
- use_rethrow_when_possible
|
||||||
- use_setters_to_change_properties
|
- use_setters_to_change_properties
|
||||||
|
- use_test_throws_matchers
|
||||||
- use_to_and_as_if_applicable
|
- use_to_and_as_if_applicable
|
||||||
- void_checks
|
- void_checks
|
||||||
- use_named_constants
|
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
|
|
28
lib/app.dart
28
lib/app.dart
|
@ -1,29 +1,29 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
||||||
|
|
||||||
import 'hooks/stores.dart';
|
|
||||||
import 'l10n/l10n.dart';
|
import 'l10n/l10n.dart';
|
||||||
import 'pages/home_page.dart';
|
import 'pages/home_page.dart';
|
||||||
|
import 'stores/config_store.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
import 'util/observer_consumers.dart';
|
||||||
|
|
||||||
class MyApp extends HookWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp();
|
const MyApp();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final configStore = useConfigStore();
|
|
||||||
|
|
||||||
return KeyboardDismisser(
|
return KeyboardDismisser(
|
||||||
child: MaterialApp(
|
child: ObserverBuilder<ConfigStore>(
|
||||||
title: 'lemmur',
|
builder: (context, store) => MaterialApp(
|
||||||
supportedLocales: L10n.supportedLocales,
|
title: 'lemmur',
|
||||||
localizationsDelegates: L10n.localizationsDelegates,
|
supportedLocales: L10n.supportedLocales,
|
||||||
themeMode: configStore.theme,
|
localizationsDelegates: L10n.localizationsDelegates,
|
||||||
darkTheme: configStore.amoledDarkMode ? amoledTheme : darkTheme,
|
themeMode: store.theme,
|
||||||
locale: configStore.locale,
|
darkTheme: store.amoledDarkMode ? amoledTheme : darkTheme,
|
||||||
theme: lightTheme,
|
locale: store.locale,
|
||||||
home: const HomePage(),
|
theme: lightTheme,
|
||||||
|
home: const HomePage(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../stores/accounts_store.dart';
|
import '../stores/accounts_store.dart';
|
||||||
import '../stores/config_store.dart';
|
|
||||||
|
|
||||||
AccountsStore useAccountsStore() => useContext().watch<AccountsStore>();
|
AccountsStore useAccountsStore() => useContext().watch<AccountsStore>();
|
||||||
T useAccountsStoreSelect<T>(T selector(AccountsStore store)) =>
|
T useAccountsStoreSelect<T>(T selector(AccountsStore store)) =>
|
||||||
useContext().select<AccountsStore, T>(selector);
|
useContext().select<AccountsStore, T>(selector);
|
||||||
|
|
||||||
ConfigStore useConfigStore() => useContext().watch<ConfigStore>();
|
V useStore<S extends Store, V>(V Function(S value) selector) {
|
||||||
T useConfigStoreSelect<T>(T selector(ConfigStore store)) =>
|
final context = useContext();
|
||||||
useContext().select<ConfigStore, T>(selector);
|
final store = context.read<S>();
|
||||||
|
final state = useState(selector(store));
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
return autorun((_) {
|
||||||
|
state.value = selector(store);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return state.value;
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
export 'package:flutter_gen/gen_l10n/l10n.dart';
|
export 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
export 'l10n_api.dart';
|
export 'l10n_api.dart';
|
||||||
export 'l10n_from_string.dart';
|
export 'l10n_from_string.dart';
|
||||||
|
|
||||||
abstract class LocaleSerde {
|
class LocaleConverter implements JsonConverter<Locale, String?> {
|
||||||
static Locale fromJson(String? json) {
|
const LocaleConverter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Locale fromJson(String? json) {
|
||||||
if (json == null) return const Locale('en');
|
if (json == null) return const Locale('en');
|
||||||
|
|
||||||
final lang = json.split('-');
|
final lang = json.split('-');
|
||||||
|
@ -14,7 +18,8 @@ abstract class LocaleSerde {
|
||||||
return Locale(lang[0], lang.length > 1 ? lang[1] : null);
|
return Locale(lang[0], lang.length > 1 ? lang[1] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String toJson(Locale locale) => locale.toLanguageTag();
|
@override
|
||||||
|
String? toJson(Locale locale) => locale.toLanguageTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
const _languageNames = {
|
const _languageNames = {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'app_config.dart';
|
import 'app_config.dart';
|
||||||
|
@ -14,16 +15,17 @@ Future<void> mainCommon(AppConfig appConfig) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
final logConsoleStore = LogConsolePageStore();
|
final logConsoleStore = LogConsolePageStore();
|
||||||
|
final sharedPrefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
_setupLogger(appConfig, logConsoleStore);
|
_setupLogger(appConfig, logConsoleStore);
|
||||||
|
|
||||||
final configStore = await ConfigStore.load();
|
final configStore = ConfigStore.load(sharedPrefs);
|
||||||
final accountsStore = await AccountsStore.load();
|
final accountsStore = await AccountsStore.load();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider.value(value: configStore),
|
Provider.value(value: configStore),
|
||||||
ChangeNotifierProvider.value(value: accountsStore),
|
ChangeNotifierProvider.value(value: accountsStore),
|
||||||
Provider.value(value: logConsoleStore),
|
Provider.value(value: logConsoleStore),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../comment_tree.dart';
|
import '../../comment_tree.dart';
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../hooks/logged_in_action.dart';
|
import '../../hooks/logged_in_action.dart';
|
||||||
import '../../stores/accounts_store.dart';
|
import '../../stores/accounts_store.dart';
|
||||||
|
|
|
@ -8,6 +8,7 @@ import '../hooks/infinite_scroll.dart';
|
||||||
import '../hooks/memo_future.dart';
|
import '../hooks/memo_future.dart';
|
||||||
import '../hooks/stores.dart';
|
import '../hooks/stores.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
|
import '../stores/config_store.dart';
|
||||||
import '../util/goto.dart';
|
import '../util/goto.dart';
|
||||||
import '../widgets/bottom_modal.dart';
|
import '../widgets/bottom_modal.dart';
|
||||||
import '../widgets/cached_network_image.dart';
|
import '../widgets/cached_network_image.dart';
|
||||||
|
@ -25,7 +26,7 @@ class HomeTab extends HookWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accStore = useAccountsStore();
|
final accStore = useAccountsStore();
|
||||||
final defaultListingType =
|
final defaultListingType =
|
||||||
useConfigStoreSelect((configStore) => configStore.defaultListingType);
|
useStore((ConfigStore store) => store.defaultListingType);
|
||||||
final selectedList = useState(_SelectedList(
|
final selectedList = useState(_SelectedList(
|
||||||
listingType: accStore.hasNoAccount &&
|
listingType: accStore.hasNoAccount &&
|
||||||
defaultListingType == PostListingType.subscribed
|
defaultListingType == PostListingType.subscribed
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../util/observer_consumers.dart';
|
import '../../util/observer_consumers.dart';
|
||||||
import '../../widgets/bottom_safe.dart';
|
import '../../widgets/bottom_safe.dart';
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../../util/async_store_listener.dart';
|
import '../../../util/async_store_listener.dart';
|
||||||
import '../../../util/extensions/api.dart';
|
import '../../../util/extensions/api.dart';
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../../hooks/stores.dart';
|
import '../../../hooks/stores.dart';
|
||||||
import '../../../l10n/l10n_from_string.dart';
|
import '../../../l10n/l10n_from_string.dart';
|
||||||
|
|
|
@ -6,7 +6,10 @@ import 'package:lemmy_api_client/v3.dart';
|
||||||
|
|
||||||
import '../../hooks/stores.dart';
|
import '../../hooks/stores.dart';
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
|
import '../../stores/config_store.dart';
|
||||||
|
import '../../util/async_store_listener.dart';
|
||||||
import '../../util/goto.dart';
|
import '../../util/goto.dart';
|
||||||
|
import '../../util/observer_consumers.dart';
|
||||||
import '../../widgets/about_tile.dart';
|
import '../../widgets/about_tile.dart';
|
||||||
import '../../widgets/bottom_modal.dart';
|
import '../../widgets/bottom_modal.dart';
|
||||||
import '../../widgets/radio_picker.dart';
|
import '../../widgets/radio_picker.dart';
|
||||||
|
@ -66,112 +69,112 @@ class SettingsPage extends HookWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings for theme color, AMOLED switch
|
/// Settings for theme color, AMOLED switch
|
||||||
class AppearanceConfigPage extends HookWidget {
|
class AppearanceConfigPage extends StatelessWidget {
|
||||||
const AppearanceConfigPage();
|
const AppearanceConfigPage();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final configStore = useConfigStore();
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Appearance')),
|
appBar: AppBar(title: const Text('Appearance')),
|
||||||
body: ListView(
|
body: ObserverBuilder<ConfigStore>(
|
||||||
children: [
|
builder: (context, store) => ListView(
|
||||||
const _SectionHeading('Theme'),
|
children: [
|
||||||
for (final theme in ThemeMode.values)
|
const _SectionHeading('Theme'),
|
||||||
RadioListTile<ThemeMode>(
|
for (final theme in ThemeMode.values)
|
||||||
value: theme,
|
RadioListTile<ThemeMode>(
|
||||||
title: Text(describeEnum(theme)),
|
value: theme,
|
||||||
groupValue: configStore.theme,
|
title: Text(describeEnum(theme)),
|
||||||
onChanged: (selected) {
|
groupValue: store.theme,
|
||||||
if (selected != null) configStore.theme = selected;
|
onChanged: (selected) {
|
||||||
|
if (selected != null) store.theme = selected;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
title: const Text('AMOLED dark mode'),
|
||||||
|
value: store.amoledDarkMode,
|
||||||
|
onChanged: (checked) {
|
||||||
|
store.amoledDarkMode = checked;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile.adaptive(
|
const SizedBox(height: 12),
|
||||||
title: const Text('AMOLED dark mode'),
|
const _SectionHeading('Other'),
|
||||||
value: configStore.amoledDarkMode,
|
SwitchListTile.adaptive(
|
||||||
onChanged: (checked) {
|
title: Text(L10n.of(context)!.show_avatars),
|
||||||
configStore.amoledDarkMode = checked;
|
value: store.showAvatars,
|
||||||
},
|
onChanged: (checked) {
|
||||||
),
|
store.showAvatars = checked;
|
||||||
const SizedBox(height: 12),
|
},
|
||||||
const _SectionHeading('Other'),
|
),
|
||||||
SwitchListTile.adaptive(
|
SwitchListTile.adaptive(
|
||||||
title: Text(L10n.of(context)!.show_avatars),
|
title: const Text('Show scores'),
|
||||||
value: configStore.showAvatars,
|
value: store.showScores,
|
||||||
onChanged: (checked) {
|
onChanged: (checked) {
|
||||||
configStore.showAvatars = checked;
|
store.showScores = checked;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile.adaptive(
|
],
|
||||||
title: const Text('Show scores'),
|
),
|
||||||
value: configStore.showScores,
|
|
||||||
onChanged: (checked) {
|
|
||||||
configStore.showScores = checked;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// General settings
|
/// General settings
|
||||||
class GeneralConfigPage extends HookWidget {
|
class GeneralConfigPage extends StatelessWidget {
|
||||||
const GeneralConfigPage();
|
const GeneralConfigPage();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final configStore = useConfigStore();
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('General')),
|
appBar: AppBar(title: const Text('General')),
|
||||||
body: ListView(
|
body: ObserverBuilder<ConfigStore>(
|
||||||
children: [
|
builder: (context, store) => ListView(
|
||||||
ListTile(
|
children: [
|
||||||
title: Text(L10n.of(context)!.sort_type),
|
ListTile(
|
||||||
trailing: SizedBox(
|
title: Text(L10n.of(context)!.sort_type),
|
||||||
width: 120,
|
trailing: SizedBox(
|
||||||
child: RadioPicker<SortType>(
|
width: 120,
|
||||||
values: SortType.values,
|
child: RadioPicker<SortType>(
|
||||||
groupValue: configStore.defaultSortType,
|
values: SortType.values,
|
||||||
onChanged: (value) => configStore.defaultSortType = value,
|
groupValue: store.defaultSortType,
|
||||||
mapValueToString: (value) => value.value,
|
onChanged: (value) => store.defaultSortType = value,
|
||||||
|
mapValueToString: (value) => value.value,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
ListTile(
|
||||||
ListTile(
|
title: Text(L10n.of(context)!.type),
|
||||||
title: Text(L10n.of(context)!.type),
|
trailing: SizedBox(
|
||||||
trailing: SizedBox(
|
width: 120,
|
||||||
width: 120,
|
child: RadioPicker<PostListingType>(
|
||||||
child: RadioPicker<PostListingType>(
|
values: const [
|
||||||
values: const [
|
PostListingType.all,
|
||||||
PostListingType.all,
|
PostListingType.local,
|
||||||
PostListingType.local,
|
PostListingType.subscribed,
|
||||||
PostListingType.subscribed,
|
],
|
||||||
],
|
groupValue: store.defaultListingType,
|
||||||
groupValue: configStore.defaultListingType,
|
onChanged: (value) => store.defaultListingType = value,
|
||||||
onChanged: (value) => configStore.defaultListingType = value,
|
mapValueToString: (value) => value.value,
|
||||||
mapValueToString: (value) => value.value,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
ListTile(
|
||||||
ListTile(
|
title: Text(L10n.of(context)!.language),
|
||||||
title: Text(L10n.of(context)!.language),
|
trailing: SizedBox(
|
||||||
trailing: SizedBox(
|
width: 120,
|
||||||
width: 120,
|
child: RadioPicker<Locale>(
|
||||||
child: RadioPicker<Locale>(
|
title: 'Choose language',
|
||||||
title: 'Choose language',
|
groupValue: store.locale,
|
||||||
groupValue: configStore.locale,
|
values: L10n.supportedLocales,
|
||||||
values: L10n.supportedLocales,
|
mapValueToString: (locale) => locale.languageName,
|
||||||
mapValueToString: (locale) => locale.languageName,
|
onChanged: (selected) {
|
||||||
onChanged: (selected) {
|
store.locale = selected;
|
||||||
configStore.locale = selected;
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -191,8 +194,6 @@ class _AccountOptions extends HookWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accountsStore = useAccountsStore();
|
final accountsStore = useAccountsStore();
|
||||||
final configStore = useConfigStore();
|
|
||||||
final importLoading = useState(false);
|
|
||||||
|
|
||||||
Future<void> removeUserDialog(String instanceHost, String username) async {
|
Future<void> removeUserDialog(String instanceHost, String username) async {
|
||||||
if (await showDialog<bool>(
|
if (await showDialog<bool>(
|
||||||
|
@ -235,34 +236,28 @@ class _AccountOptions extends HookWidget {
|
||||||
title: const Text('Remove account'),
|
title: const Text('Remove account'),
|
||||||
onTap: () => removeUserDialog(instanceHost, username),
|
onTap: () => removeUserDialog(instanceHost, username),
|
||||||
),
|
),
|
||||||
ListTile(
|
AsyncStoreListener(
|
||||||
leading: importLoading.value
|
asyncStore: context.read<ConfigStore>().lemmyImportState,
|
||||||
? const SizedBox(
|
successMessageBuilder: (context, data) => 'Import successful',
|
||||||
height: 25,
|
child: ObserverBuilder<ConfigStore>(
|
||||||
width: 25,
|
builder: (context, store) => ListTile(
|
||||||
child: CircularProgressIndicator.adaptive(),
|
leading: store.lemmyImportState.isLoading
|
||||||
)
|
? const SizedBox(
|
||||||
: const Icon(Icons.cloud_download),
|
height: 25,
|
||||||
title: const Text('Import settings to lemmur'),
|
width: 25,
|
||||||
onTap: () async {
|
child: CircularProgressIndicator.adaptive(),
|
||||||
importLoading.value = true;
|
)
|
||||||
try {
|
: const Icon(Icons.cloud_download),
|
||||||
await configStore.importLemmyUserSettings(
|
title: const Text('Import settings to lemmur'),
|
||||||
accountsStore.userDataFor(instanceHost, username)!.jwt,
|
onTap: () async {
|
||||||
);
|
await context.read<ConfigStore>().importLemmyUserSettings(
|
||||||
|
accountsStore.userDataFor(instanceHost, username)!.jwt,
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
);
|
||||||
content: Text('Import successful'),
|
|
||||||
));
|
|
||||||
} on Exception catch (err) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(err.toString()),
|
|
||||||
));
|
|
||||||
} finally {
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
importLoading.value = false;
|
},
|
||||||
}
|
),
|
||||||
}),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +1,84 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
|
import '../util/async_store.dart';
|
||||||
|
|
||||||
part 'config_store.g.dart';
|
part 'config_store.g.dart';
|
||||||
|
|
||||||
/// Store managing user-level configuration such as theme or language
|
/// Store managing user-level configuration such as theme or language
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ConfigStore extends ChangeNotifier {
|
@LocaleConverter()
|
||||||
static const prefsKey = 'v1:ConfigStore';
|
class ConfigStore extends _ConfigStore with _$ConfigStore {
|
||||||
static final _prefs = SharedPreferences.getInstance();
|
static const _prefsKey = 'v1:ConfigStore';
|
||||||
|
late final SharedPreferences _sharedPrefs;
|
||||||
|
late final ReactionDisposer _saveDisposer;
|
||||||
|
|
||||||
late ThemeMode _theme;
|
@visibleForTesting
|
||||||
|
ConfigStore();
|
||||||
|
|
||||||
|
factory ConfigStore.load(SharedPreferences sharedPrefs) {
|
||||||
|
final store = _$ConfigStoreFromJson(
|
||||||
|
jsonDecode(sharedPrefs.getString(_prefsKey) ?? '{}')
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
).._sharedPrefs = sharedPrefs;
|
||||||
|
|
||||||
|
store._saveDisposer = autorun((_) => store.save());
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
final serialized = jsonEncode(_$ConfigStoreToJson(this));
|
||||||
|
|
||||||
|
await _sharedPrefs.setString(_prefsKey, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_saveDisposer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _ConfigStore with Store {
|
||||||
|
@observable
|
||||||
@JsonKey(defaultValue: ThemeMode.system)
|
@JsonKey(defaultValue: ThemeMode.system)
|
||||||
ThemeMode get theme => _theme;
|
ThemeMode theme = ThemeMode.system;
|
||||||
set theme(ThemeMode theme) {
|
|
||||||
_theme = theme;
|
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
late bool _amoledDarkMode;
|
@observable
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool get amoledDarkMode => _amoledDarkMode;
|
bool amoledDarkMode = false;
|
||||||
set amoledDarkMode(bool amoledDarkMode) {
|
|
||||||
_amoledDarkMode = amoledDarkMode;
|
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
late Locale _locale;
|
// default value is set in the `LocaleConverter.fromJson`
|
||||||
// default value is set in the `LocaleSerde.fromJson` method because json_serializable does
|
@observable
|
||||||
// not accept non-literals as defaultValue
|
Locale locale = const Locale('en');
|
||||||
@JsonKey(fromJson: LocaleSerde.fromJson, toJson: LocaleSerde.toJson)
|
|
||||||
Locale get locale => _locale;
|
|
||||||
set locale(Locale locale) {
|
|
||||||
_locale = locale;
|
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
late bool _showAvatars;
|
@observable
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool get showAvatars => _showAvatars;
|
bool showAvatars = true;
|
||||||
set showAvatars(bool showAvatars) {
|
|
||||||
_showAvatars = showAvatars;
|
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
late bool _showScores;
|
@observable
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool get showScores => _showScores;
|
bool showScores = true;
|
||||||
set showScores(bool showScores) {
|
|
||||||
_showScores = showScores;
|
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
late SortType _defaultSortType;
|
|
||||||
// default is set in fromJson
|
// default is set in fromJson
|
||||||
|
@observable
|
||||||
@JsonKey(fromJson: _sortTypeFromJson)
|
@JsonKey(fromJson: _sortTypeFromJson)
|
||||||
SortType get defaultSortType => _defaultSortType;
|
SortType defaultSortType = SortType.hot;
|
||||||
set defaultSortType(SortType defaultSortType) {
|
|
||||||
_defaultSortType = defaultSortType;
|
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
late PostListingType _defaultListingType;
|
|
||||||
// default is set in fromJson
|
// default is set in fromJson
|
||||||
|
@observable
|
||||||
@JsonKey(fromJson: _postListingTypeFromJson)
|
@JsonKey(fromJson: _postListingTypeFromJson)
|
||||||
PostListingType get defaultListingType => _defaultListingType;
|
PostListingType defaultListingType = PostListingType.all;
|
||||||
set defaultListingType(PostListingType defaultListingType) {
|
|
||||||
_defaultListingType = defaultListingType;
|
final lemmyImportState = AsyncStore<FullSiteView>();
|
||||||
notifyListeners();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies over settings from lemmy to [ConfigStore]
|
/// Copies over settings from lemmy to [ConfigStore]
|
||||||
|
@action
|
||||||
void copyLemmyUserSettings(LocalUserSettings localUserSettings) {
|
void copyLemmyUserSettings(LocalUserSettings localUserSettings) {
|
||||||
// themes from lemmy-ui that are dark mode
|
// themes from lemmy-ui that are dark mode
|
||||||
const darkModeLemmyUiThemes = {
|
const darkModeLemmyUiThemes = {
|
||||||
|
@ -94,8 +89,8 @@ class ConfigStore extends ChangeNotifier {
|
||||||
'i386',
|
'i386',
|
||||||
};
|
};
|
||||||
|
|
||||||
_showAvatars = localUserSettings.showAvatars;
|
showAvatars = localUserSettings.showAvatars;
|
||||||
_theme = () {
|
theme = () {
|
||||||
if (localUserSettings.theme == 'browser') return ThemeMode.system;
|
if (localUserSettings.theme == 'browser') return ThemeMode.system;
|
||||||
|
|
||||||
if (darkModeLemmyUiThemes.contains(localUserSettings.theme)) {
|
if (darkModeLemmyUiThemes.contains(localUserSettings.theme)) {
|
||||||
|
@ -104,36 +99,27 @@ class ConfigStore extends ChangeNotifier {
|
||||||
|
|
||||||
return ThemeMode.light;
|
return ThemeMode.light;
|
||||||
}();
|
}();
|
||||||
_locale = L10n.supportedLocales.contains(Locale(localUserSettings.lang))
|
|
||||||
? Locale(localUserSettings.lang)
|
|
||||||
: _locale;
|
|
||||||
_showScores = localUserSettings.showScores;
|
|
||||||
_defaultSortType = localUserSettings.defaultSortType;
|
|
||||||
_defaultListingType = localUserSettings.defaultListingType;
|
|
||||||
|
|
||||||
notifyListeners();
|
if (L10n.supportedLocales.contains(Locale(localUserSettings.lang))) {
|
||||||
save();
|
locale = Locale(localUserSettings.lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
showScores = localUserSettings.showScores;
|
||||||
|
defaultSortType = localUserSettings.defaultSortType;
|
||||||
|
defaultListingType = localUserSettings.defaultListingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches [LocalUserSettings] and imports them with [.copyLemmyUserSettings]
|
/// Fetches [LocalUserSettings] and imports them with [.copyLemmyUserSettings]
|
||||||
|
@action
|
||||||
Future<void> importLemmyUserSettings(Jwt token) async {
|
Future<void> importLemmyUserSettings(Jwt token) async {
|
||||||
final site =
|
final site = await lemmyImportState.runLemmy(
|
||||||
await LemmyApiV3(token.payload.iss).run(GetSite(auth: token.raw));
|
token.payload.iss,
|
||||||
copyLemmyUserSettings(site.myUser!.localUserView.localUser);
|
GetSite(auth: token.raw),
|
||||||
}
|
|
||||||
|
|
||||||
static Future<ConfigStore> load() async {
|
|
||||||
final prefs = await _prefs;
|
|
||||||
|
|
||||||
return _$ConfigStoreFromJson(
|
|
||||||
jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map<String, dynamic>,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> save() async {
|
if (site != null) {
|
||||||
final prefs = await _prefs;
|
copyLemmyUserSettings(site.myUser!.localUserView.localUser);
|
||||||
|
}
|
||||||
await prefs.setString(prefsKey, jsonEncode(_$ConfigStoreToJson(this)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ ConfigStore _$ConfigStoreFromJson(Map<String, dynamic> json) => ConfigStore()
|
||||||
..theme =
|
..theme =
|
||||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? ThemeMode.system
|
$enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? ThemeMode.system
|
||||||
..amoledDarkMode = json['amoledDarkMode'] as bool? ?? false
|
..amoledDarkMode = json['amoledDarkMode'] as bool? ?? false
|
||||||
..locale = LocaleSerde.fromJson(json['locale'] as String?)
|
..locale = const LocaleConverter().fromJson(json['locale'] as String?)
|
||||||
..showAvatars = json['showAvatars'] as bool? ?? true
|
..showAvatars = json['showAvatars'] as bool? ?? true
|
||||||
..showScores = json['showScores'] as bool? ?? true
|
..showScores = json['showScores'] as bool? ?? true
|
||||||
..defaultSortType = _sortTypeFromJson(json['defaultSortType'] as String?)
|
..defaultSortType = _sortTypeFromJson(json['defaultSortType'] as String?)
|
||||||
|
@ -21,7 +21,7 @@ Map<String, dynamic> _$ConfigStoreToJson(ConfigStore instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'theme': _$ThemeModeEnumMap[instance.theme],
|
'theme': _$ThemeModeEnumMap[instance.theme],
|
||||||
'amoledDarkMode': instance.amoledDarkMode,
|
'amoledDarkMode': instance.amoledDarkMode,
|
||||||
'locale': LocaleSerde.toJson(instance.locale),
|
'locale': const LocaleConverter().toJson(instance.locale),
|
||||||
'showAvatars': instance.showAvatars,
|
'showAvatars': instance.showAvatars,
|
||||||
'showScores': instance.showScores,
|
'showScores': instance.showScores,
|
||||||
'defaultSortType': instance.defaultSortType,
|
'defaultSortType': instance.defaultSortType,
|
||||||
|
@ -33,3 +33,152 @@ const _$ThemeModeEnumMap = {
|
||||||
ThemeMode.light: 'light',
|
ThemeMode.light: 'light',
|
||||||
ThemeMode.dark: 'dark',
|
ThemeMode.dark: 'dark',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// StoreGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||||
|
|
||||||
|
mixin _$ConfigStore on _ConfigStore, Store {
|
||||||
|
final _$themeAtom = Atom(name: '_ConfigStore.theme');
|
||||||
|
|
||||||
|
@override
|
||||||
|
ThemeMode get theme {
|
||||||
|
_$themeAtom.reportRead();
|
||||||
|
return super.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set theme(ThemeMode value) {
|
||||||
|
_$themeAtom.reportWrite(value, super.theme, () {
|
||||||
|
super.theme = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$amoledDarkModeAtom = Atom(name: '_ConfigStore.amoledDarkMode');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get amoledDarkMode {
|
||||||
|
_$amoledDarkModeAtom.reportRead();
|
||||||
|
return super.amoledDarkMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set amoledDarkMode(bool value) {
|
||||||
|
_$amoledDarkModeAtom.reportWrite(value, super.amoledDarkMode, () {
|
||||||
|
super.amoledDarkMode = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$localeAtom = Atom(name: '_ConfigStore.locale');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Locale get locale {
|
||||||
|
_$localeAtom.reportRead();
|
||||||
|
return super.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set locale(Locale value) {
|
||||||
|
_$localeAtom.reportWrite(value, super.locale, () {
|
||||||
|
super.locale = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$showAvatarsAtom = Atom(name: '_ConfigStore.showAvatars');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get showAvatars {
|
||||||
|
_$showAvatarsAtom.reportRead();
|
||||||
|
return super.showAvatars;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set showAvatars(bool value) {
|
||||||
|
_$showAvatarsAtom.reportWrite(value, super.showAvatars, () {
|
||||||
|
super.showAvatars = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$showScoresAtom = Atom(name: '_ConfigStore.showScores');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get showScores {
|
||||||
|
_$showScoresAtom.reportRead();
|
||||||
|
return super.showScores;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set showScores(bool value) {
|
||||||
|
_$showScoresAtom.reportWrite(value, super.showScores, () {
|
||||||
|
super.showScores = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$defaultSortTypeAtom = Atom(name: '_ConfigStore.defaultSortType');
|
||||||
|
|
||||||
|
@override
|
||||||
|
SortType get defaultSortType {
|
||||||
|
_$defaultSortTypeAtom.reportRead();
|
||||||
|
return super.defaultSortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set defaultSortType(SortType value) {
|
||||||
|
_$defaultSortTypeAtom.reportWrite(value, super.defaultSortType, () {
|
||||||
|
super.defaultSortType = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$defaultListingTypeAtom =
|
||||||
|
Atom(name: '_ConfigStore.defaultListingType');
|
||||||
|
|
||||||
|
@override
|
||||||
|
PostListingType get defaultListingType {
|
||||||
|
_$defaultListingTypeAtom.reportRead();
|
||||||
|
return super.defaultListingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set defaultListingType(PostListingType value) {
|
||||||
|
_$defaultListingTypeAtom.reportWrite(value, super.defaultListingType, () {
|
||||||
|
super.defaultListingType = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$importLemmyUserSettingsAsyncAction =
|
||||||
|
AsyncAction('_ConfigStore.importLemmyUserSettings');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> importLemmyUserSettings(Jwt token) {
|
||||||
|
return _$importLemmyUserSettingsAsyncAction
|
||||||
|
.run(() => super.importLemmyUserSettings(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$_ConfigStoreActionController = ActionController(name: '_ConfigStore');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void copyLemmyUserSettings(LocalUserSettings localUserSettings) {
|
||||||
|
final _$actionInfo = _$_ConfigStoreActionController.startAction(
|
||||||
|
name: '_ConfigStore.copyLemmyUserSettings');
|
||||||
|
try {
|
||||||
|
return super.copyLemmyUserSettings(localUserSettings);
|
||||||
|
} finally {
|
||||||
|
_$_ConfigStoreActionController.endAction(_$actionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''
|
||||||
|
theme: ${theme},
|
||||||
|
amoledDarkMode: ${amoledDarkMode},
|
||||||
|
locale: ${locale},
|
||||||
|
showAvatars: ${showAvatars},
|
||||||
|
showScores: ${showScores},
|
||||||
|
defaultSortType: ${defaultSortType},
|
||||||
|
defaultListingType: ${defaultListingType}
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
typedef MobxBuilder<T extends Store> = Widget Function(BuildContext, T);
|
typedef MobxBuilder<T extends Store> = Widget Function(BuildContext, T);
|
||||||
typedef MobxListener<T extends Store> = void Function(BuildContext, T);
|
typedef MobxListener<T extends Store> = void Function(BuildContext, T);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
import '../hooks/stores.dart';
|
import '../hooks/stores.dart';
|
||||||
|
import '../stores/config_store.dart';
|
||||||
import 'cached_network_image.dart';
|
import 'cached_network_image.dart';
|
||||||
|
|
||||||
/// User's avatar. Respects the `showAvatars` setting from configStore
|
/// User's avatar. Respects the `showAvatars` setting from configStore
|
||||||
|
@ -26,8 +27,7 @@ class Avatar extends HookWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final showAvatars =
|
final showAvatars =
|
||||||
useConfigStoreSelect((configStore) => configStore.showAvatars) ||
|
useStore((ConfigStore store) => store.showAvatars) || alwaysShow;
|
||||||
alwaysShow;
|
|
||||||
|
|
||||||
final blankWidget = () {
|
final blankWidget = () {
|
||||||
if (noBlank) return const SizedBox.shrink();
|
if (noBlank) return const SizedBox.shrink();
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../comment_tree.dart';
|
import '../../comment_tree.dart';
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../hooks/logged_in_action.dart';
|
import '../../hooks/logged_in_action.dart';
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
import '../../util/icons.dart';
|
import '../../util/icons.dart';
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../util/observer_consumers.dart';
|
import '../../util/observer_consumers.dart';
|
||||||
import '../markdown_text.dart';
|
import '../markdown_text.dart';
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../l10n/l10n.dart';
|
import '../../l10n/l10n.dart';
|
||||||
import '../../util/extensions/api.dart';
|
import '../../util/extensions/api.dart';
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart' as ul;
|
import 'package:url_launcher/url_launcher.dart' as ul;
|
||||||
|
|
||||||
import '../../hooks/logged_in_action.dart';
|
import '../../hooks/logged_in_action.dart';
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../hooks/logged_in_action.dart';
|
import '../../hooks/logged_in_action.dart';
|
||||||
import '../../hooks/stores.dart';
|
import '../../hooks/stores.dart';
|
||||||
|
import '../../stores/config_store.dart';
|
||||||
import '../../util/intl.dart';
|
import '../../util/intl.dart';
|
||||||
import '../../util/observer_consumers.dart';
|
import '../../util/observer_consumers.dart';
|
||||||
import 'post_store.dart';
|
import 'post_store.dart';
|
||||||
|
@ -15,8 +15,7 @@ class PostVoting extends HookWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final showScores =
|
final showScores = useStore((ConfigStore store) => store.showScores);
|
||||||
useConfigStoreSelect((configStore) => configStore.showScores);
|
|
||||||
final loggedInAction = useLoggedInAction(context
|
final loggedInAction = useLoggedInAction(context
|
||||||
.select<PostStore, String>((store) => store.postView.instanceHost));
|
.select<PostStore, String>((store) => store.postView.instanceHost));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../hooks/logged_in_action.dart';
|
import '../../hooks/logged_in_action.dart';
|
||||||
import '../../util/observer_consumers.dart';
|
import '../../util/observer_consumers.dart';
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:lemmy_api_client/v3.dart';
|
||||||
import '../comment_tree.dart';
|
import '../comment_tree.dart';
|
||||||
import '../hooks/infinite_scroll.dart';
|
import '../hooks/infinite_scroll.dart';
|
||||||
import '../hooks/stores.dart';
|
import '../hooks/stores.dart';
|
||||||
|
import '../stores/config_store.dart';
|
||||||
import 'comment/comment.dart';
|
import 'comment/comment.dart';
|
||||||
import 'infinite_scroll.dart';
|
import 'infinite_scroll.dart';
|
||||||
import 'post/post.dart';
|
import 'post/post.dart';
|
||||||
|
@ -39,7 +40,7 @@ class SortableInfiniteList<T> extends HookWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final defaultSortType =
|
final defaultSortType =
|
||||||
useConfigStoreSelect((store) => store.defaultSortType);
|
useStore((ConfigStore store) => store.defaultSortType);
|
||||||
final defaultController = useInfiniteScrollController();
|
final defaultController = useInfiniteScrollController();
|
||||||
final isc = controller ?? defaultController;
|
final isc = controller ?? defaultController;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('blank', () {
|
|
||||||
expect(true, true);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:lemmur/stores/config_store.dart';
|
||||||
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
const _lemmyUserSettings = LocalUserSettings(
|
||||||
|
id: 1,
|
||||||
|
personId: 1,
|
||||||
|
showNsfw: true,
|
||||||
|
theme: 'browser',
|
||||||
|
defaultSortType: SortType.active,
|
||||||
|
defaultListingType: PostListingType.local,
|
||||||
|
lang: 'en',
|
||||||
|
showAvatars: true,
|
||||||
|
showScores: true,
|
||||||
|
sendNotificationsToEmail: true,
|
||||||
|
showReadPosts: true,
|
||||||
|
showBotAccounts: true,
|
||||||
|
showNewPostNotifs: true,
|
||||||
|
instanceHost: '',
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ConfigStore', () {
|
||||||
|
late SharedPreferences prefs;
|
||||||
|
late ConfigStore store;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
store = ConfigStore.load(prefs);
|
||||||
|
await prefs.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Store defaults match json defaults', () {
|
||||||
|
final store = ConfigStore();
|
||||||
|
final loaded = ConfigStore.load(prefs);
|
||||||
|
|
||||||
|
expect(store.theme, loaded.theme);
|
||||||
|
expect(store.amoledDarkMode, loaded.amoledDarkMode);
|
||||||
|
expect(store.locale, loaded.locale);
|
||||||
|
expect(store.showAvatars, loaded.showAvatars);
|
||||||
|
expect(store.showScores, loaded.showScores);
|
||||||
|
expect(store.defaultSortType, loaded.defaultSortType);
|
||||||
|
expect(store.defaultListingType, loaded.defaultListingType);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Changes are saved', () {
|
||||||
|
store.amoledDarkMode = false;
|
||||||
|
var loaded = ConfigStore.load(prefs);
|
||||||
|
expect(loaded.amoledDarkMode, false);
|
||||||
|
|
||||||
|
store.amoledDarkMode = true;
|
||||||
|
loaded = ConfigStore.load(prefs);
|
||||||
|
expect(loaded.amoledDarkMode, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Changes are not saved after disposing', () {
|
||||||
|
store.amoledDarkMode = false;
|
||||||
|
var loaded = ConfigStore.load(prefs);
|
||||||
|
expect(loaded.amoledDarkMode, false);
|
||||||
|
|
||||||
|
store
|
||||||
|
..dispose()
|
||||||
|
..amoledDarkMode = true;
|
||||||
|
loaded = ConfigStore.load(prefs);
|
||||||
|
expect(loaded.amoledDarkMode, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Copying LemmyUserSettings', () {
|
||||||
|
test('works', () {
|
||||||
|
store
|
||||||
|
..theme = ThemeMode.dark
|
||||||
|
..amoledDarkMode = false
|
||||||
|
..locale = const Locale('pl')
|
||||||
|
..showAvatars = false
|
||||||
|
..showScores = false
|
||||||
|
..defaultSortType = SortType.topYear
|
||||||
|
..defaultListingType = PostListingType.all
|
||||||
|
..copyLemmyUserSettings(_lemmyUserSettings);
|
||||||
|
|
||||||
|
expect(store.theme, ThemeMode.system);
|
||||||
|
expect(store.amoledDarkMode, false);
|
||||||
|
expect(store.locale, const Locale('en'));
|
||||||
|
expect(store.showAvatars, true);
|
||||||
|
expect(store.showScores, true);
|
||||||
|
expect(store.defaultSortType, SortType.active);
|
||||||
|
expect(store.defaultListingType, PostListingType.local);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detects dark theme', () {
|
||||||
|
store
|
||||||
|
..theme = ThemeMode.light
|
||||||
|
..copyLemmyUserSettings(_lemmyUserSettings.copyWith(theme: 'darkly'));
|
||||||
|
|
||||||
|
expect(store.theme, ThemeMode.dark);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lang ignores unrecognized', () {
|
||||||
|
store
|
||||||
|
..locale = const Locale('en')
|
||||||
|
..copyLemmyUserSettings(
|
||||||
|
_lemmyUserSettings.copyWith(lang: 'qweqweqwe'));
|
||||||
|
|
||||||
|
expect(store.locale, const Locale('en'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detects browser theme', () {
|
||||||
|
store
|
||||||
|
..theme = ThemeMode.light
|
||||||
|
..copyLemmyUserSettings(
|
||||||
|
_lemmyUserSettings.copyWith(theme: 'browser'));
|
||||||
|
|
||||||
|
expect(store.theme, ThemeMode.system);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue