diff --git a/assets/translations/en.json b/assets/translations/en.json index 214b5596..f90de6d4 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -101,6 +101,7 @@ "reboot_after_upgrade_hint": "Reboot without prompt after applying changes on server", "server_timezone": "Server timezone", "select_timezone": "Select timezone", + "timezone_search_bar": "Timezone name or time shift value", "server_id": "Server ID", "status": "Status", "cpu": "CPU", @@ -171,6 +172,7 @@ "gb": "{} GB", "mb": "{} MB", "kb": "{} KB", + "bytes": "Bytes", "extend_volume_button": "Extend volume", "extending_volume_title": "Extending volume", "extending_volume_description": "Resizing volume will allow you to store more data on your server without extending the server itself. Volume can only be extended: shrinking is not possible.", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index b882206f..ac121ccf 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -101,6 +101,7 @@ "reboot_after_upgrade_hint": "Автоматически перезагружать сервер после применения обновлений", "server_timezone": "Часовой пояс сервера", "select_timezone": "Выберите часовой пояс", + "timezone_search_bar": "Имя часового пояса или значение временного сдвига", "server_id": "ID сервера", "status": "Статус", "cpu": "Процессор", @@ -171,6 +172,7 @@ "gb": "{} GB", "mb": "{} MB", "kb": "{} KB", + "bytes": "Байт", "extend_volume_button": "Расширить хранилище", "extending_volume_title": "Расширение хранилища", "extending_volume_description": "Изменение размера хранилища позволит вам держать больше данных на вашем сервере без расширения самого сервера. Объем можно только увеличить: уменьшить нельзя.", diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 8de56f93..9946482d 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -369,7 +369,7 @@ class ServerInstallationRepository { BrandAlert( title: e.response!.data['errors'][0]['code'] == 1038 ? 'modals.you_cant_use_this_api'.tr() - : 'domain.states.error'.tr(), + : 'domain.error'.tr(), contentText: 'modals.delete_server_volume'.tr(), actions: [ ActionButton( diff --git a/lib/logic/models/disk_size.dart b/lib/logic/models/disk_size.dart index f7283689..6d335683 100644 --- a/lib/logic/models/disk_size.dart +++ b/lib/logic/models/disk_size.dart @@ -26,7 +26,7 @@ class DiskSize { @override String toString() { if (byte < 1024) { - return '${byte.toStringAsFixed(0)} ${tr('bytes')}'; + return '${byte.toStringAsFixed(0)} ${tr('storage.bytes')}'; } else if (byte < 1024 * 1024) { return 'storage.kb'.tr(args: [kibibyte.toStringAsFixed(1)]); } else if (byte < 1024 * 1024 * 1024) { diff --git a/lib/ui/pages/dns_details/dns_details.dart b/lib/ui/pages/dns_details/dns_details.dart index 3308a068..a1d6c29c 100644 --- a/lib/ui/pages/dns_details/dns_details.dart +++ b/lib/ui/pages/dns_details/dns_details.dart @@ -28,7 +28,7 @@ class _DnsDetailsPageState extends State { bool isError = false; switch (dnsState) { case DnsRecordsStatus.uninitialized: - description = 'domain.states.uninitialized'.tr(); + description = 'domain.uninitialized'.tr(); icon = const Icon( Icons.refresh, size: 24.0, @@ -36,7 +36,7 @@ class _DnsDetailsPageState extends State { isError = false; break; case DnsRecordsStatus.refreshing: - description = 'domain.states.refreshing'.tr(); + description = 'domain.refreshing'.tr(); icon = const Icon( Icons.refresh, size: 24.0, @@ -44,7 +44,7 @@ class _DnsDetailsPageState extends State { isError = false; break; case DnsRecordsStatus.good: - description = 'domain.states.ok'.tr(); + description = 'domain.ok'.tr(); icon = const Icon( Icons.check_circle_outline, size: 24.0, @@ -52,8 +52,8 @@ class _DnsDetailsPageState extends State { isError = false; break; case DnsRecordsStatus.error: - description = 'domain.states.error'.tr(); - subtitle = 'domain.states.error_subtitle'.tr(); + description = 'domain.error'.tr(); + subtitle = 'domain.error_subtitle'.tr(); icon = const Icon( Icons.error_outline, size: 24.0, diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 0b4ac005..894c9a28 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -17,17 +17,15 @@ 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/pages/server_details/charts/cpu_chart.dart'; +import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.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'; -import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart'; -import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart'; - part 'charts/chart.dart'; part 'server_settings.dart'; part 'text_details.dart'; diff --git a/lib/ui/pages/server_details/time_zone/time_zone.dart b/lib/ui/pages/server_details/time_zone/time_zone.dart index 6a7349c9..28407d7d 100644 --- a/lib/ui/pages/server_details/time_zone/time_zone.dart +++ b/lib/ui/pages/server_details/time_zone/time_zone.dart @@ -14,11 +14,21 @@ class SelectTimezone extends StatefulWidget { } class _SelectTimezoneState extends State { - final ScrollController controller = ScrollController(); + final ScrollController scrollController = ScrollController(); + final TextEditingController searchController = TextEditingController(); + + String? timezoneFilterValue; + bool isSearching = false; @override void initState() { WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + searchController.addListener(() { + setState(() { + timezoneFilterValue = + searchController.text.isNotEmpty ? searchController.text : null; + }); + }); super.initState(); } @@ -31,7 +41,7 @@ class _SelectTimezoneState extends State { print(t); if (index >= 0) { - controller.animateTo( + scrollController.animateTo( 60.0 * index, duration: const Duration(milliseconds: 300), curve: Curves.easeIn, @@ -41,93 +51,120 @@ class _SelectTimezoneState extends State { @override void dispose() { - controller.dispose(); + scrollController.dispose(); + searchController.dispose(); super.dispose(); } @override Widget build(final BuildContext context) => Scaffold( appBar: AppBar( - title: Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text('server.select_timezone'.tr()), - ), + title: isSearching + ? TextField( + readOnly: false, + textAlign: TextAlign.start, + textInputAction: TextInputAction.next, + enabled: true, + controller: searchController, + decoration: InputDecoration( + errorText: null, + hintText: 'server.timezone_search_bar'.tr(), + ), + ) + : Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text('server.select_timezone'.tr()), + ), leading: IconButton( icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), + onPressed: isSearching + ? () => setState(() => isSearching = false) + : () => Navigator.of(context).pop(), ), + actions: [ + if (!isSearching) + IconButton( + icon: const Icon(Icons.search), + onPressed: () => setState(() => isSearching = true), + ), + ], ), body: SafeArea( child: ListView( - controller: controller, + controller: scrollController, children: locations + .where( + (final Location location) => timezoneFilterValue == null + ? true + : location.name + .toLowerCase() + .contains(timezoneFilterValue!) || + Duration( + milliseconds: location.currentTimeZone.offset, + ) + .toDayHourMinuteFormat() + .contains(timezoneFilterValue!), + ) + .toList() .asMap() - .map((final key, final value) { - final duration = - Duration(milliseconds: value.currentTimeZone.offset); - final area = value.currentTimeZone.abbreviation - .replaceAll(RegExp(r'[\d+()-]'), ''); - - String timezoneName = value.name; - if (context.locale.toString() == 'ru') { - timezoneName = russian[value.name] ?? - () { - final arr = value.name.split('/')..removeAt(0); - return arr.join('/'); - }(); - } - - return MapEntry( - key, - Container( - height: 75, - padding: const EdgeInsets.symmetric(horizontal: 20), - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: BrandColors.dividerColor, - ), - ), - ), - child: InkWell( - onTap: () { - context - .read() - .repository - .setTimezone( - timezoneName, - ); - Navigator.of(context).pop(); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - BrandText.body1( - timezoneName, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - BrandText.small( - 'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}', - style: const TextStyle( - fontSize: 13, - ), - ), - ], - ), - ), - ), - ), - ); - }) + .map( + (final key, final value) => locationToListTile(key, value), + ) .values .toList(), ), ), ); + + MapEntry locationToListTile( + final int key, final Location location) { + final duration = Duration(milliseconds: location.currentTimeZone.offset); + final area = location.currentTimeZone.abbreviation + .replaceAll(RegExp(r'[\d+()-]'), ''); + + return MapEntry( + key, + Container( + height: 75, + padding: const EdgeInsets.symmetric(horizontal: 20), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: BrandColors.dividerColor, + ), + ), + ), + child: InkWell( + onTap: () { + context.read().repository.setTimezone( + location.name, + ); + Navigator.of(context).pop(); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BrandText.body1( + location.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + BrandText.small( + 'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}', + style: const TextStyle( + fontSize: 13, + ), + ), + ], + ), + ), + ), + ), + ); + } } diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index 8558c526..aa150c13 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -33,6 +33,13 @@ class _ExtendingVolumePageState extends State { super.initState(); } + @override + void dispose() { + _sizeController.dispose(); + _priceController.dispose(); + super.dispose(); + } + bool _isError = false; late double _currentSliderGbValue;