From 3c548e5aa991f631774ee8a6fc1353133cbd2823 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 7 Sep 2023 18:25:13 -0300 Subject: [PATCH 1/5] feat: Add copy-to-clipboard for email on user page - Implement setClipboard adapter and encapsulate platform dependency on clipboard service --- .../components/list_tiles/log_list_tile.dart | 10 +++--- .../backups/copy_encryption_key_modal.dart | 8 ++--- .../pages/backups/snapshot_id_list_tile.dart | 4 +-- lib/ui/pages/users/user_details.dart | 32 ++++++++++++------- lib/ui/pages/users/users.dart | 2 ++ lib/utils/platform_adapter.dart | 5 +++ 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/lib/ui/components/list_tiles/log_list_tile.dart b/lib/ui/components/list_tiles/log_list_tile.dart index 76bc6150..e83765e9 100644 --- a/lib/ui/components/list_tiles/log_list_tile.dart +++ b/lib/ui/components/list_tiles/log_list_tile.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:selfprivacy/logic/models/message.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; class LogListItem extends StatelessWidget { const LogListItem({ @@ -71,7 +71,7 @@ class _RestApiRequestMessageItem extends StatelessWidget { if (message.text != null) TextButton( onPressed: () { - Clipboard.setData(ClipboardData(text: message.text ?? '')); + PlatformAdapter.setClipboard(message.text ?? ''); }, child: Text('console_page.copy'.tr()), ), @@ -121,7 +121,7 @@ class _RestApiResponseMessageItem extends StatelessWidget { if (message.text != null) TextButton( onPressed: () { - Clipboard.setData(ClipboardData(text: message.text ?? '')); + PlatformAdapter.setClipboard(message.text ?? ''); }, child: Text('console_page.copy'.tr()), ), @@ -195,7 +195,7 @@ class _GraphQlResponseMessageItem extends StatelessWidget { if (message.text != null) TextButton( onPressed: () { - Clipboard.setData(ClipboardData(text: message.text ?? '')); + PlatformAdapter.setClipboard(message.text ?? ''); }, child: Text('console_page.copy'.tr()), ), @@ -264,7 +264,7 @@ class _GraphQlRequestMessageItem extends StatelessWidget { if (message.text != null) TextButton( onPressed: () { - Clipboard.setData(ClipboardData(text: message.text ?? '')); + PlatformAdapter.setClipboard(message.text ?? ''); }, child: Text('console_page.copy'.tr()), ), diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index dca9d705..fc16c356 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; class CopyEncryptionKeyModal extends StatefulWidget { const CopyEncryptionKeyModal({ @@ -144,11 +144,7 @@ class _CopyEncryptionKeyModalState extends State { }, ); }); - Clipboard.setData( - ClipboardData( - text: encryptionKey, - ), - ); + PlatformAdapter.setClipboard(encryptionKey); }, icon: const Icon(Icons.copy_all_outlined), label: Text( diff --git a/lib/ui/pages/backups/snapshot_id_list_tile.dart b/lib/ui/pages/backups/snapshot_id_list_tile.dart index e159d888..802b90ee 100644 --- a/lib/ui/pages/backups/snapshot_id_list_tile.dart +++ b/lib/ui/pages/backups/snapshot_id_list_tile.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; class SnapshotIdListTile extends StatelessWidget { const SnapshotIdListTile({ @@ -14,7 +14,7 @@ class SnapshotIdListTile extends StatelessWidget { @override Widget build(final BuildContext context) => ListTile( onLongPress: () { - Clipboard.setData(ClipboardData(text: snapshotId)); + PlatformAdapter.setClipboard(snapshotId); getIt().showSnackBar( 'basis.copied_to_clipboard'.tr(), behavior: SnackBarBehavior.floating, diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 7ee3a7bd..90ab1603 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -143,17 +143,27 @@ class _UserLogins extends StatelessWidget { final String domainName; @override - Widget build(final BuildContext context) => FilledCard( - child: Column( - children: [ - ListTileOnSurfaceVariant( - title: '${user.login}@$domainName', - subtitle: 'users.email_login'.tr(), - leadingIcon: Icons.alternate_email_outlined, - ), - ], - ), - ); + Widget build(final BuildContext context) { + final email = '${user.login}@$domainName'; + return FilledCard( + child: Column( + children: [ + ListTileOnSurfaceVariant( + onTap: () { + PlatformAdapter.setClipboard(email); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + behavior: SnackBarBehavior.floating, + ); + }, + title: email, + subtitle: 'users.email_login'.tr(), + leadingIcon: Icons.alternate_email_outlined, + ), + ], + ), + ); + } } class _SshKeysCard extends StatelessWidget { diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 58cb3b7d..75d89fde 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -3,6 +3,7 @@ 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/cubit/forms/user/ssh_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; @@ -22,6 +23,7 @@ import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_varian import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.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 'empty.dart'; diff --git a/lib/utils/platform_adapter.dart b/lib/utils/platform_adapter.dart index fa40d7d9..7e010dd4 100644 --- a/lib/utils/platform_adapter.dart +++ b/lib/utils/platform_adapter.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; /// SelfPrivacy wrapper for Platform information provider. class PlatformAdapter { @@ -56,4 +57,8 @@ class PlatformAdapter { return 'Unidentified'; } + + static void setClipboard(final String clipboardData) { + Clipboard.setData(ClipboardData(text: clipboardData)); + } } From d2ed9f3a752307bcbb57062e5cf524152aef3bae Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 16 Sep 2023 00:45:15 -0300 Subject: [PATCH 2/5] refactor(ui): Move service descriptions above login info for service cards - Resolves https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/341 --- lib/ui/pages/services/services.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 99bb848e..08446e78 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -160,13 +160,15 @@ class _Card extends StatelessWidget { ], ), Text( - service.loginInfo, + service.description, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 10), Text( - service.description, - style: Theme.of(context).textTheme.bodyMedium, + service.loginInfo, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), ), const SizedBox(height: 10), ], From 887302f93656ff7a787e7bfe83dfef53115c31e3 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 16 Sep 2023 01:41:02 -0300 Subject: [PATCH 3/5] refactor(ui): Add measure units to 'Extending volume' page - Resolves https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/301 --- assets/translations/en.json | 3 ++- lib/ui/pages/server_storage/extending_volume.dart | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 5fb1ce5c..07ffe375 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -304,6 +304,7 @@ "extending_volume_price_info": "Price includes VAT and is estimated from pricing data provided by your server provider. Server will be rebooted after resizing.", "extending_volume_error": "Couldn't initialize volume extending.", "size": "Size", + "price": "Price", "data_migration_title": "Data migration", "data_migration_notice": "During migration all services will be turned off.", "start_migration_button": "Start migration", @@ -625,4 +626,4 @@ "reset_onboarding_description": "Reset onboarding switch to show onboarding screen again", "cubit_statuses": "Cubit loading statuses" } -} +} \ No newline at end of file diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index 95325d0c..81414bdb 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -79,10 +79,12 @@ class _ExtendingVolumePageState extends State { } final price = snapshot.data as Price; _pricePerGb = price.value; - _sizeController.text = _currentSliderGbValue.truncate().toString(); + final currentSizeValue = _currentSliderGbValue.truncate().toString(); + _sizeController.text = 'storage.gb'.tr(args: [currentSizeValue]); _priceController.text = - (_pricePerGb * double.parse(_sizeController.text)) - .toStringAsFixed(2); + '${(_pricePerGb * double.parse(currentSizeValue)).toStringAsFixed(2)}' + ' ' + '${price.currency.shortcode}'; minSize = widget.diskVolumeToResize.sizeTotal + DiskSize.fromGibibyte(3); if (_currentSliderGbValue < 0) { @@ -130,7 +132,7 @@ class _ExtendingVolumePageState extends State { decoration: InputDecoration( border: const OutlineInputBorder(), errorText: _isError ? ' ' : null, - labelText: price.currency.shortcode, + labelText: 'storage.price'.tr(), ), ), ), From 5c329d47c1c9e9f023c3053fe1b80d4c0645e450 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 16 Sep 2023 01:18:37 -0300 Subject: [PATCH 4/5] refactor(ui): Make users be ordered properly on users page - Resolves https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/340 --- lib/logic/cubit/users/users_state.dart | 26 ++++++++++++++++++++++++++ lib/ui/pages/users/user.dart | 6 +++--- lib/ui/pages/users/users.dart | 12 ++---------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/logic/cubit/users/users_state.dart b/lib/logic/cubit/users/users_state.dart index dbd931d7..4e2ed42e 100644 --- a/lib/logic/cubit/users/users_state.dart +++ b/lib/logic/cubit/users/users_state.dart @@ -18,6 +18,32 @@ class UsersState extends ServerInstallationDependendState { @override List get props => [users, isLoading]; + /// Makes a copy of existing users list, but places 'primary' + /// to the beginning and sorts the rest alphabetically + /// + /// If found a 'root' user, it doesn't get copied into the result + List get orderedUsers { + User? primaryUser; + final List normalUsers = []; + for (final User user in users) { + if (user.type == UserType.primary) { + primaryUser = user; + continue; + } + if (user.type == UserType.root) { + continue; + } + normalUsers.add(user); + } + + normalUsers.sort( + (final User a, final User b) => + a.login.toLowerCase().compareTo(b.login.toLowerCase()), + ); + + return primaryUser == null ? normalUsers : [primaryUser] + normalUsers; + } + UsersState copyWith({ final List? users, final bool? isLoading, diff --git a/lib/ui/pages/users/user.dart b/lib/ui/pages/users/user.dart index bbc01012..a40599e0 100644 --- a/lib/ui/pages/users/user.dart +++ b/lib/ui/pages/users/user.dart @@ -3,11 +3,11 @@ part of 'users.dart'; class _User extends StatelessWidget { const _User({ required this.user, - required this.isRootUser, + required this.isUserPrimary, }); final User user; - final bool isRootUser; + final bool isUserPrimary; @override Widget build(final BuildContext context) => InkWell( onTap: () { @@ -32,7 +32,7 @@ class _User extends StatelessWidget { user.login, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Theme.of(context).colorScheme.onBackground, - decoration: isRootUser + decoration: isUserPrimary ? TextDecoration.underline : user.isFoundOnServer ? TextDecoration.none diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 58cb3b7d..4e939f20 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -45,15 +45,7 @@ class UsersPage extends StatelessWidget { } else { child = BlocBuilder( builder: (final BuildContext context, final UsersState state) { - final List users = state.users - .where((final user) => user.type != UserType.root) - .toList(); - // final List users = []; - users.sort( - (final User a, final User b) => - a.login.toLowerCase().compareTo(b.login.toLowerCase()), - ); - + final users = state.orderedUsers; if (users.isEmpty) { if (state.isLoading) { return const Center( @@ -115,7 +107,7 @@ class UsersPage extends StatelessWidget { itemBuilder: (final BuildContext context, final int index) => _User( user: users[index], - isRootUser: users[index].type == UserType.primary, + isUserPrimary: users[index].type == UserType.primary, ), ), ), From 7aa3305d237d3c8183b49e4394d7182de201f16b Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 18 Sep 2023 12:28:41 -0300 Subject: [PATCH 5/5] refactor: Rename isUserPrimary to isPrimaryUser for User --- lib/ui/pages/users/user.dart | 6 +++--- lib/ui/pages/users/users.dart | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ui/pages/users/user.dart b/lib/ui/pages/users/user.dart index a40599e0..9a90857b 100644 --- a/lib/ui/pages/users/user.dart +++ b/lib/ui/pages/users/user.dart @@ -3,11 +3,11 @@ part of 'users.dart'; class _User extends StatelessWidget { const _User({ required this.user, - required this.isUserPrimary, + required this.isPrimaryUser, }); final User user; - final bool isUserPrimary; + final bool isPrimaryUser; @override Widget build(final BuildContext context) => InkWell( onTap: () { @@ -32,7 +32,7 @@ class _User extends StatelessWidget { user.login, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Theme.of(context).colorScheme.onBackground, - decoration: isUserPrimary + decoration: isPrimaryUser ? TextDecoration.underline : user.isFoundOnServer ? TextDecoration.none diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 4e939f20..22b9e451 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -107,7 +107,7 @@ class UsersPage extends StatelessWidget { itemBuilder: (final BuildContext context, final int index) => _User( user: users[index], - isUserPrimary: users[index].type == UserType.primary, + isPrimaryUser: users[index].type == UserType.primary, ), ), ),