Add reconnect and animations to connection button
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
"connecting": "Connecting",
|
"connecting": "Connecting",
|
||||||
"disconnecting": "Disconnecting",
|
"disconnecting": "Disconnecting",
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
|
"reconnect": "Reconnect",
|
||||||
"experimentalNotice": "Experimental Features In Use",
|
"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.",
|
"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"
|
"disableExperimentalNotice": "Don't show again"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger {
|
|||||||
if (next case AsyncData(:final value) when next != previous) {
|
if (next case AsyncData(:final value) when next != previous) {
|
||||||
if (_lastUpdate == null ||
|
if (_lastUpdate == null ||
|
||||||
DateTime.now().difference(_lastUpdate!) >
|
DateTime.now().difference(_lastUpdate!) >
|
||||||
const Duration(seconds: 3)) {
|
const Duration(milliseconds: 100)) {
|
||||||
_lastUpdate = DateTime.now();
|
_lastUpdate = DateTime.now();
|
||||||
state = AsyncData(value != serviceSingboxOptions);
|
state = AsyncData(value != serviceSingboxOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import 'package:hiddify/core/localization/translations.dart';
|
|||||||
import 'package:hiddify/core/model/failures.dart';
|
import 'package:hiddify/core/model/failures.dart';
|
||||||
import 'package:hiddify/core/theme/theme_extensions.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/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/model/connection_status.dart';
|
||||||
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
|
||||||
import 'package:hiddify/features/connection/widget/experimental_feature_notice.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/gen/assets.gen.dart';
|
||||||
import 'package:hiddify/utils/alerts.dart';
|
import 'package:hiddify/utils/alerts.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -20,6 +22,8 @@ class ConnectionButton extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final t = ref.watch(translationsProvider);
|
final t = ref.watch(translationsProvider);
|
||||||
final connectionStatus = ref.watch(connectionNotifierProvider);
|
final connectionStatus = ref.watch(connectionNotifierProvider);
|
||||||
|
final requiresReconnect =
|
||||||
|
ref.watch(configOptionNotifierProvider).valueOrNull;
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
connectionNotifierProvider,
|
connectionNotifierProvider,
|
||||||
@@ -37,55 +41,58 @@ class ConnectionButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
final buttonTheme = Theme.of(context).extension<ConnectionButtonTheme>()!;
|
final buttonTheme = Theme.of(context).extension<ConnectionButtonTheme>()!;
|
||||||
|
|
||||||
switch (connectionStatus) {
|
Future<bool> showExperimentalNotice() async {
|
||||||
case AsyncData(value: final status):
|
final hasExperimental = ref.read(ConfigOptions.hasExperimentalFeatures);
|
||||||
final Color connectionLogoColor = status.isConnected
|
final canShowNotice = !ref.read(disableExperimentalFeatureNoticeProvider);
|
||||||
? buttonTheme.connectedColor!
|
|
||||||
: buttonTheme.idleColor!;
|
|
||||||
|
|
||||||
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) {
|
if (hasExperimental && canShowNotice && context.mounted) {
|
||||||
canConnect = await const ExperimentalFeatureNoticeDialog()
|
return await const ExperimentalFeatureNoticeDialog().show(context) ??
|
||||||
.show(context) ??
|
|
||||||
true;
|
true;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canConnect) {
|
return _ConnectionButton(
|
||||||
await ref
|
onTap: switch (connectionStatus) {
|
||||||
|
AsyncData(value: Disconnected()) || AsyncError() => () async {
|
||||||
|
if (await showExperimentalNotice()) {
|
||||||
|
return await ref
|
||||||
.read(connectionNotifierProvider.notifier)
|
.read(connectionNotifierProvider.notifier)
|
||||||
.toggleConnection();
|
.toggleConnection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled: !status.isSwitching,
|
AsyncData(value: Connected()) => () async {
|
||||||
label: status.present(t),
|
if (requiresReconnect == true && await showExperimentalNotice()) {
|
||||||
buttonColor: connectionLogoColor,
|
return await ref
|
||||||
);
|
.read(connectionNotifierProvider.notifier)
|
||||||
case AsyncError():
|
.reconnect(await ref.read(activeProfileProvider.future));
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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,
|
onTap: onTap,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(36),
|
padding: const EdgeInsets.all(36),
|
||||||
child: Assets.images.logo.svg(
|
child: TweenAnimationBuilder(
|
||||||
|
tween: ColorTween(end: buttonColor),
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Assets.images.logo.svg(
|
||||||
colorFilter: ColorFilter.mode(
|
colorFilter: ColorFilter.mode(
|
||||||
buttonColor,
|
value!,
|
||||||
BlendMode.srcIn,
|
BlendMode.srcIn,
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -147,11 +160,27 @@ class _ConnectionButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
ExcludeSemantics(
|
ExcludeSemantics(
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
transitionBuilder: (child, animation) {
|
||||||
|
return SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0.0, -0.2),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(animation),
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
|
key: ValueKey<String>(label),
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user