From 9ba970493f4bbce52d805dfd6d62ed380f87c85e Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Thu, 24 Aug 2023 14:50:24 +0330 Subject: [PATCH] Add misc settings ui --- assets/translations/strings.i18n.json | 5 ++ assets/translations/strings_fa.i18n.json | 5 ++ lib/features/settings/view/settings_page.dart | 5 ++ .../widgets/miscellaneous_setting_tiles.dart | 53 ++++++++++++ .../settings/widgets/override_tiles.dart | 2 +- .../widgets/settings_input_dialog.dart | 82 ++++++++++++++++++- 6 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 lib/features/settings/widgets/miscellaneous_setting_tiles.dart diff --git a/assets/translations/strings.i18n.json b/assets/translations/strings.i18n.json index 10c6665a..d5b0324b 100644 --- a/assets/translations/strings.i18n.json +++ b/assets/translations/strings.i18n.json @@ -116,6 +116,11 @@ "mode": "Mode", "logLevel": "Log Level" } + }, + "miscellaneous": { + "sectionTitle": "miscellaneous", + "connectionTestUrl": "connection test url", + "concurrentTestCount": "concurrent test count" } }, "about": { diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 8eee801b..594e9d49 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -116,6 +116,11 @@ "mode": "Mode", "logLevel": "Log Level" } + }, + "miscellaneous": { + "sectionTitle": "متفرقه", + "connectionTestUrl": "لینک تست کانکشن", + "concurrentTestCount": "شمار تست همزمان" } }, "about": { diff --git a/lib/features/settings/view/settings_page.dart b/lib/features/settings/view/settings_page.dart index 4d8a1152..ea995419 100644 --- a/lib/features/settings/view/settings_page.dart +++ b/lib/features/settings/view/settings_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/features/settings/widgets/miscellaneous_setting_tiles.dart'; import 'package:hiddify/features/settings/widgets/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:recase/recase.dart'; @@ -40,6 +41,10 @@ class SettingsPage extends HookConsumerWidget { // await const ClashOverridesRoute().push(context); // }, // ), + _SettingsSectionHeader( + t.settings.miscellaneous.sectionTitle.titleCase, + ), + const MiscellaneousSettingTiles(), const Gap(16), ], ), diff --git a/lib/features/settings/widgets/miscellaneous_setting_tiles.dart b/lib/features/settings/widgets/miscellaneous_setting_tiles.dart new file mode 100644 index 00000000..cfc2d0a7 --- /dev/null +++ b/lib/features/settings/widgets/miscellaneous_setting_tiles.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/core/prefs/misc_prefs.dart'; +import 'package:hiddify/domain/constants.dart'; +import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart'; +import 'package:hiddify/utils/utils.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:recase/recase.dart'; + +class MiscellaneousSettingTiles extends HookConsumerWidget { + const MiscellaneousSettingTiles({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + + final connectionTestUrl = ref.watch(connectionTestUrlProvider); + final concurrentTestCount = ref.watch(concurrentTestCountProvider); + + return Column( + children: [ + ListTile( + title: Text(t.settings.miscellaneous.connectionTestUrl.titleCase), + subtitle: Text(connectionTestUrl), + onTap: () async { + final url = await SettingsInputDialog( + title: t.settings.miscellaneous.connectionTestUrl.titleCase, + initialValue: connectionTestUrl, + resetValue: Defaults.connectionTestUrl, + ).show(context); + if (url == null || url.isEmpty || !isUrl(url)) return; + await ref.read(connectionTestUrlProvider.notifier).update(url); + }, + ), + ListTile( + title: Text(t.settings.miscellaneous.concurrentTestCount.titleCase), + subtitle: Text(concurrentTestCount.toString()), + onTap: () async { + final val = await SettingsInputDialog( + title: t.settings.miscellaneous.connectionTestUrl.titleCase, + initialValue: concurrentTestCount, + resetValue: Defaults.concurrentTestCount, + mapTo: (value) => int.tryParse(value), + digitsOnly: true, + ).show(context); + if (val == null || val < 1) return; + await ref.read(concurrentTestCountProvider.notifier).update(val); + }, + ), + ], + ); + } +} diff --git a/lib/features/settings/widgets/override_tiles.dart b/lib/features/settings/widgets/override_tiles.dart index 7d3627a0..b0ea84fc 100644 --- a/lib/features/settings/widgets/override_tiles.dart +++ b/lib/features/settings/widgets/override_tiles.dart @@ -32,7 +32,7 @@ class InputOverrideTile extends HookConsumerWidget { : value.toString(), ), onTap: () async { - final result = await SettingsInputDialog( + final result = await OptionalSettingsInputDialog( title: title, initialValue: value, resetValue: optionOf(resetValue), diff --git a/lib/features/settings/widgets/settings_input_dialog.dart b/lib/features/settings/widgets/settings_input_dialog.dart index 19f8664e..51175a80 100644 --- a/lib/features/settings/widgets/settings_input_dialog.dart +++ b/lib/features/settings/widgets/settings_input_dialog.dart @@ -6,8 +6,9 @@ import 'package:hiddify/core/core_providers.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -class SettingsInputDialog extends HookConsumerWidget with PresLogger { - const SettingsInputDialog({ +class OptionalSettingsInputDialog extends HookConsumerWidget + with PresLogger { + const OptionalSettingsInputDialog({ super.key, required this.title, this.initialValue, @@ -75,3 +76,80 @@ class SettingsInputDialog extends HookConsumerWidget with PresLogger { ); } } + +class SettingsInputDialog extends HookConsumerWidget with PresLogger { + const SettingsInputDialog({ + super.key, + required this.title, + required this.initialValue, + this.mapTo, + this.resetValue, + this.icon, + this.digitsOnly = false, + }); + + final String title; + final T initialValue; + final T? Function(String value)? mapTo; + final T? resetValue; + final IconData? icon; + final bool digitsOnly; + + Future show(BuildContext context) async { + return showDialog( + context: context, + useRootNavigator: true, + builder: (context) => this, + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + final localizations = MaterialLocalizations.of(context); + + final textController = useTextEditingController( + text: initialValue?.toString(), + ); + + return AlertDialog( + title: Text(title), + icon: icon != null ? Icon(icon) : null, + content: TextFormField( + controller: textController, + inputFormatters: [ + FilteringTextInputFormatter.singleLineFormatter, + if (digitsOnly) FilteringTextInputFormatter.digitsOnly, + ], + autovalidateMode: AutovalidateMode.always, + ), + actions: [ + if (resetValue != null) + TextButton( + onPressed: () async { + await Navigator.of(context).maybePop(resetValue); + }, + child: Text(t.general.reset.toUpperCase()), + ), + TextButton( + onPressed: () async { + await Navigator.of(context).maybePop(); + }, + child: Text(localizations.cancelButtonLabel.toUpperCase()), + ), + TextButton( + onPressed: () async { + if (mapTo != null) { + await Navigator.of(context) + .maybePop(mapTo!.call(textController.value.text)); + } else { + await Navigator.of(context) + .maybePop(T == String ? textController.value.text : null); + } + }, + child: Text(localizations.okButtonLabel.toUpperCase()), + ), + ], + ); + } +}