diff --git a/lib/ui/pages/users/user.dart b/lib/ui/molecules/list_items/user_list_item.dart similarity index 79% rename from lib/ui/pages/users/user.dart rename to lib/ui/molecules/list_items/user_list_item.dart index e9459c66..7d34074e 100644 --- a/lib/ui/pages/users/user.dart +++ b/lib/ui/molecules/list_items/user_list_item.dart @@ -1,9 +1,14 @@ -part of 'users.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/ui/router/router.dart'; -class _User extends StatelessWidget { - const _User({ +class UserListItem extends StatelessWidget { + const UserListItem({ required this.user, required this.isPrimaryUser, + super.key, }); final User user; diff --git a/lib/ui/organisms/modals/new_ssh_key_modal.dart b/lib/ui/organisms/modals/new_ssh_key_modal.dart new file mode 100644 index 00000000..efdfb2ab --- /dev/null +++ b/lib/ui/organisms/modals/new_ssh_key_modal.dart @@ -0,0 +1,82 @@ +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/user/ssh_form_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/job.dart'; +import 'package:selfprivacy/ui/atoms/buttons/brand_button.dart'; + +class NewSshKeyModal extends StatelessWidget { + const NewSshKeyModal({ + required this.user, + required this.scrollController, + super.key, + }); + + final User user; + final ScrollController scrollController; + + @override + Widget build(final BuildContext context) => BlocProvider( + create: (final context) { + final jobCubit = context.read(); + final jobState = jobCubit.state; + if (jobState is JobsStateWithJobs) { + final jobs = jobState.clientJobList; + for (final job in jobs) { + if (job is CreateSSHKeyJob && job.user.login == user.login) { + user.sshKeys.add(job.publicKey); + } + } + } + return SshFormCubit( + jobsCubit: jobCubit, + user: user, + ); + }, + child: Builder( + builder: (final context) { + final formCubitState = context.watch().state; + + return BlocListener( + listener: (final context, final state) { + if (state.isSubmitted) { + Navigator.pop(context); + } + }, + child: ListView( + controller: scrollController, + padding: const EdgeInsets.all(16), + children: [ + const Gap(16), + Text( + user.login, + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const Gap(16), + IntrinsicHeight( + child: CubitFormTextField( + autofocus: true, + formFieldCubit: context.read().key, + decoration: InputDecoration( + labelText: 'ssh.input_label'.tr(), + ), + ), + ), + const Gap(16), + BrandButton.filled( + onPressed: formCubitState.isSubmitting + ? null + : () => context.read().trySubmit(), + title: 'ssh.create'.tr(), + ), + ], + ), + ); + }, + ), + ); +} diff --git a/lib/ui/organisms/modals/reset_password_modal.dart b/lib/ui/organisms/modals/reset_password_modal.dart new file mode 100644 index 00000000..2970c1be --- /dev/null +++ b/lib/ui/organisms/modals/reset_password_modal.dart @@ -0,0 +1,83 @@ +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; +import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/ui/atoms/buttons/brand_button.dart'; + +class ResetPasswordModal extends StatelessWidget { + const ResetPasswordModal({ + required this.user, + required this.scrollController, + super.key, + }); + + final User user; + final ScrollController scrollController; + + @override + Widget build(final BuildContext context) => BlocProvider( + create: (final BuildContext context) => UserFormCubit( + jobsCubit: context.read(), + fieldFactory: FieldCubitFactory(context), + initialUser: user, + ), + child: Builder( + builder: (final BuildContext context) { + final FormCubitState formCubitState = + context.watch().state; + + return BlocListener( + listener: + (final BuildContext context, final FormCubitState state) { + if (state.isSubmitted) { + Navigator.pop(context); + } + }, + child: ListView( + controller: scrollController, + padding: const EdgeInsets.all(16), + children: [ + const Gap(16), + Text( + 'users.reset_password'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const Gap(16), + CubitFormTextField( + autofocus: true, + formFieldCubit: context.read().password, + decoration: InputDecoration( + alignLabelWithHint: false, + labelText: 'basis.password'.tr(), + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 8), + child: IconButton( + icon: Icon( + Icons.refresh, + color: Theme.of(context).colorScheme.secondary, + ), + onPressed: + context.read().genNewPassword, + ), + ), + ), + ), + const Gap(16), + BrandButton.filled( + onPressed: formCubitState.isSubmitting + ? null + : () => context.read().trySubmit(), + title: 'basis.apply'.tr(), + ), + ], + ), + ); + }, + ), + ); +} diff --git a/lib/ui/pages/users/new_user.dart b/lib/ui/pages/users/new_user.dart index c8939c8f..52d9e004 100644 --- a/lib/ui/pages/users/new_user.dart +++ b/lib/ui/pages/users/new_user.dart @@ -1,4 +1,16 @@ -part of 'users.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; +import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/ui/atoms/buttons/brand_button.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; +import 'package:selfprivacy/utils/ui_helpers.dart'; @RoutePage() class NewUserPage extends StatelessWidget { diff --git a/lib/ui/pages/users/reset_password.dart b/lib/ui/pages/users/reset_password.dart deleted file mode 100644 index 1585ffbc..00000000 --- a/lib/ui/pages/users/reset_password.dart +++ /dev/null @@ -1,82 +0,0 @@ -part of 'users.dart'; - -class ResetPassword extends StatelessWidget { - const ResetPassword({ - required this.user, - super.key, - }); - - final User user; - - @override - Widget build(final BuildContext context) => BlocProvider( - create: (final BuildContext context) => UserFormCubit( - jobsCubit: context.read(), - fieldFactory: FieldCubitFactory(context), - initialUser: user, - ), - child: Builder( - builder: (final BuildContext context) { - final FormCubitState formCubitState = - context.watch().state; - - return BlocListener( - listener: - (final BuildContext context, final FormCubitState state) { - if (state.isSubmitted) { - Navigator.pop(context); - } - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - BrandHeader( - title: 'users.reset_password'.tr(), - ), - const SizedBox(width: 14), - Padding( - padding: paddingH16V0, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CubitFormTextField( - autofocus: true, - formFieldCubit: - context.read().password, - decoration: InputDecoration( - alignLabelWithHint: false, - labelText: 'basis.password'.tr(), - suffixIcon: Padding( - padding: const EdgeInsets.only(right: 8), - child: IconButton( - icon: Icon( - Icons.refresh, - color: - Theme.of(context).colorScheme.secondary, - ), - onPressed: context - .read() - .genNewPassword, - ), - ), - ), - ), - const SizedBox(height: 30), - BrandButton.filled( - onPressed: formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), - title: 'basis.apply'.tr(), - ), - const SizedBox(height: 30), - ], - ), - ), - ], - ), - ); - }, - ), - ); -} diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 667ac7d7..6de6b228 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -1,4 +1,21 @@ -part of 'users.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/bloc/users/users_bloc.dart'; +import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/job.dart'; +import 'package:selfprivacy/ui/atoms/cards/filled_card.dart'; +import 'package:selfprivacy/ui/atoms/list_tiles/list_tile_on_surface_variant.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/molecules/info_box/info_box.dart'; +import 'package:selfprivacy/ui/organisms/modals/new_ssh_key_modal.dart'; +import 'package:selfprivacy/ui/organisms/modals/reset_password_modal.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; +import 'package:selfprivacy/utils/ui_helpers.dart'; @RoutePage() class UserDetailsPage extends StatelessWidget { @@ -50,9 +67,16 @@ class UserDetailsPage extends StatelessWidget { context: context, isScrollControlled: true, useRootNavigator: true, - builder: (final BuildContext context) => Padding( - padding: MediaQuery.of(context).viewInsets, - child: ResetPassword(user: user), + builder: (final BuildContext context) => DraggableScrollableSheet( + expand: false, + maxChildSize: 0.9, + minChildSize: 0.3, + initialChildSize: 0.5, + builder: (final context, final scrollController) => + ResetPasswordModal( + user: user, + scrollController: scrollController, + ), ), ), leading: const Icon(Icons.lock_reset_outlined), @@ -185,13 +209,21 @@ class _SshKeysCard extends StatelessWidget { title: 'ssh.create'.tr(), leadingIcon: Icons.add_circle_outline, onTap: () { - showModalBottomSheet( + showModalBottomSheet( context: context, isScrollControlled: true, useRootNavigator: true, - builder: (final BuildContext context) => Padding( - padding: MediaQuery.of(context).viewInsets, - child: NewSshKey(user), + builder: (final BuildContext context) => + DraggableScrollableSheet( + expand: false, + maxChildSize: 0.9, + minChildSize: 0.3, + initialChildSize: 0.5, + builder: (final context, final scrollController) => + NewSshKeyModal( + user: user, + scrollController: scrollController, + ), ), ); }, @@ -272,76 +304,3 @@ class _SshKeysCard extends StatelessWidget { ); } } - -class NewSshKey extends StatelessWidget { - const NewSshKey(this.user, {super.key}); - final User user; - - @override - Widget build(final BuildContext context) => BlocProvider( - create: (final context) { - final jobCubit = context.read(); - final jobState = jobCubit.state; - if (jobState is JobsStateWithJobs) { - final jobs = jobState.clientJobList; - for (final job in jobs) { - if (job is CreateSSHKeyJob && job.user.login == user.login) { - user.sshKeys.add(job.publicKey); - } - } - } - return SshFormCubit( - jobsCubit: jobCubit, - user: user, - ); - }, - child: Builder( - builder: (final context) { - final formCubitState = context.watch().state; - - return BlocListener( - listener: (final context, final state) { - if (state.isSubmitted) { - Navigator.pop(context); - } - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - BrandHeader( - title: user.login, - ), - const SizedBox(width: 14), - Padding( - padding: paddingH16V0, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - IntrinsicHeight( - child: CubitFormTextField( - autofocus: true, - formFieldCubit: context.read().key, - decoration: InputDecoration( - labelText: 'ssh.input_label'.tr(), - ), - ), - ), - const SizedBox(height: 30), - BrandButton.filled( - onPressed: formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), - title: 'ssh.create'.tr(), - ), - const SizedBox(height: 30), - ], - ), - ), - ], - ), - ); - }, - ), - ); -} diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 9b5e8e83..1e958184 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -2,37 +2,18 @@ import 'package:auto_route/auto_route.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_theme.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/bloc/outdated_server_checker/outdated_server_checker_bloc.dart'; import 'package:selfprivacy/logic/bloc/users/users_bloc.dart'; -import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; -import 'package:selfprivacy/logic/cubit/forms/user/ssh_form_cubit.dart'; -import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; -import 'package:selfprivacy/logic/models/job.dart'; -import 'package:selfprivacy/ui/atoms/buttons/brand_button.dart'; import 'package:selfprivacy/ui/atoms/buttons/outlined_button.dart'; -import 'package:selfprivacy/ui/atoms/cards/filled_card.dart'; import 'package:selfprivacy/ui/atoms/icons/brand_icons.dart'; -import 'package:selfprivacy/ui/atoms/list_tiles/list_tile_on_surface_variant.dart'; -import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/molecules/cards/server_outdated_card.dart'; -import 'package:selfprivacy/ui/molecules/info_box/info_box.dart'; +import 'package:selfprivacy/ui/molecules/list_items/user_list_item.dart'; import 'package:selfprivacy/ui/molecules/placeholders/empty_page_placeholder.dart'; import 'package:selfprivacy/ui/organisms/headers/brand_header.dart'; import 'package:selfprivacy/ui/router/router.dart'; import 'package:selfprivacy/utils/breakpoints.dart'; -import 'package:selfprivacy/utils/platform_adapter.dart'; -import 'package:selfprivacy/utils/ui_helpers.dart'; - -part 'new_user.dart'; -part 'reset_password.dart'; -part 'user.dart'; -part 'user_details.dart'; @RoutePage() class UsersPage extends StatelessWidget { @@ -131,7 +112,8 @@ class UsersPage extends StatelessWidget { child: ListView.builder( itemCount: users.length, itemBuilder: - (final BuildContext context, final int index) => _User( + (final BuildContext context, final int index) => + UserListItem( user: users[index], isPrimaryUser: users[index].type == UserType.primary, ), diff --git a/lib/ui/router/router.dart b/lib/ui/router/router.dart index 053566f3..903771e5 100644 --- a/lib/ui/router/router.dart +++ b/lib/ui/router/router.dart @@ -31,6 +31,8 @@ import 'package:selfprivacy/ui/pages/services/service_settings.dart'; import 'package:selfprivacy/ui/pages/services/services.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; +import 'package:selfprivacy/ui/pages/users/new_user.dart'; +import 'package:selfprivacy/ui/pages/users/user_details.dart'; import 'package:selfprivacy/ui/pages/users/users.dart'; part 'router.gr.dart';