30a19fcc1e
Added: - changePassword to change the password NOTE: this requires the old password, just to prevent account hijacking. - some basic user limit information - theme selector NOTE: the system theme is meant to function like auto-theme, and is directly translated into a flutter ThemeMode, however, this does not appear to be working on the web. This commit also adds the logout and delete account buttons, but they do not yet delete all rooms, nor do they properly logout the user. BUG: User is not logged out correctly, reloading the page fixes this. Maybe localstore.listen does not detect deletion?
267 lines
10 KiB
Dart
267 lines
10 KiB
Dart
import 'package:flutter/material.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:routemaster/routemaster.dart';
|
|
|
|
class SettingsPage extends StatefulWidget {
|
|
const SettingsPage({super.key});
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _SettingsPageState();
|
|
}
|
|
|
|
class _SettingsPageState extends State<SettingsPage> {
|
|
User? user;
|
|
AccountMeta? meta;
|
|
|
|
void fetchMeta() {
|
|
doNetworkRequest(ScaffoldMessenger.of(context), req: (user) {
|
|
setState(() {
|
|
this.user = user;
|
|
});
|
|
|
|
return postWithCreadentials(
|
|
path: 'getMyAccount',
|
|
credentials: user!,
|
|
target: user.server,
|
|
body: {});
|
|
}, onOK: (body) {
|
|
final meta = AccountMeta.fromJSON(body['data']);
|
|
setState(() {
|
|
this.meta = meta;
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => fetchMeta());
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context)
|
|
.textTheme
|
|
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Settings'),
|
|
leading: IconButton(
|
|
onPressed: () {
|
|
// go back
|
|
Navigator.of(context).pop();
|
|
},
|
|
icon: const Icon(Icons.arrow_back),
|
|
tooltip: "Go back",
|
|
),
|
|
),
|
|
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: const Text('Room count limit:'),
|
|
subtitle: const Text(
|
|
'How many rooms you are allowed to own'),
|
|
trailing: Text('${meta?.maxRoomCount ?? ""}'),
|
|
),
|
|
ListTile(
|
|
title: const Text('Room size limit:'),
|
|
subtitle: const Text(
|
|
'How many items/products/categories each room may contain'),
|
|
trailing: Text('${meta?.maxRoomSize ?? ""}'),
|
|
),
|
|
ListTile(
|
|
title: const Text('Room member limit:'),
|
|
subtitle: const Text(
|
|
'How many members each of your rooms may have'),
|
|
trailing:
|
|
Text('${meta?.maxRoomMemberCount ?? ""}')),
|
|
ListTile(
|
|
title: const Text('Discoverable'),
|
|
subtitle: const Text(
|
|
'Determines if your account can be discovered by users from other servers'),
|
|
trailing: Checkbox(
|
|
tristate: true,
|
|
value: meta?.discoverable,
|
|
onChanged: (_) {},
|
|
))
|
|
],
|
|
)))),
|
|
|
|
// change theme button
|
|
ListTile(
|
|
title: const Text('Change Theme'),
|
|
subtitle: const Text(
|
|
'You can change between a light theme, a dark theme and automatic theme selection'),
|
|
// NOTE: have the trailing item be a value select widget
|
|
trailing: 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));
|
|
}).toList(),
|
|
onSelectionChanged: (item) async {
|
|
try {
|
|
await item.first.toDisk();
|
|
} catch(_) {}
|
|
},
|
|
),
|
|
),
|
|
|
|
// change password button
|
|
ListTile(
|
|
title: const Text('Change password'),
|
|
subtitle: const Text('Choose a new password for your account'),
|
|
onTap: () {
|
|
// TODO: show confirm dialog
|
|
// NOTE: needs an input field for the current password
|
|
// NOTE: might want to show a message explaining,
|
|
// that there is no way to reset the password
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => const ChangePasswordDialog());
|
|
},
|
|
trailing: const Icon(Icons.chevron_right),
|
|
),
|
|
|
|
// export account to json
|
|
ListTile(
|
|
title: const Text('Export account'),
|
|
subtitle: const Text('Export account data'),
|
|
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: const Text('Delete account'),
|
|
subtitle: const Text('Delete your account from your homeserver'),
|
|
onTap: () {
|
|
// show confirm dialog
|
|
// NOTE: same as logout
|
|
// and performs a network request
|
|
// but deletes account beforehand
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('Delete account'),
|
|
content: const Text(
|
|
'Do you really want to delete your account?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
// close popup
|
|
Navigator.of(ctx).pop();
|
|
},
|
|
child: const Text('Cancel'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () async {
|
|
// send request
|
|
final scaffMgr = ScaffoldMessenger.of(ctx);
|
|
final nav = Navigator.of(ctx);
|
|
final rmaster = Routemaster.of(ctx);
|
|
|
|
doNetworkRequest(scaffMgr,
|
|
req: (user) => 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
|
|
rmaster.replace('/');
|
|
},
|
|
after: () {
|
|
// close popup
|
|
nav.pop();
|
|
});
|
|
},
|
|
child: const Text('Delete Account'),
|
|
)
|
|
],
|
|
));
|
|
},
|
|
trailing: const Icon(Icons.chevron_right),
|
|
),
|
|
|
|
// logout button
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: FilledButton.tonal(
|
|
child: const Text('Log out'),
|
|
onPressed: () {
|
|
// show confirm dialog
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('Log out'),
|
|
content:
|
|
const Text('Do you really want to log out?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
// close popup
|
|
Navigator.of(ctx).pop();
|
|
},
|
|
child: const Text('Cancel'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () async {
|
|
// send request
|
|
final rmaster = Routemaster.of(ctx);
|
|
|
|
// delete everything
|
|
// delete user data (meta)
|
|
try {
|
|
await User.removeDisk();
|
|
} catch (_) {}
|
|
// TODO: delete all rooms
|
|
|
|
// go back home
|
|
rmaster.replace('/');
|
|
},
|
|
child: const Text('Log out'),
|
|
)
|
|
],
|
|
));
|
|
},
|
|
))
|
|
]))));
|
|
}
|
|
}
|