diff --git a/assets/translations/en.json b/assets/translations/en.json index f3ec3523..485d4076 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -114,6 +114,19 @@ "reboot_after_upgrade_hint": "Reboot without prompt after applying changes on server", "server_timezone": "Server timezone", "select_timezone": "Select timezone" + }, + "info": { + "server_id": "Server ID", + "status": "Status", + "cpu": "CPU", + "ram": "Memory", + "disk": "Disk local", + "monthly_cost": "Monthly cost", + "location": "Location", + "core_count": { + "one": "{} core", + "other": "{} cores" + } } }, "domain": { diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 5072c88b..a6b4bc59 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -113,6 +113,22 @@ "reboot_after_upgrade_hint": "Автоматически перезагружать сервер после применения обновлений", "server_timezone": "Часовой пояс сервера", "select_timezone": "Выберите часовой пояс" + }, + "info": { + "server_id": "ID сервера", + "status": "Статус", + "cpu": "Процессор", + "ram": "Операивная память", + "disk": "Диск", + "monthly_cost": "Ежемесячная стоимость", + "location": "Размещение", + "core_count": { + "one": "{} ядро", + "two": "{} ядра", + "few": "{} ядра", + "many": "{} ядер", + "other": "{} ядер" + } } }, "domain": { diff --git a/lib/ui/components/list_tiles/list_tile_on_surface_variant.dart b/lib/ui/components/list_tiles/list_tile_on_surface_variant.dart new file mode 100644 index 00000000..c31315bd --- /dev/null +++ b/lib/ui/components/list_tiles/list_tile_on_surface_variant.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class ListTileOnSurfaceVariant extends StatelessWidget { + const ListTileOnSurfaceVariant({ + required this.title, + this.subtitle, + this.leadingIcon, + this.onTap, + this.disableSubtitleOverflow = false, + final super.key, + }); + + final String title; + final String? subtitle; + final IconData? leadingIcon; + final Function()? onTap; + final bool disableSubtitleOverflow; + + Widget? getSubtitle() { + if (subtitle == null) { + return null; + } + if (disableSubtitleOverflow) { + return Text( + subtitle!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + return Text( + subtitle!, + ); + } + + @override + Widget build(final BuildContext context) => ListTile( + title: Text(title), + subtitle: getSubtitle(), + onTap: onTap, + textColor: Theme.of(context).colorScheme.onSurfaceVariant, + leading: leadingIcon != null ? Icon(leadingIcon) : null, + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, + ); +} diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 9d25e8ba..4d5d5ebd 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -15,10 +15,12 @@ import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.da import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart'; import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart'; import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart'; import 'package:selfprivacy/ui/pages/server_details/time_zone/lang.dart'; import 'package:selfprivacy/utils/extensions/duration.dart'; +import 'package:selfprivacy/utils/extensions/string_extensions.dart'; import 'package:selfprivacy/utils/named_font_weight.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:timezone/timezone.dart'; diff --git a/lib/ui/pages/server_details/text_details.dart b/lib/ui/pages/server_details/text_details.dart index 2285d305..f512e917 100644 --- a/lib/ui/pages/server_details/text_details.dart +++ b/lib/ui/pages/server_details/text_details.dart @@ -11,115 +11,53 @@ class _TextDetails extends StatelessWidget { return _TempMessage(message: 'basis.no_data'.tr()); } else if (details is Loaded) { final data = details.serverInfo; - final checkTime = details.checkTime; - return Column( - children: [ - Center(child: BrandText.h3('providers.server.bottom_sheet.2'.tr())), - const SizedBox(height: 10), - Table( - columnWidths: const { - 0: FractionColumnWidth(.5), - 1: FractionColumnWidth(.5), - }, - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow( - children: [ - getRowTitle('Last check:'), - getRowValue(formatter.format(checkTime)), - ], + return FilledCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'providers.server.bottom_sheet.2'.tr(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), - TableRow( - children: [ - getRowTitle('Server Id:'), - getRowValue(data.id.toString()), - ], - ), - TableRow( - children: [ - getRowTitle('Status:'), - getRowValue( - data.status.toString().split('.')[1].toUpperCase(), - isBold: true, - ), - ], - ), - TableRow( - children: [ - getRowTitle('CPU:'), - getRowValue( - data.serverType.cores.toString(), - ), - ], - ), - TableRow( - children: [ - getRowTitle('Memory:'), - getRowValue( - '${data.serverType.memory.toString()} GB', - ), - ], - ), - TableRow( - children: [ - getRowTitle('Disk Local:'), - getRowValue( - '${data.serverType.disk.toString()} GB', - ), - ], - ), - TableRow( - children: [ - getRowTitle('Price monthly:'), - getRowValue( - data.serverType.prices[1].monthly.toString(), - ), - ], - ), - TableRow( - children: [ - getRowTitle('Price hourly:'), - getRowValue( - data.serverType.prices[1].hourly.toString(), - ), - ], - ), - ], - ), - const SizedBox(height: 30), - Center(child: BrandText.h3('providers.server.bottom_sheet.3'.tr())), - const SizedBox(height: 10), - Table( - columnWidths: const { - 0: FractionColumnWidth(.5), - 1: FractionColumnWidth(.5), - }, - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow( - children: [ - getRowTitle('Country:'), - getRowValue( - data.location.country, - ), - ], - ), - TableRow( - children: [ - getRowTitle('City:'), - getRowValue(data.location.city), - ], - ), - TableRow( - children: [ - getRowTitle('Description:'), - getRowValue(data.location.description), - ], - ), - ], - ), - const SizedBox(height: 20), - ], + ), + ListTileOnSurfaceVariant( + leadingIcon: Icons.numbers_outlined, + title: data.id.toString(), + subtitle: 'providers.server.info.server_id'.tr(), + ), + ListTileOnSurfaceVariant( + leadingIcon: Icons.mode_standby_outlined, + title: data.status.toString().split('.')[1].capitalize(), + subtitle: 'providers.server.info.status'.tr(), + ), + ListTileOnSurfaceVariant( + leadingIcon: Icons.memory_outlined, + title: 'providers.server.info.core_count' + .plural(data.serverType.cores), + subtitle: 'providers.server.info.cpu'.tr(), + ), + ListTileOnSurfaceVariant( + leadingIcon: Icons.memory_outlined, + title: '${data.serverType.memory.toString()} GB', + subtitle: 'providers.server.info.ram'.tr(), + ), + ListTileOnSurfaceVariant( + leadingIcon: Icons.euro_outlined, + title: data.serverType.prices[1].monthly.toStringAsFixed(2), + subtitle: 'providers.server.info.monthly_cost'.tr(), + ), + // Server location + ListTileOnSurfaceVariant( + leadingIcon: Icons.location_on_outlined, + title: '${data.location.city}, ${data.location.country}', + subtitle: 'providers.server.info.location'.tr(), + ), + ], + ), ); } else { throw Exception('wrong state'); diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 2ec6baee..24f2457f 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -133,12 +133,10 @@ class _UserLogins extends StatelessWidget { Widget build(final BuildContext context) => FilledCard( 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, + ListTileOnSurfaceVariant( + title: '${user.login}@$domainName', + subtitle: 'users.email_login'.tr(), + leadingIcon: Icons.alternate_email_outlined, ), ], ), @@ -156,18 +154,13 @@ class _SshKeysCard extends StatelessWidget { Widget build(final BuildContext context) => FilledCard( child: Column( children: [ - ListTile( - title: Text('ssh.title'.tr()), - textColor: Theme.of(context).colorScheme.onSurfaceVariant, + ListTileOnSurfaceVariant( + title: 'ssh.title'.tr(), ), 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), + ListTileOnSurfaceVariant( + title: 'ssh.create'.tr(), + leadingIcon: Icons.add_circle_outline, onTap: () { showModalBottomSheet( context: context, @@ -188,15 +181,11 @@ class _SshKeysCard extends StatelessWidget { 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)'), + return ListTileOnSurfaceVariant( + title: '$keyName ($keyType)', + disableSubtitleOverflow: true, // do not overflow text - subtitle: Text( - publicKey, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + subtitle: publicKey, onTap: () { showDialog( context: context, diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index 6b8709fd..7e255935 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -20,6 +20,7 @@ import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.da 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/info_box/info_box.dart'; +import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:selfprivacy/utils/ui_helpers.dart'; diff --git a/lib/utils/extensions/string_extensions.dart b/lib/utils/extensions/string_extensions.dart new file mode 100644 index 00000000..91cb543b --- /dev/null +++ b/lib/utils/extensions/string_extensions.dart @@ -0,0 +1,4 @@ +extension StringExtension on String { + String capitalize() => + '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; +}