| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- import 'package:flutter/material.dart' hide ConnectionState;
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'dart:math' as math;
- import 'dart:async';
- import 'package:get/get.dart';
- import 'package:nomo/app/widgets/ix_image.dart';
- import '../../../constants/assets.dart';
- import '../../../../config/theme/theme_extensions/theme_extension.dart';
- import '../../../../config/translations/strings_enum.dart';
- import '../../../constants/enums.dart';
- class ConnectionButton extends StatefulWidget {
- final ConnectionState state;
- final VoidCallback? onTap;
- const ConnectionButton({super.key, required this.state, this.onTap});
- @override
- State<ConnectionButton> createState() => _ConnectionButtonState();
- }
- class _ConnectionButtonState extends State<ConnectionButton>
- with TickerProviderStateMixin {
- bool _isAtTop = false; // 控制按钮位置,false=底部,true=顶部
- List<Color>? _previousGradientColor; // 保存前一个渐变色用于动画过渡
- late AnimationController _rotationController; // 旋转动画控制器
- Timer? _connectingTimer; // 连接中状态的计时器
- int _connectingTextIndex = 0; // 当前显示的连接文本索引(0-4)
- ConnectionState? _previousState; // 保存前一个连接状态
- @override
- void initState() {
- super.initState();
- // 初始化旋转动画控制器
- _rotationController = AnimationController(
- duration: const Duration(seconds: 2),
- vsync: this,
- );
- }
- @override
- void dispose() {
- _rotationController.dispose();
- _connectingTimer?.cancel();
- super.dispose();
- }
- void _onTap() {
- if (widget.onTap != null) {
- widget.onTap!();
- }
- }
- // 启动连接中状态的文本轮播计时器
- void _startConnectingTimer() {
- _connectingTimer?.cancel();
- _connectingTextIndex = 0;
- _connectingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
- if (mounted) {
- setState(() {
- // 每秒切换到下一个文本,循环显示0-4
- _connectingTextIndex = (_connectingTextIndex + 1) % 5;
- });
- }
- });
- }
- // 停止连接中状态的文本轮播计时器
- void _stopConnectingTimer() {
- _connectingTimer?.cancel();
- _connectingTimer = null;
- _connectingTextIndex = 0;
- }
- // 根据索引获取对应的连接文本
- String _getConnectingText() {
- switch (_connectingTextIndex) {
- case 0:
- return Strings.securingData.tr;
- case 1:
- return Strings.encryptingTraffic.tr;
- case 2:
- return Strings.protectingPrivacy.tr;
- case 3:
- return Strings.safeConnection.tr;
- case 4:
- return Strings.yourDataIsSafe.tr;
- default:
- return Strings.connecting.tr;
- }
- }
- // 比较两个颜色列表是否相等
- bool _colorsEqual(List<Color> a, List<Color> b) {
- if (a.length != b.length) return false;
- for (int i = 0; i < a.length; i++) {
- if (a[i].value != b[i].value) return false;
- }
- return true;
- }
- Widget _buildImageWithAnimation(String imgPath) {
- // 如果是 connectingVirtual/connecting 或 disconnecting 状态,添加旋转动画
- if (widget.state == ConnectionState.connectingVirtual ||
- widget.state == ConnectionState.connecting ||
- widget.state == ConnectionState.disconnecting) {
- // 启动旋转动画
- if (!_rotationController.isAnimating) {
- _rotationController.repeat();
- }
- return AnimatedBuilder(
- animation: _rotationController,
- builder: (context, child) {
- return Transform.rotate(
- angle: _rotationController.value * 2 * math.pi,
- child: IXImage(
- source: imgPath,
- sourceType: ImageSourceType.asset,
- width: 30.w,
- height: 30.w,
- ),
- );
- },
- );
- } else {
- // 其他状态停止旋转动画
- if (_rotationController.isAnimating) {
- _rotationController.stop();
- }
- return IXImage(
- source: imgPath,
- sourceType: ImageSourceType.asset,
- width: 30.w,
- height: 30.w,
- );
- }
- }
- @override
- Widget build(BuildContext context) {
- return GestureDetector(onTap: _onTap, child: _buildMainButton());
- }
- Widget _buildMainButton() {
- // 根据状态和主题获取颜色
- List<Color> gradientColor;
- String imgPath;
- String statusImgPath;
- String text;
- Color textColor;
- switch (widget.state) {
- case ConnectionState.disconnected:
- gradientColor = [
- Get.reactiveTheme.highlightColor,
- Get.reactiveTheme.hintColor,
- ];
- imgPath = Assets.switchStatusDisconnected;
- statusImgPath = Assets.disconnected;
- text = Strings.disconnected.tr;
- textColor = Get.reactiveTheme.hintColor;
- // 只在状态改变时执行一次
- if (_previousState != ConnectionState.disconnected && _isAtTop) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- setState(() {
- _isAtTop = false;
- });
- }
- });
- }
- _stopConnectingTimer(); // 停止计时器
- break;
- case ConnectionState.connectingVirtual:
- case ConnectionState.connecting:
- gradientColor = [
- Get.reactiveTheme.highlightColor,
- Get.reactiveTheme.hintColor,
- ];
- imgPath = Assets.switchStatusConnecting;
- statusImgPath = Assets.connecting;
- text = _getConnectingText(); // 使用轮播文本
- textColor = Get.reactiveTheme.hintColor;
- // 只在状态改变时执行一次切换位置
- if (_previousState != ConnectionState.connectingVirtual &&
- _previousState != ConnectionState.connecting) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- setState(() {
- _isAtTop = !_isAtTop;
- });
- }
- });
- }
- // 启动连接文本轮播计时器
- if (_connectingTimer == null || !_connectingTimer!.isActive) {
- _startConnectingTimer();
- }
- break;
- case ConnectionState.disconnecting:
- gradientColor = [
- Get.reactiveTheme.highlightColor,
- Get.reactiveTheme.hintColor,
- ];
- imgPath = Assets.switchStatusConnecting;
- statusImgPath = Assets.connecting;
- text = Strings.disconnecting.tr;
- textColor = Get.reactiveTheme.hintColor;
- _stopConnectingTimer(); // 停止计时器
- break;
- case ConnectionState.connected:
- gradientColor = [
- Get.reactiveTheme.shadowColor,
- Get.reactiveTheme.primaryColor,
- ];
- imgPath = Assets.switchStatusConnected;
- statusImgPath = Assets.connected;
- text = Strings.connected.tr;
- textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
- // 只在状态改变时执行一次
- if (_previousState != ConnectionState.connected && !_isAtTop) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- setState(() {
- _isAtTop = true;
- });
- }
- });
- }
- _stopConnectingTimer(); // 停止计时器
- break;
- case ConnectionState.error:
- gradientColor = [
- Get.reactiveTheme.highlightColor,
- Get.reactiveTheme.hintColor,
- ];
- imgPath = Assets.switchStatusDisconnected;
- statusImgPath = Assets.error;
- text = Strings.error.tr;
- textColor = Get.reactiveTheme.hintColor;
- _stopConnectingTimer(); // 停止计时器
- break;
- }
- // 保存前一个渐变色用于动画
- final previousColor = _previousGradientColor ?? gradientColor;
- // 只在颜色真正改变时更新
- if (_previousGradientColor == null ||
- _previousGradientColor!.length != gradientColor.length ||
- !_colorsEqual(_previousGradientColor!, gradientColor)) {
- // 延迟更新,避免在 build 中调用 setState
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- _previousGradientColor = gradientColor;
- }
- });
- }
- // 更新前一个状态
- if (_previousState != widget.state) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- _previousState = widget.state;
- }
- });
- }
- return Column(
- children: [
- TweenAnimationBuilder<List<Color>>(
- duration: const Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- tween: ColorListTween(begin: previousColor, end: gradientColor),
- builder: (context, colors, child) {
- return Container(
- width: 72.w,
- height: 132.w,
- padding: EdgeInsets.all(6.w),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20.r),
- gradient: LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: colors,
- ),
- ),
- child: child,
- );
- },
- child: AnimatedAlign(
- duration: const Duration(milliseconds: 300),
- curve: Curves.fastOutSlowIn,
- alignment: _isAtTop ? Alignment.topCenter : Alignment.bottomCenter,
- child: Container(
- width: 60.w,
- height: 60.w,
- alignment: Alignment.center,
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(14.r),
- ),
- child: AnimatedSwitcher(
- duration: const Duration(milliseconds: 300),
- transitionBuilder: (Widget child, Animation<double> animation) {
- return FadeTransition(
- opacity: animation,
- child: ScaleTransition(
- scale: Tween<double>(
- begin: 0.85,
- end: 1.0,
- ).animate(animation),
- child: child,
- ),
- );
- },
- child: _buildImageWithAnimation(imgPath),
- ),
- ),
- ),
- ),
- 20.verticalSpaceFromWidth,
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 400),
- switchInCurve: Curves.easeIn,
- switchOutCurve: Curves.easeOut,
- transitionBuilder: (Widget child, Animation<double> animation) {
- return FadeTransition(opacity: animation, child: child);
- },
- child: Row(
- key: ValueKey(text), // 使用文本作为 key,确保文字改变时触发动画
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- IXImage(
- source: statusImgPath,
- sourceType: ImageSourceType.asset,
- width: 14.w,
- height: 14.w,
- ),
- 4.horizontalSpace,
- Text(
- text,
- style: TextStyle(
- fontSize: 14.sp,
- fontWeight: FontWeight.w500,
- height: 1.4,
- color: textColor,
- ),
- ),
- ],
- ),
- ),
- ],
- );
- }
- }
- // 自定义颜色列表插值器,用于渐变色动画
- class ColorListTween extends Tween<List<Color>> {
- ColorListTween({super.begin, super.end});
- @override
- List<Color> lerp(double t) {
- if (begin == null || end == null) {
- return end ?? begin ?? [];
- }
- final length = math.max(begin!.length, end!.length);
- return List.generate(length, (index) {
- final beginColor = index < begin!.length ? begin![index] : begin!.last;
- final endColor = index < end!.length ? end![index] : end!.last;
- return Color.lerp(beginColor, endColor, t) ?? endColor;
- });
- }
- }
|