diff --git a/lib/features/home/widget/home_page.dart b/lib/features/home/widget/home_page.dart index 8118b70a..9b43ba45 100644 --- a/lib/features/home/widget/home_page.dart +++ b/lib/features/home/widget/home_page.dart @@ -11,6 +11,7 @@ import 'package:hiddify/features/home/widget/connection_button.dart'; import 'package:hiddify/features/home/widget/empty_profiles_home_body.dart'; import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart'; import 'package:hiddify/features/profile/widget/profile_tile.dart'; +import 'package:hiddify/features/proxy/active/active_proxy_delay_indicator.dart'; import 'package:hiddify/features/proxy/active/active_proxy_footer.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -61,7 +62,17 @@ class HomePage extends HookConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Expanded(child: ConnectionButton()), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const ConnectionButton(), + if (Platform.isAndroid || Platform.isIOS) + const ActiveProxyDelayIndicator(), + ], + ), + ), if (Platform.isAndroid || Platform.isIOS) const ActiveProxyFooter(), ], diff --git a/lib/features/proxy/active/active_proxy_delay_indicator.dart b/lib/features/proxy/active/active_proxy_delay_indicator.dart new file mode 100644 index 00000000..605d8188 --- /dev/null +++ b/lib/features/proxy/active/active_proxy_delay_indicator.dart @@ -0,0 +1,78 @@ +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:gap/gap.dart'; +import 'package:hiddify/core/widget/animated_visibility.dart'; +import 'package:hiddify/core/widget/skeleton_widget.dart'; +import 'package:hiddify/features/proxy/active/active_proxy_notifier.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ActiveProxyDelayIndicator extends HookConsumerWidget { + const ActiveProxyDelayIndicator({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final asyncState = ref.watch(activeProxyNotifierProvider); + + return AnimatedVisibility( + axis: Axis.vertical, + visible: asyncState is AsyncData, + child: () { + switch (asyncState) { + case AsyncData(:final value): + final delay = value.proxy.urlTestDelay; + return Center( + child: InkWell( + onTap: () async { + await ref + .read(activeProxyNotifierProvider.notifier) + .urlTest(value.proxy.tag); + }, + borderRadius: BorderRadius.circular(24), + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(FluentIcons.wifi_1_24_regular), + const Gap(8), + if (delay > 0) + Text.rich( + TextSpan( + children: [ + TextSpan( + text: delay.toString(), + style: theme.textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.bold), + ), + const TextSpan(text: " ms"), + ], + ), + ) + else + const Skeleton( + height: 18, + width: 48, + ) + .animate( + onPlay: (controller) => controller.loop(), + ) + .shimmer( + duration: 1000.ms, + angle: 45, + color: Theme.of(context).colorScheme.secondary, + ), + ], + ), + ), + ), + ); + default: + return const SizedBox(); + } + }(), + ); + } +} diff --git a/lib/features/proxy/active/active_proxy_notifier.dart b/lib/features/proxy/active/active_proxy_notifier.dart index 9926a24a..4dd32eee 100644 --- a/lib/features/proxy/active/active_proxy_notifier.dart +++ b/lib/features/proxy/active/active_proxy_notifier.dart @@ -56,6 +56,9 @@ class ActiveProxyNotifier extends _$ActiveProxyNotifier with AppLogger { @override AsyncValue build() { ref.disposeDelay(const Duration(seconds: 20)); + ref.onDispose(() { + _urlTestDebouncer.dispose(); + }); final ipInfo = ref.watch(proxyIpInfoProvider); final activeProxies = ref.watch(activeProxyGroupProvider); return switch (activeProxies) { @@ -67,9 +70,28 @@ class ActiveProxyNotifier extends _$ActiveProxyNotifier with AppLogger { }; } + final _urlTestDebouncer = CallbackDebouncer(const Duration(seconds: 1)); + Future refreshIpInfo() async { if (state case AsyncData(:final value) when !value.ipInfo.isLoading) { ref.invalidate(proxyIpInfoProvider); } } + + Future urlTest(String groupTag) async { + _urlTestDebouncer( + () async { + loggy.debug("testing group: [$groupTag]"); + if (state case AsyncData()) { + await ref + .read(proxyRepositoryProvider) + .urlTest(groupTag) + .getOrElse((err) { + loggy.error("error testing group", err); + throw err; + }).run(); + } + }, + ); + } }