From 55d7508807b21b82f0b4da0d5fadf4ab59bffb75 Mon Sep 17 00:00:00 2001 From: speakeloudest Date: Thu, 27 Nov 2025 22:58:42 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=9A=E6=9C=AA=E8=B4=AD=E4=B9=B0?= =?UTF-8?q?=E7=9A=84=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hi_menu/widgets/user_info_card.dart | 50 +-- .../hi_user_info/views/hi_user_info_view.dart | 184 +++++----- .../views/kr_delete_account_view.dart | 85 ++--- .../views/hi_animated_connect_button.dart | 14 +- .../modules/kr_home/views/kr_home_view.dart | 343 +++++++++--------- .../modules/kr_login/views/kr_login_view.dart | 111 ++---- .../widgets/kr_subscription_expiry_text.dart | 67 ++++ 7 files changed, 403 insertions(+), 451 deletions(-) create mode 100644 lib/app/widgets/kr_subscription_expiry_text.dart diff --git a/lib/app/modules/hi_menu/widgets/user_info_card.dart b/lib/app/modules/hi_menu/widgets/user_info_card.dart index ab51f25..43223bc 100644 --- a/lib/app/modules/hi_menu/widgets/user_info_card.dart +++ b/lib/app/modules/hi_menu/widgets/user_info_card.dart @@ -6,6 +6,7 @@ import 'package:get/get.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/controllers/hi_menu_controller.dart'; import 'package:kaer_with_panels/app/routes/app_pages.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; +import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart'; /// 用户信息展示卡片 Widget /// @@ -67,51 +68,10 @@ class UserInfoCard extends StatelessWidget { fontWeight: FontWeight.bold, ), ), - Obx(() { - final currentSubscribe = - controller.kr_subscribeService.kr_currentSubscribe.value; - String expiryText; - - if (currentSubscribe == null) { - expiryText = '尚未购买套餐'; - } else { - final now = DateTime.now(); - DateTime? expireDateTime; - try { - expireDateTime = - DateTime.parse(currentSubscribe.expireTime); - } catch (e) { - expireDateTime = null; - } - - if (expireDateTime == null) { - expiryText = '套餐信息无效'; - } else if (expireDateTime.isBefore(now)) { - final formattedExpireDate = - '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; - expiryText = '已于 $formattedExpireDate 到期'; - } else { - final year = expireDateTime.year; - final month = expireDateTime.month.toString().padLeft(2, '0'); - final day = expireDateTime.day.toString().padLeft(2, '0'); - final hour = expireDateTime.hour.toString().padLeft(2, '0'); - final minute = expireDateTime.minute.toString().padLeft(2, '0'); - final second = expireDateTime.second.toString().padLeft(2, '0'); - - // 2. 拼接成最终的字符串 - final formattedDateTime = '$year/$month/$day $hour:$minute:$second'; - - expiryText = '到期时间:$formattedDateTime'; - } - } - return Text( - expiryText, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - ), - ); - }), + KRSubscriptionExpiryText( + expireTimeProvider: () => controller.kr_subscribeService + .kr_currentSubscribe.value?.expireTime, + ), ], ), ), diff --git a/lib/app/modules/hi_user_info/views/hi_user_info_view.dart b/lib/app/modules/hi_user_info/views/hi_user_info_view.dart index c954cdc..3aa6188 100755 --- a/lib/app/modules/hi_user_info/views/hi_user_info_view.dart +++ b/lib/app/modules/hi_user_info/views/hi_user_info_view.dart @@ -19,13 +19,14 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart'; import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; import 'package:kaer_with_panels/app/common/app_config.dart'; +import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart'; class HIUserInfoView extends GetView { const HIUserInfoView({super.key}); @override Widget build(BuildContext context) { - final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin(); + final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin(); return HIBaseScaffold( child: Stack( children: [ @@ -69,9 +70,14 @@ class HIUserInfoView extends GetView { mainAxisAlignment: MainAxisAlignment.center, children: [ Obx(() { - final account = KRAppRunData.getInstance().kr_account.value; - final isDeviceLogin = account != null && account.startsWith('9000'); - final accountText = isDeviceLogin ? '待绑定' : 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}'; + final account = KRAppRunData.getInstance() + .kr_account + .value; + final isDeviceLogin = account != null && + account.startsWith('9000'); + final accountText = isDeviceLogin + ? '待绑定' + : 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}'; return Text( accountText, style: TextStyle( @@ -84,51 +90,13 @@ class HIUserInfoView extends GetView { overflow: TextOverflow.ellipsis, ); }), - Obx(() { - final currentSubscribe = - controller.kr_subscribeService.kr_currentSubscribe.value; - String expiryText; - - if (currentSubscribe == null) { - expiryText = '尚未购买套餐'; - } else { - final now = DateTime.now(); - DateTime? expireDateTime; - try { - expireDateTime = - DateTime.parse(currentSubscribe.expireTime); - } catch (e) { - expireDateTime = null; - } - - if (expireDateTime == null) { - expiryText = '套餐信息无效'; - } else if (expireDateTime.isBefore(now)) { - final formattedExpireDate = - '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; - expiryText = '已于 $formattedExpireDate 到期'; - } else { - final year = expireDateTime.year; - final month = expireDateTime.month.toString().padLeft(2, '0'); - final day = expireDateTime.day.toString().padLeft(2, '0'); - final hour = expireDateTime.hour.toString().padLeft(2, '0'); - final minute = expireDateTime.minute.toString().padLeft(2, '0'); - final second = expireDateTime.second.toString().padLeft(2, '0'); - - // 2. 拼接成最终的字符串 - final formattedDateTime = '$year/$month/$day $hour:$minute:$second'; - - expiryText = '到期时间:$formattedDateTime'; - } - } - return Text( - expiryText, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - ), - ); - }), + KRSubscriptionExpiryText( + expireTimeProvider: () => controller + .kr_subscribeService + .kr_currentSubscribe + .value + ?.expireTime, + ), ], ), ), @@ -138,7 +106,8 @@ class HIUserInfoView extends GetView { SizedBox(height: 12.w), // 动态:如果已有账号,显示“修改密码”,否则显示“绑定邮箱” Obx(() { - final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin(); + final isDeviceLogin = + KRAppRunData.getInstance().isDeviceLogin(); if (!isDeviceLogin) return SizedBox.shrink(); return InkWell( onTap: () { @@ -155,14 +124,18 @@ class HIUserInfoView extends GetView { } }, child: Container( - margin: EdgeInsets.symmetric(horizontal: 0).copyWith(bottom: 10), // 在这里增加底部间距 - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), + margin: EdgeInsets.symmetric(horizontal: 0) + .copyWith(bottom: 10), // 在这里增加底部间距 + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(22), - border: Border.all(color: Colors.white, width: 2), + border: + Border.all(color: Colors.white, width: 2), ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Text( isDeviceLogin ? '绑定邮箱' : '修改密码', @@ -203,7 +176,8 @@ class HIUserInfoView extends GetView { // 2. 让 GridView 根据内容自动调整高度 shrinkWrap: true, // 3. 设置网格的配置 - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, // 强制两列 crossAxisSpacing: 10, // 水平间距 mainAxisSpacing: 10, // 垂直间距 @@ -226,8 +200,10 @@ class HIUserInfoView extends GetView { child: Text( '请确认是否移除此设备?', style: KrAppTextStyle( - color: - Theme.of(context).textTheme.bodyMedium?.color, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color, fontSize: 14, fontWeight: FontWeight.w600, ), @@ -240,8 +216,7 @@ class HIUserInfoView extends GetView { }, onCancel: () { controller.deleteDevice(id); - } - ); + }); }, ); } else { @@ -261,9 +236,11 @@ class HIUserInfoView extends GetView { ), // 👇 核心改动 3: 将固定在底部的按钮放在 Expanded 外部 Obx(() { - if((KRAppRunData.getInstance().kr_account.value != null && - KRAppRunData.getInstance().kr_account.value!.startsWith('9000'))) - return SizedBox.shrink(); + if ((KRAppRunData.getInstance().kr_account.value != null && + KRAppRunData.getInstance() + .kr_account + .value! + .startsWith('9000'))) return SizedBox.shrink(); return Padding( padding: EdgeInsets.symmetric(horizontal: 40.w), child: Column( @@ -278,7 +255,10 @@ class HIUserInfoView extends GetView { '注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1. 修改为正确的提示信息 textAlign: TextAlign.left, // 文本居中 style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color, fontSize: 14.sp, fontWeight: FontWeight.w600, ), @@ -292,8 +272,7 @@ class HIUserInfoView extends GetView { Get.toNamed(Routes.KR_DELETE_ACCOUNT); }, // 4. onConfirm 对应“返回”按钮的点击事件 - onConfirm: () { - }, + onConfirm: () {}, ); }, child: Container( @@ -301,7 +280,8 @@ class HIUserInfoView extends GetView { padding: EdgeInsets.symmetric(vertical: 12.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24.w), - border: Border.all(color: const Color(0xFFFF2ED1), width: 2), + border: Border.all( + color: const Color(0xFFFF2ED1), width: 2), ), alignment: Alignment.center, child: Text( @@ -324,7 +304,10 @@ class HIUserInfoView extends GetView { '确认要退出您的账号?', // 1. 修改为正确的提示信息 textAlign: TextAlign.center, // 文本居中 style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color, fontSize: 14.sp, fontWeight: FontWeight.w600, ), @@ -333,15 +316,14 @@ class HIUserInfoView extends GetView { cancelText: '确认', confirmText: '返回', onCancel: () async { - final currentDevice = controller.devices.firstWhere( - (device) => device['is_current'] == true, + final currentDevice = + controller.devices.firstWhere( + (device) => device['is_current'] == true, ); final deviceId = currentDevice['id'] as String; await controller.deleteDevice(deviceId); }, - onConfirm: () { - - }, + onConfirm: () {}, ); }, child: Container( @@ -376,6 +358,7 @@ class HIUserInfoView extends GetView { ), ); } + /// 构建“当前设备信息卡”的私有方法 Widget _buildDeviceCard({ required BuildContext context, @@ -396,7 +379,8 @@ class HIUserInfoView extends GetView { final deviceType = deviceInfo['type'] as String; final iconName = deviceInfo['icon'] as String; - return Stack( // 使用 Stack 来放置删除按钮 + return Stack( + // 使用 Stack 来放置删除按钮 children: [ // 主体内容容器 Container( @@ -486,31 +470,31 @@ class HIUserInfoView extends GetView { // 在这里处理点击“添加设备”的逻辑 final device_limit = AppConfig.getInstance().device_limit; HIDialog.show( - customMessageWidget: Padding( - padding: EdgeInsets.only(top: 16.w, bottom: 16.w), - child: Column( - mainAxisSize: MainAxisSize.min, // 让 Column 高度包裹内容 - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 2. 第一个 Text 组件 - Text( - '请使用绑定邮箱登录新设备', - style: KrAppTextStyle( - color: Colors.black, // 可以使用稍浅 - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), + customMessageWidget: Padding( + padding: EdgeInsets.only(top: 16.w, bottom: 16.w), + child: Column( + mainAxisSize: MainAxisSize.min, // 让 Column 高度包裹内容 + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 2. 第一个 Text 组件 + Text( + '请使用绑定邮箱登录新设备', + style: KrAppTextStyle( + color: Colors.black, // 可以使用稍浅 + fontSize: 14.sp, + fontWeight: FontWeight.w600, ), - SizedBox(height: 20.w), - Text( - "每个账号最多允许同时使用${device_limit}台设备同时在线", - style: KrAppTextStyle( - color: Colors.black, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - ) - /*Obx(() { + ), + SizedBox(height: 20.w), + Text( + "每个账号最多允许同时使用${device_limit}台设备同时在线", + style: KrAppTextStyle( + color: Colors.black, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ) + /*Obx(() { // final current = controller.kr_subscribeService.kr_currentSubscribe.value; // 如果 current 为 null,显示默认值 0 @@ -518,9 +502,9 @@ class HIUserInfoView extends GetView { return }),*/ - ], - ), + ], ), + ), confirmText: KRAppRunData.getInstance().isDeviceLogin() ? '前往' : null, cancelText: '取消', onConfirm: () { @@ -591,7 +575,7 @@ class HIUserInfoView extends GetView { final RegExp regExp = RegExp(r'\((.*?)\)'); final Match? match = regExp.firstMatch(deviceName); - if (match != null && match.groupCount >= 1 ) { + if (match != null && match.groupCount >= 1) { // 获取括号内内容 final inside = match.group(1)!; // "Android; google Pixel 9; 15" diff --git a/lib/app/modules/kr_delete_account/views/kr_delete_account_view.dart b/lib/app/modules/kr_delete_account/views/kr_delete_account_view.dart index 4a35455..6cbe66d 100755 --- a/lib/app/modules/kr_delete_account/views/kr_delete_account_view.dart +++ b/lib/app/modules/kr_delete_account/views/kr_delete_account_view.dart @@ -4,11 +4,11 @@ import 'package:get/get.dart'; import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; +import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart'; import '../controllers/kr_delete_account_controller.dart'; import 'package:kaer_with_panels/app/localization/app_translations.dart'; import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; - class KRDeleteAccountView extends GetView { const KRDeleteAccountView({super.key}); @@ -46,7 +46,8 @@ class KRDeleteAccountView extends GetView { padding: EdgeInsets.symmetric(vertical: 12.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24.w), - border: Border.all(color: const Color(0xFFFF2ED1), width: 2), + border: + Border.all(color: const Color(0xFFFF2ED1), width: 2), ), alignment: Alignment.center, child: Text( @@ -87,7 +88,6 @@ class KRDeleteAccountView extends GetView { ); } - Widget _buildUserInfoSection() { return Row( children: [ @@ -129,12 +129,16 @@ class KRDeleteAccountView extends GetView { ); }), Obx(() { - final _ = KRAppRunData.getInstance().kr_isLogin.value; - final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString(); - final deviceId = KRAppRunData.getInstance().deviceId ?? ''; - + final account = KRAppRunData.getInstance() + .kr_account + .value; + final isDeviceLogin = account != null && + account.startsWith('9000'); + final accountText = isDeviceLogin + ? '待绑定' + : 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}'; return Text( - 'ID: ${userId.isNotEmpty ? userId : deviceId}', + accountText, style: TextStyle( color: Colors.white.withOpacity(0.85), fontSize: 14.sp, @@ -142,51 +146,14 @@ class KRDeleteAccountView extends GetView { ), ); }), - Obx(() { - final currentSubscribe = - controller.kr_subscribeService.kr_currentSubscribe.value; - String expiryText; - - if (currentSubscribe == null) { - expiryText = '尚未购买套餐'; - } else { - final now = DateTime.now(); - DateTime? expireDateTime; - try { - expireDateTime = - DateTime.parse(currentSubscribe.expireTime); - } catch (e) { - expireDateTime = null; - } - - if (expireDateTime == null) { - expiryText = '套餐信息无效'; - } else if (expireDateTime.isBefore(now)) { - final formattedExpireDate = - '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; - expiryText = '已于 $formattedExpireDate 到期'; - } else { - expiryText = '到期时间:${expireDateTime}';final year = expireDateTime.year; - final month = expireDateTime.month.toString().padLeft(2, '0'); - final day = expireDateTime.day.toString().padLeft(2, '0'); - final hour = expireDateTime.hour.toString().padLeft(2, '0'); - final minute = expireDateTime.minute.toString().padLeft(2, '0'); - final second = expireDateTime.second.toString().padLeft(2, '0'); - - // 2. 拼接成最终的字符串 - final formattedDateTime = '$year/$month/$day $hour:$minute:$second'; - - expiryText = '到期时间:$formattedDateTime'; - } - } - return Text( - expiryText, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - ), - ); - }), + KRSubscriptionExpiryText( + expireTimeProvider: () => controller + .kr_subscribeService.kr_currentSubscribe.value?.expireTime, + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + ), + ), ], ), ), @@ -213,7 +180,8 @@ class KRDeleteAccountView extends GetView { color: const Color(0xFFA6A6A6), fontWeight: FontWeight.w600, ), - contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), + contentPadding: + EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), border: OutlineInputBorder( borderRadius: BorderRadius.circular(25.w), borderSide: BorderSide(color: Colors.white, width: 2), @@ -233,14 +201,15 @@ class KRDeleteAccountView extends GetView { suffixIcon: Padding( padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), child: Obx(() { - return GestureDetector( - onTap: controller.kr_canSendCode.value ? controller.kr_sendCode : null, + onTap: controller.kr_canSendCode.value + ? controller.kr_sendCode + : null, child: Container( width: 100.w, alignment: Alignment.center, decoration: BoxDecoration( - color: controller.kr_canSendCode.value + color: controller.kr_canSendCode.value ? Theme.of(Get.context!).primaryColor : const Color(0xFFD5D5D5), borderRadius: BorderRadius.circular(100.r), // 药丸状 @@ -254,7 +223,7 @@ class KRDeleteAccountView extends GetView { fontWeight: FontWeight.w600, color: controller.kr_canSendCode.value ? Colors.black - : const Color(0xFF464655) , + : const Color(0xFF464655), ), ), ), diff --git a/lib/app/modules/kr_home/views/hi_animated_connect_button.dart b/lib/app/modules/kr_home/views/hi_animated_connect_button.dart index 1615748..b12e2a8 100644 --- a/lib/app/modules/kr_home/views/hi_animated_connect_button.dart +++ b/lib/app/modules/kr_home/views/hi_animated_connect_button.dart @@ -86,8 +86,17 @@ class HIAnimatedConnectButton extends GetView { print('🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching'); return; } - final hasValidSubscription = - controller.kr_subscribeService.kr_availableSubscribes.isNotEmpty; + final current = controller.kr_subscribeService.kr_currentSubscribe.value; + bool hasValidSubscription = false; + if (current != null) { + DateTime? expire; + try { + expire = DateTime.parse(current.expireTime); + } catch (_) { + expire = null; + } + hasValidSubscription = expire != null && expire.isAfter(DateTime.now()); + } if (hasValidSubscription) { controller.kr_toggleSwitch(!controller.kr_isConnected.value); } else { @@ -291,4 +300,3 @@ class _ContinuousRippleEffectState extends State ); } } - diff --git a/lib/app/modules/kr_home/views/kr_home_view.dart b/lib/app/modules/kr_home/views/kr_home_view.dart index 30d2727..a621679 100755 --- a/lib/app/modules/kr_home/views/kr_home_view.dart +++ b/lib/app/modules/kr_home/views/kr_home_view.dart @@ -18,7 +18,6 @@ import 'kr_home_subscription_view.dart'; import './hi_animated_connect_button.dart'; import 'package:kaer_with_panels/app/services/global_overlay_service.dart'; - class KRHomeView extends StatefulWidget { const KRHomeView({super.key}); @@ -35,8 +34,6 @@ class _KRHomeViewState extends State { controller = Get.find(); } - - @override Widget build(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -50,174 +47,186 @@ class _KRHomeViewState extends State { children: [ HIBaseScaffold( showMenuButton: true, - topContentAreaHeight: 80, - child: Stack( - fit: StackFit.expand, - children: [ - Align( - alignment: Alignment.topLeft, - child: - SafeArea( - child: Padding( - padding: EdgeInsets.fromLTRB(40.w, 8.w, 0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Hi快VPN-网在我在,网快我快', - style: TextStyle( - color: Colors.white, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - ), - // 订阅信息 - Obx(() { - // 1. 先获取订阅信息 - final currentSubscribe = - controller.kr_subscribeService.kr_currentSubscribe.value; - - // 2. 准备一个 Widget 变量,用于存放最终要显示的内容 - Widget content; - - // --- 定义统一的文本样式,并设置行高 --- - final textStyle = TextStyle( - color: Colors.white, - fontSize: 12.sp, - fontWeight: FontWeight.w600, - height: 1.2, // 您可以微调这个值来达到想要的视觉效果 - ); - - // 3. 开始条件判断 - if (currentSubscribe == null) { - // --- 情况1: 没有任何订阅信息 --- - content = Text( - '尚未购买套餐', - style: textStyle, - ); - } else { - // --- 情况2: 有订阅信息,需要判断是否过期 --- - final now = DateTime.now(); - DateTime? expireDateTime; - try { - expireDateTime = DateTime.parse(currentSubscribe.expireTime); - } catch (e) { - // 日期格式解析失败 - } - - if (expireDateTime != null && expireDateTime.isBefore(now)) { - // --- 情况2.1: 订阅已过期 --- - final formattedExpireDate = - '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; - // 使用换行符 \n 合并为单个 Text 组件 - content = Text( - '您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐', - style: textStyle, - ); - } else { - // --- 情况2.2: 订阅有效 --- - final difference = expireDateTime?.difference(now); - final remainingDaysText = (difference?.inDays ?? 0) > 0 - ? '${difference!.inDays} 天' - : '不足一天'; - // 使用换行符 \n 合并为单个 Text 组件 - content = Text( - '套餐剩余:$remainingDaysText\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}', - style: textStyle, - ); - } - } - - // 4. 返回包裹在公共 Container 中的最终内容 - return Container( - alignment: Alignment.centerLeft, - padding: EdgeInsets.only(top: 8.h), - child: content, - ); - }), - SizedBox(height: 8.h), // 添加一些垂直间距 - - // --- 自定义“闪连”Checkbox --- - Obx(() { - return Row( - mainAxisSize: MainAxisSize.min, // 让 Row 的宽度包裹其内容 - children: [ - // --- 可点击的 Checkbox 区域 --- - GestureDetector( - // 点击方框和文字都可以切换状态 - onTap: () => controller.toggleQuickConnect(!controller.isQuickConnectEnabled.value), - child: AbsorbPointer( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // 1. 自定义 Checkbox 的方框外观 - Container( - width: 20.w, - height: 20.w, - decoration: BoxDecoration( - color: controller.isQuickConnectEnabled.value - ? Theme.of(context).primaryColor - : Colors.transparent, - border: Border.all( - color: Theme.of(context).primaryColor, - width: 1.5, - ), - borderRadius: BorderRadius.circular(4.r), - ), - child: controller.isQuickConnectEnabled.value - ? Icon( - Icons.check, - size: 14.w, - color: Colors.black, - ) - : null, - ), - SizedBox(width: 8.w), - // 2. “闪连”标签 - Text( - '闪连', - style: TextStyle( - color: Colors.white, - fontSize: 20.sp, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), + topContentAreaHeight: 80, + child: Stack( + fit: StackFit.expand, + children: [ + Align( + alignment: Alignment.topLeft, + child: SafeArea( + child: Padding( + padding: EdgeInsets.fromLTRB(40.w, 8.w, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Hi快VPN-网在我在,网快我快', + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, ), - // --- 问号图标和点击弹窗区域 --- - SizedBox(width: 6.w), // 文字和问号图标之间的间距 - GestureDetector( - onTap: () { - HIDialog.show( - title: '*闪连功能', - message: '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开', + ), + // 订阅信息 + Obx(() { + // 1. 先获取订阅信息 + final currentSubscribe = controller + .kr_subscribeService.kr_currentSubscribe.value; + + // 2. 准备一个 Widget 变量,用于存放最终要显示的内容 + Widget content; + + // --- 定义统一的文本样式,并设置行高 --- + final normalStyle = TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + height: 1.2, + ); + final highlightStyle = normalStyle.copyWith( + color: const Color(0xFFFF00B7), + ); + + // 3. 开始条件判断 + if (currentSubscribe == null) { + // --- 情况1: 没有任何订阅信息 --- + content = Text( + '尚未购买套餐', + style: highlightStyle, + ); + } else { + // --- 情况2: 有订阅信息,需要判断是否过期 --- + final now = DateTime.now(); + DateTime? expireDateTime; + try { + expireDateTime = + DateTime.parse(currentSubscribe.expireTime); + } catch (e) { + // 日期格式解析失败 + } + + if (expireDateTime != null && + expireDateTime.isBefore(now)) { + // --- 情况2.1: 订阅已过期 --- + final formattedExpireDate = + '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; + // 使用换行符 \n 合并为单个 Text 组件 + content = Text( + '您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐', + style: highlightStyle, ); - }, - child: KrLocalImage( - imageName: 'question-icon', // 图片名称 - imageType: ImageType.svg, // 图片类型 - width: 24.w, - height: 24.w, - color: Colors.white, // 让图标颜色与边框协调 - ), - ), - ], - ); - }), - ], + } else { + // --- 情况2.2: 订阅有效 --- + final difference = + expireDateTime?.difference(now); + final remainingDaysText = + (difference?.inDays ?? 0) > 0 + ? '${difference!.inDays} 天' + : '不足一天'; + // 使用换行符 \n 合并为单个 Text 组件 + content = Text( + '套餐剩余:$remainingDaysText\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}', + style: normalStyle, + ); + } + } + + // 4. 返回包裹在公共 Container 中的最终内容 + return Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(top: 8.h), + child: content, + ); + }), + SizedBox(height: 8.h), // 添加一些垂直间距 + + // --- 自定义“闪连”Checkbox --- + Obx(() { + return Row( + mainAxisSize: MainAxisSize.min, // 让 Row 的宽度包裹其内容 + children: [ + // --- 可点击的 Checkbox 区域 --- + GestureDetector( + // 点击方框和文字都可以切换状态 + onTap: () => controller.toggleQuickConnect( + !controller.isQuickConnectEnabled.value), + child: AbsorbPointer( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 1. 自定义 Checkbox 的方框外观 + Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + color: controller + .isQuickConnectEnabled.value + ? Theme.of(context).primaryColor + : Colors.transparent, + border: Border.all( + color: + Theme.of(context).primaryColor, + width: 1.5, + ), + borderRadius: + BorderRadius.circular(4.r), + ), + child: controller + .isQuickConnectEnabled.value + ? Icon( + Icons.check, + size: 14.w, + color: Colors.black, + ) + : null, + ), + SizedBox(width: 8.w), + // 2. “闪连”标签 + Text( + '闪连', + style: TextStyle( + color: Colors.white, + fontSize: 20.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + // --- 问号图标和点击弹窗区域 --- + SizedBox(width: 6.w), // 文字和问号图标之间的间距 + GestureDetector( + onTap: () { + HIDialog.show( + title: '*闪连功能', + message: + '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开', + ); + }, + child: KrLocalImage( + imageName: 'question-icon', // 图片名称 + imageType: ImageType.svg, // 图片类型 + width: 24.w, + height: 24.w, + color: Colors.white, // 让图标颜色与边框协调 + ), + ), + ], + ); + }), + ], + ), + ), ), ), - ), + ], ), - ], - ), - ), - // 将 HIAnimatedConnectButton 移到 HIBaseScaffold 外部,避免 SafeArea 约束 - HIAnimatedConnectButton(), - ], -); + ), + // 将 HIAnimatedConnectButton 移到 HIBaseScaffold 外部,避免 SafeArea 约束 + HIAnimatedConnectButton(), + ], + ); } } diff --git a/lib/app/modules/kr_login/views/kr_login_view.dart b/lib/app/modules/kr_login/views/kr_login_view.dart index 88ee535..25f6777 100755 --- a/lib/app/modules/kr_login/views/kr_login_view.dart +++ b/lib/app/modules/kr_login/views/kr_login_view.dart @@ -12,6 +12,7 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart'; import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; import 'package:flutter/foundation.dart'; +import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart'; class KRLoginView extends GetView { const KRLoginView({super.key}); @@ -19,7 +20,8 @@ class KRLoginView extends GetView { @override Widget build(BuildContext context) { final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0; - final isHideBack = (Get.arguments as Map?)?['is-back'] ?? false; + final isHideBack = + (Get.arguments as Map?)?['is-back'] ?? false; return HIBaseScaffold( showBack: !isHideBack, resizeToAvoidBottomInset: true, @@ -27,7 +29,9 @@ class KRLoginView extends GetView { children: [ SingleChildScrollView( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 40.w,), + padding: EdgeInsets.symmetric( + horizontal: 40.w, + ), child: Column( children: [ // Text( @@ -43,8 +47,7 @@ class KRLoginView extends GetView { ), ), ), - if (!isKeyboardVisible) - const HIHelpEntrance(), + if (!isKeyboardVisible) const HIHelpEntrance(), ], ), ); @@ -76,30 +79,17 @@ class KRLoginView extends GetView { mainAxisAlignment: MainAxisAlignment.center, children: [ Obx(() { - final account = KRAppRunData.getInstance().kr_account.value; - final isDeviceLogin = account != null && account.startsWith('9000'); - - if (isDeviceLogin) return const SizedBox(); + final account = KRAppRunData.getInstance() + .kr_account + .value; + final isDeviceLogin = account != null && + account.startsWith('9000'); + final accountText = isDeviceLogin + ? '待绑定' + : 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}'; return Text( - account ?? '', - style: TextStyle( - color: Colors.white, - fontSize: 20.sp, - fontWeight: FontWeight.bold, - height: 0.9, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }), - Obx(() { - final _ = KRAppRunData.getInstance().kr_isLogin.value; - final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString(); - final deviceId = KRAppRunData.getInstance().deviceId ?? ''; - - return Text( - 'ID: ${userId.isNotEmpty ? userId : deviceId}', + accountText, style: TextStyle( color: Colors.white.withOpacity(0.85), fontSize: 14.sp, @@ -107,51 +97,14 @@ class KRLoginView extends GetView { ), ); }), - Obx(() { - final currentSubscribe = - controller.kr_subscribeService.kr_currentSubscribe.value; - String expiryText; - - if (currentSubscribe == null) { - expiryText = '尚未购买套餐'; - } else { - final now = DateTime.now(); - DateTime? expireDateTime; - try { - expireDateTime = - DateTime.parse(currentSubscribe.expireTime); - } catch (e) { - expireDateTime = null; - } - - if (expireDateTime == null) { - expiryText = '套餐信息无效'; - } else if (expireDateTime.isBefore(now)) { - final formattedExpireDate = - '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; - expiryText = '已于 $formattedExpireDate 到期'; - } else { - expiryText = '到期时间:${expireDateTime}';final year = expireDateTime.year; - final month = expireDateTime.month.toString().padLeft(2, '0'); - final day = expireDateTime.day.toString().padLeft(2, '0'); - final hour = expireDateTime.hour.toString().padLeft(2, '0'); - final minute = expireDateTime.minute.toString().padLeft(2, '0'); - final second = expireDateTime.second.toString().padLeft(2, '0'); - - // 2. 拼接成最终的字符串 - final formattedDateTime = '$year/$month/$day $hour:$minute:$second'; - - expiryText = '到期时间:$formattedDateTime'; - } - } - return Text( - expiryText, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - ), - ); - }), + KRSubscriptionExpiryText( + expireTimeProvider: () => controller + .kr_subscribeService.kr_currentSubscribe.value?.expireTime, + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + ), + ), ], ), ), @@ -283,7 +236,8 @@ class KRLoginView extends GetView { color: const Color(0xFFA6A6A6), fontWeight: FontWeight.w600, ), - contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), + contentPadding: + EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), border: OutlineInputBorder( borderRadius: BorderRadius.circular(25.w), // 调整为更圆的角 borderSide: BorderSide(color: Colors.white, width: 2), @@ -327,7 +281,8 @@ class KRLoginView extends GetView { color: const Color(0xFFA6A6A6), fontWeight: FontWeight.w600, ), - contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), + contentPadding: + EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), border: OutlineInputBorder( borderRadius: BorderRadius.circular(25.w), borderSide: BorderSide(color: Colors.white, width: 2), @@ -347,14 +302,15 @@ class KRLoginView extends GetView { suffixIcon: Padding( padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), child: Obx(() { - return GestureDetector( - onTap: controller.kr_canSendCode.value ? () => controller.kr_sendCode() : null, + onTap: controller.kr_canSendCode.value + ? () => controller.kr_sendCode() + : null, child: Container( width: 100.w, alignment: Alignment.center, decoration: BoxDecoration( - color: controller.kr_canSendCode.value + color: controller.kr_canSendCode.value ? Theme.of(Get.context!).primaryColor : const Color(0xFFD5D5D5), borderRadius: BorderRadius.circular(100.r), // 药丸状 @@ -366,7 +322,7 @@ class KRLoginView extends GetView { fontWeight: FontWeight.w600, color: controller.kr_canSendCode.value ? Colors.black - : const Color(0xFF464655) , + : const Color(0xFF464655), ), ), ), @@ -378,7 +334,6 @@ class KRLoginView extends GetView { ); } - Widget _buildSaveButton() { return GestureDetector( onTap: () { diff --git a/lib/app/widgets/kr_subscription_expiry_text.dart b/lib/app/widgets/kr_subscription_expiry_text.dart new file mode 100644 index 0000000..4820217 --- /dev/null +++ b/lib/app/widgets/kr_subscription_expiry_text.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class KRSubscriptionExpiryText extends StatelessWidget { + final String? Function() expireTimeProvider; + final TextStyle? style; + + const KRSubscriptionExpiryText({ + Key? key, + required this.expireTimeProvider, + this.style, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + final expireTimeStr = expireTimeProvider(); + String expiryText; + bool highlight = false; + + if (expireTimeStr == null || expireTimeStr.isEmpty) { + expiryText = '尚未购买套餐'; + highlight = true; + } else { + DateTime? expireDateTime; + try { + expireDateTime = DateTime.parse(expireTimeStr); + } catch (_) { + expireDateTime = null; + } + + if (expireDateTime == null) { + expiryText = '套餐信息无效'; + } else if (expireDateTime.isBefore(DateTime.now())) { + final formattedExpireDate = + '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}'; + expiryText = '已于 $formattedExpireDate 到期'; + highlight = true; + } else { + final year = expireDateTime.year; + final month = expireDateTime.month.toString().padLeft(2, '0'); + final day = expireDateTime.day.toString().padLeft(2, '0'); + final hour = expireDateTime.hour.toString().padLeft(2, '0'); + final minute = expireDateTime.minute.toString().padLeft(2, '0'); + final second = expireDateTime.second.toString().padLeft(2, '0'); + + final formattedDateTime = '$year/$month/$day $hour:$minute:$second'; + expiryText = '到期时间:$formattedDateTime'; + } + } + + final baseStyle = style ?? + const TextStyle( + color: Colors.white, + fontSize: 12, + ); + final appliedStyle = highlight + ? baseStyle.copyWith(color: const Color(0xFFFF00B7)) + : baseStyle; + + return Text( + expiryText, + style: appliedStyle, + ); + }); + } +}