From e36a94ded5dfce30a09995564b6f615adb03a01a Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 6 Oct 2022 14:46:29 +0000 Subject: [PATCH] feat(timezone): Implement search bar for 'Select Timezone' page --- assets/translations/en.json | 1 + assets/translations/ru.json | 1 + .../server_details/server_details_screen.dart | 1 + .../server_details/time_zone/time_zone.dart | 188 +++++++++++------- .../server_storage/extending_volume.dart | 7 + 5 files changed, 123 insertions(+), 75 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 032bd0da..6920d6c2 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", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 7b60e4e9..9d9b47e4 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": "Процессор", diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 0b4ac005..d1eada36 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -12,6 +12,7 @@ import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.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_loader/brand_loader.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..2073d305 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,20 @@ class SelectTimezone extends StatefulWidget { } class _SelectTimezoneState extends State { - final ScrollController controller = ScrollController(); + final ScrollController scrollController = ScrollController(); + final TextEditingController searchController = TextEditingController(); + + String? timezoneFilterValue; @override void initState() { WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + searchController.addListener(() { + setState(() { + timezoneFilterValue = + searchController.text.isNotEmpty ? searchController.text : null; + }); + }); super.initState(); } @@ -31,7 +40,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 +50,122 @@ 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()), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), + appBar: PreferredSize( + preferredSize: const Size.fromHeight(156), + child: Column( + children: [ + BrandHeader( + title: 'server.select_timezone'.tr(), + ), + Row( + children: [ + const SizedBox(width: 16), + SizedBox( + height: 52, + child: TextField( + readOnly: false, + textAlign: TextAlign.start, + textInputAction: TextInputAction.next, + enabled: true, + controller: searchController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: null, + labelText: 'server.timezone_search_bar'.tr(), + ), + ), + ), + const SizedBox(width: 16), + const Icon(Icons.search_outlined), + const SizedBox(width: 16), + ], + ), + ], ), ), 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 e465c2b2..fd4491f7 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -32,6 +32,13 @@ class _ExtendingVolumePageState extends State { super.initState(); } + @override + void dispose() { + _sizeController.dispose(); + _priceController.dispose(); + super.dispose(); + } + bool _isError = false; late double _currentSliderGbValue;