fix: 更新诸多bug
This commit is contained in:
@@ -1,253 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../localization/app_translations.dart';
|
||||
import '../../../widgets/kr_app_text_style.dart';
|
||||
import '../../../widgets/kr_loading_animation.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
import 'kr_home_connection_info_view.dart';
|
||||
import 'kr_home_connection_options_view.dart';
|
||||
import 'kr_home_node_list_view.dart';
|
||||
import '../widgets/kr_subscription_card.dart';
|
||||
import 'kr_home_trial_card.dart';
|
||||
import 'kr_home_last_day_card.dart';
|
||||
import '../../../utils/kr_log_util.dart';
|
||||
|
||||
class KRHomeBottomPanel extends GetView<KRHomeController> {
|
||||
const KRHomeBottomPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
final currentStatus = controller.kr_currentListStatus.value;
|
||||
|
||||
KRLogUtil.kr_i('构建底部面板', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('当前视图状态: ${controller.kr_currentViewStatus.value}',
|
||||
tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('当前高度: ${controller.kr_bottomPanelHeight.value}',
|
||||
tag: 'HomeBottomPanel');
|
||||
|
||||
if (controller.kr_currentListStatus.value ==
|
||||
KRHomeViewsListStatus.kr_loading) {
|
||||
return _kr_buildLoadingView();
|
||||
}
|
||||
|
||||
if (controller.kr_currentListStatus.value ==
|
||||
KRHomeViewsListStatus.kr_error) {
|
||||
return _kr_buildErrorView(context);
|
||||
}
|
||||
|
||||
if (currentStatus == KRHomeViewsListStatus.kr_serverList ||
|
||||
currentStatus == KRHomeViewsListStatus.kr_countrySubscribeList ||
|
||||
currentStatus == KRHomeViewsListStatus.kr_serverSubscribeList ||
|
||||
currentStatus == KRHomeViewsListStatus.kr_subscribeList) {
|
||||
return const KRHomeNodeListView();
|
||||
}
|
||||
|
||||
return _kr_buildDefaultView(context);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _kr_buildDefaultView(BuildContext context) {
|
||||
// 🔧 Android 15 增强:增加防御性检查,避免空指针
|
||||
bool hasValidSubscription = false;
|
||||
bool isTrial = false;
|
||||
bool isLastDay = false;
|
||||
|
||||
try {
|
||||
hasValidSubscription = controller.kr_subscribeService.kr_currentSubscribe.value != null;
|
||||
isTrial = controller.kr_subscribeService.kr_isTrial.value;
|
||||
isLastDay = controller.kr_subscribeService.kr_isLastDayOfSubscription.value;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('获取订阅数据异常: $e', tag: 'HomeBottomPanel');
|
||||
}
|
||||
|
||||
final isNotLoggedIn = controller.kr_currentViewStatus.value ==
|
||||
KRHomeViewsStatus.kr_notLoggedIn;
|
||||
|
||||
KRLogUtil.kr_i('=' * 60, tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('🎨 构建默认视图', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否未登录: $isNotLoggedIn', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否有有效订阅: $hasValidSubscription', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('订阅列表数量: ${controller.kr_subscribeService.kr_availableSubscribes.length}', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('当前选中订阅: ${controller.kr_subscribeService.kr_currentSubscribe.value?.name ?? "null"}', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否试用: $isTrial', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('当前高度: ${controller.kr_bottomPanelHeight.value}', tag: 'HomeBottomPanel');
|
||||
|
||||
// 🔧 新增:详细的 UI 渲染决策日志
|
||||
if (hasValidSubscription) {
|
||||
KRLogUtil.kr_i('✅ 将渲染: 连接信息卡片 (KRHomeConnectionInfoView)', tag: 'HomeBottomPanel');
|
||||
} else {
|
||||
KRLogUtil.kr_i('✅ 将渲染: 订阅卡片 (KRSubscriptionCard) - 开通会员界面', tag: 'HomeBottomPanel');
|
||||
}
|
||||
KRLogUtil.kr_i('=' * 60, tag: 'HomeBottomPanel');
|
||||
|
||||
// 🔧 关键修复:统一布局逻辑,确保无论登录状态如何都显示完整UI
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 主要内容区域 - 始终使用 Expanded + ScrollView 确保内容可见
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 🔧 核心修复:无论登录状态,都显示核心卡片(订阅或连接信息)
|
||||
if (hasValidSubscription)
|
||||
// 已订阅:显示连接信息卡片
|
||||
Builder(builder: (context) {
|
||||
KRLogUtil.kr_i('🔹 渲染连接信息卡片,margin top: ${12}', tag: 'HomeBottomPanel');
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
child: const KRHomeConnectionInfoView(),
|
||||
);
|
||||
})
|
||||
else
|
||||
// 未订阅(包括未登录):始终显示订阅卡片
|
||||
Builder(builder: (context) {
|
||||
KRLogUtil.kr_i('🔹 渲染订阅卡片,margin: top=${12}, left=${12}, right=${12}', tag: 'HomeBottomPanel');
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 12, left: 12, right: 12),
|
||||
child: const KRSubscriptionCard(),
|
||||
);
|
||||
}),
|
||||
|
||||
// 2. 如果已订阅且是试用,展示试用卡片
|
||||
if (hasValidSubscription && isTrial)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
child: const KRHomeTrialCard(),
|
||||
),
|
||||
|
||||
// 3. 如果已订阅且是最后一天,展示最后一天卡片
|
||||
if (hasValidSubscription && isLastDay && !isTrial)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
child: const KRHomeLastDayCard(),
|
||||
),
|
||||
|
||||
// 4. 连接选项(分组和国家入口)- 始终显示
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: KRHomeConnectionOptionsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _kr_buildLoadingView() {
|
||||
KRLogUtil.kr_i('构建加载视图', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('当前高度: ${controller.kr_bottomPanelHeight.value}',
|
||||
tag: 'HomeBottomPanel');
|
||||
|
||||
// 🔧 Android 15 紧急修复:加载时显示完整的默认内容 + 加载指示器
|
||||
// 而不是只显示一个转圈圈,避免用户看到空白面板
|
||||
return Stack(
|
||||
children: [
|
||||
// 底层:显示默认内容(半透明)
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: _kr_buildDefaultView(Get.context!),
|
||||
),
|
||||
// 顶层:加载指示器
|
||||
Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Get.context!.theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
color: Colors.green,
|
||||
strokeWidth: 3.0,
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'正在加载...',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Get.context!.theme.textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _kr_buildErrorView(BuildContext context) {
|
||||
return Container(
|
||||
height: 200,
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 48,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
AppTranslations.kr_home.error,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
AppTranslations.kr_home.checkNetwork,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
height: 44,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => controller.kr_refreshAll(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
AppTranslations.kr_home.retry,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../widgets/kr_simple_loading.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
const KRHomeConnectionInfoView({super.key});
|
||||
|
||||
@override
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return _buildConnectCard(context);
|
||||
}
|
||||
|
||||
/// 当前连接
|
||||
Widget _buildConnectCard(BuildContext context) {
|
||||
return Obx(() {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
width: double.infinity,
|
||||
height: 116,
|
||||
decoration: ShapeDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.currentConnectionTitle,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
// 切换节点按钮
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
controller.kr_switchListStatus(KRHomeViewsListStatus.kr_subscribeList);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.switchNode,
|
||||
style: KrAppTextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// 🔧 修复:使用 Obx 包裹确保国旗响应式更新
|
||||
Obx(() {
|
||||
final countryCode = controller.kr_getCurrentNodeCountry();
|
||||
if (kDebugMode) {
|
||||
print('🌍 ConnectionInfo 更新,国家代码: $countryCode');
|
||||
}
|
||||
return KRCountryFlag(
|
||||
countryCode: countryCode,
|
||||
);
|
||||
}),
|
||||
SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.kr_currentNodeName.value,
|
||||
style: KrAppTextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Obx(() {
|
||||
final delay = controller.kr_currentNodeLatency.value;
|
||||
if (kDebugMode) {
|
||||
print('🔵 UI延迟显示更新: delay=$delay');
|
||||
}
|
||||
|
||||
// 获取延迟颜色
|
||||
Color getLatencyColor(int delay) {
|
||||
if (delay == -2) {
|
||||
return Colors.green;
|
||||
} else if (delay == -1) {
|
||||
return Theme.of(context).primaryColor;
|
||||
} else if (delay < 500) {
|
||||
return Colors.green;
|
||||
} else if (delay < 3000) {
|
||||
return Color(0xFFFFB700);
|
||||
} else {
|
||||
return Colors.red;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取延迟文本
|
||||
String getLatencyText(int delay) {
|
||||
if (delay == -2) {
|
||||
return '--';
|
||||
} else if (delay == -1) {
|
||||
return AppTranslations.kr_home.connecting;
|
||||
} else if (delay == 0) {
|
||||
return AppTranslations.kr_home.connected;
|
||||
} else if (delay >= 3000) {
|
||||
return AppTranslations.kr_home.timeout;
|
||||
} else {
|
||||
return '${delay}ms';
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 修复:只有 delay == -1 时才显示 connecting 动画
|
||||
if (delay == -1) {
|
||||
return Row(
|
||||
children: [
|
||||
KRSimpleLoading(
|
||||
color: Colors.green,
|
||||
size: 12,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
),
|
||||
SizedBox(width: 2),
|
||||
Text(
|
||||
AppTranslations.kr_home.connecting,
|
||||
style: TextStyle(
|
||||
color: Colors.green,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Icon(Icons.signal_cellular_alt,
|
||||
size: 12,
|
||||
color: getLatencyColor(delay)),
|
||||
SizedBox(width: 2),
|
||||
Text(
|
||||
getLatencyText(delay),
|
||||
style: KrAppTextStyle(
|
||||
color: getLatencyColor(delay),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
// 只在非连接中状态显示上下行
|
||||
Obx(() {
|
||||
final delay = controller.kr_currentNodeLatency.value;
|
||||
if (delay == -1) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(width: 10),
|
||||
Icon(Icons.arrow_upward,
|
||||
size: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color),
|
||||
Text(
|
||||
controller.kr_formatBytes(KRSingBoxImp.instance.kr_stats.value.uplink),
|
||||
style: KrAppTextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Icon(Icons.arrow_downward,
|
||||
size: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color),
|
||||
Text(
|
||||
controller.kr_formatBytes(KRSingBoxImp.instance.kr_stats.value.downlink),
|
||||
style: KrAppTextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// 🔧 修复: 使用多层监听确保状态更新
|
||||
Obx(() {
|
||||
// 🔧 关键: 强制读取两个 observable 确保追踪
|
||||
final _ = KRSingBoxImp.instance.kr_status.value; // 强制追踪
|
||||
final isConnected = controller.kr_isConnected.value; // 使用 controller 的状态
|
||||
|
||||
// 再次读取状态用于判断
|
||||
final status = KRSingBoxImp.instance.kr_status.value;
|
||||
final isSwitching = status is SingboxStarting || status is SingboxStopping;
|
||||
|
||||
// 🔧 调试日志
|
||||
if (kDebugMode) {
|
||||
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
}
|
||||
|
||||
return CupertinoSwitch(
|
||||
value: isConnected,
|
||||
// 🔧 关键: 切换中时 onChanged 为 null,Switch 自动禁用
|
||||
onChanged: isSwitching
|
||||
? null
|
||||
: (bool value) {
|
||||
if (kDebugMode) {
|
||||
print('🔵 Switch onChanged 触发: 请求=$value, 当前状态=$status');
|
||||
}
|
||||
controller.kr_toggleSwitch(value);
|
||||
},
|
||||
activeColor: Colors.blue,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_subscribe_navigation_util.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
|
||||
class KRHomeConnectionOptionsView extends GetView<KRHomeController> {
|
||||
const KRHomeConnectionOptionsView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print('🔌 [ConnectionOptions] 开始构建连接选项组件');
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.connectionSectionTitle,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildConnectionOption(
|
||||
"home_ct",
|
||||
AppTranslations.kr_home.countryRegion,
|
||||
context,
|
||||
onTap: () {
|
||||
controller.kr_switchListStatus(KRHomeViewsListStatus.kr_countrySubscribeList);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConnectionOption(String icon, String label, BuildContext context,
|
||||
{VoidCallback? onTap}) {
|
||||
print('🔌 [ConnectionOptions] 构建连接选项: $label');
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
print('🔌 [ConnectionOptions] 选项被点击: $label');
|
||||
if (controller.kr_subscribeService.kr_currentSubscribe.value == null) {
|
||||
// 未订阅状态下,使用统一的订阅导航工具
|
||||
KRSubscribeNavigationUtil.navigateToPurchase(tag: 'ConnectionOptions');
|
||||
} else {
|
||||
// 已订阅状态下执行原有的点击事件
|
||||
onTap?.call();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
KrLocalImage(
|
||||
imageName: icon,
|
||||
width: 36,
|
||||
height: 36,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Colors.black87,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black87,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white54
|
||||
: Colors.black45,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../../../utils/kr_subscribe_navigation_util.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
|
||||
|
||||
/// 最后一天卡片组件
|
||||
class KRHomeLastDayCard extends GetView<KRHomeController> {
|
||||
const KRHomeLastDayCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 顶部标题和订阅按钮
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.lastDaySubscriptionStatus,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => KRSubscribeNavigationUtil.navigateToPurchase(tag: 'LastDayCard'),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.subscribe,
|
||||
style: KrAppTextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 倒计时显示
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.timer_outlined,
|
||||
color: Colors.blue,
|
||||
size: 16,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
AppTranslations.kr_home.lastDaySubscriptionMessage,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(() {
|
||||
final isLastDay =
|
||||
controller.kr_subscribeService.kr_isLastDayOfSubscription.value;
|
||||
final remainingTime = controller.kr_subscribeService.kr_subscriptionRemainingTime.value;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
remainingTime,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isLastDay
|
||||
? (DateTime.now().millisecondsSinceEpoch %
|
||||
2000 <
|
||||
1000
|
||||
? Colors.red
|
||||
: Colors.blue)
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.subscriptionEndMessage,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,950 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart';
|
||||
import '../../../model/business/kr_outbound_item.dart';
|
||||
import '../../../utils/kr_log_util.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_network_image.dart';
|
||||
import '../../../widgets/kr_simple_loading.dart';
|
||||
|
||||
import '../../../../singbox/model/singbox_proxy_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 节点列表视图组件
|
||||
/// 用于展示所有节点相关的列表视图
|
||||
class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
const KRHomeNodeListView({super.key});
|
||||
|
||||
// 添加常量定义
|
||||
static const Color krModernGreen = Color(0xFF4CAF50);
|
||||
static const Color krModernGreenLight = Color(0xFF81C784);
|
||||
|
||||
// 🔧 修复无限刷新:添加标志位确保自动测试只触发一次
|
||||
static bool _hasTriggeredAutoTest = false;
|
||||
|
||||
/// 获取显示的延迟值
|
||||
/// ✅ 修复:始终显示真实的 TCP 测试结果
|
||||
int _getDisplayDelay(KRHomeController controller, KROutboundItem item) {
|
||||
// 直接返回真实的延迟测试结果
|
||||
// 无论是否连接VPN,都使用 item.urlTestDelay.value
|
||||
// - 已连接:通过 SingBox 代理测试的真实延迟
|
||||
// - 未连接:通过 TCP Socket 直连测试的真实延迟
|
||||
return item.urlTestDelay.value;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
// 根据列表状态选择不同的视图
|
||||
switch (controller.kr_currentListStatus.value) {
|
||||
case KRHomeViewsListStatus.kr_serverList:
|
||||
return _buildServerList(context);
|
||||
case KRHomeViewsListStatus.kr_subscribeList:
|
||||
return _buildSubscribeList(context);
|
||||
case KRHomeViewsListStatus.kr_countrySubscribeList:
|
||||
return _kr_buildRegionList(context);
|
||||
case KRHomeViewsListStatus.kr_serverSubscribeList:
|
||||
return _kr_buildServerSubscribeList(context);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 服务器列表视图
|
||||
|
||||
/// 构建专用服务器列表
|
||||
Widget _buildServerList(BuildContext context) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 360, // 减小高度比例
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 标题栏
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.serverListTitle,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 24,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 列表内容
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
if (controller.kr_subscribeService.groupOutboundList.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppTranslations.kr_home.noServers,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: controller.kr_subscribeService.groupOutboundList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final group =
|
||||
controller.kr_subscribeService.groupOutboundList[index];
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
controller.kr_setCurrentGroup(group);
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_serverSubscribeList;
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
KRNetworkImage(
|
||||
kr_imageUrl: group.icon,
|
||||
kr_width: 32,
|
||||
kr_height: 32,
|
||||
kr_fit: BoxFit.cover,
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
group.tag,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 国家订阅列表视图
|
||||
Widget _kr_buildRegionList(BuildContext context) {
|
||||
return _kr_buildListPage(
|
||||
context,
|
||||
title: AppTranslations.kr_home.countryListTitle,
|
||||
listContent: Obx(() {
|
||||
if (controller.kr_subscribeService.groupOutboundList.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppTranslations.kr_home.noRegions,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return _kr_buildListContainer(
|
||||
context,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.fromLTRB(16, 8, 16, 0),
|
||||
itemCount:
|
||||
controller.kr_subscribeService.countryOutboundList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final country =
|
||||
controller.kr_subscribeService.countryOutboundList[index];
|
||||
return Column(
|
||||
children: [
|
||||
// 主区域
|
||||
InkWell(
|
||||
onTap: () {
|
||||
country.isExpand.value = !country.isExpand.value;
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
KRCountryFlag(
|
||||
countryCode: country.country,
|
||||
width: 40,
|
||||
height: 40,
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller
|
||||
.kr_getCountryFullName(country.country),
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
return Icon(
|
||||
country.isExpand.value
|
||||
? Icons.keyboard_arrow_down
|
||||
: Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color:
|
||||
Theme.of(context).textTheme.bodySmall?.color,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 展开的服务器列表
|
||||
Obx(() {
|
||||
final isExpanded = country.isExpand.value;
|
||||
if (!isExpanded) return const SizedBox();
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
itemCount: country.outboundList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final server = country.outboundList[index];
|
||||
return Column(
|
||||
children: [
|
||||
InkWell(
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
if (kDebugMode) {
|
||||
print('🔄 用户点击节点: ${server.tag}');
|
||||
}
|
||||
// 使用统一的节点切换方法,等待完成
|
||||
final success = await controller
|
||||
.kr_performNodeSwitch(server.tag);
|
||||
|
||||
// 只有切换成功才关闭列表
|
||||
if (success) {
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
if (kDebugMode) {
|
||||
print('✅ 节点切换成功,关闭列表');
|
||||
}
|
||||
} else {
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点切换失败,列表保持打开');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点切换异常: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('节点切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
// 添加轻微的背景色以区分点击区域
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: _kr_buildNodeListItem(
|
||||
context,
|
||||
item: server,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 添加分隔线
|
||||
if (index < country.outboundList.length - 1)
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// 服务器订阅列表视图
|
||||
// 修改服务器订阅列表视图
|
||||
Widget _kr_buildServerSubscribeList(BuildContext context) {
|
||||
return _kr_buildListPage(
|
||||
context,
|
||||
title: controller.kr_currentGroup.value?.tag ?? '',
|
||||
onBack: () => controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_serverList,
|
||||
listContent: Obx(() {
|
||||
final servers = controller.kr_currentGroup.value?.outboundList ?? [];
|
||||
if (servers.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppTranslations.kr_home.noNodes,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return _kr_buildListContainer(
|
||||
context,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
itemCount: servers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final server = servers[index];
|
||||
return Column(
|
||||
children: [
|
||||
InkWell(
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔄 用户点击节点: ${server.tag}');
|
||||
// 使用统一的节点切换方法,等待完成
|
||||
final success = await controller
|
||||
.kr_performNodeSwitch(server.tag);
|
||||
|
||||
// 只有切换成功才关闭列表
|
||||
if (success) {
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
KRLogUtil.kr_i('✅ 节点切换成功,关闭列表');
|
||||
} else {
|
||||
KRLogUtil.kr_w('❌ 节点切换失败,列表保持打开');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 节点切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: _kr_buildNodeListItem(
|
||||
context,
|
||||
item: server,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (index < servers.length - 1)
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _kr_buildListPage(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
VoidCallback? onBack,
|
||||
required Widget listContent,
|
||||
}) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_kr_buildTitleBar(
|
||||
context,
|
||||
title: title,
|
||||
onBack: onBack,
|
||||
onClose: () =>
|
||||
controller.kr_currentListStatus.value = KRHomeViewsListStatus.kr_none,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: listContent),
|
||||
// 添加底部间距
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 抽取公共的标题栏组件
|
||||
Widget _kr_buildTitleBar(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
VoidCallback? onBack,
|
||||
VoidCallback? onClose,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 16, right: 16, top: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (onBack != null) ...[
|
||||
GestureDetector(
|
||||
onTap: onBack,
|
||||
child: Icon(
|
||||
Icons.arrow_back_ios,
|
||||
size: 20,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
Text(
|
||||
title,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (onClose != null)
|
||||
GestureDetector(
|
||||
onTap: onClose,
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 24,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// 构建列表容器
|
||||
Widget _kr_buildListContainer(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
}) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建节点列表项
|
||||
Widget _kr_buildNodeListItem(
|
||||
BuildContext context, {
|
||||
required KROutboundItem item,
|
||||
}) {
|
||||
// 获取延迟颜色
|
||||
Color getLatencyColor(int delay) {
|
||||
if (delay == 0) {
|
||||
return Colors.transparent;
|
||||
} else if (delay < 500) {
|
||||
return krModernGreen;
|
||||
} else if (delay < 3000) {
|
||||
return Color(0xFFFFB700); // 使用更容易看清的黄色
|
||||
} else {
|
||||
return Colors.red;
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
key: ValueKey(item.id),
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// 🔧 修改:显示国旗代替图标
|
||||
KRCountryFlag(
|
||||
countryCode: item.country,
|
||||
width: 36,
|
||||
height: 36,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
item.tag,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
Obx(
|
||||
() => controller.kr_cutTag.value == item.tag
|
||||
? Container(
|
||||
margin: EdgeInsets.only(left: 4),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: krModernGreenLight.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
AppTranslations.kr_home.selected,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: krModernGreen,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
item.city,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 显示延迟速度
|
||||
GetBuilder<KRHomeController>(
|
||||
id: item.tag,
|
||||
builder: (controller) {
|
||||
// 获取显示的延迟值
|
||||
int displayDelay = _getDisplayDelay(controller, item);
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
displayDelay == 0
|
||||
? ''
|
||||
: displayDelay >= 3000
|
||||
? AppTranslations.kr_home.timeout
|
||||
: '${displayDelay}ms',
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: getLatencyColor(displayDelay),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 修改订阅列表视图
|
||||
Widget _buildSubscribeList(BuildContext context) {
|
||||
return _kr_buildListPage(
|
||||
context,
|
||||
title: AppTranslations.kr_home.nodeListTitle,
|
||||
listContent: Obx(() {
|
||||
if (controller.kr_subscribeService.allList.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppTranslations.kr_home.noNodes,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 🔧 修复无限刷新:自动触发延迟测试(仅在未连接状态下,且只触发一次)
|
||||
if (!_hasTriggeredAutoTest) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!controller.kr_isConnected.value && !controller.kr_isLatency.value && !_hasTriggeredAutoTest) {
|
||||
_hasTriggeredAutoTest = true; // 标记已触发
|
||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试(首次)', tag: 'NodeListView');
|
||||
controller.kr_urlTest();
|
||||
}
|
||||
});
|
||||
}
|
||||
return _kr_buildListContainer(
|
||||
context,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||
children: [
|
||||
// 延迟测试按钮作为第一个列表项
|
||||
InkWell(
|
||||
onTap: () => controller.kr_urlTest(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
margin: EdgeInsets.only(top: 8), // 添加上方间距
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: krModernGreenLight.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: controller.kr_isLatency.value
|
||||
? KRSimpleLoading(
|
||||
color: krModernGreen,
|
||||
size: 24,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
)
|
||||
: Icon(
|
||||
Icons.speed,
|
||||
size: 24,
|
||||
color: krModernGreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.kr_isLatency.value
|
||||
? AppTranslations.kr_home.testing
|
||||
: AppTranslations.kr_home.testLatency,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: controller.kr_isLatency.value
|
||||
? Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.color
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.color,
|
||||
fontWeight: controller.kr_isLatency.value
|
||||
? FontWeight.normal
|
||||
: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (!controller.kr_isLatency.value) ...[
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
AppTranslations.kr_home.refreshLatencyDesc,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!controller.kr_isLatency.value)
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 分隔线
|
||||
Divider(
|
||||
height: 16,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
// Auto 选项
|
||||
InkWell(
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
final success =
|
||||
await controller.kr_performNodeSwitch('auto');
|
||||
if (success) {
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('Auto选项切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
KrLocalImage(
|
||||
imageName: "home_list_location",
|
||||
width: 36,
|
||||
height: 36,
|
||||
color: controller.kr_cutTag.value == 'auto'
|
||||
? Colors.green
|
||||
: null,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.autoSelect,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
if (controller.kr_cutTag.value == 'auto')
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 4),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
krModernGreenLight.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
AppTranslations.kr_home.selected,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: krModernGreen,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Obx(() {
|
||||
// 获取当前自动选择的节点
|
||||
String selectedNode =
|
||||
AppTranslations.kr_home.autoSelect;
|
||||
int delay = 0;
|
||||
|
||||
for (var group
|
||||
in KRSingBoxImp.instance.kr_activeGroups) {
|
||||
if (group.type == ProxyType.urltest) {
|
||||
selectedNode = group.selected;
|
||||
delay = controller
|
||||
.kr_subscribeService.keyList[group.selected]
|
||||
?.urlTestDelay.value ??
|
||||
0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Text(
|
||||
selectedNode,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.color,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
// 获取当前自动选择的节点
|
||||
String selectedNode =
|
||||
AppTranslations.kr_home.autoSelect;
|
||||
int delay = 0;
|
||||
|
||||
for (var group
|
||||
in KRSingBoxImp.instance.kr_activeGroups) {
|
||||
if (group.type == ProxyType.urltest) {
|
||||
selectedNode = group.selected;
|
||||
delay = controller
|
||||
.kr_subscribeService
|
||||
.keyList[group.selected]
|
||||
?.urlTestDelay
|
||||
.value ??
|
||||
0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return delay > 0
|
||||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
delay < 3000
|
||||
? '${delay}ms'
|
||||
: AppTranslations.kr_home.timeout,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: delay < 3000
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 分隔线
|
||||
Divider(
|
||||
height: 16,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
// 节点列表
|
||||
...controller.kr_subscribeService.allList
|
||||
.map((node) => Column(
|
||||
children: [
|
||||
InkWell(
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
KRLogUtil.kr_i(
|
||||
'🔄 用户点击节点: ${node.tag}');
|
||||
final success = await controller
|
||||
.kr_performNodeSwitch(node.tag);
|
||||
if (success) {
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e(
|
||||
'节点切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: _kr_buildNodeListItem(
|
||||
context,
|
||||
item: node,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (node !=
|
||||
controller.kr_subscribeService.allList.last)
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
))
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:math';
|
||||
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/model/response/kr_user_available_subscribe.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
|
||||
|
||||
class KRHomeSubscriptionView extends GetView<KRHomeController> {
|
||||
const KRHomeSubscriptionView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (!KRAppRunData().kr_isLogin.value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final currentSubscribe =
|
||||
controller.kr_subscribeService.kr_currentSubscribe.value;
|
||||
if (currentSubscribe == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('当前订阅名称: ${currentSubscribe.name}',
|
||||
tag: 'SubscriptionView');
|
||||
|
||||
final totalTraffic = currentSubscribe.traffic;
|
||||
final usedTraffic = currentSubscribe.download + currentSubscribe.upload;
|
||||
final hasTrafficLimit = totalTraffic > 0;
|
||||
var trafficPercentage =
|
||||
hasTrafficLimit ? (usedTraffic / totalTraffic).clamp(0.0, 1.0) : 0.0;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.bolt_rounded,
|
||||
size: 14.w,
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black54
|
||||
: Colors.white54,
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
Expanded(
|
||||
child: Text(
|
||||
currentSubscribe.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
Container(
|
||||
height: 3.h,
|
||||
width: 20.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.grey[200]
|
||||
: Colors.grey[800],
|
||||
borderRadius: BorderRadius.circular(1.5.r),
|
||||
),
|
||||
child: FractionallySizedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
widthFactor: trafficPercentage,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _getTrafficColor(trafficPercentage),
|
||||
borderRadius: BorderRadius.circular(1.5.r),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
Icon(
|
||||
Icons.swap_horiz,
|
||||
size: 14.w,
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(0.5)
|
||||
: Colors.white.withOpacity(0.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildLoadingView(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.white
|
||||
: Colors.grey[900]!,
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.grey[50]!
|
||||
: Colors.grey[800]!,
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
AppTranslations.kr_home.loading,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getStatusIcon(KRUserAvailableSubscribeItem subscribe) {
|
||||
final now = DateTime.now();
|
||||
final expireTime = DateTime.parse(subscribe.expireTime);
|
||||
final difference = expireTime.difference(now);
|
||||
|
||||
if (difference.isNegative) {
|
||||
return Icons.error_outline_rounded;
|
||||
} else if (difference.inDays <= 1) {
|
||||
return Icons.warning_amber_rounded;
|
||||
} else {
|
||||
return Icons.check_circle_outline_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getStatusColor(
|
||||
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
|
||||
final now = DateTime.now();
|
||||
final expireTime = DateTime.parse(subscribe.expireTime);
|
||||
final difference = expireTime.difference(now);
|
||||
|
||||
if (difference.isNegative) {
|
||||
return Colors.red;
|
||||
} else if (difference.inDays <= 1) {
|
||||
return Colors.orange;
|
||||
} else {
|
||||
return const Color(0xFF00E52B);
|
||||
}
|
||||
}
|
||||
|
||||
String _getStatusText(KRUserAvailableSubscribeItem subscribe) {
|
||||
final now = DateTime.now();
|
||||
final expireTime = DateTime.parse(subscribe.expireTime);
|
||||
final difference = expireTime.difference(now);
|
||||
|
||||
if (difference.isNegative) {
|
||||
return '已过期';
|
||||
} else if (difference.inDays <= 1) {
|
||||
return '即将到期';
|
||||
} else {
|
||||
return '有效';
|
||||
}
|
||||
}
|
||||
|
||||
Color _getTrafficColor(double percentage) {
|
||||
if (percentage >= 0.9) {
|
||||
return Colors.red;
|
||||
} else if (percentage >= 0.7) {
|
||||
return Colors.orange;
|
||||
} else {
|
||||
return const Color(0xFF00E52B);
|
||||
}
|
||||
}
|
||||
|
||||
String _formatTraffic(int bytes) {
|
||||
if (bytes < 1024) {
|
||||
return '$bytes B';
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||
} else {
|
||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDate(String dateStr) {
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../../../services/kr_subscribe_service.dart';
|
||||
import '../../../utils/kr_log_util.dart';
|
||||
import '../../../utils/kr_subscribe_navigation_util.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
|
||||
/// 试用卡片组件
|
||||
class KRHomeTrialCard extends GetView<KRHomeController> {
|
||||
const KRHomeTrialCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 顶部标题和订阅按钮
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.trialStatus,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => KRSubscribeNavigationUtil.navigateToPurchase(tag: 'TrialCard'),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_home.subscribe,
|
||||
style: KrAppTextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 倒计时显示
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.timer_outlined,
|
||||
color: Colors.blue,
|
||||
size: 16,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
AppTranslations.kr_home.trialing,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildCountdown(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCountdown() {
|
||||
return Obx(() {
|
||||
final subscribeService = KRSubscribeService();
|
||||
final remainingTime = subscribeService.kr_trialRemainingTime.value;
|
||||
final isExpired = remainingTime.isEmpty;
|
||||
|
||||
return Builder(
|
||||
builder: (context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
remainingTime,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isExpired
|
||||
? (DateTime.now().millisecondsSinceEpoch % 2000 < 1000
|
||||
? Colors.red
|
||||
: Colors.blue)
|
||||
: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.trialEndMessage,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,6 @@ import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
|
||||
import '../../../services/kr_subscribe_service.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
import '../widgets/kr_subscribe_selector_view.dart';
|
||||
import 'kr_home_bottom_panel.dart';
|
||||
import 'kr_home_subscription_view.dart';
|
||||
import './hi_animated_connect_button.dart';
|
||||
import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
||||
|
||||
@@ -138,18 +135,15 @@ class _KRHomeViewState extends State<KRHomeView> {
|
||||
style: highlightStyle,
|
||||
);
|
||||
} 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,
|
||||
);
|
||||
// --- 情况2.2: 订阅有效 ---
|
||||
final formattedExpireTime = expireDateTime != null
|
||||
? '${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')} ${expireDateTime.hour.toString().padLeft(2, '0')}:${expireDateTime.minute.toString().padLeft(2, '0')}'
|
||||
: '未知';
|
||||
// 使用换行符 \n 合并为单个 Text 组件
|
||||
content = Text(
|
||||
'套餐到期时间:$formattedExpireTime\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}',
|
||||
style: normalStyle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +215,7 @@ class _KRHomeViewState extends State<KRHomeView> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
HIDialog.show(
|
||||
title: '*闪连功能',
|
||||
title: '闪连功能',
|
||||
message:
|
||||
'开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user