From 28ece8fb2e4e80a644aceab0629bcfe44b8c91e7 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Tue, 13 Feb 2024 16:59:35 +0330 Subject: [PATCH] Fix connection info --- lib/core/utils/throttler.dart | 16 +++ .../active/active_proxy_delay_indicator.dart | 13 +-- .../proxy/active/active_proxy_footer.dart | 29 +++-- .../proxy/active/active_proxy_notifier.dart | 110 +++++++----------- .../active/active_proxy_sidebar_card.dart | 73 +++++------- 5 files changed, 105 insertions(+), 136 deletions(-) create mode 100644 lib/core/utils/throttler.dart diff --git a/lib/core/utils/throttler.dart b/lib/core/utils/throttler.dart new file mode 100644 index 00000000..d85d31c0 --- /dev/null +++ b/lib/core/utils/throttler.dart @@ -0,0 +1,16 @@ +import 'package:flutter/foundation.dart'; + +class Throttler { + Throttler(this.throttleFor); + + final Duration throttleFor; + DateTime? _lastCall; + + void call(VoidCallback callback) { + if (_lastCall == null || + DateTime.now().difference(_lastCall!) > throttleFor) { + callback(); + _lastCall = DateTime.now(); + } + } +} diff --git a/lib/features/proxy/active/active_proxy_delay_indicator.dart b/lib/features/proxy/active/active_proxy_delay_indicator.dart index e1cd3f2b..b89bca9a 100644 --- a/lib/features/proxy/active/active_proxy_delay_indicator.dart +++ b/lib/features/proxy/active/active_proxy_delay_indicator.dart @@ -12,22 +12,21 @@ class ActiveProxyDelayIndicator extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); - final asyncState = ref.watch(activeProxyNotifierProvider); + final activeProxy = ref.watch(activeProxyNotifierProvider); return AnimatedVisibility( axis: Axis.vertical, - visible: asyncState is AsyncData, + visible: activeProxy is AsyncData, child: () { - switch (asyncState) { - case AsyncData(:final value): - final delay = value.proxy.urlTestDelay; + switch (activeProxy) { + case AsyncData(value: final proxy): + final delay = proxy.urlTestDelay; return Center( child: InkWell( onTap: () async { await ref .read(activeProxyNotifierProvider.notifier) - // .urlTest(value.proxy.tag); - .urlTest("auto"); + .urlTest(proxy.tag); }, borderRadius: BorderRadius.circular(24), child: Padding( diff --git a/lib/features/proxy/active/active_proxy_footer.dart b/lib/features/proxy/active/active_proxy_footer.dart index eb896337..241302fe 100644 --- a/lib/features/proxy/active/active_proxy_footer.dart +++ b/lib/features/proxy/active/active_proxy_footer.dart @@ -17,13 +17,14 @@ class ActiveProxyFooter extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); - final asyncState = ref.watch(activeProxyNotifierProvider); + final activeProxy = ref.watch(activeProxyNotifierProvider); + final ipInfo = ref.watch(ipInfoNotifierProvider); return AnimatedVisibility( axis: Axis.vertical, - visible: asyncState is AsyncData, - child: switch (asyncState) { - AsyncData(value: final info) => Padding( + visible: activeProxy is AsyncData, + child: switch (activeProxy) { + AsyncData(value: final proxy) => Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -34,24 +35,22 @@ class ActiveProxyFooter extends HookConsumerWidget { children: [ _InfoProp( icon: FluentIcons.arrow_routing_20_regular, - text: info.proxy.selectedName.isNotNullOrBlank - ? info.proxy.selectedName! - : info.proxy.name, + text: proxy.selectedName.isNotNullOrBlank + ? proxy.selectedName! + : proxy.name, ), const Gap(8), - switch (info.ipInfo) { - AsyncData(value: final ipInfo?) => Row( + switch (ipInfo) { + AsyncData(value: final info) => Row( children: [ - IPCountryFlag(countryCode: ipInfo.countryCode), + IPCountryFlag(countryCode: info.countryCode), const Gap(8), IPText( - ip: ipInfo.ip, + ip: info.ip, onLongPress: () async { ref - .read( - activeProxyNotifierProvider.notifier, - ) - .refreshIpInfo(); + .read(ipInfoNotifierProvider.notifier) + .refresh(); }, ), ], diff --git a/lib/features/proxy/active/active_proxy_notifier.dart b/lib/features/proxy/active/active_proxy_notifier.dart index 6cd06a97..5d5351eb 100644 --- a/lib/features/proxy/active/active_proxy_notifier.dart +++ b/lib/features/proxy/active/active_proxy_notifier.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:hiddify/core/utils/throttler.dart'; import 'package:hiddify/features/connection/notifier/connection_notifier.dart'; import 'package:hiddify/features/proxy/data/proxy_data_providers.dart'; import 'package:hiddify/features/proxy/model/ip_info_entity.dart'; @@ -6,98 +7,65 @@ import 'package:hiddify/features/proxy/model/proxy_entity.dart'; import 'package:hiddify/features/proxy/model/proxy_failure.dart'; import 'package:hiddify/utils/riverpod_utils.dart'; import 'package:hiddify/utils/utils.dart'; -import 'package:loggy/loggy.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'active_proxy_notifier.g.dart'; -typedef ActiveProxyInfo = ({ - ProxyItemEntity proxy, - AsyncValue ipInfo, -}); - @riverpod -Stream activeProxyGroup(ActiveProxyGroupRef ref) async* { - final serviceRunning = await ref.watch(serviceRunningProvider.future); - if (!serviceRunning) { - throw const ServiceNotRunning(); - } - yield* ref - .watch(proxyRepositoryProvider) - .watchActiveProxies() - .map((event) => event.getOrElse((l) => throw l)) - .map((event) { - final Map itemMap = - event.fold({}, (Map map, item) { - map[item.tag] = item; - return map; +class IpInfoNotifier extends _$IpInfoNotifier with AppLogger { + @override + Future build() async { + ref.disposeDelay(const Duration(seconds: 20)); + final cancelToken = CancelToken(); + ref.onDispose(() { + loggy.debug("disposing"); + cancelToken.cancel(); }); - // Start from the item with the tag "Select" - var current = itemMap["select"]; - ProxyItemEntity? selected; - - // Traverse through the map based on the selected tag until reaching the end - while (current != null) { - selected = current.items.firstOrNull; - current = itemMap[selected?.tag]; + final serviceRunning = await ref.watch(serviceRunningProvider.future); + if (!serviceRunning) { + throw const ServiceNotRunning(); } - return selected; - }); -} - -@riverpod -Future proxyIpInfo(ProxyIpInfoRef ref) async { - final serviceRunning = await ref.watch(serviceRunningProvider.future); - if (!serviceRunning) { - return null; + return ref + .watch(proxyRepositoryProvider) + .getCurrentIpInfo(cancelToken) + .getOrElse( + (err) { + loggy.error("error getting proxy ip info", err); + throw err; + }, + ).run(); + } + + Future refresh() async { + loggy.debug("refreshing"); + ref.invalidateSelf(); } - final cancelToken = CancelToken(); - ref.onDispose(() { - Loggy("ProxyIpInfo").debug("canceling"); - cancelToken.cancel(); - }); - return ref - .watch(proxyRepositoryProvider) - .getCurrentIpInfo(cancelToken) - .getOrElse( - (err) { - Loggy("ProxyIpInfo").error("error getting proxy ip info", err); - throw err; - }, - ).run(); } @riverpod class ActiveProxyNotifier extends _$ActiveProxyNotifier with AppLogger { @override - AsyncValue build() { + Stream build() async* { ref.disposeDelay(const Duration(seconds: 20)); - ref.onDispose(() { - _urlTestDebouncer.dispose(); - }); - final ipInfo = ref.watch(proxyIpInfoProvider); - final activeProxies = ref.watch(activeProxyGroupProvider); - return switch (activeProxies) { - AsyncData(value: final activeGroup?) => - AsyncData((proxy: activeGroup, ipInfo: ipInfo)), - AsyncError(:final error, :final stackTrace) => - AsyncError(error, stackTrace), - _ => const AsyncLoading(), - }; - } - final _urlTestDebouncer = CallbackDebouncer(const Duration(seconds: 1)); - - Future refreshIpInfo() async { - if (state case AsyncData(:final value) when !value.ipInfo.isLoading) { - ref.invalidate(proxyIpInfoProvider); + final serviceRunning = await ref.watch(serviceRunningProvider.future); + if (!serviceRunning) { + throw const ServiceNotRunning(); } + + yield* ref + .watch(proxyRepositoryProvider) + .watchActiveProxies() + .map((event) => event.getOrElse((l) => throw l)) + .map((event) => event.firstOrNull!.items.first); } + final _urlTestThrottler = Throttler(const Duration(seconds: 2)); + Future urlTest(String groupTag) async { - _urlTestDebouncer( + _urlTestThrottler( () async { loggy.debug("testing group: [$groupTag]"); if (state case AsyncData()) { diff --git a/lib/features/proxy/active/active_proxy_sidebar_card.dart b/lib/features/proxy/active/active_proxy_sidebar_card.dart index d1082aba..f0dc6bd3 100644 --- a/lib/features/proxy/active/active_proxy_sidebar_card.dart +++ b/lib/features/proxy/active/active_proxy_sidebar_card.dart @@ -26,7 +26,8 @@ class ActiveProxySideBarCard extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final t = ref.watch(translationsProvider); - final asyncState = ref.watch(activeProxyNotifierProvider); + final activeProxy = ref.watch(activeProxyNotifierProvider); + final ipInfo = ref.watch(ipInfoNotifierProvider); Widget propText(String txt) { return Text( @@ -50,13 +51,13 @@ class ActiveProxySideBarCard extends HookConsumerWidget { children: [ Text(t.home.stats.connection), const Gap(4), - switch (asyncState) { - AsyncData(:final value) => buildProp( + switch (activeProxy) { + AsyncData(value: final proxy) => buildProp( const Icon(FluentIcons.arrow_routing_20_regular), propText( - value.proxy.selectedName.isNotNullOrBlank - ? value.proxy.selectedName! - : value.proxy.name, + proxy.selectedName.isNotNullOrBlank + ? proxy.selectedName! + : proxy.name, ), ), _ => buildProp( @@ -65,43 +66,29 @@ class ActiveProxySideBarCard extends HookConsumerWidget { ), }, const Gap(4), - () { - if (asyncState case AsyncData(:final value)) { - switch (value.ipInfo) { - case AsyncData(value: final ipInfo?): - return buildProp( - IPCountryFlag( - countryCode: ipInfo.countryCode, size: 16), - IPText( - ip: ipInfo.ip, - onLongPress: () async { - ref - .read( - activeProxyNotifierProvider.notifier, - ) - .refreshIpInfo(); - }, - constrained: true, - ), - ); - case AsyncError(): - return buildProp( - const Icon(FluentIcons.error_circle_20_regular), - propText(t.general.unknown), - ); - case AsyncLoading(): - return buildProp( - const Icon(FluentIcons.question_circle_20_regular), - const ShimmerSkeleton(widthFactor: .85, height: 14), - ); - } - } - - return buildProp( - const Icon(FluentIcons.question_circle_20_regular), - propText(t.general.unknown), - ); - }(), + switch (ipInfo) { + AsyncData(value: final info) => buildProp( + IPCountryFlag( + countryCode: info.countryCode, + size: 16, + ), + IPText( + ip: info.ip, + onLongPress: () async { + ref.read(ipInfoNotifierProvider.notifier).refresh(); + }, + constrained: true, + ), + ), + AsyncLoading() => buildProp( + const Icon(FluentIcons.question_circle_20_regular), + const ShimmerSkeleton(widthFactor: .85, height: 14), + ), + _ => buildProp( + const Icon(FluentIcons.error_circle_20_regular), + propText(t.general.unknown), + ), + }, ], ), ),