diff --git a/lib/config/routes.dart b/lib/config/routes.dart index a6d2d70ce..03efd9425 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -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( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 4226d4fdc..aa09a4159 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -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" } \ No newline at end of file diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index e8ad37dfe..25d4f93bc 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -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), diff --git a/lib/pages/settings_profile/settings_profile_page.dart b/lib/pages/settings_profile/settings_profile_page.dart new file mode 100644 index 000000000..aa4e26a76 --- /dev/null +++ b/lib/pages/settings_profile/settings_profile_page.dart @@ -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), + ), + ), + ), + ); + } +} diff --git a/lib/pages/settings_profile/settings_profile_presenter.dart b/lib/pages/settings_profile/settings_profile_presenter.dart new file mode 100644 index 000000000..7265b4955 --- /dev/null +++ b/lib/pages/settings_profile/settings_profile_presenter.dart @@ -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 createState() => + _SettingsProfilePresenterState(); +} + +class _SettingsProfilePresenterState extends State { + 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 _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( + timezoneFallbackKey, + ); + _pronounsController = TextEditingController( + text: cachedProfile.additionalProperties.tryGet(pronounsKey), + ); + }); + } + + Future _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(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, + ); +} diff --git a/pubspec.lock b/pubspec.lock index 982b471c2..ec45da49d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1898,7 +1898,7 @@ packages: source: hosted version: "0.6.16" timezone: - dependency: transitive + dependency: "direct main" description: name: timezone sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 diff --git a/pubspec.yaml b/pubspec.yaml index aeeb8e91c..f1c07a577 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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