8fffafde47
Translations are provided in *.arb* format. Some keys have descriptions (indicated by leading @-symbol). Descriptions should not be copied into the translation itself. Currently only English is supported (app_en.arb), but German is planned. Apparently weblate merged .arb support at some time, so it would be nice to enable community translations at some point.
264 lines
11 KiB
Dart
264 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:outbag_app/backend/request.dart';
|
|
import 'package:outbag_app/backend/themes.dart';
|
|
import 'package:outbag_app/backend/user.dart';
|
|
import 'package:outbag_app/screens/settings/dialogs/password.dart';
|
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
|
|
class SettingsPage extends StatefulWidget {
|
|
const SettingsPage({super.key});
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _SettingsPageState();
|
|
}
|
|
|
|
class _SettingsPageState extends State<SettingsPage> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context)
|
|
.textTheme
|
|
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
|
|
|
final user = context.watch<User>();
|
|
final meta = context.watch<AccountMeta?>();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(AppLocalizations.of(context)!.settings),
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Center(
|
|
child: Column(children: [
|
|
// uswer information widget
|
|
Padding(
|
|
padding: const EdgeInsets.all(14),
|
|
child: Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Text(user.humanReadable,
|
|
style: textTheme.titleLarge)),
|
|
ListTile(
|
|
title: Text(
|
|
AppLocalizations.of(context)!.limitRoomCount),
|
|
subtitle: Text(AppLocalizations.of(context)!
|
|
.limitRoomCountSubtitle),
|
|
trailing: Text('${meta?.maxRoomCount ?? ""}'),
|
|
),
|
|
ListTile(
|
|
title: Text(
|
|
AppLocalizations.of(context)!.limitRoomSize),
|
|
subtitle: Text(AppLocalizations.of(context)!
|
|
.limitRoomSizeSubtitle),
|
|
trailing: Text('${meta?.maxRoomSize ?? ""}'),
|
|
),
|
|
ListTile(
|
|
title: Text(AppLocalizations.of(context)!
|
|
.limitRoomMemberCount),
|
|
subtitle: Text(AppLocalizations.of(context)!
|
|
.limitRoomMemberCountSubtitle),
|
|
trailing:
|
|
Text('${meta?.maxRoomMemberCount ?? ""}')),
|
|
ListTile(
|
|
title: Text(AppLocalizations.of(context)!
|
|
.userDiscoverable),
|
|
subtitle: Text(AppLocalizations.of(context)!
|
|
.userDiscoverableSubtitle),
|
|
trailing: Checkbox(
|
|
tristate: true,
|
|
value: meta?.discoverable,
|
|
onChanged: (_) {
|
|
// TODO: implement changeVisibility
|
|
},
|
|
))
|
|
],
|
|
)))),
|
|
|
|
// change theme button
|
|
ListTile(
|
|
title: Text(AppLocalizations.of(context)!.changeThemeTitle),
|
|
subtitle: Text(AppLocalizations.of(context)!.changeThemeSubtitle),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(
|
|
AppLocalizations.of(context)!.changeThemeTitle),
|
|
content: SingleChildScrollView(
|
|
child: Column(children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Text(AppLocalizations.of(context)!
|
|
.changeThemeSubtitle),
|
|
),
|
|
SegmentedButton<AppTheme>(
|
|
selected: {context.watch<AppTheme>()},
|
|
selectedIcon: Icon(context.watch<AppTheme>().icon),
|
|
showSelectedIcon: true,
|
|
multiSelectionEnabled: false,
|
|
emptySelectionAllowed: false,
|
|
segments: AppTheme.list().map((item) {
|
|
return ButtonSegment<AppTheme>(
|
|
value: item,
|
|
icon: Icon(item.icon),
|
|
label: Text(item.name(context)));
|
|
}).toList(),
|
|
onSelectionChanged: (item) async {
|
|
try {
|
|
await item.first.toDisk();
|
|
} catch (_) {}
|
|
},
|
|
),
|
|
])),
|
|
actions: [
|
|
FilledButton(
|
|
child: Text(AppLocalizations.of(context)!.close),
|
|
onPressed: () {
|
|
context.pop();
|
|
},
|
|
)
|
|
],
|
|
));
|
|
},
|
|
),
|
|
|
|
// change password button
|
|
ListTile(
|
|
title: Text(AppLocalizations.of(context)!.changePasswordTitle),
|
|
subtitle:
|
|
Text(AppLocalizations.of(context)!.changePasswordSubtitle),
|
|
onTap: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => const ChangePasswordDialog());
|
|
},
|
|
trailing: const Icon(Icons.chevron_right),
|
|
),
|
|
|
|
// export account to json
|
|
ListTile(
|
|
title: Text(AppLocalizations.of(context)!.exportAccountTitle),
|
|
subtitle: Text(AppLocalizations.of(context)!.exportAccountSubtitle),
|
|
onTap: () {
|
|
// TODO: show confirm dialog
|
|
// NOTE: json dump the localstore
|
|
// including users and rooms
|
|
// NOTE: feature not confirmed
|
|
},
|
|
trailing: const Icon(Icons.chevron_right),
|
|
),
|
|
|
|
// delete account button
|
|
ListTile(
|
|
title: Text(AppLocalizations.of(context)!.deleteAccountTitle),
|
|
subtitle: Text(AppLocalizations.of(context)!.deleteAccountSubtitle),
|
|
onTap: () {
|
|
// show confirm dialog
|
|
// NOTE: same as logout
|
|
// and performs a network request
|
|
// but deletes account beforehand
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: Text(
|
|
AppLocalizations.of(context)!.deleteAccountTitle),
|
|
content: Text(
|
|
AppLocalizations.of(context)!.deleteAccountConfirm),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
// close popup
|
|
Navigator.of(ctx).pop();
|
|
},
|
|
child: Text(AppLocalizations.of(context)!.cancel),
|
|
),
|
|
FilledButton(
|
|
onPressed: () async {
|
|
// send request
|
|
final scaffMgr = ScaffoldMessenger.of(ctx);
|
|
final nav = Navigator.of(ctx);
|
|
final router = GoRouter.of(ctx);
|
|
|
|
doNetworkRequest(scaffMgr,
|
|
req: () => postWithCreadentials(
|
|
path: 'deleteAccount',
|
|
target: user.server,
|
|
body: {},
|
|
credentials: user),
|
|
onOK: (_) async {
|
|
// delete everything
|
|
// delete user data (meta)
|
|
try {
|
|
await User.removeDisk();
|
|
} catch (_) {}
|
|
// TODO: delete all rooms
|
|
|
|
// go back home
|
|
router.pushReplacementNamed('home');
|
|
},
|
|
after: () {
|
|
// close popup
|
|
nav.pop();
|
|
});
|
|
},
|
|
child: Text(AppLocalizations.of(context)!.yes),
|
|
)
|
|
],
|
|
));
|
|
},
|
|
trailing: const Icon(Icons.chevron_right),
|
|
),
|
|
|
|
// logout button
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: FilledButton.tonal(
|
|
child: Text(AppLocalizations.of(context)!.logOut),
|
|
onPressed: () {
|
|
// show confirm dialog
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: Text(AppLocalizations.of(context)!.logOut),
|
|
content: Text(
|
|
AppLocalizations.of(context)!.logOutConfirm),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
// close popup
|
|
Navigator.of(ctx).pop();
|
|
},
|
|
child:
|
|
Text(AppLocalizations.of(context)!.cancel),
|
|
),
|
|
FilledButton(
|
|
onPressed: () async {
|
|
// send request
|
|
final router = GoRouter.of(ctx);
|
|
|
|
// delete everything
|
|
// delete user data (meta)
|
|
try {
|
|
await User.removeDisk();
|
|
} catch (_) {}
|
|
// TODO: delete all rooms
|
|
|
|
// go back home
|
|
router.pushReplacementNamed('home');
|
|
},
|
|
child: Text(AppLocalizations.of(context)!.yes),
|
|
)
|
|
],
|
|
));
|
|
},
|
|
))
|
|
]))));
|
|
}
|
|
}
|