From fe72865a465ecf00a370be1923dfe4d7218a349c Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Fri, 18 Jul 2025 13:55:39 +0530 Subject: [PATCH] [client] Basic profile dialog --- .../ui/layout/explorer_page_collapsed.dart | 33 ++---- client/lib/ui/layout/nav_list.dart | 22 ---- client/lib/ui/profile/profile_dialog.dart | 110 ++++++++++++++++++ client/lib/ui/profile/profile_view.dart | 49 ++++++++ client/lib/util/avatar.dart | 7 -- 5 files changed, 166 insertions(+), 55 deletions(-) create mode 100644 client/lib/ui/profile/profile_dialog.dart create mode 100644 client/lib/ui/profile/profile_view.dart delete mode 100644 client/lib/util/avatar.dart diff --git a/client/lib/ui/layout/explorer_page_collapsed.dart b/client/lib/ui/layout/explorer_page_collapsed.dart index 93f7169b..a185ed2e 100644 --- a/client/lib/ui/layout/explorer_page_collapsed.dart +++ b/client/lib/ui/layout/explorer_page_collapsed.dart @@ -1,4 +1,3 @@ -import 'package:circular_profile_avatar/circular_profile_avatar.dart'; import 'package:flutter/material.dart'; import 'package:phylum/libphylum/phylum_account.dart'; import 'package:phylum/ui/explorer/explorer_controller.dart'; @@ -10,7 +9,8 @@ import 'package:phylum/ui/layout/search.dart'; import 'package:phylum/ui/layout/sync_status_button.dart'; import 'package:phylum/ui/menu/bottom_sheet.dart'; import 'package:phylum/ui/menu/option_groups.dart'; -import 'package:phylum/util/avatar.dart'; +import 'package:phylum/ui/profile/profile_dialog.dart'; +import 'package:phylum/ui/profile/profile_view.dart'; import 'package:provider/provider.dart'; class ExplorerPageCollapsed extends StatelessWidget { @@ -79,30 +79,11 @@ class ExplorerPageCollapsed extends StatelessWidget { StreamBuilder( stream: account.userNotifier.stream, initialData: account.user, - builder: (context, snapshot) { - final data = snapshot.data!; - String initials = data.name.split(' ').map((s) => s.isEmpty ? null : s.substring(0, 1)).join(''); - if (initials.isEmpty) { - initials = data.email.substring(0, 2); - } - final colorScheme = getColorFromString(data.email); - - return Padding( - padding: const EdgeInsets.all(6.0), - child: CircularProfileAvatar( - '', - initialsText: Text( - initials, - style: TextStyle(fontWeight: FontWeight.bold), - ), - foregroundColor: colorScheme.onPrimaryContainer, - backgroundColor: colorScheme.primaryContainer, - borderWidth: 1, - borderColor: colorScheme.onPrimaryContainer, - radius: 18, - ), - ); - }), + builder: (context, snapshot) => ProfileView( + name: snapshot.data!.name, + email: snapshot.data!.email, + onTap: () => ProfileDialog.show(context), + )), ], ); } diff --git a/client/lib/ui/layout/nav_list.dart b/client/lib/ui/layout/nav_list.dart index 13fc86fa..6bb72b0d 100644 --- a/client/lib/ui/layout/nav_list.dart +++ b/client/lib/ui/layout/nav_list.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_state_notifier/flutter_state_notifier.dart'; import 'package:offtheline/offtheline.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:phylum/libphylum/user_permission.dart'; import 'package:phylum/ui/app/shortcuts.dart'; import 'package:phylum/libphylum/db/db.dart'; @@ -166,27 +165,6 @@ class NavList extends StatelessWidget { : SizedBox(); }), if (!kIsWeb) const DownloadButton(), - ListTile( - leading: const Icon(Icons.help), - title: const Text('About'), - onTap: () async { - Scaffold.maybeOf(context)?.closeDrawer(); - final info = await PackageInfo.fromPlatform(); - if (!context.mounted) return; - showAboutDialog( - context: context, - applicationVersion: '${info.packageName} - ${info.version} (${info.buildNumber})', - ); - }, - ), - ListTile( - leading: const Icon(Icons.logout), - title: const Text('Log Out'), - onTap: () { - Scaffold.maybeOf(context)?.closeDrawer(); - context.read().close(clearData: true); - }, - ), ], ), ), diff --git a/client/lib/ui/profile/profile_dialog.dart b/client/lib/ui/profile/profile_dialog.dart new file mode 100644 index 00000000..942ee0d5 --- /dev/null +++ b/client/lib/ui/profile/profile_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:phylum/libphylum/phylum_account.dart'; +import 'package:phylum/ui/profile/profile_view.dart'; +import 'package:phylum/util/dialogs.dart'; +import 'package:provider/provider.dart'; + +class ProfileDialog extends StatelessWidget { + const ProfileDialog._(); + + static void show(BuildContext context) { + final account = context.read(); + showDialog( + context: context, + builder: (context) => MultiProvider( + providers: [ + Provider.value(value: account), + ], + child: AlertDialog( + scrollable: true, + content: SizedBox(width: 360, child: ProfileDialog._()), + actions: [ + ElevatedButton(onPressed: Navigator.of(context).pop, child: Text('OK')), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final account = context.read(); + return Column( + children: [ + Card( + child: Column( + children: [ + ListTile( + leading: ProfileView(name: account.user.name, email: account.user.email), + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + fit: FlexFit.loose, + child: Text( + account.userRepository.getUserDisplayName(account.user.id), + maxLines: 1, + overflow: TextOverflow.fade, + softWrap: false, + ), + ), + IconButton( + icon: Icon(Icons.edit, size: 18), + splashRadius: 6, + padding: EdgeInsets.symmetric(vertical: 3, horizontal: 6), + constraints: BoxConstraints(), + onPressed: () async { + final name = await showInputDialog( + context, + title: 'Change Name', + labelText: 'Name', + preset: account.user.name, + ); + if (name == null) return; + }, + ), + ], + ), + subtitle: Text(context.read().apiClient.serverUrl.host), + ), + Divider(thickness: 2), + const ListTile( + visualDensity: VisualDensity.compact, + leading: Icon(Icons.password), + title: Text('Change Password'), + ), + ListTile( + visualDensity: VisualDensity.compact, + leading: const Icon(Icons.logout), + title: const Text('Log Out'), + onTap: () { + Navigator.of(context).pop(); + context.read().close(clearData: true); + }, + ), + ], + ), + ), + const ListTile( + visualDensity: VisualDensity.compact, + leading: Icon(Icons.settings), + title: Text('Settings'), + ), + ListTile( + visualDensity: VisualDensity.compact, + leading: const Icon(Icons.help), + title: const Text('About'), + onTap: () async { + final info = await PackageInfo.fromPlatform(); + if (!context.mounted) return; + showAboutDialog( + context: context, + applicationVersion: '${info.packageName} - ${info.version} (${info.buildNumber})', + ); + }, + ), + ], + ); + } +} diff --git a/client/lib/ui/profile/profile_view.dart b/client/lib/ui/profile/profile_view.dart new file mode 100644 index 00000000..f350e2dd --- /dev/null +++ b/client/lib/ui/profile/profile_view.dart @@ -0,0 +1,49 @@ +import 'package:circular_profile_avatar/circular_profile_avatar.dart'; +import 'package:flutter/material.dart'; + +class ProfileView extends StatelessWidget { + final String name; + final String email; + final Function()? onTap; + + const ProfileView({super.key, required this.name, required this.email, this.onTap}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(6.0), + child: onTap == null + ? _buildAvatar() + : InkWell( + onTap: onTap, + child: _buildAvatar(), + ), + ); + } + + Widget _buildAvatar() { + String initials = name.split(' ').map((s) => s.isEmpty ? null : s.substring(0, 1)).join(''); + if (initials.isEmpty) { + initials = email.substring(0, 2); + } + final colorScheme = getColorFromString(email); + return CircularProfileAvatar( + '', + initialsText: Text( + initials, + style: TextStyle(fontWeight: FontWeight.bold), + ), + foregroundColor: colorScheme.onPrimaryContainer, + backgroundColor: colorScheme.primaryContainer, + borderWidth: 1, + borderColor: colorScheme.onPrimaryContainer, + radius: 18, + ); + } +} + +ColorScheme getColorFromString(String input) { + return ColorScheme.fromSeed( + seedColor: Colors.primaries[(input.hashCode) % Colors.primaries.length], + ); +} diff --git a/client/lib/util/avatar.dart b/client/lib/util/avatar.dart deleted file mode 100644 index 97cc2e1e..00000000 --- a/client/lib/util/avatar.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter/material.dart'; - -ColorScheme getColorFromString(String input) { - return ColorScheme.fromSeed( - seedColor: Colors.primaries[(input.hashCode) % Colors.primaries.length], - ); -}