diff --git a/assets/translations/en.json b/assets/translations/en.json index b68c6b82..501efaff 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -272,6 +272,7 @@ "_comment": "'Users' tab", "add_new_user": "Add a first user", "new_user": "New user", + "delete_user": "Delete user", "not_ready": "Please connect server, domain and DNS in the Providers tab, to be able to add a first user", "nobody_here": "Nobody here", "login": "Login", @@ -287,7 +288,9 @@ "refresh_users": "Refresh users list", "could_not_create_user": "Couldn't create user", "could_not_delete_user": "Couldn't delete user", - "could_not_add_ssh_key": "Couldn't add SSH key" + "could_not_add_ssh_key": "Couldn't add SSH key", + "email_login": "Email login", + "no_sso_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon." }, "initializing": { "_comment": "initializing page", diff --git a/lib/ui/pages/ssh_keys/new_ssh_key.dart b/lib/ui/pages/ssh_keys/new_ssh_key.dart index fc558be2..042707ef 100644 --- a/lib/ui/pages/ssh_keys/new_ssh_key.dart +++ b/lib/ui/pages/ssh_keys/new_ssh_key.dart @@ -1,7 +1,7 @@ part of 'ssh_keys.dart'; -class _NewSshKey extends StatelessWidget { - const _NewSshKey(this.user); +class NewSshKey extends StatelessWidget { + const NewSshKey(this.user, {final super.key}); final User user; @override diff --git a/lib/ui/pages/ssh_keys/ssh_keys.dart b/lib/ui/pages/ssh_keys/ssh_keys.dart index 64c1af9c..4bc48e8a 100644 --- a/lib/ui/pages/ssh_keys/ssh_keys.dart +++ b/lib/ui/pages/ssh_keys/ssh_keys.dart @@ -65,7 +65,7 @@ class _SshKeysPageState extends State { backgroundColor: Colors.transparent, builder: (final BuildContext context) => Padding( padding: MediaQuery.of(context).viewInsets, - child: _NewSshKey(widget.user), + child: NewSshKey(widget.user), ), ); }, diff --git a/lib/ui/pages/users/user.dart b/lib/ui/pages/users/user.dart index 69a2e5dc..1602427d 100644 --- a/lib/ui/pages/users/user.dart +++ b/lib/ui/pages/users/user.dart @@ -11,10 +11,8 @@ class _User extends StatelessWidget { @override Widget build(final BuildContext context) => InkWell( onTap: () { - showBrandBottomSheet( - context: context, - builder: (final BuildContext context) => - _UserDetails(user: user, isRootUser: isRootUser), + Navigator.of(context).push( + materialRoute(_UserDetails(user: user, isRootUser: isRootUser)), ); }, child: Container( diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index d758c1f4..0bfbce8c 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -15,179 +15,192 @@ class _UserDetails extends StatelessWidget { final String domainName = UiHelpers.getDomainName(config); - return BrandBottomSheet( - isExpended: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 200, - decoration: BoxDecoration( - color: user.color, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), + return BrandHeroScreen( + hasBackButton: true, + heroTitle: user.login, + children: [ + BrandCards.filled( + child: Column( + children: [ + ListTile( + title: Text('${user.login}@$domainName'), + subtitle: Text('users.email_login'.tr()), + textColor: Theme.of(context).colorScheme.onSurfaceVariant, + leading: const Icon(Icons.alternate_email_outlined), + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (!isRootUser) - Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4, - horizontal: 2, - ), - child: PopupMenuButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - onSelected: (final PopupMenuItemType result) { - switch (result) { - case PopupMenuItemType.delete: - showDialog( - context: context, - builder: (final BuildContext context) => - AlertDialog( - title: Text('basis.confirmation'.tr()), - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'users.delete_confirm_question'.tr(), - ), - ], - ), - ), - actions: [ - TextButton( - child: Text('basis.cancel'.tr()), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text( - 'basis.delete'.tr(), - style: const TextStyle( - color: BrandColors.red1, - ), - ), - onPressed: () { - context - .read() - .addJob(DeleteUserJob(user: user)); - Navigator.of(context) - ..pop() - ..pop(); - }, - ), - ], - ), - ); - break; - } - }, - icon: const Icon(Icons.more_vert), - itemBuilder: (final BuildContext context) => [ - PopupMenuItem( - value: PopupMenuItemType.delete, - child: Container( - padding: const EdgeInsets.only(left: 5), - child: Text( - 'basis.delete'.tr(), - style: const TextStyle(color: BrandColors.red1), - ), + ], + ), + ), + const SizedBox(height: 8), + BrandCards.filled( + child: Column( + children: [ + ListTile( + title: Text('ssh.title'.tr()), + textColor: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const Divider(height: 0), + ListTile( + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, + textColor: Theme.of(context).colorScheme.onSurfaceVariant, + title: Text( + 'ssh.create'.tr(), + ), + leading: const Icon(Icons.add_circle_outlined), + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (final BuildContext context) => Padding( + padding: MediaQuery.of(context).viewInsets, + child: NewSshKey(user), + ), + ); + }, + ), + Column( + children: user.sshKeys.map((final String key) { + final publicKey = + key.split(' ').length > 1 ? key.split(' ')[1] : key; + final keyType = key.split(' ')[0]; + final keyName = key.split(' ').length > 2 + ? key.split(' ')[2] + : 'ssh.no_key_name'.tr(); + return ListTile( + textColor: Theme.of(context).colorScheme.onSurfaceVariant, + title: Text('$keyName ($keyType)'), + // do not overflow text + subtitle: Text( + publicKey, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + onTap: () { + showDialog( + context: context, + builder: (final BuildContext context) => AlertDialog( + title: Text('ssh.delete'.tr()), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text('ssh.delete_confirm_question'.tr()), + Text('$keyName ($keyType)'), + Text(publicKey), + ], ), ), - ], - ), + actions: [ + TextButton( + child: Text('basis.cancel'.tr()), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text( + 'basis.delete'.tr(), + style: const TextStyle( + color: BrandColors.red1, + ), + ), + onPressed: () { + context.read().addJob( + DeleteSSHKeyJob( + user: user, + publicKey: key, + ), + ); + Navigator.of(context) + ..pop() + ..pop(); + }, + ), + ], + ), + ); + }, + ); + }).toList(), + ), + ], + ), + ), + const SizedBox(height: 8), + ListTile( + iconColor: Theme.of(context).colorScheme.onBackground, + onTap: () => {}, + leading: const Icon(Icons.lock_reset_outlined), + title: Text( + 'users.reset_password'.tr(), + ), + ), + if (!isRootUser) + ListTile( + iconColor: Theme.of(context).colorScheme.error, + textColor: Theme.of(context).colorScheme.error, + onTap: () => { + showDialog( + context: context, + builder: (final BuildContext context) => AlertDialog( + title: Text('basis.confirmation'.tr()), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text( + 'users.delete_confirm_question'.tr(), + ), + ], ), ), - const Spacer(), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 20, - horizontal: 15, - ), - child: AutoSizeText( - user.login, - style: headline1Style, - softWrap: true, - minFontSize: 9, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), + actions: [ + TextButton( + child: Text('basis.cancel'.tr()), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text( + 'basis.delete'.tr(), + style: const TextStyle( + color: BrandColors.red1, + ), + ), + onPressed: () { + context + .read() + .addJob(DeleteUserJob(user: user)); + Navigator.of(context) + ..pop() + ..pop(); + }, + ), + ], ), - ], + ) + }, + leading: const Icon(Icons.person_remove_outlined), + title: Text( + 'users.delete_user'.tr(), ), ), - const SizedBox(height: 20), - Padding( - padding: paddingH15V0.copyWith(bottom: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BrandText.small('users.account'.tr()), - Container( - height: 40, - alignment: Alignment.centerLeft, - child: BrandText.h4('${user.login}@$domainName'), - ), - if (user.password != null) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 14), - BrandText.small('basis.password'.tr()), - Container( - height: 40, - alignment: Alignment.centerLeft, - child: BrandText.h4(user.password), - ), - ], - ), - const SizedBox(height: 24), - const BrandDivider(), - const SizedBox(height: 20), - ListTile( - onTap: () { - Navigator.of(context) - .push(materialRoute(SshKeysPage(user: user))); - }, - title: Text('ssh.title'.tr()), - subtitle: user.sshKeys.isNotEmpty - ? Text( - 'ssh.subtitle_with_keys' - .tr(args: [user.sshKeys.length.toString()]), - ) - : Text('ssh.subtitle_without_keys'.tr()), - trailing: const Icon(BrandIcons.key), - ), - const SizedBox(height: 20), - ListTile( - onTap: () { - Share.share( - 'login: ${user.login}, password: ${user.password}', - ); - }, - title: Text( - 'users.send_registration_data'.tr(), - ), - trailing: const Icon(BrandIcons.share), - ), - ], - ), - ) - ], - ), + const Divider(height: 8), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.warning_amber_outlined, size: 24), + const SizedBox(height: 16), + Text( + 'users.no_sso_notice'.tr(), + ), + ], + ), + ), + ], ); } } - -enum PopupMenuItemType { - // reset, - delete, -} diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index fb526970..9c215ef8 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -1,10 +1,8 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart'; @@ -15,15 +13,14 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart'; -import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; +import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; +import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; -import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/pages/ssh_keys/ssh_keys.dart'; import 'package:selfprivacy/utils/ui_helpers.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';