From be1c1bb580ec039044e8057ae1469153008cdb4d Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Thu, 7 Mar 2024 19:17:05 +0330 Subject: [PATCH] Add reconnect and animations to connection button --- assets/translations/strings_en.i18n.json | 1 + .../notifier/config_option_notifier.dart | 2 +- .../home/widget/connection_button.dart | 133 +++++++++++------- 3 files changed, 83 insertions(+), 53 deletions(-) diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index 7526e05b..2bb34ce8 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -33,6 +33,7 @@ "connecting": "Connecting", "disconnecting": "Disconnecting", "connected": "Connected", + "reconnect": "Reconnect", "experimentalNotice": "Experimental Features In Use", "experimentalNoticeMsg": "You've enabled some experimental features which might affect connection quality and cause unexpected errors. You can always change or reset these options from Config options page.", "disableExperimentalNotice": "Don't show again" diff --git a/lib/features/config_option/notifier/config_option_notifier.dart b/lib/features/config_option/notifier/config_option_notifier.dart index 4c04e621..cd7793b5 100644 --- a/lib/features/config_option/notifier/config_option_notifier.dart +++ b/lib/features/config_option/notifier/config_option_notifier.dart @@ -24,7 +24,7 @@ class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger { if (next case AsyncData(:final value) when next != previous) { if (_lastUpdate == null || DateTime.now().difference(_lastUpdate!) > - const Duration(seconds: 3)) { + const Duration(milliseconds: 100)) { _lastUpdate = DateTime.now(); state = AsyncData(value != serviceSingboxOptions); } diff --git a/lib/features/home/widget/connection_button.dart b/lib/features/home/widget/connection_button.dart index 7ea7d0bf..d7074460 100644 --- a/lib/features/home/widget/connection_button.dart +++ b/lib/features/home/widget/connection_button.dart @@ -5,9 +5,11 @@ import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/model/failures.dart'; import 'package:hiddify/core/theme/theme_extensions.dart'; import 'package:hiddify/features/config_option/data/config_option_repository.dart'; +import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart'; import 'package:hiddify/features/connection/model/connection_status.dart'; import 'package:hiddify/features/connection/notifier/connection_notifier.dart'; import 'package:hiddify/features/connection/widget/experimental_feature_notice.dart'; +import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart'; import 'package:hiddify/gen/assets.gen.dart'; import 'package:hiddify/utils/alerts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -20,6 +22,8 @@ class ConnectionButton extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); final connectionStatus = ref.watch(connectionNotifierProvider); + final requiresReconnect = + ref.watch(configOptionNotifierProvider).valueOrNull; ref.listen( connectionNotifierProvider, @@ -37,55 +41,58 @@ class ConnectionButton extends HookConsumerWidget { final buttonTheme = Theme.of(context).extension()!; - switch (connectionStatus) { - case AsyncData(value: final status): - final Color connectionLogoColor = status.isConnected - ? buttonTheme.connectedColor! - : buttonTheme.idleColor!; + Future showExperimentalNotice() async { + final hasExperimental = ref.read(ConfigOptions.hasExperimentalFeatures); + final canShowNotice = !ref.read(disableExperimentalFeatureNoticeProvider); + if (hasExperimental && canShowNotice && context.mounted) { + return await const ExperimentalFeatureNoticeDialog().show(context) ?? + true; + } + return true; + } - return _ConnectionButton( - onTap: () async { - var canConnect = true; - if (status case Disconnected()) { - final hasExperimental = - ref.read(ConfigOptions.hasExperimentalFeatures); - final canShowNotice = - !ref.read(disableExperimentalFeatureNoticeProvider); - - if (hasExperimental && canShowNotice && context.mounted) { - canConnect = await const ExperimentalFeatureNoticeDialog() - .show(context) ?? - true; - } - } - - if (canConnect) { - await ref + return _ConnectionButton( + onTap: switch (connectionStatus) { + AsyncData(value: Disconnected()) || AsyncError() => () async { + if (await showExperimentalNotice()) { + return await ref .read(connectionNotifierProvider.notifier) .toggleConnection(); } }, - enabled: !status.isSwitching, - label: status.present(t), - buttonColor: connectionLogoColor, - ); - case AsyncError(): - return _ConnectionButton( - onTap: () => - ref.read(connectionNotifierProvider.notifier).toggleConnection(), - enabled: true, - label: const Disconnected().present(t), - buttonColor: buttonTheme.idleColor!, - ); - default: - // HACK - return _ConnectionButton( - onTap: () {}, - enabled: false, - label: "", - buttonColor: Colors.red, - ); - } + AsyncData(value: Connected()) => () async { + if (requiresReconnect == true && await showExperimentalNotice()) { + return await ref + .read(connectionNotifierProvider.notifier) + .reconnect(await ref.read(activeProfileProvider.future)); + } + return await ref + .read(connectionNotifierProvider.notifier) + .toggleConnection(); + }, + _ => () {}, + }, + enabled: switch (connectionStatus) { + AsyncData(value: Connected()) || + AsyncData(value: Disconnected()) || + AsyncError() => + true, + _ => false, + }, + label: switch (connectionStatus) { + AsyncData(value: Connected()) when requiresReconnect == true => + t.home.connection.reconnect, + AsyncData(value: final status) => status.present(t), + _ => "", + }, + buttonColor: switch (connectionStatus) { + AsyncData(value: Connected()) when requiresReconnect == true => + Colors.teal, + AsyncData(value: Connected()) => buttonTheme.connectedColor!, + AsyncData(value: _) => buttonTheme.idleColor!, + _ => Colors.red, + }, + ); } } @@ -132,11 +139,17 @@ class _ConnectionButton extends StatelessWidget { onTap: onTap, child: Padding( padding: const EdgeInsets.all(36), - child: Assets.images.logo.svg( - colorFilter: ColorFilter.mode( - buttonColor, - BlendMode.srcIn, - ), + child: TweenAnimationBuilder( + tween: ColorTween(end: buttonColor), + duration: const Duration(milliseconds: 250), + builder: (context, value, child) { + return Assets.images.logo.svg( + colorFilter: ColorFilter.mode( + value!, + BlendMode.srcIn, + ), + ); + }, ), ), ), @@ -147,9 +160,25 @@ class _ConnectionButton extends StatelessWidget { ), const Gap(16), ExcludeSemantics( - child: Text( - label, - style: Theme.of(context).textTheme.titleMedium, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (child, animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, -0.2), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + child: Text( + label, + key: ValueKey(label), + style: Theme.of(context).textTheme.titleMedium, + ), ), ), ],