Browse Source

fix: 首页ui改版

(cherry picked from commit d28dedbb4324d5230474945cfafa4f744a063fc0)
lilu 3 tháng trước cách đây
mục cha
commit
5d7e86cb76

+ 7 - 0
lib/app/modules/home/controllers/home_controller.dart

@@ -37,6 +37,13 @@ class HomeController extends BaseController {
   set isRecentLocationsExpanded(bool value) =>
       _isRecentLocationsExpanded.value = value;
 
+  /// 收起最近位置列表
+  void collapseRecentLocations() {
+    if (_isRecentLocationsExpanded.value) {
+      _isRecentLocationsExpanded.value = false;
+    }
+  }
+
   // 统计信息
   final _uplinkBytes = 0.obs;
   final _downlinkBytes = 0.obs;

+ 210 - 207
lib/app/modules/home/views/home_view.dart

@@ -1,4 +1,4 @@
-import 'dart:io';
+import 'dart:ui';
 
 import 'package:carousel_slider/carousel_slider.dart';
 import 'package:flutter/material.dart' hide ConnectionState;
@@ -6,6 +6,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
 import 'package:nomo/app/constants/iconfont/iconfont.dart';
 import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
+import '../../../../config/theme/dark_theme_colors.dart';
 import '../../../../config/theme/theme_extensions/theme_extension.dart';
 import '../../../../config/translations/strings_enum.dart';
 import '../../../base/base_view.dart';
@@ -27,242 +28,242 @@ class HomeView extends BaseView<HomeController> {
 
   @override
   Widget buildContent(BuildContext context) {
+    return _buildCustomScrollView();
+  }
+
+  Widget _buildCustomScrollView() {
     return SafeArea(
-      child: Container(
-        margin: EdgeInsets.symmetric(horizontal: 14.w),
-        child: Stack(
+      child: Padding(
+        padding: EdgeInsets.symmetric(horizontal: 14.w),
+        child: Column(
           children: [
-            Column(
-              children: [
-                Row(
-                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                  children: [
-                    ClipRRect(
-                      borderRadius: BorderRadius.circular(100.r),
-                      child: Container(
-                        decoration: const BoxDecoration(
-                          color: Color(0x0DFFD900),
-                        ),
-                        child: Stack(
-                          children: [
-                            // 左上角光晕
-                            Positioned(
-                              left: -8.w,
-                              top: -8.w,
-                              child: Container(
-                                width: 32.w,
-                                height: 32.w,
-                                decoration: BoxDecoration(
-                                  shape: BoxShape.circle,
-                                  gradient: RadialGradient(
-                                    colors: [
-                                      const Color(
-                                        0xFFFCEFCF,
-                                      ).withValues(alpha: 0.4),
-                                      const Color(
-                                        0xFFFCEFCF,
-                                      ).withValues(alpha: 0.0),
-                                    ],
-                                    stops: const [0.0, 1.0],
-                                  ),
-                                ),
-                              ),
-                            ),
-                            // 右下角光晕
-                            Positioned(
-                              right: -12.w,
-                              bottom: -42.w,
-                              child: Container(
-                                width: 84.w,
-                                height: 84.w,
-                                decoration: BoxDecoration(
-                                  shape: BoxShape.rectangle,
-                                  gradient: RadialGradient(
-                                    colors: [
-                                      const Color(
-                                        0xFFFCEFCF,
-                                      ).withValues(alpha: 0.35),
-                                      const Color(
-                                        0xFFFCEFCF,
-                                      ).withValues(alpha: 0.0),
-                                    ],
-                                    stops: const [0.0, 1.0],
+            _buildAppBar(),
+            20.verticalSpaceFromWidth,
+            Expanded(
+              child: LayoutBuilder(
+                builder: (context, constraints) {
+                  // 确保 viewportHeight 不会为负数(键盘弹出时)
+                  final double viewportHeight = (constraints.maxHeight - 388.w)
+                      .clamp(0.0, double.infinity);
+                  return GestureDetector(
+                    behavior: HitTestBehavior.translucent,
+                    onTap: () {
+                      controller.collapseRecentLocations();
+                    },
+                    child: SmartRefresher(
+                      enablePullDown: true,
+                      enablePullUp: false,
+                      controller: controller.refreshController,
+                      onRefresh: controller.onRefresh,
+                      child: CustomScrollView(
+                        slivers: [
+                          // 1. 顶部和中间部分
+                          SliverList(
+                            delegate: SliverChildListDelegate([
+                              20.verticalSpaceFromWidth,
+                              Stack(
+                                children: [
+                                  Container(
+                                    alignment: Alignment.center,
+                                    margin: EdgeInsets.only(top: 138.w),
+                                    child: _buildConnectionButton(),
                                   ),
-                                ),
+                                  _buildLocationStack(),
+                                ],
                               ),
-                            ),
-                            // 内容
-                            Padding(
-                              padding: EdgeInsets.symmetric(
-                                horizontal: 6.w,
-                                vertical: 4.w,
+                              20.verticalSpaceFromWidth,
+                              // // 第二部分:中间(内容可长可短)
+                              // Container(
+                              //   color: Colors.green[100],
+                              //   height: 400, // 修改这个高度来测试滚动效果
+                              //   child: Center(child: Text("中间内容区域")),
+                              // ),
+                            ]),
+                          ),
+
+                          // 2. 底部部分:关键所在
+                          SliverFillRemaining(
+                            hasScrollBody: false,
+                            child: ConstrainedBox(
+                              constraints: BoxConstraints(
+                                minHeight: viewportHeight,
                               ),
-                              child: Row(
+                              child: Column(
                                 children: [
-                                  Obx(
-                                    () => ClickOpacity(
-                                      onTap: () =>
-                                          Get.toNamed(Routes.SUBSCRIPTION),
-                                      child: IXImage(
-                                        source:
-                                            controller
-                                                    .apiController
-                                                    .userLevel ==
-                                                3
-                                            ? Assets.homePremium
-                                            : controller
-                                                      .apiController
-                                                      .userLevel ==
-                                                  9999
-                                            ? Assets.homeTest
-                                            : Assets.homeFree,
-                                        width:
-                                            controller
-                                                    .apiController
-                                                    .userLevel ==
-                                                3
-                                            ? 92.w
-                                            : 64.w,
-                                        height: 28.w,
-                                        sourceType: ImageSourceType.asset,
-                                      ),
+                                  Spacer(), // 自动撑开上方空间,将底部内容挤下去
+                                  Padding(
+                                    padding: EdgeInsets.symmetric(
+                                      vertical: 14.w,
                                     ),
-                                  ),
-                                  Obx(
-                                    () => Text(
-                                      controller.apiController.isGuest &&
-                                              !controller
-                                                  .apiController
-                                                  .isPremium &&
-                                              controller
-                                                      .apiController
-                                                      .remainTimeSeconds >
-                                                  0
-                                          ? controller
-                                                .apiController
-                                                .remainTimeFormatted
-                                          : controller.coreController.timer,
-                                      style: TextStyle(
-                                        fontSize: 13.sp,
-                                        height: 1.5,
-                                        fontStyle: FontStyle.italic,
-                                        fontWeight: FontWeight.w500,
-                                        color:
-                                            controller
-                                                        .apiController
-                                                        .userLevel ==
-                                                    3 ||
-                                                controller
-                                                        .apiController
-                                                        .userLevel ==
-                                                    9999
-                                            ? Get
-                                                  .reactiveTheme
-                                                  .textTheme
-                                                  .bodyLarge!
-                                                  .color
-                                            : Get.reactiveTheme.hintColor,
+                                    child: CarouselSlider(
+                                      options: CarouselOptions(
+                                        height: 80.w,
+                                        viewportFraction: 1.0,
                                       ),
+                                      items: [1, 2, 3, 4, 5].map((i) {
+                                        return Builder(
+                                          builder: (BuildContext context) {
+                                            return IXImage(
+                                              source: Assets.bannerTest,
+                                              width: double.infinity,
+                                              height: 80.w,
+                                              sourceType: ImageSourceType.asset,
+                                              borderRadius: 14.r,
+                                            );
+                                          },
+                                        );
+                                      }).toList(),
                                     ),
                                   ),
+                                  MenuList(),
                                 ],
                               ),
                             ),
-                          ],
-                        ),
+                          ),
+                        ],
                       ),
                     ),
+                  );
+                },
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
 
-                    ClickOpacity(
-                      child: Padding(
-                        padding: EdgeInsets.only(
-                          left: 10.w,
-                          right: 0.w,
-                          top: 10.w,
-                          bottom: 10.w,
-                        ),
-                        child: Icon(
-                          IconFont.icon09,
-                          size: 26.w,
-                          color: Get.reactiveTheme.hintColor,
+  Widget _buildAppBar() {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+      children: [
+        Obx(() {
+          final bgColor = controller.apiController.userLevel == 3
+              ? DarkThemeColors.homePremiumColor
+              : controller.apiController.userLevel == 9999
+              ? DarkThemeColors.homeTestColor
+              : DarkThemeColors.homeFreeColor;
+          return ClipRRect(
+            borderRadius: BorderRadius.circular(100.r),
+            child: Container(
+              decoration: BoxDecoration(color: bgColor.withValues(alpha: 0.05)),
+              child: Stack(
+                children: [
+                  // 左上角光晕
+                  Positioned(
+                    left: -8.w,
+                    top: -16.w,
+                    child: Container(
+                      width: 32.w,
+                      height: 32.w,
+                      decoration: BoxDecoration(
+                        shape: BoxShape.circle,
+                        gradient: RadialGradient(
+                          colors: [
+                            bgColor.withValues(alpha: 0.85),
+                            bgColor.withValues(alpha: 0.05),
+                          ],
+                          stops: const [0.0, 1.0],
                         ),
                       ),
-                      onTap: () {
-                        Get.toNamed(Routes.SETTING);
-                        // ErrorDialog.show(
-                        //   message:
-                        //       "The VPN was disconnected unexpectedly. Would you like to reconnect now to stay protected?",
-                        // );
-                      },
                     ),
-                  ],
-                ),
-                20.verticalSpaceFromWidth,
-                Expanded(
-                  child: SmartRefresher(
-                    enablePullDown: true,
-                    enablePullUp: false,
-                    controller: controller.refreshController,
-                    onRefresh: controller.onRefresh,
-                    child: SingleChildScrollView(
-                      physics: const ClampingScrollPhysics(),
-                      child: Column(
-                        crossAxisAlignment: CrossAxisAlignment.start,
-                        children: [
-                          // 位置选择按钮和最近位置(叠在一起的效果)
-                          20.verticalSpaceFromWidth,
-                          Stack(
-                            children: [
-                              Container(
-                                alignment: Alignment.center,
-                                margin: EdgeInsets.only(top: 138.w),
-                                child: _buildConnectionButton(),
-                              ),
-                              _buildLocationStack(),
-                            ],
-                          ),
-                        ],
+                  ),
+                  // 右下角光晕
+                  Positioned(
+                    right: 12.w,
+                    bottom: -12.w,
+                    child: ImageFiltered(
+                      imageFilter: ImageFilter.blur(
+                        sigmaX: 8,
+                        sigmaY: 8,
+                        tileMode: TileMode.decal,
+                      ),
+                      child: Container(
+                        width: 42.w,
+                        height: 16.w,
+                        decoration: BoxDecoration(
+                          borderRadius: BorderRadius.circular(8.w),
+                          color: bgColor.withValues(alpha: 0.85),
+                        ),
                       ),
                     ),
                   ),
-                ),
-              ],
-            ),
-            Positioned(
-              bottom: Platform.isAndroid ? 10.w : 0,
-              left: 0,
-              right: 0,
-              child: Column(
-                children: [
+                  // 内容
                   Padding(
-                    padding: EdgeInsets.symmetric(vertical: 14.w),
-                    child: CarouselSlider(
-                      options: CarouselOptions(
-                        height: 80.w,
-                        viewportFraction: 1.0,
-                      ),
-                      items: [1, 2, 3, 4, 5].map((i) {
-                        return Builder(
-                          builder: (BuildContext context) {
-                            return IXImage(
-                              source: Assets.bannerTest,
-                              width: double.infinity,
-                              height: 80.w,
+                    padding: EdgeInsets.symmetric(
+                      horizontal: 6.w,
+                      vertical: 4.w,
+                    ),
+                    child: Row(
+                      children: [
+                        Obx(
+                          () => ClickOpacity(
+                            onTap: () => Get.toNamed(Routes.SUBSCRIPTION),
+                            child: IXImage(
+                              source: controller.apiController.userLevel == 3
+                                  ? Assets.homePremium
+                                  : controller.apiController.userLevel == 9999
+                                  ? Assets.homeTest
+                                  : Assets.homeFree,
+                              width: controller.apiController.userLevel == 3
+                                  ? 92.w
+                                  : 64.w,
+                              height: 28.w,
                               sourceType: ImageSourceType.asset,
-                              borderRadius: 14.r,
-                            );
-                          },
-                        );
-                      }).toList(),
+                            ),
+                          ),
+                        ),
+                        Obx(
+                          () => Text(
+                            controller.apiController.isGuest &&
+                                    !controller.apiController.isPremium &&
+                                    controller.apiController.remainTimeSeconds >
+                                        0
+                                ? controller.apiController.remainTimeFormatted
+                                : controller.coreController.timer,
+                            style: TextStyle(
+                              fontSize: 13.sp,
+                              height: 1.5,
+                              fontStyle: FontStyle.italic,
+                              fontWeight: FontWeight.w500,
+                              fontFeatures: [FontFeature.tabularFigures()],
+                              color:
+                                  controller.apiController.userLevel == 3 ||
+                                      controller.apiController.userLevel == 9999
+                                  ? Get.reactiveTheme.textTheme.bodyLarge!.color
+                                  : Get.reactiveTheme.hintColor,
+                            ),
+                          ),
+                        ),
+                      ],
                     ),
                   ),
-                  MenuList(),
                 ],
               ),
             ),
-          ],
+          );
+        }),
+
+        ClickOpacity(
+          child: Padding(
+            padding: EdgeInsets.only(
+              left: 10.w,
+              right: 0.w,
+              top: 10.w,
+              bottom: 10.w,
+            ),
+            child: Icon(
+              IconFont.icon09,
+              size: 26.w,
+              color: Get.reactiveTheme.hintColor,
+            ),
+          ),
+          onTap: () {
+            controller.collapseRecentLocations();
+            Get.toNamed(Routes.SETTING);
+          },
         ),
-      ),
+      ],
     );
   }
 
@@ -271,6 +272,7 @@ class HomeView extends BaseView<HomeController> {
       () => ConnectionRoundButton(
         state: controller.coreController.state,
         onTap: () {
+          controller.collapseRecentLocations();
           controller.setDefaultAutoConnect();
         },
       ),
@@ -300,6 +302,7 @@ class HomeView extends BaseView<HomeController> {
   Widget _buildSelectedLocationCard() {
     return GestureDetector(
       onTap: () {
+        controller.collapseRecentLocations();
         Get.toNamed(Routes.NODE);
       },
       child: Obx(() {

+ 261 - 43
lib/app/modules/home/widgets/connection_round_button.dart

@@ -10,6 +10,183 @@ import '../../../../config/theme/theme_extensions/theme_extension.dart';
 import '../../../../config/translations/strings_enum.dart';
 import '../../../constants/enums.dart';
 
+/// 自定义圆环画笔 - 绘制带渐变和发光效果的圆环
+class RingPainter extends CustomPainter {
+  final ConnectionState state;
+  final double rotationAngle;
+  final double glowIntensity;
+  final Color primaryColor;
+  final Color secondaryColor;
+
+  RingPainter({
+    required this.state,
+    this.rotationAngle = 0,
+    this.glowIntensity = 0.5,
+    required this.primaryColor,
+    required this.secondaryColor,
+  });
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final center = Offset(size.width / 2, size.height / 2);
+    final radius = size.width / 2 - 8;
+    final strokeWidth = 2.5;
+
+    // 根据状态选择绘制方式
+    switch (state) {
+      case ConnectionState.disconnected:
+      case ConnectionState.error:
+        _drawDashedRing(canvas, center, radius, strokeWidth);
+        break;
+      case ConnectionState.connecting:
+        _drawGradientRing(canvas, center, radius, strokeWidth, true);
+        break;
+      case ConnectionState.connected:
+        _drawGradientRing(canvas, center, radius, strokeWidth, false);
+        _drawGlowEffect(canvas, center, radius);
+        break;
+    }
+  }
+
+  /// 绘制虚线圆环(断开/错误状态)
+  void _drawDashedRing(
+    Canvas canvas,
+    Offset center,
+    double radius,
+    double strokeWidth,
+  ) {
+    final paint = Paint()
+      ..color = secondaryColor.withValues(alpha: 0.4)
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = strokeWidth
+      ..strokeCap = StrokeCap.round
+      ..isAntiAlias = true;
+
+    const int dashCount = 60;
+    const double dashRatio = 0.5; // 虚线占比
+    final double dashAngle = (2 * math.pi / dashCount) * dashRatio;
+    final double gapAngle = (2 * math.pi / dashCount) * (1 - dashRatio);
+
+    for (int i = 0; i < dashCount; i++) {
+      final startAngle = i * (dashAngle + gapAngle) - math.pi / 2;
+      canvas.drawArc(
+        Rect.fromCircle(center: center, radius: radius),
+        startAngle,
+        dashAngle,
+        false,
+        paint,
+      );
+    }
+  }
+
+  /// 绘制渐变圆环(连接中/已连接状态)
+  void _drawGradientRing(
+    Canvas canvas,
+    Offset center,
+    double radius,
+    double strokeWidth,
+    bool isAnimating,
+  ) {
+    final rect = Rect.fromCircle(center: center, radius: radius);
+
+    if (isAnimating) {
+      // 连接中状态:绘制平滑的流光尾巴效果
+      final gradient = SweepGradient(
+        startAngle: 0,
+        endAngle: 2 * math.pi,
+        colors: [
+          primaryColor.withValues(alpha: 0.0),
+          primaryColor.withValues(alpha: 0.05),
+          primaryColor.withValues(alpha: 0.2),
+          primaryColor.withValues(alpha: 0.5),
+          primaryColor.withValues(alpha: 0.8),
+          primaryColor,
+          secondaryColor,
+          secondaryColor.withValues(alpha: 0.8),
+          secondaryColor.withValues(alpha: 0.4),
+          secondaryColor.withValues(alpha: 0.1),
+          primaryColor.withValues(alpha: 0.0),
+        ],
+        stops: const [
+          0.0,
+          0.1,
+          0.2,
+          0.35,
+          0.45,
+          0.5,
+          0.55,
+          0.65,
+          0.8,
+          0.9,
+          1.0,
+        ],
+        transform: GradientRotation(rotationAngle - math.pi / 2),
+      );
+
+      final paint = Paint()
+        ..shader = gradient.createShader(rect)
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = strokeWidth
+        ..strokeCap = StrokeCap.round
+        ..isAntiAlias = true;
+
+      // 绘制完整圆环,渐变本身形成流光效果
+      canvas.drawCircle(center, radius, paint);
+    } else {
+      // 已连接状态:绘制平滑渐变圆环
+      final gradient = SweepGradient(
+        startAngle: 0,
+        endAngle: 2 * math.pi,
+        colors: [
+          primaryColor,
+          primaryColor.withValues(alpha: 0.9),
+          secondaryColor.withValues(alpha: 0.9),
+          secondaryColor,
+          secondaryColor,
+          secondaryColor.withValues(alpha: 0.9),
+          primaryColor.withValues(alpha: 0.9),
+          primaryColor,
+        ],
+        stops: const [0.0, 0.15, 0.35, 0.5, 0.5, 0.65, 0.85, 1.0],
+        transform: GradientRotation(rotationAngle),
+      );
+
+      final paint = Paint()
+        ..shader = gradient.createShader(rect)
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = strokeWidth
+        ..strokeCap = StrokeCap.round
+        ..isAntiAlias = true;
+
+      canvas.drawCircle(center, radius, paint);
+    }
+  }
+
+  /// 绘制发光效果(已连接状态)
+  void _drawGlowEffect(Canvas canvas, Offset center, double radius) {
+    // 外层发光 - 使用更多层次和更柔和的模糊
+    for (int i = 4; i > 0; i--) {
+      final glowPaint = Paint()
+        ..color = primaryColor.withValues(alpha: glowIntensity * 0.12 / i)
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 2.0 + i * 2.5
+        ..maskFilter = MaskFilter.blur(BlurStyle.normal, i * 1.5)
+        ..isAntiAlias = true;
+
+      canvas.drawCircle(center, radius, glowPaint);
+    }
+  }
+
+  @override
+  bool shouldRepaint(covariant RingPainter oldDelegate) {
+    return oldDelegate.state != state ||
+        oldDelegate.rotationAngle != rotationAngle ||
+        oldDelegate.glowIntensity != glowIntensity ||
+        oldDelegate.primaryColor != primaryColor ||
+        oldDelegate.secondaryColor != secondaryColor;
+  }
+}
+
 class ConnectionRoundButton extends StatefulWidget {
   final ConnectionState state;
   final VoidCallback? onTap;
@@ -118,7 +295,7 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
   void _startConnectingTimer() {
     _connectingTimer?.cancel();
     _connectingTextIndex = -1;
-    _connectingTimer = Timer.periodic(const Duration(seconds: 4), (timer) {
+    _connectingTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
       if (mounted) {
         setState(() {
           // 每秒切换到下一个文本,循环显示0-4
@@ -153,31 +330,74 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
     }
   }
 
-  // 构建旋转的圆环
-  Widget _buildRoundRing(String svgPath, bool shouldRotate) {
-    final ringWidget = IXImage(
-      key: ValueKey('ring_$svgPath'),
-      source: svgPath,
-      sourceType: ImageSourceType.asset,
-      width: 170.w,
-      height: 170.w,
-    );
+  // 获取圆环主色调
+  Color _getRingPrimaryColor(ConnectionState state) {
+    switch (state) {
+      case ConnectionState.disconnected:
+      case ConnectionState.error:
+        return Get.reactiveTheme.hintColor.withValues(alpha: 0.5);
+      case ConnectionState.connecting:
+        return const Color(0xFF4FC3F7); // 浅蓝色
+      case ConnectionState.connected:
+        return const Color(0xFF66BB6A); // 绿色
+    }
+  }
+
+  // 获取圆环副色调
+  Color _getRingSecondaryColor(ConnectionState state) {
+    switch (state) {
+      case ConnectionState.disconnected:
+      case ConnectionState.error:
+        return Get.reactiveTheme.hintColor.withValues(alpha: 0.3);
+      case ConnectionState.connecting:
+        return const Color(0xFF7E57C2); // 紫色
+      case ConnectionState.connected:
+        return const Color(0xFF26C6DA); // 青色
+    }
+  }
+
+  // 构建圆环 - 使用自定义绘制
+  Widget _buildRoundRing(ConnectionState state, bool shouldRotate) {
+    final primaryColor = _getRingPrimaryColor(state);
+    final secondaryColor = _getRingSecondaryColor(state);
 
     if (shouldRotate) {
       return AnimatedBuilder(
-        key: const ValueKey('rotating_ring'),
+        key: ValueKey('rotating_ring_$state'),
         animation: _rotationController,
         builder: (context, child) {
-          return Transform.rotate(
-            angle: _rotationController.value * 2 * math.pi,
-            child: child,
+          return CustomPaint(
+            size: Size(170.w, 170.w),
+            painter: RingPainter(
+              state: state,
+              rotationAngle: _rotationController.value * 2 * math.pi,
+              glowIntensity: 0.5,
+              primaryColor: primaryColor,
+              secondaryColor: secondaryColor,
+            ),
           );
         },
-        child: ringWidget,
       );
     }
 
-    return ringWidget;
+    return TweenAnimationBuilder<double>(
+      key: ValueKey('static_ring_$state'),
+      tween: Tween(begin: 0.0, end: 1.0),
+      duration: const Duration(milliseconds: 800),
+      curve: Curves.easeOutCubic,
+      builder: (context, value, child) {
+        return CustomPaint(
+          size: Size(170.w, 170.w),
+          painter: RingPainter(
+            state: state,
+            rotationAngle: 0,
+            glowIntensity: state == ConnectionState.connected ? value : 0.3,
+            primaryColor: primaryColor,
+            secondaryColor: secondaryColor,
+          ),
+        );
+      },
+    );
   }
 
   // 构建电源图标
@@ -198,7 +418,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
 
   Widget _buildMainButton() {
     // 根据状态获取对应的资源和样式
-    String ringPath;
     String statusImgPath;
     String text;
     Color textColor;
@@ -207,7 +426,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
 
     switch (widget.state) {
       case ConnectionState.disconnected:
-        ringPath = Assets.disconnectedRound;
         statusImgPath = Assets.disconnected;
         text = Strings.disconnected.tr;
         textColor = Get.reactiveTheme.hintColor;
@@ -215,7 +433,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
         shouldRotate = false;
         break;
       case ConnectionState.connecting:
-        ringPath = Assets.connectingRound;
         statusImgPath = Assets.connecting;
         text = _getConnectingText(); // 使用轮播文本
         textColor = Get.reactiveTheme.hintColor;
@@ -223,7 +440,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
         shouldRotate = true;
         break;
       case ConnectionState.connected:
-        ringPath = Assets.connectedRound;
         statusImgPath = Assets.connected;
         text = Strings.connected.tr;
         textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
@@ -231,7 +447,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
         shouldRotate = false;
         break;
       case ConnectionState.error:
-        ringPath = Assets.disconnectedRound;
         statusImgPath = Assets.error;
         text = Strings.error.tr;
         textColor = Get.reactiveTheme.hintColor;
@@ -282,7 +497,7 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
                     ],
                   );
                 },
-                child: _buildRoundRing(ringPath, shouldRotate),
+                child: _buildRoundRing(widget.state, shouldRotate),
               ),
               // 电源图标 - 纯淡入淡出
               AnimatedSwitcher(
@@ -339,29 +554,32 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
               ),
             );
           },
-          child: Row(
+          child: SizedBox(
             key: ValueKey('status_$text'), // 使用文本作为 key,确保文字改变时触发动画
-            mainAxisAlignment: MainAxisAlignment.center,
-            crossAxisAlignment: CrossAxisAlignment.center,
-            mainAxisSize: MainAxisSize.min,
-            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,
+            height: 20.w,
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.center,
+              crossAxisAlignment: CrossAxisAlignment.center,
+              mainAxisSize: MainAxisSize.min,
+              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,
+                  ),
+                ),
+              ],
+            ),
           ),
         ),
       ],

+ 65 - 14
lib/app/modules/home/widgets/menu_list.dart

@@ -25,6 +25,14 @@ class MenuItem {
 }
 
 /// 菜单列表组件
+/// 支持放在 SliverFillRemaining 中
+/// 排列规则:
+/// - 6个:2排,每排3个
+/// - 5个:2排,第一排3个,第二排2个
+/// - 4个:2排,每排2个
+/// - 3个:1排,每排3个
+/// - 2个:1排,每排2个
+/// - 1个:1排,占满
 class MenuList extends StatelessWidget {
   const MenuList({super.key});
 
@@ -36,7 +44,6 @@ class MenuList extends StatelessWidget {
         title: Strings.moviesAndTV.tr,
         iconColor: const Color(0xFFFF3B30),
         onTap: () {
-          // 处理点击事件
           print('Movies&TV tapped');
           Get.toNamed(Routes.MEDIALOCATION);
         },
@@ -86,23 +93,66 @@ class MenuList extends StatelessWidget {
     ];
   }
 
+  /// 根据数量计算每排的布局
+  /// 返回一个二维列表,每个子列表表示一排的菜单项
+  List<List<MenuItem>> _calculateLayout(List<MenuItem> items) {
+    final count = items.length.clamp(0, 6);
+    if (count == 0) return [];
+
+    switch (count) {
+      case 1:
+        // 1个:1排占满
+        return [items.sublist(0, 1)];
+      case 2:
+        // 2个:1排,每排2个
+        return [items.sublist(0, 2)];
+      case 3:
+        // 3个:1排,每排3个
+        return [items.sublist(0, 3)];
+      case 4:
+        // 4个:2排,每排2个
+        return [items.sublist(0, 2), items.sublist(2, 4)];
+      case 5:
+        // 5个:2排,第一排3个,第二排2个
+        return [items.sublist(0, 3), items.sublist(3, 5)];
+      case 6:
+      default:
+        // 6个:2排,每排3个
+        return [items.sublist(0, 3), items.sublist(3, 6)];
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     final menuItems = _getMenuItems();
+    final layout = _calculateLayout(menuItems);
 
-    return GridView.builder(
-      shrinkWrap: true,
-      physics: const NeverScrollableScrollPhysics(),
-      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
-        crossAxisCount: 3, // 每排3个
-        crossAxisSpacing: 8.w, // 水平间距
-        mainAxisSpacing: 8.w, // 垂直间距
-        childAspectRatio: 3 / 2, // 宽高比
-      ),
-      itemCount: menuItems.length > 6 ? 6 : menuItems.length, // 最多6个
-      itemBuilder: (context, index) {
-        return _buildMenuItem(menuItems[index]);
-      },
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: layout.asMap().entries.map((entry) {
+        final rowIndex = entry.key;
+        final rowItems = entry.value;
+        return Padding(
+          padding: EdgeInsets.only(top: rowIndex > 0 ? 8.w : 0),
+          child: _buildRow(rowItems),
+        );
+      }).toList(),
+    );
+  }
+
+  /// 构建一排菜单项
+  Widget _buildRow(List<MenuItem> items) {
+    return Row(
+      children: items.asMap().entries.map((entry) {
+        final index = entry.key;
+        final item = entry.value;
+        return Expanded(
+          child: Padding(
+            padding: EdgeInsets.only(left: index > 0 ? 8.w : 0),
+            child: _buildMenuItem(item),
+          ),
+        );
+      }).toList(),
     );
   }
 
@@ -111,6 +161,7 @@ class MenuList extends StatelessWidget {
     return GestureDetector(
       onTap: item.onTap,
       child: Container(
+        height: 56.w,
         decoration: BoxDecoration(
           color: Get.reactiveTheme.cardColor,
           borderRadius: BorderRadius.circular(10.r),

+ 4 - 0
lib/config/theme/dark_theme_colors.dart

@@ -100,4 +100,8 @@ class DarkThemeColors {
   static const Color disconnectedIconColor = Color(0xFF646776);
   static const Color connectingIconColor = Color(0xFFEA9800);
   static const Color connectedIconColor = Color(0xFF5182E1);
+
+  static const Color homePremiumColor = Color(0xFFFCEFCF);
+  static const Color homeFreeColor = Color(0xFFFFFFFF);
+  static const Color homeTestColor = Color(0x0D2CD5FF);
 }