lemmur-app-android/lib/pages/settings/settings.dart

422 lines
13 KiB
Dart
Raw Normal View History

2020-08-31 16:17:39 +02:00
import 'package:flutter/material.dart';
2020-09-08 00:34:09 +02:00
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
2021-04-16 21:50:23 +02:00
import 'package:lemmy_api_client/v3.dart';
2020-08-31 16:17:39 +02:00
2021-09-14 23:45:26 +02:00
import '../../hooks/stores.dart';
import '../../l10n/l10n.dart';
import '../../stores/config_store.dart';
import '../../util/async_store_listener.dart';
2021-09-14 23:45:26 +02:00
import '../../util/goto.dart';
import '../../util/observer_consumers.dart';
2021-09-14 23:45:26 +02:00
import '../../widgets/about_tile.dart';
import '../../widgets/bottom_modal.dart';
import '../../widgets/radio_picker.dart';
import '../manage_account.dart';
import 'add_account_page.dart';
import 'add_instance_page.dart';
2021-10-02 17:42:28 +02:00
import 'blocks/blocks.dart';
2020-08-31 16:17:39 +02:00
2020-09-30 19:05:00 +02:00
/// Page with a list of different settings sections
2021-09-14 23:45:26 +02:00
class SettingsPage extends HookWidget {
2021-01-03 19:43:39 +01:00
const SettingsPage();
2020-08-31 16:17:39 +02:00
@override
2021-09-14 23:45:26 +02:00
Widget build(BuildContext context) {
final hasAnyUsers = useAccountsStoreSelect((store) => !store.hasNoAccount);
2021-09-14 23:45:26 +02:00
return Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).settings),
2021-09-14 23:45:26 +02:00
),
body: ListView(
children: [
ListTile(
leading: const Icon(Icons.settings),
title: const Text('General'),
onTap: () {
goTo(context, (_) => const GeneralConfigPage());
},
),
ListTile(
leading: const Icon(Icons.person),
title: const Text('Accounts'),
onTap: () {
goTo(context, (_) => AccountsConfigPage());
},
),
if (hasAnyUsers)
2021-02-09 15:12:13 +01:00
ListTile(
2021-09-14 23:45:26 +02:00
leading: const Icon(Icons.block),
title: const Text('Blocks'),
2021-02-09 15:12:13 +01:00
onTap: () {
Navigator.of(context).push(BlocksPage.route());
2021-02-09 15:12:13 +01:00
},
),
2021-09-14 23:45:26 +02:00
ListTile(
leading: const Icon(Icons.color_lens),
title: const Text('Appearance'),
onTap: () {
goTo(context, (_) => const AppearanceConfigPage());
},
),
const AboutTile()
],
),
);
}
2020-08-31 16:17:39 +02:00
}
2020-09-30 19:05:00 +02:00
/// Settings for theme color, AMOLED switch
class AppearanceConfigPage extends StatelessWidget {
2021-01-03 19:43:39 +01:00
const AppearanceConfigPage();
2020-08-31 16:17:39 +02:00
@override
Widget build(BuildContext context) {
return Scaffold(
2021-04-18 16:32:35 +02:00
appBar: AppBar(title: const Text('Appearance')),
body: ObserverBuilder<ConfigStore>(
builder: (context, store) => ListView(
children: [
const _SectionHeading('Theme'),
for (final theme in ThemeMode.values)
RadioListTile<ThemeMode>(
value: theme,
2022-01-06 16:05:46 +01:00
title: Text(theme.name),
groupValue: store.theme,
onChanged: (selected) {
if (selected != null) store.theme = selected;
},
),
SwitchListTile.adaptive(
title: const Text('AMOLED dark mode'),
value: store.amoledDarkMode,
onChanged: (checked) {
store.amoledDarkMode = checked;
},
),
const SizedBox(height: 12),
const _SectionHeading('Other'),
SwitchListTile.adaptive(
title: Text(L10n.of(context).show_avatars),
value: store.showAvatars,
onChanged: (checked) {
store.showAvatars = checked;
},
),
SwitchListTile.adaptive(
title: const Text('Show scores'),
value: store.showScores,
onChanged: (checked) {
store.showScores = checked;
},
),
],
),
2021-04-18 16:32:35 +02:00
),
);
}
}
/// General settings
class GeneralConfigPage extends StatelessWidget {
2021-04-18 16:32:35 +02:00
const GeneralConfigPage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('General')),
body: ObserverBuilder<ConfigStore>(
builder: (context, store) => ListView(
children: [
ListTile(
title: Text(L10n.of(context).sort_type),
trailing: SizedBox(
width: 120,
child: RadioPicker<SortType>(
values: SortType.values,
groupValue: store.defaultSortType,
onChanged: (value) => store.defaultSortType = value,
mapValueToString: (value) => value.value,
),
2021-02-28 20:01:32 +01:00
),
2021-02-27 17:02:55 +01:00
),
ListTile(
title: Text(L10n.of(context).type),
trailing: SizedBox(
width: 120,
child: RadioPicker<PostListingType>(
values: const [
PostListingType.all,
PostListingType.local,
PostListingType.subscribed,
],
groupValue: store.defaultListingType,
onChanged: (value) => store.defaultListingType = value,
mapValueToString: (value) => value.value,
),
2021-04-16 21:50:23 +02:00
),
),
ListTile(
title: Text(L10n.of(context).language),
trailing: SizedBox(
width: 120,
child: RadioPicker<Locale>(
title: 'Choose language',
groupValue: store.locale,
values: L10n.supportedLocales,
mapValueToString: (locale) => locale.languageName,
onChanged: (selected) {
store.locale = selected;
},
),
2021-04-16 21:50:23 +02:00
),
),
],
),
2020-08-31 16:17:39 +02:00
),
);
}
}
2021-04-16 21:19:59 +02:00
/// Popup for an account
class _AccountOptions extends HookWidget {
final String instanceHost;
final String username;
const _AccountOptions({
required this.instanceHost,
required this.username,
2022-05-11 22:23:18 +02:00
});
2020-09-08 00:34:09 +02:00
@override
Widget build(BuildContext context) {
2020-09-16 23:15:42 +02:00
final accountsStore = useAccountsStore();
2021-04-16 21:19:59 +02:00
Future<void> removeUserDialog(String instanceHost, String username) async {
if (await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
2021-04-16 21:19:59 +02:00
title: const Text('Remove user?'),
content: Text(
'Are you sure you want to remove $username@$instanceHost?'),
actions: [
2021-02-09 15:12:13 +01:00
TextButton(
2020-09-23 23:37:26 +02:00
onPressed: () => Navigator.of(context).pop(false),
child: Text(L10n.of(context).no),
2020-09-23 23:37:26 +02:00
),
2021-02-09 15:12:13 +01:00
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(L10n.of(context).yes),
),
],
),
) ??
false) {
2021-04-16 21:19:59 +02:00
await accountsStore.removeAccount(instanceHost, username);
2021-04-06 14:42:42 +02:00
Navigator.of(context).pop();
}
}
2021-04-16 21:19:59 +02:00
return Column(
children: [
if (accountsStore.defaultUsernameFor(instanceHost) != username)
ListTile(
leading: const Icon(Icons.check_circle_outline),
title: const Text('Set as default'),
onTap: () {
accountsStore.setDefaultAccountFor(instanceHost, username);
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.delete),
title: const Text('Remove account'),
onTap: () => removeUserDialog(instanceHost, username),
),
AsyncStoreListener(
asyncStore: context.read<ConfigStore>().lemmyImportState,
successMessageBuilder: (context, data) => 'Import successful',
child: ObserverBuilder<ConfigStore>(
builder: (context, store) => ListTile(
leading: store.lemmyImportState.isLoading
? const SizedBox(
height: 25,
width: 25,
child: CircularProgressIndicator.adaptive(),
)
: const Icon(Icons.cloud_download),
title: const Text('Import settings to lemmur'),
onTap: () async {
await context.read<ConfigStore>().importLemmyUserSettings(
accountsStore.userDataFor(instanceHost, username)!.jwt,
);
2021-04-21 16:12:18 +02:00
Navigator.of(context).pop();
},
),
),
),
2021-04-16 21:19:59 +02:00
],
);
}
}
/// Settings for managing accounts
class AccountsConfigPage extends HookWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final accountsStore = useAccountsStore();
removeInstanceDialog(String instanceHost) async {
if (await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
2021-04-16 21:19:59 +02:00
title: const Text('Remove instance?'),
content: Text('Are you sure you want to remove $instanceHost?'),
actions: [
2021-02-09 15:12:13 +01:00
TextButton(
2020-09-23 23:37:26 +02:00
onPressed: () => Navigator.of(context).pop(false),
child: Text(L10n.of(context).no),
2020-09-23 23:37:26 +02:00
),
2021-02-09 15:12:13 +01:00
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(L10n.of(context).yes),
),
],
),
) ??
false) {
2021-04-16 21:19:59 +02:00
await accountsStore.removeInstance(instanceHost);
2021-04-06 14:42:42 +02:00
Navigator.of(context).pop();
}
}
2021-04-06 14:42:42 +02:00
void accountActions(String instanceHost, String username) {
showBottomModal(
context: context,
2021-04-16 21:19:59 +02:00
builder: (context) => _AccountOptions(
instanceHost: instanceHost,
username: username,
2021-04-06 14:42:42 +02:00
),
);
}
void instanceActions(String instanceHost) {
showBottomModal(
context: context,
builder: (context) => Column(
children: [
ListTile(
leading: const Icon(Icons.delete),
title: const Text('Remove instance'),
onTap: () => removeInstanceDialog(instanceHost),
),
],
),
);
}
return Scaffold(
2020-09-08 00:34:09 +02:00
key: _scaffoldKey,
appBar: AppBar(
2021-03-03 12:31:36 +01:00
title: const Text('Accounts'),
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_close, // TODO: change to + => x
curve: Curves.bounceIn,
tooltip: 'Add account or instance',
children: [
SpeedDialChild(
2021-01-03 19:43:39 +01:00
child: const Icon(Icons.person_add),
2021-03-03 12:31:36 +01:00
label: 'Add account',
onTap: () => Navigator.of(context)
.push(AddAccountPage.route(accountsStore.instances.last)),
),
SpeedDialChild(
2021-01-03 19:43:39 +01:00
child: const Icon(Icons.dns),
2021-03-03 12:31:36 +01:00
label: 'Add instance',
onTap: () => Navigator.of(context).push(AddInstancePage.route()),
),
],
2021-01-03 19:43:39 +01:00
child: const Icon(Icons.add),
2020-09-08 00:34:09 +02:00
),
body: ListView(
children: [
2021-01-17 17:35:47 +01:00
if (accountsStore.instances.isEmpty)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(top: 100),
2021-02-09 15:12:13 +01:00
child: TextButton.icon(
onPressed: () =>
Navigator.of(context).push(AddInstancePage.route()),
2021-02-09 15:12:13 +01:00
icon: const Icon(Icons.add),
2021-03-03 12:31:36 +01:00
label: const Text('Add instance'),
2021-02-09 15:12:13 +01:00
),
),
],
),
2021-01-06 00:51:19 +01:00
for (final instance in accountsStore.instances) ...[
2021-01-03 19:43:39 +01:00
const SizedBox(height: 40),
2021-04-06 14:42:42 +02:00
ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
onLongPress: () => instanceActions(instance),
title: _SectionHeading(instance),
),
2021-01-17 17:35:47 +01:00
for (final username in accountsStore.usernamesFor(instance)) ...[
2021-04-06 14:42:42 +02:00
ListTile(
trailing: username == accountsStore.defaultUsernameFor(instance)
? Icon(
Icons.check_circle_outline,
2021-09-14 23:45:26 +02:00
color: theme.colorScheme.secondary,
2021-04-06 14:42:42 +02:00
)
: null,
title: Text(username),
onLongPress: () => accountActions(instance, username),
onTap: () {
goTo(
context,
(_) => ManageAccountPage(
instanceHost: instance,
username: username,
));
},
),
2020-09-08 16:37:58 +02:00
],
2021-01-17 17:35:47 +01:00
if (accountsStore.usernamesFor(instance).isEmpty)
ListTile(
2021-01-03 19:43:39 +01:00
leading: const Icon(Icons.add),
2021-03-03 12:31:36 +01:00
title: const Text('Add account'),
onTap: () {
Navigator.of(context).push(AddAccountPage.route(instance));
},
),
]
],
),
);
}
}
2020-09-03 23:14:26 +02:00
class _SectionHeading extends StatelessWidget {
final String text;
const _SectionHeading(this.text);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
2021-01-03 19:43:39 +01:00
padding: const EdgeInsets.only(left: 20),
2020-09-03 23:14:26 +02:00
child: Text(text.toUpperCase(),
2021-09-14 23:45:26 +02:00
style: theme.textTheme.subtitle2
?.copyWith(color: theme.colorScheme.secondary)),
2020-09-03 23:14:26 +02:00
);
}
}