feat: Implement profile settings

This commit is contained in:
Christian Kußowski 2026-03-09 12:44:09 +01:00
parent a3a2a37a14
commit e66bf0a10c
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
7 changed files with 198 additions and 20 deletions

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:fluffychat/pages/settings_profile/settings_profile_presenter.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@ -218,6 +219,15 @@ abstract class AppRoutes {
: const Settings(),
),
routes: [
GoRoute(
path: 'profile_settings',
pageBuilder: (context, state) => defaultPageBuilder(
context,
state,
const SettingsProfilePresenter(),
),
redirect: loggedOutRedirect,
),
GoRoute(
path: 'notifications',
pageBuilder: (context, state) => defaultPageBuilder(

View file

@ -2782,5 +2782,9 @@
"description": "This should be a very short string because there is not much space in the button!"
},
"createNewChat": "Create new chat",
"reset": "Reset"
"reset": "Reset",
"profileSettings": "Profile settings",
"timezone": "Timezone",
"pronouns": "Pronouns",
"save": "Save"
}

View file

@ -118,24 +118,6 @@ class SettingsView extends StatelessWidget {
);
},
),
FutureBuilder(
future: Matrix.of(context).client.getAuthMetadata(),
builder: (context, snapshot) {
final accountManageUrl = snapshot.data?.issuer;
if (accountManageUrl == null) {
return const SizedBox.shrink();
}
return ListTile(
leading: const Icon(Icons.account_circle_outlined),
title: Text(L10n.of(context).manageAccount),
trailing: const Icon(Icons.open_in_new_outlined),
onTap: () => launchUrl(
accountManageUrl,
mode: LaunchMode.inAppBrowserView,
),
);
},
),
Divider(color: theme.dividerColor),
SwitchListTile.adaptive(
controlAffinity: ListTileControlAffinity.trailing,
@ -145,6 +127,34 @@ class SettingsView extends StatelessWidget {
onChanged: controller.firstRunBootstrapAction,
),
Divider(color: theme.dividerColor),
FutureBuilder(
future: Matrix.of(context).client.getAuthMetadata(),
builder: (context, snapshot) {
final accountManageUrl = snapshot.data?.issuer;
if (accountManageUrl == null) {
return const SizedBox.shrink();
}
return ListTile(
leading: const Icon(Icons.admin_panel_settings_outlined),
title: Text(L10n.of(context).manageAccount),
trailing: const Icon(Icons.open_in_new_outlined),
onTap: () => launchUrl(
accountManageUrl,
mode: LaunchMode.inAppBrowserView,
),
);
},
),
ListTile(
leading: const Icon(Icons.account_circle_outlined),
title: Text(L10n.of(context).profileSettings),
tileColor:
activeRoute.startsWith('/rooms/settings/profile_settings')
? theme.colorScheme.surfaceContainerHigh
: null,
onTap: () => context.go('/rooms/settings/profile_settings'),
),
ListTile(
leading: const Icon(Icons.format_paint_outlined),
title: Text(L10n.of(context).changeTheme),

View file

@ -0,0 +1,62 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:flutter/material.dart';
class SettingsProfilePage extends StatelessWidget {
final TextEditingController? pronounsController;
final String? timezone;
final VoidCallback save;
final bool isLoading;
const SettingsProfilePage({
super.key,
required this.pronounsController,
required this.save,
required this.timezone,
required this.isLoading,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: !FluffyThemes.isColumnMode(context),
title: Text(L10n.of(context).profileSettings),
),
body: MaxWidthBody(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
crossAxisAlignment: .stretch,
spacing: 32,
children: [
TextField(
controller: pronounsController,
readOnly: pronounsController == null,
decoration: InputDecoration(
labelText: L10n.of(context).pronouns,
),
),
],
),
),
),
bottomNavigationBar: Material(
elevation: 8,
shadowColor: theme.appBarTheme.shadowColor,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(
onPressed: pronounsController == null || isLoading ? null : save,
child: isLoading
? LinearProgressIndicator()
: Text(L10n.of(context).saveChanges),
),
),
),
);
}
}

View file

@ -0,0 +1,91 @@
import 'package:fluffychat/pages/settings_profile/settings_profile_page.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
class SettingsProfilePresenter extends StatefulWidget {
const SettingsProfilePresenter({super.key});
@override
State<SettingsProfilePresenter> createState() =>
_SettingsProfilePresenterState();
}
class _SettingsProfilePresenterState extends State<SettingsProfilePresenter> {
static const String pronounsKey = 'io.fsky.nyx.pronouns';
static const String timezoneFallbackKey = 'us.cloke.msc4175.tz';
TextEditingController? _pronounsController;
String? _timezone;
bool _isLoading = false;
@override
void initState() {
_loadProfile();
super.initState();
}
Future<void> _loadProfile() async {
final client = Matrix.of(context).client;
final cachedProfile = await client.getUserProfile(
client.userID!,
maxCacheAge: Duration.zero,
);
print(cachedProfile.additionalProperties);
setState(() {
_timezone =
cachedProfile.mTz ??
cachedProfile.additionalProperties.tryGet<String>(
timezoneFallbackKey,
);
_pronounsController = TextEditingController(
text: cachedProfile.additionalProperties.tryGet<String>(pronounsKey),
);
});
}
Future<void> _save() async {
final client = Matrix.of(context).client;
final cachedProfile = await client.getUserProfile(client.userID!);
setState(() {
_isLoading = true;
});
try {
final newPronouns = _pronounsController!.text.trim();
if (newPronouns !=
cachedProfile.additionalProperties.tryGet<String>(pronounsKey)) {
await client.setProfileField(client.userID!, pronounsKey, {
pronounsKey: newPronouns,
});
}
if (cachedProfile.mTz != _timezone) {
await client.setProfileField(client.userID!, 'm.tz', {
'm.tz': _timezone,
});
}
} catch (e, s) {
Logs().e('Unable to update profile', e, s);
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) => SettingsProfilePage(
pronounsController: _pronounsController,
save: _save,
timezone: _timezone,
isLoading: _isLoading,
);
}

View file

@ -1898,7 +1898,7 @@ packages:
source: hosted
version: "0.6.16"
timezone:
dependency: transitive
dependency: "direct main"
description:
name: timezone
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1

View file

@ -77,6 +77,7 @@ dependencies:
sqflite_common_ffi: ^2.3.7+1
sqlcipher_flutter_libs: ^0.6.8
swipe_to_action: ^0.3.0
timezone: ^0.10.1
unifiedpush: ^6.2.0
unifiedpush_ui: ^0.2.0
universal_html: ^2.3.0