import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:lemmy_api_client/v3.dart'; import '../../hooks/stores.dart'; import '../../l10n/l10n.dart'; import '../../stores/config_store.dart'; import '../../util/async_store_listener.dart'; import '../../util/goto.dart'; import '../../util/observer_consumers.dart'; 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'; import 'blocks/blocks.dart'; /// Page with a list of different settings sections class SettingsPage extends HookWidget { const SettingsPage(); @override Widget build(BuildContext context) { final hasAnyUsers = useAccountsStoreSelect((store) => !store.hasNoAccount); return Scaffold( appBar: AppBar( title: Text(L10n.of(context).settings), ), 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) ListTile( leading: const Icon(Icons.block), title: const Text('Blocks'), onTap: () { Navigator.of(context).push(BlocksPage.route()); }, ), ListTile( leading: const Icon(Icons.color_lens), title: const Text('Appearance'), onTap: () { goTo(context, (_) => const AppearanceConfigPage()); }, ), const AboutTile() ], ), ); } } /// Settings for theme color, AMOLED switch class AppearanceConfigPage extends StatelessWidget { const AppearanceConfigPage(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Appearance')), body: ObserverBuilder( builder: (context, store) => ListView( children: [ const _SectionHeading('Theme'), for (final theme in ThemeMode.values) RadioListTile( value: theme, 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; }, ), ], ), ), ); } } /// General settings class GeneralConfigPage extends StatelessWidget { const GeneralConfigPage(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('General')), body: ObserverBuilder( builder: (context, store) => ListView( children: [ ListTile( title: Text(L10n.of(context).sort_type), trailing: SizedBox( width: 120, child: RadioPicker( values: SortType.values, groupValue: store.defaultSortType, onChanged: (value) => store.defaultSortType = value, mapValueToString: (value) => value.value, ), ), ), ListTile( title: Text(L10n.of(context).type), trailing: SizedBox( width: 120, child: RadioPicker( values: const [ PostListingType.all, PostListingType.local, PostListingType.subscribed, ], groupValue: store.defaultListingType, onChanged: (value) => store.defaultListingType = value, mapValueToString: (value) => value.value, ), ), ), ListTile( title: Text(L10n.of(context).language), trailing: SizedBox( width: 120, child: RadioPicker( title: 'Choose language', groupValue: store.locale, values: L10n.supportedLocales, mapValueToString: (locale) => locale.languageName, onChanged: (selected) { store.locale = selected; }, ), ), ), ], ), ), ); } } /// Popup for an account class _AccountOptions extends HookWidget { final String instanceHost; final String username; const _AccountOptions({ required this.instanceHost, required this.username, }); @override Widget build(BuildContext context) { final accountsStore = useAccountsStore(); Future removeUserDialog(String instanceHost, String username) async { if (await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Remove user?'), content: Text( 'Are you sure you want to remove $username@$instanceHost?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text(L10n.of(context).no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text(L10n.of(context).yes), ), ], ), ) ?? false) { await accountsStore.removeAccount(instanceHost, username); Navigator.of(context).pop(); } } 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().lemmyImportState, successMessageBuilder: (context, data) => 'Import successful', child: ObserverBuilder( 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().importLemmyUserSettings( accountsStore.userDataFor(instanceHost, username)!.jwt, ); Navigator.of(context).pop(); }, ), ), ), ], ); } } /// Settings for managing accounts class AccountsConfigPage extends HookWidget { final GlobalKey _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { final theme = Theme.of(context); final accountsStore = useAccountsStore(); removeInstanceDialog(String instanceHost) async { if (await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Remove instance?'), content: Text('Are you sure you want to remove $instanceHost?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text(L10n.of(context).no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text(L10n.of(context).yes), ), ], ), ) ?? false) { await accountsStore.removeInstance(instanceHost); Navigator.of(context).pop(); } } void accountActions(String instanceHost, String username) { showBottomModal( context: context, builder: (context) => _AccountOptions( instanceHost: instanceHost, username: username, ), ); } 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( key: _scaffoldKey, appBar: AppBar( title: const Text('Accounts'), ), floatingActionButton: SpeedDial( animatedIcon: AnimatedIcons.menu_close, // TODO: change to + => x curve: Curves.bounceIn, tooltip: 'Add account or instance', children: [ SpeedDialChild( child: const Icon(Icons.person_add), label: 'Add account', onTap: () => Navigator.of(context) .push(AddAccountPage.route(accountsStore.instances.last)), ), SpeedDialChild( child: const Icon(Icons.dns), label: 'Add instance', onTap: () => Navigator.of(context).push(AddInstancePage.route()), ), ], child: const Icon(Icons.add), ), body: ListView( children: [ if (accountsStore.instances.isEmpty) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(top: 100), child: TextButton.icon( onPressed: () => Navigator.of(context).push(AddInstancePage.route()), icon: const Icon(Icons.add), label: const Text('Add instance'), ), ), ], ), for (final instance in accountsStore.instances) ...[ const SizedBox(height: 40), ListTile( dense: true, contentPadding: EdgeInsets.zero, onLongPress: () => instanceActions(instance), title: _SectionHeading(instance), ), for (final username in accountsStore.usernamesFor(instance)) ...[ ListTile( trailing: username == accountsStore.defaultUsernameFor(instance) ? Icon( Icons.check_circle_outline, color: theme.colorScheme.secondary, ) : null, title: Text(username), onLongPress: () => accountActions(instance, username), onTap: () { goTo( context, (_) => ManageAccountPage( instanceHost: instance, username: username, )); }, ), ], if (accountsStore.usernamesFor(instance).isEmpty) ListTile( leading: const Icon(Icons.add), title: const Text('Add account'), onTap: () { Navigator.of(context).push(AddAccountPage.route(instance)); }, ), ] ], ), ); } } class _SectionHeading extends StatelessWidget { final String text; const _SectionHeading(this.text); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.only(left: 20), child: Text(text.toUpperCase(), style: theme.textTheme.subtitle2 ?.copyWith(color: theme.colorScheme.secondary)), ); } }