diff --git a/assets/images/Frame 8.svg b/assets/images/Frame 8.svg deleted file mode 100755 index 6935d79..0000000 --- a/assets/images/Frame 8.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/Frame_8.svg b/assets/images/Frame_8.svg deleted file mode 100755 index 6935d79..0000000 --- a/assets/images/Frame_8.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/connect_norouz.PNG b/assets/images/connect_norouz.PNG deleted file mode 100755 index 280edc5..0000000 Binary files a/assets/images/connect_norouz.PNG and /dev/null differ diff --git a/assets/images/delete_account.png b/assets/images/delete_account.png deleted file mode 100755 index 47fe965..0000000 Binary files a/assets/images/delete_account.png and /dev/null differ diff --git a/assets/images/disconnect_norouz.PNG b/assets/images/disconnect_norouz.PNG deleted file mode 100755 index 05bcb56..0000000 Binary files a/assets/images/disconnect_norouz.PNG and /dev/null differ diff --git a/assets/images/home_ct.svg b/assets/images/home_ct.svg deleted file mode 100755 index 76e260d..0000000 --- a/assets/images/home_ct.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/home_list_location.svg b/assets/images/home_list_location.svg deleted file mode 100755 index bbf9706..0000000 --- a/assets/images/home_list_location.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/home_msg.png b/assets/images/home_msg.png deleted file mode 100755 index 8519261..0000000 Binary files a/assets/images/home_msg.png and /dev/null differ diff --git a/assets/images/home_server.svg b/assets/images/home_server.svg deleted file mode 100755 index f324dbd..0000000 --- a/assets/images/home_server.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/invite_top_bg.png b/assets/images/invite_top_bg.png deleted file mode 100755 index 7760514..0000000 Binary files a/assets/images/invite_top_bg.png and /dev/null differ diff --git a/assets/images/language_switch.svg b/assets/images/language_switch.svg deleted file mode 100755 index 100e9c4..0000000 --- a/assets/images/language_switch.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/location.svg b/assets/images/location.svg deleted file mode 100755 index 23a3507..0000000 --- a/assets/images/location.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/images/login_account.svg b/assets/images/login_account.svg deleted file mode 100755 index 94d8865..0000000 --- a/assets/images/login_account.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/images/login_close.svg b/assets/images/login_close.svg deleted file mode 100755 index 7041c2f..0000000 --- a/assets/images/login_close.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/assets/images/login_code.svg b/assets/images/login_code.svg deleted file mode 100755 index 7231181..0000000 --- a/assets/images/login_code.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/login_psd.svg b/assets/images/login_psd.svg deleted file mode 100755 index a13860c..0000000 --- a/assets/images/login_psd.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/logo.svg b/assets/images/logo.svg deleted file mode 100755 index 3771822..0000000 --- a/assets/images/logo.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/my_ads.svg b/assets/images/my_ads.svg deleted file mode 100755 index b8348eb..0000000 --- a/assets/images/my_ads.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_buy_tp.svg b/assets/images/my_buy_tp.svg deleted file mode 100755 index 5584044..0000000 --- a/assets/images/my_buy_tp.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_cn_us.svg b/assets/images/my_cn_us.svg deleted file mode 100755 index 35eaf95..0000000 --- a/assets/images/my_cn_us.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_dns.svg b/assets/images/my_dns.svg deleted file mode 100755 index 6e5ab66..0000000 --- a/assets/images/my_dns.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_email.svg b/assets/images/my_email.svg deleted file mode 100755 index 242b7c7..0000000 --- a/assets/images/my_email.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_et.svg b/assets/images/my_et.svg deleted file mode 100755 index 0fbfe08..0000000 --- a/assets/images/my_et.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_jinggao.svg b/assets/images/my_jinggao.svg deleted file mode 100755 index aab0952..0000000 --- a/assets/images/my_jinggao.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_kf.svg b/assets/images/my_kf.svg deleted file mode 100755 index d6c9cd2..0000000 --- a/assets/images/my_kf.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_kf_msg.svg b/assets/images/my_kf_msg.svg deleted file mode 100755 index ad1b815..0000000 --- a/assets/images/my_kf_msg.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_net_index.svg b/assets/images/my_net_index.svg deleted file mode 100755 index 6935d79..0000000 --- a/assets/images/my_net_index.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_phone.svg b/assets/images/my_phone.svg deleted file mode 100755 index 2fbc548..0000000 --- a/assets/images/my_phone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_set.svg b/assets/images/my_set.svg deleted file mode 100755 index c24f3c3..0000000 --- a/assets/images/my_set.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/my_telegram.svg b/assets/images/my_telegram.svg deleted file mode 100755 index 569f12e..0000000 --- a/assets/images/my_telegram.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/assets/images/payment_success.svg b/assets/images/payment_success.svg deleted file mode 100755 index 8f75329..0000000 --- a/assets/images/payment_success.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/splash_illustration.svg b/assets/images/splash_illustration.svg deleted file mode 100755 index 84847ca..0000000 --- a/assets/images/splash_illustration.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/tab_home_n.svg b/assets/images/tab_home_n.svg deleted file mode 100755 index 99081e8..0000000 --- a/assets/images/tab_home_n.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/tab_home_s.svg b/assets/images/tab_home_s.svg deleted file mode 100755 index bd5dbca..0000000 --- a/assets/images/tab_home_s.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/tab_invite_n.svg b/assets/images/tab_invite_n.svg deleted file mode 100755 index be3e986..0000000 --- a/assets/images/tab_invite_n.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/assets/images/tab_invite_s.svg b/assets/images/tab_invite_s.svg deleted file mode 100755 index c51aab7..0000000 --- a/assets/images/tab_invite_s.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/assets/images/tab_my_n.svg b/assets/images/tab_my_n.svg deleted file mode 100755 index 3ae0326..0000000 --- a/assets/images/tab_my_n.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/tab_my_s.svg b/assets/images/tab_my_s.svg deleted file mode 100755 index 0f98b01..0000000 --- a/assets/images/tab_my_s.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/tab_statistics_n.svg b/assets/images/tab_statistics_n.svg deleted file mode 100755 index 2e63c45..0000000 --- a/assets/images/tab_statistics_n.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/images/tab_statistics_s.svg b/assets/images/tab_statistics_s.svg deleted file mode 100755 index 7b5a94d..0000000 --- a/assets/images/tab_statistics_s.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/devtools_options.yaml b/devtools_options.yaml index fa0b357..f592d85 100755 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -1,3 +1,4 @@ description: This file stores settings for Dart & Flutter DevTools. documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: + - drift: true \ No newline at end of file diff --git a/lib/app/common/app_config.dart b/lib/app/common/app_config.dart index 385cab1..3c91f6a 100755 --- a/lib/app/common/app_config.dart +++ b/lib/app/common/app_config.dart @@ -3,6 +3,7 @@ import '../services/api_service/kr_api.user.dart'; import '../utils/kr_update_util.dart'; import '../utils/kr_secure_storage.dart'; import '../utils/kr_log_util.dart'; +import '../utils/kr_http_adapter_util.dart'; import '../services/singbox_imp/kr_sing_box_imp.dart'; import '../utils/kr_init_log_collector.dart'; // 🔧 新增:导入日志收集器 import 'dart:async'; @@ -31,7 +32,7 @@ class KRDomain { // static String kr_currentDomain = "apicn.bearvpn.top"; static List kr_baseDomains = ["api.hifast.biz", "api.airovpn.tel",]; - static String kr_currentDomain = "api.hifast.biz1"; + static String kr_currentDomain = "api.hifast.biz"; // 备用域名获取地址列表 static List kr_backupDomainUrls = [ @@ -67,21 +68,14 @@ class KRDomain { // Dio 实例及初始化 static final Dio _dio = (() { - final dio = Dio(); - // 🔧 配置HttpClientAdapter使用sing-box的mixed代理 - dio.httpClientAdapter = IOHttpClientAdapter( - createHttpClient: () { - final client = HttpClient(); - client.findProxy = (url) { - final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule(); - KRLogUtil.kr_i( - '🔍 KRDomain 请求使用代理: $proxyConfig, url: $url', - tag: 'KRDomain', - ); - return proxyConfig; - }; - return client; - }, + final dio = Dio(BaseOptions( + connectTimeout: const Duration(seconds: kr_domainTimeout), + sendTimeout: const Duration(seconds: kr_domainTimeout), + receiveTimeout: const Duration(seconds: kr_domainTimeout), + )); + // 🔧 使用统一的 Adapter 转换工具 + dio.httpClientAdapter = KRHttpAdapterUtil.createAdapter( + timeout: const Duration(seconds: kr_domainTimeout), ); return dio; })(); diff --git a/lib/app/common/app_run_data.dart b/lib/app/common/app_run_data.dart index ebc7297..9d60ea6 100755 --- a/lib/app/common/app_run_data.dart +++ b/lib/app/common/app_run_data.dart @@ -44,6 +44,9 @@ class KRAppRunData { /// 用户邀请码(从用户信息接口获取) final RxString kr_referCode = ''.obs; + /// 邀请人ID(谁邀请了当前用户) + final RxInt kr_refererId = 0.obs; + /// 用户余额 final RxInt kr_balance = 0.obs; @@ -597,12 +600,14 @@ class KRAppRunData { // 保存到全局状态 kr_referCode.value = userInfo.referCode; + kr_refererId.value = userInfo.refererId; kr_account.value = authType == 'device' ? '9000${userInfo.id}' : authIdentifier; kr_balance.value = userInfo.balance; kr_commission.value = userInfo.commission; KRLogUtil.kr_i('💾 [AppRunData] 用户信息已保存到全局状态:', tag: 'AppRunData'); KRLogUtil.kr_i(' - kr_referCode: "${kr_referCode.value}"', tag: 'AppRunData'); + KRLogUtil.kr_i(' - kr_refererId: ${kr_refererId.value}', tag: 'AppRunData'); KRLogUtil.kr_i(' - kr_balance: ${kr_balance.value}', tag: 'AppRunData'); KRLogUtil.kr_i(' - kr_commission: ${kr_commission.value}', tag: 'AppRunData'); }, diff --git a/lib/app/modules/kr_country_selector/bindings/kr_country_selector_binding.dart b/lib/app/modules/kr_country_selector/bindings/kr_country_selector_binding.dart deleted file mode 100755 index 8716802..0000000 --- a/lib/app/modules/kr_country_selector/bindings/kr_country_selector_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/kr_country_selector_controller.dart'; - -class KRCountrySelectorBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => KRCountrySelectorController(), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_country_selector/controllers/kr_country_selector_controller.dart b/lib/app/modules/kr_country_selector/controllers/kr_country_selector_controller.dart deleted file mode 100755 index 4c602f2..0000000 --- a/lib/app/modules/kr_country_selector/controllers/kr_country_selector_controller.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/common/app_config.dart'; -import 'package:kaer_with_panels/app/utils/kr_country_util.dart'; - -import '../../../services/singbox_imp/kr_sing_box_imp.dart'; - -class KRCountrySelectorController extends GetxController { - // 使用 KRCountry 枚举来加载国家 - final RxList kr_countries = [].obs; - // 当前选中的国家 - final Rx kr_selectedCountry = KRCountry.cn.obs; - - @override - void onInit() { - super.onInit(); - kr_selectedCountry.value = KRCountryUtil.kr_currentCountry.value; - kr_loadCountries(); - } - - // 加载国家数据 - void kr_loadCountries() { - kr_countries.value = KRCountryUtil.kr_getSupportedCountries(); - - } - - // 选择国家 - Future kr_selectCountry(KRCountry country) async { - kr_selectedCountry.value = country; - // try { - // await KRSingBoxImp().kr_updateCountry(country); - // // Get.back(); - // } catch (err) { - - // } - } - - @override - void onClose() { - // TODO: implement onClose - super.onClose(); - KRSingBoxImp().kr_updateCountry(kr_selectedCountry.value); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_country_selector/views/kr_country_selector_view.dart b/lib/app/modules/kr_country_selector/views/kr_country_selector_view.dart deleted file mode 100755 index 6295e70..0000000 --- a/lib/app/modules/kr_country_selector/views/kr_country_selector_view.dart +++ /dev/null @@ -1,116 +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/utils/kr_country_util.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 '../controllers/kr_country_selector_controller.dart'; - -class KRCountrySelectorView extends GetView { - const KRCountrySelectorView({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - backgroundColor: Theme.of(context).primaryColor, - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color.fromRGBO(23, 151, 255, 0.15), // 渐变开始颜色 - Color.fromRGBO(23, 151, 255, 0.05), // 中间过渡颜色 - // 非渐变色区域 - ], - stops: [0.0, 0.28], // 调整渐变结束位置 - ), - ), - child: Column( - children: [ - AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - size: 20.r, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - onPressed: () => Get.back(), - ), - title: Text( - AppTranslations.kr_setting.countrySelector, - style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - centerTitle: true, - ), - Expanded( - child: Obx( - () => ListView.separated( - padding: EdgeInsets.all(16.r), - itemCount: controller.kr_countries.length, - separatorBuilder: (context, index) => SizedBox(height: 12.h), - itemBuilder: (context, index) { - final country = controller.kr_countries[index]; - return _kr_buildCountryCard(country, context); - }, - ), - ), - ), - ], - ), - ), - ); - } - - // 构建国家卡片 - Widget _kr_buildCountryCard(KRCountry country, BuildContext context) { - return Obx( - () => InkWell( - onTap: () => controller.kr_selectCountry(country), - child: Container( - padding: EdgeInsets.all(16.r), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12.r), - ), - child: Row( - children: [ - // 国家图标 - KRCountryFlag( - countryCode: country.kr_code, - width: 24.r, - height: 24.r, - ), - SizedBox(width: 12.w), - // 国家名称 - Text( - KRCountryUtil.kr_getCountryName(country), - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - const Spacer(), - // 选中标记 - if (controller.kr_selectedCountry.value == country) - Icon( - Icons.check_circle, - color: Colors.blue, - size: 20.r, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/app/modules/kr_device_management/bindings/kr_device_management_binding.dart b/lib/app/modules/kr_device_management/bindings/kr_device_management_binding.dart deleted file mode 100644 index 28d0aa7..0000000 --- a/lib/app/modules/kr_device_management/bindings/kr_device_management_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/kr_device_management_controller.dart'; - -class KRDeviceManagementBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => KRDeviceManagementController(), - ); - } -} diff --git a/lib/app/modules/kr_device_management/controllers/kr_device_management_controller.dart b/lib/app/modules/kr_device_management/controllers/kr_device_management_controller.dart deleted file mode 100644 index 89c0499..0000000 --- a/lib/app/modules/kr_device_management/controllers/kr_device_management_controller.dart +++ /dev/null @@ -1,294 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; -import 'package:kaer_with_panels/app/services/api_service/kr_api.user.dart'; -import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart'; -import 'package:kaer_with_panels/app/common/app_run_data.dart'; -import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart'; -import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; -import 'package:kaer_with_panels/app/services/kr_device_info_service.dart'; -import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart'; -import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart'; -import 'package:kaer_with_panels/app/localization/app_translations.dart'; -import 'dart:io'; -import 'dart:math'; - -class KRDeviceManagementController extends GetxController { - // 设备列表 - final RxList> devices = >[].obs; - - // 加载状态 - final RxBool isLoading = true.obs; - - // 当前设备ID - String? currentDeviceId; - - @override - void onInit() { - super.onInit(); - _initDeviceId(); - loadDeviceList(); - } - - /// 初始化当前设备ID - Future _initDeviceId() async { - try { - currentDeviceId = KRDeviceInfoService().deviceId ?? 'unknown'; - KRLogUtil.kr_i('当前设备ID: $currentDeviceId', tag: 'DeviceManagement'); - } catch (e) { - KRLogUtil.kr_e('获取设备ID失败: $e', tag: 'DeviceManagement'); - } - } - - /// 加载设备列表 - Future loadDeviceList() async { - try { - isLoading.value = true; - KRLogUtil.kr_i('开始加载设备列表', tag: 'DeviceManagement'); - - // 调用API获取设备列表 - final result = await KRUserApi().kr_getUserDevices(); - - result.fold( - (error) { - KRLogUtil.kr_e('加载设备列表失败: ${error.msg}', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.error, error.msg); - }, - (deviceList) { - KRLogUtil.kr_i('获取到 ${deviceList.length} 个设备', tag: 'DeviceManagement'); - - // 转换设备数据格式 - devices.value = deviceList.map((device) { - final identifier = device['identifier']?.toString() ?? ''; - final isCurrent = identifier == currentDeviceId; - - return { - 'id': device['id']?.toString() ?? '', - 'identifier': identifier, - 'device_name': device['user_agent'] ?? '未知设备', - 'ip': device['ip'] ?? '', - 'last_login': device['updated_at'] ?? device['created_at'] ?? '', - 'is_current': isCurrent, - 'enabled': device['enabled'] ?? true, - 'online': device['online'] ?? false, - }; - }).toList(); - }, - ); - } catch (e, stackTrace) { - KRLogUtil.kr_e('加载设备列表异常: $e', tag: 'DeviceManagement'); - KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.loadDeviceListFailed); - } finally { - isLoading.value = false; - } - } - - /// 删除设备 - Future deleteDevice(String id) async { - try { - // 检查是否是本机设备 - final device = devices.firstWhere( - (d) => d['id'] == id, - orElse: () => {}, - ); - - if (device.isEmpty) return; - - final isCurrent = device['is_current'] ?? false; - - // 使用响应式变量来接收确认结果 - bool? confirmed; - - // 显示确认对话框 - await KRDialog.show( - title: AppTranslations.kr_deviceManagement.deleteConfirmTitle, - message: isCurrent - ? AppTranslations.kr_deviceManagement.deleteCurrentDeviceMessage - : AppTranslations.kr_deviceManagement.deleteOtherDeviceMessage, - icon: Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.warning_rounded, - color: Colors.red, - size: 32, - ), - ), - confirmText: AppTranslations.kr_dialog.delete, - cancelText: AppTranslations.kr_dialog.kr_cancel, - onConfirm: () { - confirmed = true; - }, - onCancel: () { - confirmed = false; - }, - ); - - if (confirmed != true) return; - - KRLogUtil.kr_i('开始解绑设备 - id: $id, isCurrent: $isCurrent', tag: 'DeviceManagement'); - - // 调用API解绑设备 - final result = await KRUserApi().kr_unbindUserDevice(id); - - result.fold( - (error) { - KRLogUtil.kr_e('删除设备失败: ${error.msg}', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.deleteFailed(error.msg)); - }, - (_) async { - KRLogUtil.kr_i('设备删除成功', tag: 'DeviceManagement'); - - if (isCurrent) { - // 如果删除的是本机设备,重新进行设备登录 - KRLogUtil.kr_i('本机设备已删除,准备重新登录', tag: 'DeviceManagement'); - - // 先关闭当前设备管理页面 - Get.back(); - - // 执行重新登录 - await _reloginWithDevice(); - } else { - // 删除其他设备,从列表中移除 - devices.removeWhere((device) => device['id'] == id); - Get.snackbar(AppTranslations.kr_dialog.success, AppTranslations.kr_deviceManagement.deleteSuccess); - } - }, - ); - } catch (e, stackTrace) { - KRLogUtil.kr_e('删除设备异常: $e', tag: 'DeviceManagement'); - KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.deleteFailed(e.toString())); - } - } - - /// 重新使用设备登录 - Future _reloginWithDevice() async { - try { - KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'DeviceManagement'); - KRLogUtil.kr_i('开始重新进行设备登录', tag: 'DeviceManagement'); - KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'DeviceManagement'); - - // 先清除当前的用户信息(但不调用 kr_loginOut,避免显示登录界面) - final appRunData = KRAppRunData.getInstance(); - appRunData.kr_isLogin.value = false; - appRunData.kr_token = null; - appRunData.kr_account.value = null; - appRunData.kr_userId.value = null; - - // 检查是否启用设备登录 - final siteConfigService = KRSiteConfigService(); - final isDeviceLoginEnabled = siteConfigService.isDeviceLoginEnabled(); - - if (!isDeviceLoginEnabled) { - KRLogUtil.kr_w('设备登录未启用,执行完整退出登录', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.tip, AppTranslations.kr_deviceManagement.deviceLoginDisabled); - await appRunData.kr_loginOut(); - return; - } - - KRLogUtil.kr_i('设备登录已启用,开始调用设备登录接口', tag: 'DeviceManagement'); - - // 初始化设备信息服务(如果还没初始化) - await KRDeviceInfoService().initialize(); - - // 调用设备登录接口 - final authApi = KRAuthApi(); - final result = await authApi.kr_deviceLogin(); - - result.fold( - (error) { - // 设备登录失败 - KRLogUtil.kr_e('设备登录失败: ${error.msg}', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.reloginFailed(error.msg)); - - // 执行完整退出登录,显示登录界面 - appRunData.kr_loginOut(); - }, - (token) async { - // 设备登录成功 - KRLogUtil.kr_i('✅ 设备登录成功!', tag: 'DeviceManagement'); - KRLogUtil.kr_i('🎫 Token: ${token.substring(0, min(20, token.length))}...', tag: 'DeviceManagement'); - - // 保存新的用户信息 - final deviceId = KRDeviceInfoService().deviceId ?? 'unknown'; - await appRunData.kr_saveUserInfo( - token, - 'device_$deviceId', - KRLoginType.kr_email, - null, - ); - - KRLogUtil.kr_i('✅ 设备重新登录成功,已更新用户信息', tag: 'DeviceManagement'); - - // 等待一小段时间,确保登录状态已经更新 - await Future.delayed(const Duration(milliseconds: 300)); - - // 刷新订阅信息 - KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement'); - try { - await KRSubscribeService().kr_refreshAll(); - KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement'); - } catch (e) { - KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement'); - } - - Get.snackbar(AppTranslations.kr_dialog.success, AppTranslations.kr_deviceManagement.reloginSuccess); - }, - ); - } catch (e, stackTrace) { - KRLogUtil.kr_e('设备重新登录异常: $e', tag: 'DeviceManagement'); - KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement'); - Get.snackbar(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.reloginFailedGeneric); - - // 发生异常,执行完整退出登录 - await KRAppRunData.getInstance().kr_loginOut(); - } - } - - /// 获取设备类型和图标 - Map getDeviceTypeInfo(String userAgent) { - String deviceType = AppTranslations.kr_deviceManagement.deviceTypeUnknown; - String iconName = 'devices'; - - if (userAgent.contains('Android') || userAgent.toLowerCase().contains('android')) { - deviceType = AppTranslations.kr_deviceManagement.deviceTypeAndroid; - iconName = 'phone_android'; - } else if (userAgent.contains('iOS') || userAgent.contains('iPhone') || userAgent.toLowerCase().contains('ios')) { - deviceType = AppTranslations.kr_deviceManagement.deviceTypeIos; - iconName = 'phone_iphone'; - } else if (userAgent.contains('iPad')) { - deviceType = AppTranslations.kr_deviceManagement.deviceTypeIpad; - iconName = 'tablet'; - } else if (userAgent.contains('macOS') || userAgent.contains('Mac') || userAgent.toLowerCase().contains('mac')) { - deviceType = AppTranslations.kr_deviceManagement.deviceTypeMacos; - iconName = 'desktop_mac'; - } else if (userAgent.contains('Windows') || userAgent.toLowerCase().contains('windows')) { - deviceType = AppTranslations.kr_deviceManagement.deviceTypeWindows; - iconName = 'computer'; - } else if (userAgent.contains('Linux') || userAgent.toLowerCase().contains('linux')) { - deviceType = AppTranslations.kr_deviceManagement.deviceTypeLinux; - iconName = 'computer'; - } - - return { - 'type': deviceType, - 'icon': iconName, - }; - } - - @override - void onReady() { - super.onReady(); - } - - @override - void onClose() { - super.onClose(); - } -} diff --git a/lib/app/modules/kr_device_management/views/kr_device_management_view.dart b/lib/app/modules/kr_device_management/views/kr_device_management_view.dart deleted file mode 100644 index c6097fd..0000000 --- a/lib/app/modules/kr_device_management/views/kr_device_management_view.dart +++ /dev/null @@ -1,313 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; - -import '../controllers/kr_device_management_controller.dart'; - -class KRDeviceManagementView extends GetView { - const KRDeviceManagementView({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - backgroundColor: Theme.of(context).primaryColor, - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color.fromRGBO(23, 151, 255, 0.15), - Color.fromRGBO(23, 151, 255, 0.05), - ], - stops: [0.0, 0.28], - ), - ), - child: Column( - children: [ - // 顶部导航栏 - AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - color: Theme.of(context).iconTheme.color, - size: 20.w, - ), - onPressed: () => Get.back(), - ), - title: Text( - '设备管理', - style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - // 内容区域 - Expanded( - child: Obx(() { - if (controller.isLoading.value) { - return Center( - child: CircularProgressIndicator(), - ); - } - - if (controller.devices.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.devices_other, - size: 64.w, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - SizedBox(height: 16.h), - Text( - '暂无登录设备', - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ), - ], - ), - ); - } - - return RefreshIndicator( - onRefresh: () => controller.loadDeviceList(), - child: ListView.builder( - padding: EdgeInsets.all(16.w), - itemCount: controller.devices.length, - itemBuilder: (context, index) { - return _buildDeviceItem( - context, - controller.devices[index], - ); - }, - ), - ); - }), - ), - ], - ), - ), - ); - } - - /// 构建设备项 - Widget _buildDeviceItem( - BuildContext context, Map device) { - final id = device['id'] ?? ''; - final identifier = device['identifier'] ?? ''; - final userAgent = device['device_name'] ?? '未知设备'; - final isCurrent = device['is_current'] ?? false; - final ip = device['ip'] ?? ''; - final lastLoginRaw = device['last_login']; - final String lastLogin = lastLoginRaw?.toString() ?? ''; - - // 获取设备类型信息 - final deviceInfo = controller.getDeviceTypeInfo(userAgent); - final deviceType = deviceInfo['type'] as String; - final iconName = deviceInfo['icon'] as String; - - return Container( - margin: EdgeInsets.only(bottom: 12.w), - padding: EdgeInsets.all(16.w), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12.w), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 10.w, - offset: Offset(0, 2.w), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 设备类型和操作按钮 - Row( - children: [ - // 设备图标 - Container( - width: 48.w, - height: 48.w, - decoration: BoxDecoration( - color: const Color(0xFF1797FF).withOpacity(0.1), - borderRadius: BorderRadius.circular(8.w), - ), - child: Icon( - _getIconData(iconName), - color: const Color(0xFF1797FF), - size: 24.w, - ), - ), - SizedBox(width: 12.w), - // 设备信息 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - deviceType, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - if (isCurrent) ...[ - SizedBox(width: 8.w), - Container( - padding: EdgeInsets.symmetric( - horizontal: 8.w, - vertical: 2.h, - ), - decoration: BoxDecoration( - color: Colors.green.withOpacity(0.1), - borderRadius: BorderRadius.circular(4.w), - ), - child: Text( - '本机', - style: KrAppTextStyle( - fontSize: 10, - color: Colors.green, - ), - ), - ), - ], - ], - ), - SizedBox(height: 4.h), - Text( - 'ID: ${identifier.substring(0, identifier.length > 12 ? 12 : identifier.length)}...', - style: KrAppTextStyle( - fontSize: 12, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ), - ], - ), - ), - // 删除按钮 - TextButton( - onPressed: () => controller.deleteDevice(id), - style: TextButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.error, - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), - ), - child: Text( - '删除', - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.error, - ), - ), - ), - ], - ), - // 分隔线 - if (ip.isNotEmpty || lastLogin.isNotEmpty) ...[ - SizedBox(height: 12.h), - Divider(height: 1, color: Theme.of(context).dividerColor), - SizedBox(height: 12.h), - ], - // 详细信息 - if (ip.isNotEmpty) - _buildInfoRow( - context, - 'IP地址', - ip, - ), - if (ip.isNotEmpty && lastLogin.isNotEmpty) SizedBox(height: 8.h), - if (lastLogin.isNotEmpty) - _buildInfoRow( - context, - '最后登录', - _formatDateTime(lastLoginRaw), - ), - ], - ), - ); - } - - /// 构建信息行 - Widget _buildInfoRow(BuildContext context, String label, String value) { - return Row( - children: [ - Text( - '$label: ', - style: KrAppTextStyle( - fontSize: 12, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ), - Expanded( - child: Text( - value, - style: KrAppTextStyle( - fontSize: 12, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } - - /// 格式化时间 - String _formatDateTime(dynamic timestamp) { - if (timestamp == null) return '未知'; - - try { - DateTime dateTime; - if (timestamp is int) { - // 判断是秒级时间戳(10位)还是毫秒级时间戳(13位) - if (timestamp > 9999999999) { - // 毫秒级时间戳 - dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp); - } else { - // 秒级时间戳 - dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); - } - } else if (timestamp is String) { - dateTime = DateTime.parse(timestamp); - } else { - return '未知'; - } - return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; - } catch (e) { - return '未知'; - } - } - - /// 获取图标数据 - IconData _getIconData(String iconName) { - switch (iconName) { - case 'phone_android': - return Icons.phone_android; - case 'phone_iphone': - return Icons.phone_iphone; - case 'tablet': - return Icons.tablet_mac; - case 'desktop_mac': - return Icons.desktop_mac; - case 'computer': - return Icons.computer; - default: - return Icons.devices; - } - } -} diff --git a/lib/app/modules/kr_home/views/kr_home_bottom_panel.dart b/lib/app/modules/kr_home/views/kr_home_bottom_panel.dart deleted file mode 100755 index 82781c8..0000000 --- a/lib/app/modules/kr_home/views/kr_home_bottom_panel.dart +++ /dev/null @@ -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 { - 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, - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/app/modules/kr_home/views/kr_home_connection_info_view.dart b/lib/app/modules/kr_home/views/kr_home_connection_info_view.dart deleted file mode 100755 index b76324a..0000000 --- a/lib/app/modules/kr_home/views/kr_home_connection_info_view.dart +++ /dev/null @@ -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 { - 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, - ); - }), - ], - ), - ], - ), - ), - ); - }); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_home/views/kr_home_connection_options_view.dart b/lib/app/modules/kr_home/views/kr_home_connection_options_view.dart deleted file mode 100755 index df0f6b2..0000000 --- a/lib/app/modules/kr_home/views/kr_home_connection_options_view.dart +++ /dev/null @@ -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 { - 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, - ), - ], - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_home/views/kr_home_last_day_card.dart b/lib/app/modules/kr_home/views/kr_home_last_day_card.dart deleted file mode 100755 index fe7e1ef..0000000 --- a/lib/app/modules/kr_home/views/kr_home_last_day_card.dart +++ /dev/null @@ -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 { - 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, - ), - ), - ], - ); - }), - ], - ), - ), - ], - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_home/views/kr_home_node_list_view.dart b/lib/app/modules/kr_home/views/kr_home_node_list_view.dart deleted file mode 100755 index b41c1d9..0000000 --- a/lib/app/modules/kr_home/views/kr_home_node_list_view.dart +++ /dev/null @@ -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 { - 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( - 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(), - ], - ), - ); - }), - ); - } -} diff --git a/lib/app/modules/kr_home/views/kr_home_subscription_view.dart b/lib/app/modules/kr_home/views/kr_home_subscription_view.dart deleted file mode 100755 index 15177dd..0000000 --- a/lib/app/modules/kr_home/views/kr_home_subscription_view.dart +++ /dev/null @@ -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 { - 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; - } - } -} diff --git a/lib/app/modules/kr_home/views/kr_home_trial_card.dart b/lib/app/modules/kr_home/views/kr_home_trial_card.dart deleted file mode 100755 index bf21b06..0000000 --- a/lib/app/modules/kr_home/views/kr_home_trial_card.dart +++ /dev/null @@ -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 { - 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, - ), - ), - ], - ), - ); - }); - } -} \ No newline at end of file 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 44bb1a2..a232e5d 100755 --- a/lib/app/modules/kr_home/views/kr_home_view.dart +++ b/lib/app/modules/kr_home/views/kr_home_view.dart @@ -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 { 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 { GestureDetector( onTap: () { HIDialog.show( - title: '*闪连功能', + title: '闪连功能', message: '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开', ); diff --git a/lib/app/modules/kr_home/widgets/kr_subscribe_selector_view.dart b/lib/app/modules/kr_home/widgets/kr_subscribe_selector_view.dart deleted file mode 100755 index 38e7b53..0000000 --- a/lib/app/modules/kr_home/widgets/kr_subscribe_selector_view.dart +++ /dev/null @@ -1,275 +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/modules/kr_home/controllers/kr_home_controller.dart'; -import 'package:kaer_with_panels/app/model/response/kr_user_available_subscribe.dart'; -import 'package:kaer_with_panels/app/localization/app_translations.dart'; -import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; - -class KRSubscribeSelectorView extends StatelessWidget { - final KRHomeController? controller; - - const KRSubscribeSelectorView({ - super.key, - this.controller, - }); - - @override - Widget build(BuildContext context) { - final homeController = controller ?? Get.find(); - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - return Container( - width: MediaQuery.of(context).size.width * 0.85, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(20.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10.r, - offset: Offset(0, 2.w), - spreadRadius: 0, - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.05), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20.r), - topRight: Radius.circular(20.r), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppTranslations.kr_purchaseMembership.selectPackage, - style: KrAppTextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Theme.of(context).textTheme.titleLarge?.color, - ), - ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => Navigator.pop(context), - borderRadius: BorderRadius.circular(20.r), - child: Padding( - padding: EdgeInsets.all(4.w), - child: Icon( - Icons.close_rounded, - color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.6), - size: 18.w, - ), - ), - ), - ), - ], - ), - ), - Obx(() { - final subscribes = homeController.kr_subscribeService.kr_availableSubscribes; - if (subscribes.isEmpty) { - return Padding( - padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w), - child: Column( - children: [ - Icon( - Icons.subscriptions_outlined, - size: 48.w, - color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.3), - ), - SizedBox(height: 12.h), - Text( - AppTranslations.kr_purchaseMembership.noData, - style: KrAppTextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.5), - ), - ), - ], - ), - ); - } - return ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.5, - ), - child: ListView.builder( - shrinkWrap: true, - padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 4.w), - itemCount: subscribes.length, - itemBuilder: (context, index) { - final subscribe = subscribes[index]; - final isCurrent = subscribe.id == homeController.kr_subscribeService.kr_currentSubscribe.value?.id; - - return _SubscribeItem( - subscribe: subscribe, - isCurrent: isCurrent, - onTap: () { - homeController.kr_switchSubscribe(subscribe); - Get.back(); - }, - ); - }, - ), - ); - }), - SizedBox(height: 8.h), - ], - ), - ); - } -} - -class _SubscribeItem extends StatelessWidget { - final KRUserAvailableSubscribeItem subscribe; - final bool isCurrent; - final VoidCallback onTap; - - const _SubscribeItem({ - required this.subscribe, - required this.isCurrent, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final usedTraffic = (subscribe.download + subscribe.upload) / 1024 / 1024 / 1024; - final totalTraffic = subscribe.traffic / 1024 / 1024 / 1024; - var percentage = totalTraffic > 0 ? usedTraffic / totalTraffic : 0.0; - - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - final isUnlimited = subscribe.traffic == 0; - - String getUsedTrafficDisplay() { - if (usedTraffic < 1) { - return '${(usedTraffic * 1024).toStringAsFixed(2)}MB'; - } else { - return '${usedTraffic.toStringAsFixed(2)}GB'; - } - } - - return Padding( - padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12.r), - child: Ink( - padding: EdgeInsets.all(12.w), - decoration: BoxDecoration( - color: isCurrent - ? Colors.blue.withOpacity(isDarkMode ? 0.15 : 0.08) - : Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12.r), - border: Border.all( - color: isCurrent - ? Colors.blue.withOpacity(isDarkMode ? 0.5 : 0.3) - : Theme.of(context).dividerColor.withOpacity(0.3), - width: 1, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - subscribe.name, - style: KrAppTextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Theme.of(context).textTheme.titleLarge?.color, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - if (isCurrent) - Container( - padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.circular(16.r), - boxShadow: [ - BoxShadow( - color: Colors.blue.withOpacity(0.2), - blurRadius: 6.w, - offset: Offset(0, 1.w), - ), - ], - ), - child: Text( - AppTranslations.kr_home.currentConnectionTitle, - style: KrAppTextStyle( - color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - SizedBox(height: 8.h), - Text( - isUnlimited - ? AppTranslations.kr_purchaseMembership.unlimitedTraffic - : '${getUsedTrafficDisplay()} / ${totalTraffic.toStringAsFixed(2)}GB', - style: KrAppTextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.8), - ), - ), - if (!isUnlimited) ...[ - SizedBox(height: 8.h), - ClipRRect( - borderRadius: BorderRadius.circular(4.r), - child: LinearProgressIndicator( - value: percentage.clamp(0.0, 1.0), - backgroundColor: isDarkMode - ? Colors.grey[700]?.withOpacity(0.7) - : Colors.grey[300]?.withOpacity(0.9), - valueColor: AlwaysStoppedAnimation( - _getTrafficColor(percentage, isDarkMode), - ), - minHeight: 4.w, - ), - ), - ], - ], - ), - ), - ), - ), - ); - } - - Color _getTrafficColor(double percentage, bool isDarkMode) { - if (percentage >= 0.9) { - return isDarkMode - ? Colors.red.withOpacity(0.8) - : Colors.red.withOpacity(0.7); - } else if (percentage >= 0.7) { - return isDarkMode - ? Colors.orange.withOpacity(0.8) - : Colors.orange.withOpacity(0.7); - } else { - return isDarkMode - ? Colors.blue.withOpacity(0.8) - : Colors.blue.withOpacity(0.7); - } - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_home/widgets/kr_subscription_card.dart b/lib/app/modules/kr_home/widgets/kr_subscription_card.dart deleted file mode 100755 index c72e300..0000000 --- a/lib/app/modules/kr_home/widgets/kr_subscription_card.dart +++ /dev/null @@ -1,116 +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/routes/app_pages.dart'; -import 'package:kaer_with_panels/app/utils/kr_subscribe_navigation_util.dart'; -import '../../../widgets/kr_app_text_style.dart'; - -/// 订阅卡片组件 -class KRSubscriptionCard extends StatelessWidget { - const KRSubscriptionCard({ - super.key, - - }); - - - - @override - Widget build(BuildContext context) { - return _kr_buildSubscriptionCard(context); - } - - // 构建订阅卡片 - Widget _kr_buildSubscriptionCard(BuildContext context) { - // 🔧 关键修复:完全移除 ScreenUtil,使用固定像素值避免缩放问题 - return Container( - // 添加固定高度,确保卡片可见 - constraints: const BoxConstraints(minHeight: 200), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 图标 - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.language, - color: Colors.blue, - size: 28, - ), - ), - const SizedBox(height: 16), - // 描述文字 - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - AppTranslations.kr_home.subscriptionDescription, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - height: 1.5, - // 🔧 关键修复:确保文本颜色可见 - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black87, - ), - ), - ), - const SizedBox(height: 20), - // 订阅按钮 - SizedBox( - width: double.infinity, - height: 46, - child: ElevatedButton( - onPressed: () { - KRSubscribeNavigationUtil.navigateToPurchase(tag: 'SubscriptionCard'); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Text( - AppTranslations.kr_home.subscribe, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - ), - ), - ), - ], - ), - ); - } - - Widget _kr_buildListContainer( - BuildContext context, { - required Widget child, - EdgeInsetsGeometry? margin, - bool addBottomPadding = true, - }) { - return Container( - margin: margin ?? EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12), - ), - child: IntrinsicWidth( - child: child, - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart b/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart index c93ef55..aa13d1f 100755 --- a/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart +++ b/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart @@ -43,6 +43,7 @@ class KRInviteController extends GetxController { totalCommission: 0, ).obs; final kr_referCode = ''.obs; + final kr_refererId = 0.obs; final kr_isLoading = false.obs; final count = 0.obs; final EasyRefreshController refreshController = EasyRefreshController(); @@ -65,6 +66,7 @@ class KRInviteController extends GetxController { totalCommission: 0, ); kr_referCode.value = ''; + kr_refererId.value = 0; } }); if (KRAppRunData.getInstance().kr_isLogin.value) { @@ -87,11 +89,14 @@ class KRInviteController extends GetxController { KRLogUtil.kr_i(' - kr_isLogin: ${appData.kr_isLogin.value}', tag: 'InviteController'); KRLogUtil.kr_i(' - kr_account: ${appData.kr_account.value}', tag: 'InviteController'); KRLogUtil.kr_i(' - kr_referCode: ${appData.kr_referCode.value}', tag: 'InviteController'); + KRLogUtil.kr_i(' - kr_refererId: ${appData.kr_refererId.value}', tag: 'InviteController'); KRLogUtil.kr_i(' - kr_balance: ${appData.kr_balance.value}', tag: 'InviteController'); KRLogUtil.kr_i(' - kr_commission: ${appData.kr_commission.value}', tag: 'InviteController'); kr_referCode.value = appData.kr_referCode.value; + kr_refererId.value = appData.kr_refererId.value; KRLogUtil.kr_i('📋 [InviteController] 获取到邀请码: "${kr_referCode.value}"', tag: 'InviteController'); + KRLogUtil.kr_i('📋 [InviteController] 获取到邀请人ID: ${kr_refererId.value}', tag: 'InviteController'); if (kr_referCode.value.isEmpty) { KRLogUtil.kr_w('⚠️ [InviteController] 邀请码为空!', tag: 'InviteController'); @@ -148,19 +153,23 @@ class KRInviteController extends GetxController { /// 处理绑定邀请码 Future kr_handleBindInviteCode() async { final text = otherInviteCodeController.text; - print('输入的邀请码是: $text'); if (text.isEmpty) { KRCommonUtil.kr_showToast('请输入邀请码'); return; } + if (text.trim().toLowerCase() == kr_referCode.value.trim().toLowerCase()) { + KRCommonUtil.kr_showToast('您不可以邀请自己'); + return; + } try { final either = await KRUserApi().hi_inviteCode(text); either.fold( (error) => KRCommonUtil.kr_showToast(error.msg), - (affiliateCount) { + (success) { KRCommonUtil.kr_showToast('绑定成功: $text'); otherInviteCodeController.text = ''; + _kr_fetchUserInfo(); // 刷新用户信息以更新 refererId 并隐藏输入框 }, ); } catch (e) { diff --git a/lib/app/modules/kr_invite/views/kr_invite_view.dart b/lib/app/modules/kr_invite/views/kr_invite_view.dart index 9de5b0a..5deb99a 100755 --- a/lib/app/modules/kr_invite/views/kr_invite_view.dart +++ b/lib/app/modules/kr_invite/views/kr_invite_view.dart @@ -29,187 +29,201 @@ class KRInviteView extends GetView { children: [ // 1. 背景层/滚动层 Positioned.fill( - child: SingleChildScrollView( - // 保持手动管理,增强稳定性 - keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.manual, - physics: const AlwaysScrollableScrollPhysics(), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 40.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 20.w), - // 🟢 第一行:奖励说明 - Container( - width: double.infinity, - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(25.r), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - KrLocalImage( - imageName: 'hi-home-logo', - imageType: ImageType.svg, - width: 54.w, - color: Colors.black, - ), - SizedBox(width: 16.w), - Flexible( - child: Text( - '受邀用户首次付款时,他将与您分别获得3天免费使用时长', - style: TextStyle( - color: Colors.black, - fontSize: 14.sp, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ), - - SizedBox(height: 26.w), - // 🟢 第二行:我的邀请码 - Container( - padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.w), - decoration: BoxDecoration( - border: Border.all(color: Colors.white, width: 2.0), - borderRadius: BorderRadius.circular(1000.r), - ), - child: Obx( - () => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 40.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox(height: 20.w), + // 🟢 第一行:奖励说明 Container( - width: 100.w, - height: 40.w, - alignment: Alignment.center, + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w), decoration: BoxDecoration( color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(25.r), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + KrLocalImage( + imageName: 'hi-home-logo', + imageType: ImageType.svg, + width: 54.w, + color: Colors.black, + ), + SizedBox(width: 16.w), + Flexible( + child: Text( + '受邀用户首次付款时,他将与您分别获得3天免费使用时长', + style: TextStyle( + color: Colors.black, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + + SizedBox(height: 26.w), + // 🟢 第二行:我的邀请码 + Container( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.w), + decoration: BoxDecoration( + border: Border.all(color: Colors.white, width: 2.0), borderRadius: BorderRadius.circular(1000.r), ), - child: Text( - '邀请码', - style: TextStyle( - color: Colors.black, - fontSize: 16.sp, - fontWeight: FontWeight.w600, + child: Obx( + () => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 100.w, + height: 40.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(1000.r), + ), + child: Text( + '邀请码', + style: TextStyle( + color: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + Expanded( + child: Text( + controller.kr_referCode.value, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 20.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + IconButton( + icon: const KrLocalImage( + imageName: 'share-icon', + imageType: ImageType.svg, + color: Colors.white, + ), + onPressed: () { + if (controller.kr_referCode.value.isNotEmpty) { + final code = controller.kr_referCode.value; + final text = '#您的好友邀请您使用Hi快网络加速器\n' + '安装完毕后,在软件内<邀请好友>页面粘贴以下邀请码\n' + '$code\n' + '您和您的好友将会分别获得3天免费时长\n\n' + '点击此处进入下载页面\n' + '或在浏览器输入hifastvpn.com下载#'; + if (GetPlatform.isIOS) { + Share.share(text, subject: '直接分享Hi快VPN邀请链接'); + } else { + Clipboard.setData(ClipboardData(text: text)); + KRCommonUtil.kr_showToast(AppTranslations.kr_invite.inviteCodeCopied); + } + } + }, + ), + ], ), ), ), - Expanded( - child: Text( - controller.kr_referCode.value, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 20.sp, - fontWeight: FontWeight.bold, - ), - ), - ), - IconButton( - icon: const KrLocalImage( - imageName: 'share-icon', - imageType: ImageType.svg, - color: Colors.white, - ), - onPressed: () { - if (controller.kr_referCode.value.isNotEmpty) { - final code = controller.kr_referCode.value; - final text = '#您的好友邀请您使用Hi快网络加速器\n' - '安装完毕后,在软件内<邀请好友>页面粘贴以下邀请码\n' - '$code\n' - '您和您的好友将会分别获得3天免费时长\n\n' - '点击此处进入下载页面\n' - '或在浏览器输入hifastvpn.com下载#'; - if (GetPlatform.isIOS) { - Share.share(text, subject: '直接分享Hi快VPN邀请链接'); - } else { - Clipboard.setData(ClipboardData(text: text)); - KRCommonUtil.kr_showToast(AppTranslations.kr_invite.inviteCodeCopied); - } - } - }, - ), + + const Spacer(), + + // 🟢 第三部分:接受他人邀请 + Obx(() { + // 只有当没有被邀请时才显示 + if (controller.kr_refererId.value != 0) { + return const SizedBox.shrink(); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '接受他人邀请', + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8.h), + RepaintBoundary( + child: TextField( + controller: controller.otherInviteCodeController, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + decoration: InputDecoration( + hintText: '填入邀请人邀请码兑换免费时长...', + hintStyle: const TextStyle(color: Color(0xFFA6A6A6)), + filled: true, + fillColor: Colors.transparent, + contentPadding: EdgeInsets.symmetric(horizontal: 22.w), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(1000.r), + borderSide: const BorderSide(color: Colors.white, width: 2.0), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(1000.r), + borderSide: const BorderSide(color: Colors.white, width: 2.0), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(1000.r), + borderSide: const BorderSide(color: Colors.white, width: 2.0), + ), + constraints: BoxConstraints(maxHeight: 50.h), + ), + ), + ), + SizedBox(height: 10.w), + SizedBox( + width: double.infinity, + height: 50.w, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000.r), + ), + ), + onPressed: () => controller.kr_handleBindInviteCode(), + child: Text( + '保存', + style: TextStyle( + color: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); + }), + // 底部留白,确保键盘弹出后能滚过遮挡区域 + SizedBox(height: 90.w), ], ), ), ), - SizedBox(height: 160.w), - // 🟢 第三部分:接受他人邀请 - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '接受他人邀请', - style: TextStyle( - color: Colors.white, - fontSize: 14.sp, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 8.h), - // 使用 RepaintBoundary 隔离,减少父级重绘对 TextField 的影响 - RepaintBoundary( - child: TextField( - controller: controller.otherInviteCodeController, - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - decoration: InputDecoration( - hintText: '填入邀请人邀请码兑换免费时长...', - hintStyle: const TextStyle(color: Color(0xFFA6A6A6)), - filled: true, - fillColor: Colors.transparent, - contentPadding: EdgeInsets.symmetric(horizontal: 22.w), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(1000.r), - borderSide: const BorderSide(color: Colors.white, width: 2.0), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(1000.r), - borderSide: const BorderSide(color: Colors.white, width: 2.0), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(1000.r), - borderSide: const BorderSide(color: Colors.white, width: 2.0), - ), - constraints: BoxConstraints(maxHeight: 50.h), - ), - ), - ), - SizedBox(height: 10.w), - SizedBox( - width: double.infinity, - height: 50.w, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(1000.r), - ), - ), - onPressed: () => controller.kr_handleBindInviteCode(), - child: Text( - '保存', - style: TextStyle( - color: Colors.black, - fontSize: 16.sp, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - // 底部留白,确保键盘弹出后能滚过遮挡区域 - SizedBox(height: 250.w), - ], - ), - ), + ), + ); + }, ), ), diff --git a/lib/app/modules/kr_language_selector/bindings/kr_language_selector_binding.dart b/lib/app/modules/kr_language_selector/bindings/kr_language_selector_binding.dart deleted file mode 100755 index 93a7f68..0000000 --- a/lib/app/modules/kr_language_selector/bindings/kr_language_selector_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/kr_language_selector_controller.dart'; - -class KRLanguageSelectorBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => KRLanguageSelectorController(), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_language_selector/controllers/kr_language_selector_controller.dart b/lib/app/modules/kr_language_selector/controllers/kr_language_selector_controller.dart deleted file mode 100755 index 4135ef6..0000000 --- a/lib/app/modules/kr_language_selector/controllers/kr_language_selector_controller.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; - -class KRLanguageSelectorController extends GetxController { - // 使用 KRLanguage 枚举来加载语言 - final RxList kr_languages = [].obs; - // 当前选中的语言代码 - final RxString kr_selectedLanguage = ''.obs; - - @override - void onInit() { - super.onInit(); - - kr_selectedLanguage.value = KRLanguageUtils.getCurrentLanguage().countryCode; - kr_loadLanguages(); - } - - // 加载语言数据 - void kr_loadLanguages() { - // 将英语放在前面 - final sortedLanguages = KRLanguage.values.toList() - ..sort((a, b) => a == KRLanguage.en ? -1 : 1); - - kr_languages.value = sortedLanguages; - } - - // 选择语言 - Future kr_selectLanguage(KRLanguage language) async { - try { - // 先更新选中状态 - kr_selectedLanguage.value = language.countryCode; - // 然后切换语言 - await KRLanguageUtils.switchLanguage(language); - } catch (err) { - Get.snackbar( - '错误', - '切换语言失败: $err', - snackPosition: SnackPosition.BOTTOM, - ); - } - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_language_selector/views/kr_language_selector_view.dart b/lib/app/modules/kr_language_selector/views/kr_language_selector_view.dart deleted file mode 100755 index 116d467..0000000 --- a/lib/app/modules/kr_language_selector/views/kr_language_selector_view.dart +++ /dev/null @@ -1,115 +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/localization/kr_language_utils.dart'; -import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; -import '../controllers/kr_language_selector_controller.dart'; - -class KRLanguageSelectorView extends GetView { - const KRLanguageSelectorView({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - backgroundColor: Theme.of(context).primaryColor, - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color.fromRGBO(23, 151, 255, 0.15), // 渐变开始颜色 - Color.fromRGBO(23, 151, 255, 0.05), // 中间过渡颜色 - // 非渐变色区域 - ], - stops: [0.0, 0.28], // 调整渐变结束位置 - ), - ), - child: Column( - children: [ - AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - size: 20.r, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - onPressed: () => Get.back(), - ), - title: Text( - AppTranslations.kr_setting.switchLanguage, - style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - centerTitle: true, - ), - Expanded( - child: Obx( - () => ListView.separated( - padding: EdgeInsets.all(16.r), - itemCount: controller.kr_languages.length, - separatorBuilder: (context, index) => SizedBox(height: 12.h), - itemBuilder: (context, index) { - final language = controller.kr_languages[index]; - return _kr_buildLanguageCard(language, context); - }, - ), - ), - ), - ], - ), - ), - ); - } - - // 构建语言卡片 - Widget _kr_buildLanguageCard(KRLanguage language, BuildContext context) { - return InkWell( - onTap: () => controller.kr_selectLanguage(language), - child: Container( - padding: EdgeInsets.all(16.r), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12.r), - ), - child: Row( - children: [ - // 国旗图标 - CircleAvatar( - radius: 16.r, - backgroundColor: Colors.blue.withOpacity(0.1), - child: Text( - language.flagEmoji, - style: TextStyle(fontSize: 16), - ), - ), - SizedBox(width: 12.w), - // 语言名称 - Text( - language.languageName, - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - const Spacer(), - // 选中标记 - if (controller.kr_selectedLanguage.value == language.countryCode) - Icon( - Icons.check_circle, - color: Colors.blue, - size: 20.r, - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_login/controllers/kr_login_controller.dart b/lib/app/modules/kr_login/controllers/kr_login_controller.dart index 5654b95..336306a 100755 --- a/lib/app/modules/kr_login/controllers/kr_login_controller.dart +++ b/lib/app/modules/kr_login/controllers/kr_login_controller.dart @@ -80,6 +80,7 @@ class KRLoginController extends GetxController /// 验证码倒计时 var _countdown = 60; // 倒计时初始值 + DateTime? _endTime; // 倒计时结束时间 late Timer _timer; var kr_countdownText = AppTranslations.kr_login.sendCode.obs; @@ -294,6 +295,11 @@ class KRLoginController extends GetxController return; } + if (!validateEmail(accountController.text.trim())) { + KRCommonUtil.kr_showToast('请输入有效的邮箱地址'); + return; + } + if (psdController.text.isEmpty) { KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterPassword); return; @@ -306,7 +312,12 @@ class KRLoginController extends GetxController /// 发送验证码(仅支持邮箱) void kr_sendCode() async { if (accountController.text.isEmpty) { - KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterAccount); + KRCommonUtil.kr_showToast('请输入邮箱'); + return; + } + + if (!validateEmail(accountController.text.trim())) { + KRCommonUtil.kr_showToast('请输入有效的邮箱地址'); return; } @@ -356,8 +367,14 @@ class KRLoginController extends GetxController /// 开始注册(仅支持邮箱,验证码和邀请码可选) void kr_register() async { // 验证邮箱 - if (accountController.text.isEmpty) { - KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterAccount); + final email = accountController.text.trim(); + if (email.isEmpty) { + KRCommonUtil.kr_showToast('请输入邮箱'); + return; + } + + if (!validateEmail(email)) { + KRCommonUtil.kr_showToast('请输入有效的邮箱地址'); return; } @@ -509,21 +526,31 @@ class KRLoginController extends GetxController /// 开始倒计时 void _startCountdown() { kr_canSendCode.value = false; + _endTime = DateTime.now().add(const Duration(seconds: 60)); - _timer = Timer.periodic(Duration(seconds: 1), (timer) { - if (_countdown > 0) { - _countdown -= 1; - kr_countdownText.value = "${_countdown}s"; + _timer.cancel(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + final now = DateTime.now(); + if (_endTime != null && _endTime!.isAfter(now)) { + final remaining = _endTime!.difference(now).inSeconds; + _countdown = remaining; + kr_countdownText.value = "${remaining}s"; } else { kr_canSendCode.value = true; kr_countdownText.value = AppTranslations.kr_login.sendCode; _countdown = 60; + _endTime = null; timer.cancel(); _onCountdownFinished(); } }); } + /// 验证邮箱格式 + bool validateEmail(String str) { + return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$').hasMatch(str); + } + /// 设置登录数据(仅支持邮箱) void _saveLoginData(String token) { KRAppRunData.getInstance().kr_saveUserInfo( diff --git a/lib/app/modules/kr_login/controllers/kr_search_area_controller.dart b/lib/app/modules/kr_login/controllers/kr_search_area_controller.dart deleted file mode 100755 index 0919398..0000000 --- a/lib/app/modules/kr_login/controllers/kr_search_area_controller.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:get/get.dart'; - -import 'package:kaer_with_panels/app/model/kr_area_code.dart'; // 假设这个文件中有 KRAreaCode 类 - -class KRSearchAreaController extends GetxController { - final areas = [].obs; - final searchQuery = ''.obs; - - @override - void onInit() { - super.onInit(); - areas.assignAll(KRAreaCode.kr_getCodeList()); - } - - List get filteredAreas { - if (searchQuery.value.isEmpty) { - return areas; - } else { - return areas - .where((area) => area.kr_dialCode.toLowerCase().contains(searchQuery.value.toLowerCase())) - .toList(); - } - } -} \ No newline at end of file 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 d8a2e75..71d767c 100755 --- a/lib/app/modules/kr_login/views/kr_login_view.dart +++ b/lib/app/modules/kr_login/views/kr_login_view.dart @@ -117,46 +117,6 @@ class KRLoginView extends GetView { ); } - Widget _buildContentByEntry() { - final entry = (Get.arguments as Map?)?['entry']; - if (entry == 'forget_psd') { - return _buildForgetPasswordLayout(); - } else if (entry == 'bind_email') { - return _buildBindEmailLayout(); - } else if (entry == 'login') { - return _buildLoginEmailLayout(); - } - - return _buildForgetPasswordLayout(); // 默认显示修改密码 - } - - Widget _buildForgetPasswordLayout() { - return Column( - children: [ - Container( - constraints: BoxConstraints(minHeight: 300.w), - child: Column( - children: [ - _buildStandardInputField( - controller: controller.psdController, - hintText: '新密码', - isPassword: true, - ), - SizedBox(height: 10.w), - _buildStandardInputField( - controller: controller.agPsdController, - hintText: '确认密码', - isPassword: true, - ), - ], - ), - ), - SizedBox(height: 30.h), - _buildSaveButton(), - ], - ); - } - Widget _buildBindEmailLayout() { return Column( children: [ @@ -192,33 +152,6 @@ class KRLoginView extends GetView { ); } - Widget _buildLoginEmailLayout() { - return Column( - children: [ - Container( - constraints: BoxConstraints(minHeight: 300.w), - child: Column( - children: [ - _buildStandardInputField( - controller: controller.accountController, - hintText: 'Email', - ), - SizedBox(height: 10), - _buildStandardInputField( - controller: controller.psdController, - hintText: '密码', - isPassword: true, - ), - SizedBox(height: 10.w), - ], - ), - ), - SizedBox(height: 30.h), - _buildSaveButton(), - ], - ); - } - /// 构建标准输入框 Widget _buildStandardInputField({ required TextEditingController controller, @@ -283,14 +216,6 @@ class KRLoginView extends GetView { inputFormatters: [FilteringTextInputFormatter.digitsOnly], onChanged: (value) { var v = value.replaceAll(RegExp("\\s+"), ""); - if (v.length % 2 == 0 && v.isNotEmpty) { - final half = v.length ~/ 2; - final first = v.substring(0, half); - final second = v.substring(half); - if (first == second) { - v = first; - } - } const maxLen = 6; if (v.length > maxLen) { v = v.substring(0, maxLen); diff --git a/lib/app/modules/kr_login/views/kr_search_area_code_view.dart b/lib/app/modules/kr_login/views/kr_search_area_code_view.dart deleted file mode 100755 index e4ab2ec..0000000 --- a/lib/app/modules/kr_login/views/kr_search_area_code_view.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/model/kr_area_code.dart'; -import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; - -import '../controllers/kr_search_area_controller.dart'; - -class KRSearchAreaView extends GetView { - final Function(KRAreaCodeItem, int) onSelect; - - const KRSearchAreaView({super.key, required this.onSelect}); - - static void show(Function(KRAreaCodeItem, int) onSelect) { - Get.dialog( - KRSearchAreaView(onSelect: onSelect), - barrierDismissible: true, - ); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); // 获取当前主题 - Get.lazyPut( - () => KRSearchAreaController(), - ); - - - return GestureDetector( - onTap: () => Get.back(), // 点击背景关闭弹框 - child: Scaffold( - backgroundColor: Colors.black.withOpacity(0.0), - body: Center( - child: GestureDetector( - onTap: () {}, // 阻止点击事件传递到背景 - child: Container( - width: 300.w, - height: 450.h, - padding: EdgeInsets.all(16.w), - decoration: BoxDecoration( - color: theme.primaryColor, - borderRadius: BorderRadius.circular(15.w), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'login.selectOtherRegion'.tr, - style: KrAppTextStyle( - fontSize: 15.w, - fontWeight: FontWeight.w500, - color: theme.textTheme.titleMedium?.color), - ), - SizedBox(height: 10.h), - TextField( - onChanged: (value) => controller.searchQuery.value = value, - decoration: InputDecoration( - prefixIcon: Icon(Icons.search, color: Colors.grey), - hintText: 'login.search'.tr, - hintStyle: TextStyle(color: Colors.grey), - filled: true, - // fillColor: Colors.grey.shade200, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.w), - borderSide: BorderSide.none, - ), - contentPadding: EdgeInsets.symmetric(vertical: 10.h), - ), - style: theme.textTheme.bodyMedium?.copyWith(fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular',), - ), - // SizedBox(height: 5.h), - Obx(() => Expanded( - child: ListView.builder( - padding: EdgeInsets.zero, - shrinkWrap: true, - itemCount: controller.filteredAreas.length, - itemBuilder: (context, index) { - final area = controller.filteredAreas[index]; - return GestureDetector( - onTap: () { - onSelect(area, index); // 调用回调函数 - Get.back(); - }, - child: Container( - padding: EdgeInsets.symmetric( - vertical: 10.h, horizontal: 0), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.grey.shade300, - width: 0.2, - ), - ), - ), - child: Row( - children: [ - - Text(area.kr_icon, - style: TextStyle(fontSize: 20.w)), - SizedBox(width: 12.w), - Expanded( - child: Text( - area.kr_name, - style: KrAppTextStyle( - fontSize: 13.w, - fontWeight: FontWeight.w500), - ), - ), - Text( - "+" + area.kr_dialCode, - style: KrAppTextStyle( - fontSize: 13.w, - fontWeight: FontWeight.w500), - ), - ], - ), - ), - ); - }, - ), - )), - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart b/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart index 46b58c2..e1b8357 100755 --- a/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart +++ b/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart @@ -246,13 +246,13 @@ class KRPurchaseMembershipController extends GetxController { /// 获取用户已订阅套餐 Future kr_getAlreadySubscribe() async { - final either = await _kr_subscribeApi.kr_getAlreadySubscribe(); + final either = await _kr_subscribeApi.kr_getAlreadySubscribe(includeExpired: 'all'); either.fold( (error) => KRCommonUtil.kr_showToast(error.msg), (alreadySubscribe) { _kr_alreadySubscribe = alreadySubscribe; KRLogUtil.kr_i( - '已订阅套餐: ${_kr_alreadySubscribe.map((e) => e.subscribeId).toList()}', + '已获取所有订阅记录(含过期): ${_kr_alreadySubscribe.map((e) => "ID:${e.userSubscribeId}, SubID:${e.subscribeId}").toList()}', tag: 'PurchaseMembershipController'); }, ); @@ -558,19 +558,18 @@ class KRPurchaseMembershipController extends GetxController { // ========================================================================= final quantity = kr_getSelectedQuantity(); - // 判断是续订还是新购 - final isRenewal = _kr_alreadySubscribe - .any((subscribe) => subscribe.subscribeId == selectedPlan.kr_id); - final subscribeId = isRenewal - ? _kr_alreadySubscribe - .firstWhere( - (subscribe) => subscribe.subscribeId == selectedPlan.kr_id, - orElse: () => - KRAlreadySubscribe(userSubscribeId: 0, subscribeId: 0), // 默认值 - ) - .userSubscribeId + // 判断是续订还是新购:查找匹配选中套餐 ID 的最后一项记录(最新的记录,可能已过期) + final matchingSubscribes = _kr_alreadySubscribe + .where((subscribe) => subscribe.subscribeId == selectedPlan.kr_id) + .toList(); + + final bool isRenewal = matchingSubscribes.isNotEmpty; + final int subscribeId = isRenewal + ? matchingSubscribes.last.userSubscribeId : 0; + print('📊 [Purchase] 订阅判断: isRenewal=$isRenewal, userSubscribeId=$subscribeId'); + // 根据判断结果调用不同的接口 final purchaseEither = isRenewal ? await _kr_subscribeApi.kr_renewal( diff --git a/lib/app/modules/kr_purchase_membership/widgets/kr_plan_details_dialog.dart b/lib/app/modules/kr_purchase_membership/widgets/kr_plan_details_dialog.dart deleted file mode 100755 index 7104ebd..0000000 --- a/lib/app/modules/kr_purchase_membership/widgets/kr_plan_details_dialog.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/model/response/kr_package_list.dart'; -import 'package:kaer_with_panels/app/localization/app_translations.dart'; - -/// 套餐详情弹框 -class KRPlanDetailsDialog extends StatelessWidget { - final List kr_features; - - const KRPlanDetailsDialog({ - Key? key, - required this.kr_features, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Container( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppTranslations.kr_purchaseMembership.planDetails, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Get.back(), - ), - ], - ), - const SizedBox(height: 16), - Flexible( - child: ListView.builder( - shrinkWrap: true, - itemCount: kr_features.length, - itemBuilder: (context, index) { - final feature = kr_features[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - feature.kr_label, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - ...feature.kr_details.map((detail) => Padding( - padding: const EdgeInsets.only(left: 16, bottom: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon( - Icons.check_circle_outline, - size: 16, - color: Colors.green, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - detail.kr_description, - style: const TextStyle( - fontSize: 14, - color: Colors.black87, - ), - ), - ), - ], - ), - )), - if (index < kr_features.length - 1) - const Divider(height: 24), - ], - ); - }, - ), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_setting/bindings/kr_setting_binding.dart b/lib/app/modules/kr_setting/bindings/kr_setting_binding.dart deleted file mode 100755 index fc2da66..0000000 --- a/lib/app/modules/kr_setting/bindings/kr_setting_binding.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:get/get.dart'; - -import '../controllers/kr_setting_controller.dart'; - -class KRSettingBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => KRSettingController(), - ); - } -} diff --git a/lib/app/modules/kr_setting/controllers/kr_setting_controller.dart b/lib/app/modules/kr_setting/controllers/kr_setting_controller.dart deleted file mode 100755 index bf5eb44..0000000 --- a/lib/app/modules/kr_setting/controllers/kr_setting_controller.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; -import 'package:kaer_with_panels/app/routes/app_pages.dart'; -import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; -import 'package:kaer_with_panels/app/utils/kr_country_util.dart'; -import '../../../localization/app_translations.dart'; -import '../../../themes/kr_theme_service.dart'; -import 'package:flutter/material.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:kaer_with_panels/app/common/app_run_data.dart'; -import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; - -class KRSettingController extends GetxController { - // 创建 AppTranslationsSetting 的实例 - final AppTranslationsSetting kr_appTranslationsSetting = - AppTranslationsSetting(); - - // 当前选择的国家 - final RxString kr_currentCountry = ''.obs; - - // 自动连接开关 - final RxBool kr_autoConnect = true.obs; - - // 通知开关 - final RxBool kr_notification = true.obs; - - // 帮助改进开关 - final RxBool kr_helpImprove = true.obs; - - // 版本号 - final RxString kr_version = ''.obs; - - // IOS评分 - final String kr_iosRating = ''; - - // 当前语言 - final RxString kr_language = ''.obs; - - // 当前主题选项 - final RxString kr_themeOption = ''.obs; - - final RxString kr_vpnMode = ''.obs; - - final RxString kr_vpnModeRemark = ''.obs; - - // 修改 VPN 模式切换方法 - void kr_changeVPNMode(String mode) { - KRLogUtil.kr_i('设置的VPN模式文本: ${kr_vpnMode.value}', tag: 'SettingController'); - } - - // 切换语言 - void kr_changeLanguage() { - Get.toNamed(Routes.KR_LANGUAGE_SELECTOR); - } - - // 删除账号 - void kr_deleteAccount() { - // 检查是否已登录 - if (!KRAppRunData.getInstance().kr_isLogin.value) { - // 如果未登录,跳转到登录页面 - // Get.toNamed(Routes.MR_LOGIN); - return; - } - // 已登录,跳转到删除账号页面 - Get.toNamed(Routes.KR_DELETE_ACCOUNT); - } - - @override - void onInit() { - super.onInit(); - _loadThemeOption(); - kr_language.value = KRLanguageUtils.getCurrentLanguage().languageName; - - // 语言变化时更新所有翻译文本 - ever(KRLanguageUtils.kr_language, (_) { - kr_language.value = KRLanguageUtils.kr_language.value; - _loadThemeOption(); - - kr_currentCountry.value = ""; - kr_currentCountry.value = KRCountryUtil.kr_getCurrentCountryName(); - - kr_vpnMode.value = ''; - kr_vpnMode.value = - kr_getConnectionTypeString(KRSingBoxImp().kr_connectionType.value); - - kr_vpnModeRemark.value = ''; - kr_vpnModeRemark.value = kr_getConnectionTypeRemark(KRSingBoxImp().kr_connectionType.value); - }); - - ever(KRCountryUtil.kr_currentCountry, (_) { - kr_currentCountry.value = KRCountryUtil.kr_getCurrentCountryName(); - }); - - kr_currentCountry.value = KRCountryUtil.kr_getCurrentCountryName(); - kr_vpnMode.value = - kr_getConnectionTypeString(KRSingBoxImp().kr_connectionType.value); - kr_vpnModeRemark.value = kr_getConnectionTypeRemark(KRSingBoxImp().kr_connectionType.value); - _kr_getVersion(); - } - - String kr_getConnectionTypeString(KRConnectionType type) { - switch (type) { - case KRConnectionType.global: - return AppTranslations.kr_setting.connectionTypeGlobal; - case KRConnectionType.rule: - return AppTranslations.kr_setting.connectionTypeRule; - // case KRConnectionType.direct: - // return AppTranslations.kr_setting.connectionTypeDirect; - } - } - - String kr_getConnectionTypeRemark(KRConnectionType type) { - - switch (type) { - case KRConnectionType.global: - return AppTranslations.kr_setting.connectionTypeGlobalRemark; - case KRConnectionType.rule: - return AppTranslations.kr_setting.connectionTypeRuleRemark; - // case KRConnectionType.direct: - // return AppTranslations.kr_setting.connectionTypeDirectRemark; - } - } - - void _loadThemeOption() async { - final KRThemeService themeService = KRThemeService(); - await themeService.init(); - - switch (themeService.kr_Theme) { - case ThemeMode.system: - kr_themeOption.value = AppTranslations.kr_setting.system; - break; - case ThemeMode.light: - kr_themeOption.value = AppTranslations.kr_setting.light; - break; - case ThemeMode.dark: - kr_themeOption.value = AppTranslations.kr_setting.dark; - break; - } - } - - final count = 0.obs; - - @override - void onReady() { - super.onReady(); - } - - @override - void onClose() { - super.onClose(); - } - - void increment() => count.value++; - - void kr_updateConnectionType(KRConnectionType newType) { - if (KRSingBoxImp().kr_connectionType.value != newType) { - KRLogUtil.kr_i('更新连接类型: $newType', tag: 'SettingController'); - KRSingBoxImp().kr_updateConnectionType(newType); - kr_vpnMode.value = kr_getConnectionTypeString(newType); - kr_vpnModeRemark.value = kr_getConnectionTypeRemark(newType); - // 这里可以添加其他需要的逻辑 - } - } - - // 获取版本号 - Future _kr_getVersion() async { - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - kr_version.value = packageInfo.version; - } -} diff --git a/lib/app/modules/kr_setting/views/kr_setting_view.dart b/lib/app/modules/kr_setting/views/kr_setting_view.dart deleted file mode 100755 index b2d21c4..0000000 --- a/lib/app/modules/kr_setting/views/kr_setting_view.dart +++ /dev/null @@ -1,493 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:get/get.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; -import '../../../services/singbox_imp/kr_sing_box_imp.dart'; -import '../controllers/kr_setting_controller.dart'; -import '../../../themes/kr_theme_service.dart'; -import '../../../localization/app_translations.dart'; -import '../../../routes/app_pages.dart'; -import '../../../common/app_run_data.dart'; - -class KRSettingView extends GetView { - const KRSettingView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - backgroundColor: Theme.of(context).primaryColor, - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - size: 20.r, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - onPressed: () => Get.back(), - ), - centerTitle: true, - title: Text( - AppTranslations.kr_setting.title, - style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - body: Obx(() { - return Container( - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color.fromRGBO(23, 151, 255, 0.15), - Color.fromRGBO(23, 151, 255, 0.05), - ], - stops: [0.0, 0.3], - ), - ), - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: kToolbarHeight + 20.w), - _kr_buildSectionTitle( - context, AppTranslations.kr_setting.vpnConnection), - _kr_buildVPNSection(context), - _kr_buildSectionTitle( - context, AppTranslations.kr_setting.general), - _kr_buildGeneralSection(context), - SizedBox(height: 100.h), - ], - ), - ), - ), - ); - }), - ); - } - - Widget _kr_buildSectionTitle(BuildContext context, String title) { - return Padding( - padding: EdgeInsets.fromLTRB(16.w, 24.h, 16.w, 8.h), - child: Text( - title, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - ); - } - - Widget _kr_buildVPNSection(BuildContext context) { - return Container( - margin: EdgeInsets.symmetric(horizontal: 16.w), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12.r), - ), - child: Column( - children: [ - _kr_buildSelectionTile( - context, - title: AppTranslations.kr_setting.mode, - value: controller.kr_vpnMode.value, - // subtitle: controller.kr_vpnModeRemark.value, - onTap: () => _kr_showRouteRuleSelectionSheet(context), - ), - _kr_buildDivider(), - // _kr_buildSwitchTile( - // context, - // title: AppTranslations.kr_setting.autoConnect, - // value: controller.kr_autoConnect, - // onChanged: (value) => controller.kr_autoConnect.value = value, - // ), - // _kr_buildDivider(), - // _kr_buildSelectionTile( - // context, - // title: AppTranslations.kr_setting.routeRule, - // value: controller.kr_routeRule.value, - // onTap: () => _kr_showRouteRuleSelectionSheet(context), - // ), - // _kr_buildDivider(), - _kr_buildSelectionTile( - context, - title: AppTranslations.kr_setting.countrySelector, - subtitle: AppTranslations.kr_setting.connectionTypeRuleRemark, - value: controller.kr_currentCountry.value, - onTap: () => Get.toNamed(Routes.KR_COUNTRY_SELECTOR), - ), - ], - ), - ); - } - - void _kr_showVPNModeSelectionSheet(BuildContext context) { - Get.bottomSheet( - Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), - ), - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom + 16.r), - child: Wrap( - children: [ - ListTile( - title: Center( - child: Text(AppTranslations.kr_setting.vpnModeSmart), - ), - onTap: () { - controller.kr_changeVPNMode(AppTranslations.kr_setting.vpnModeSmart); - Get.back(); - }, - ), - ListTile( - title: Center( - child: Text(AppTranslations.kr_setting.vpnModeSecure), - ), - onTap: () { - controller.kr_changeVPNMode(AppTranslations.kr_setting.vpnModeSecure); - Get.back(); - }, - ), - ], - ), - ), - ), - ); - } - - void _kr_showRouteRuleSelectionSheet(BuildContext context) { - Get.bottomSheet( - Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), - ), - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom + 16.r), - child: Wrap( - children: KRConnectionType.values.map((type) { - return ListTile( - title: Center( - child: Text( - controller.kr_getConnectionTypeString(type), - style: KrAppTextStyle( - fontSize: 16, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - ), - onTap: () { - controller.kr_updateConnectionType(type); - Get.back(); - }, - ); - }).toList(), - ), - ), - ), - ); - } - - Widget _kr_buildGeneralSection(BuildContext context) { - return Container( - margin: EdgeInsets.symmetric(horizontal: 16.w), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(12.r), - ), - child: Column( - children: [ - Obx(() => _kr_buildSelectionTile( - context, - title: AppTranslations.kr_setting.appearance, - value: controller.kr_themeOption.value, - onTap: () => _showThemeSelectionSheet(context), - )), - _kr_buildDivider(), - _kr_buildSwitchTile( - context, - title: AppTranslations.kr_setting.notifications, - value: controller.kr_notification, - onChanged: (value) => controller.kr_notification.value = value, - ), - _kr_buildDivider(), - _kr_buildSwitchTile( - context, - title: AppTranslations.kr_setting.helpImprove, - value: controller.kr_helpImprove, - onChanged: (value) => controller.kr_helpImprove.value = value, - ), - _kr_buildDivider(), - Obx(() { - final appRunData = KRAppRunData.getInstance(); - final isLoggedIn = appRunData.kr_isLogin.value; - final isDeviceLogin = appRunData.isDeviceLogin(); - - if (!isLoggedIn) { - // 未登录,不显示此项 - return SizedBox.shrink(); - } else if (isDeviceLogin) { - // 设备登录(游客模式),显示"点击这里登录/注册" - return _kr_buildActionTile( - context, - title: 'userInfo.loginRegister'.tr, - trailing: "", - onTap: () => Get.toNamed(Routes.MR_LOGIN), - ); - } else { - // 正常登录,显示用户邮箱 - final userEmail = appRunData.kr_account.value ?? AppTranslations.kr_userInfo.myAccount; - return _kr_buildActionTile( - context, - title: userEmail, - trailing: AppTranslations.kr_setting.goToDelete, - onTap: controller.kr_deleteAccount, - ); - } - }), - _kr_buildDivider(), - // _kr_buildTitleTile( - // context, - // title: AppTranslations.kr_setting.rateUs, - // ), - // _kr_buildDivider(), - Obx(() => _kr_buildValueTile( - context, - title: AppTranslations.kr_setting.version, - value: controller.kr_version.value, - )), - _kr_buildDivider(), - _kr_buildSelectionTile( - context, - title: AppTranslations.kr_setting.switchLanguage, - value: controller.kr_language.value, - onTap: controller.kr_changeLanguage, - ), - ], - ), - ); - } - - void _showThemeSelectionSheet(BuildContext context) { - Get.bottomSheet( - Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), - ), - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom + 16.r), - child: Wrap( - children: ThemeMode.values.map((option) { - String optionText; - switch (option) { - case ThemeMode.system: - optionText = AppTranslations.kr_setting.system; - break; - case ThemeMode.light: - optionText = AppTranslations.kr_setting.light; - break; - case ThemeMode.dark: - optionText = AppTranslations.kr_setting.dark; - break; - } - return ListTile( - title: Center( - child: Text( - optionText, - style: KrAppTextStyle( - fontSize: 16, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - ), - onTap: () async { - final KRThemeService themeService = KRThemeService(); - await themeService.kr_switchTheme(option); - - controller.kr_themeOption.value = optionText; - Get.back(); - }, - ); - }).toList(), - ), - ), - ), - ); - } - - Widget _kr_buildSelectionTile( - BuildContext context, { - required String title, - required String value, - String? subtitle, - required VoidCallback onTap, - }) { - return ListTile( - title: Text( - title, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - subtitle: subtitle != null - ? Text( - subtitle, - style: KrAppTextStyle( - fontSize: 12, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ) - : null, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - value, - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - SizedBox(width: 4.w), - Icon( - Icons.arrow_forward_ios, - size: 16.r, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ], - ), - onTap: onTap, - ); - } - - Widget _kr_buildSwitchTile( - BuildContext context, { - required String title, - String? subtitle, - required RxBool value, - required Function(bool) onChanged, - }) { - return ListTile( - title: Text( - title, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - subtitle: subtitle != null - ? Text( - subtitle, - style: KrAppTextStyle( - fontSize: 12, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ) - : null, - trailing: Obx( - () => CupertinoSwitch( - value: value.value, - onChanged: onChanged, - activeColor: Colors.blue, - ), - ), - ); - } - - Widget _kr_buildActionTile( - BuildContext context, { - required String title, - required String trailing, - required VoidCallback onTap, - }) { - return ListTile( - title: Text( - title, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ), - trailing: Text( - trailing, - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ), - onTap: onTap, - ); - } - - Widget _kr_buildTitleTile( - BuildContext context, { - required String title, - }) { - return ListTile( - title: Text( - title, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - ); - } - - Widget _kr_buildValueTile( - BuildContext context, { - required String title, - required String value, - }) { - return ListTile( - title: Text( - title, - style: KrAppTextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - trailing: Text( - value, - style: KrAppTextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodySmall?.color, - ), - ), - ); - } - - Widget _kr_buildDivider() { - return Divider( - height: 1.h, - thickness: 0.2, - color: const Color(0xFFEEEEEE), - ); - } -} diff --git a/lib/app/modules/kr_webview/bindings/kr_webview_binding.dart b/lib/app/modules/kr_webview/bindings/kr_webview_binding.dart deleted file mode 100755 index 0bd25f1..0000000 --- a/lib/app/modules/kr_webview/bindings/kr_webview_binding.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/kr_webview_controller.dart'; - -class KRWebViewBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut( - () => KRWebViewController(), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/kr_webview/controllers/kr_webview_controller.dart b/lib/app/modules/kr_webview/controllers/kr_webview_controller.dart deleted file mode 100755 index e7061c8..0000000 --- a/lib/app/modules/kr_webview/controllers/kr_webview_controller.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'package:get/get.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'dart:io' show Platform; - -import 'package:kaer_with_panels/app/services/api_service/api.dart'; -import 'package:kaer_with_panels/app/services/api_service/kr_web_api.dart'; -import 'package:kaer_with_panels/app/localization/app_translations.dart'; - -import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; - -/// WebView 控制器 -/// 用于管理 WebView 的状态和行为 -class KRWebViewController extends GetxController { - // 页面加载状态 - final RxBool kr_isLoading = true.obs; - - // 页面标题 - final RxString kr_title = ''.obs; - - // WebView 控制器 - late final WebViewController kr_webViewController; - - // 默认URL - static const String kr_defaultUrl = ''; - - final String kr_url = Get.arguments['url'] as String; - - // Web API 实例 - final KRWebApi _kr_webApi = KRWebApi(); - - // 内容类型 - final RxBool kr_isHtml = false.obs; - final RxBool kr_isMarkdown = false.obs; - final RxString kr_content = ''.obs; - - @override - void onInit() { - super.onInit(); - // 根据 URL 类型决定初始化方式 - if (kr_url.contains(Api.kr_getSiteTos) || kr_url.contains(Api.kr_getSitePrivacy)) { - // 用户协议和隐私政策页面,直接获取文本内容 - if (kr_url.contains(Api.kr_getSiteTos)) { - kr_title.value = AppTranslations.kr_login.termsOfService; - } else { - kr_title.value = AppTranslations.kr_login.privacyPolicy; - } - kr_getWebText(); - } else { - // 其他页面,初始化 WebView - kr_initWebView(); - } - } - - /// 初始化 WebView - void kr_initWebView() { - kr_webViewController = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate( - onNavigationRequest: (NavigationRequest request) async { - // 只在移动平台处理支付应用跳转 - if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { - KRLogUtil.kr_i('处理支付链接: ${request.url}', tag: 'WebViewController'); - // 处理支付链接 - if (await kr_handleUrlLaunch(request.url)) { - return NavigationDecision.prevent; - } - } - return NavigationDecision.navigate; - }, - onPageStarted: kr_handlePageStarted, - onPageFinished: kr_handlePageFinished, - ), - ); - - // 检查是否是用户协议或隐私政策 - if (kr_url.contains(Api.kr_getSiteTos) || kr_url.contains(Api.kr_getSitePrivacy)) { - kr_getWebText(); - } else { - kr_webViewController.loadRequest(Uri.parse(kr_url)); - } - } - - /// 获取网页文本内容并加载到 WebView - Future kr_getWebText() async { - try { - final response = await _kr_webApi.kr_getWebText(kr_url); - response.fold( - (error) async { - KRLogUtil.kr_e('获取网页内容失败: $error', tag: 'WebViewController'); - // 如果获取失败,直接设置错误内容 - kr_content.value = 'Failed to load, please try again later'; - kr_isLoading.value = false; - }, - (content) async { - KRLogUtil.kr_i('获取到内容: $content', tag: 'WebViewController'); - // 判断内容类型,优先判断 Markdown - kr_isMarkdown.value = content.contains('**') || - content.contains('*') || - content.contains('#') || - content.contains('- ') || - content.contains('['); - kr_isHtml.value = !kr_isMarkdown.value && content.contains('<') && content.contains('>'); - - KRLogUtil.kr_i('内容类型 - Markdown: ${kr_isMarkdown.value}, HTML: ${kr_isHtml.value}', tag: 'WebViewController'); - - if (kr_isMarkdown.value) { - // 如果是 Markdown 内容,直接使用 - kr_content.value = content; - } else if (kr_isHtml.value) { - // 如果是 HTML 内容,直接使用 - kr_content.value = content; - } else { - // 如果是普通文本,直接使用 - kr_content.value = content; - } - - kr_isLoading.value = false; - }, - ); - } catch (e) { - KRLogUtil.kr_e('获取网页内容出错: $e', tag: 'WebViewController'); - kr_content.value = 'Loading error, please try again later'; - kr_isLoading.value = false; - } - } - - /// 处理页面开始加载事件 - void kr_handlePageStarted(String url) { - kr_isLoading.value = true; - } - - /// 处理页面加载完成事件 - void kr_handlePageFinished(String url) async { - kr_isLoading.value = false; - await kr_updateTitle(); - } - - /// 更新页面标题 - Future kr_updateTitle() async { - final String? kr_pageTitle = await kr_webViewController.getTitle(); - kr_title.value = kr_pageTitle ?? ''; - } - - /// 重新加载页面 - Future kr_reloadPage() async { - await kr_webViewController.reload(); - } - - /// 加载新的URL - Future kr_loadUrl(String url) async { - await kr_webViewController.loadRequest(Uri.parse(url)); - } - - /// 处理URL启动 - Future kr_handleUrlLaunch(String url) async { - try { - KRLogUtil.kr_i('正在处理URL跳转: $url', tag: 'WebViewController'); - final uri = Uri.parse(url); - // 处理支付应用和外部链接 - if (uri.scheme == 'alipays' || - uri.scheme == 'alipay' || - uri.scheme == 'weixin' || - uri.scheme == 'wx') { - KRLogUtil.kr_i('检测到支付应用scheme: ${uri.scheme}', tag: 'WebViewController'); - // 尝试打开支付应用 - if (await canLaunchUrl(uri)) { - return await launchUrl( - uri, - mode: LaunchMode.externalApplication, - ); - } - // 如果支付应用无法打开,尝试使用外部浏览器打开 - final httpUri = Uri.parse('https://${uri.host}${uri.path}?${uri.query}'); - KRLogUtil.kr_i('尝试使用浏览器打开: $httpUri', tag: 'WebViewController'); - if (await canLaunchUrl(httpUri)) { - return await launchUrl( - httpUri, - mode: LaunchMode.externalApplication, - ); - } - KRLogUtil.kr_e('无法启动URL: $url', tag: 'WebViewController'); - } - return false; - } catch (e) { - KRLogUtil.kr_e('URL跳转错误: $e', tag: 'WebViewController'); - return false; - } - } -} diff --git a/lib/app/modules/kr_webview/views/kr_webview_view.dart b/lib/app/modules/kr_webview/views/kr_webview_view.dart deleted file mode 100755 index c99641d..0000000 --- a/lib/app/modules/kr_webview/views/kr_webview_view.dart +++ /dev/null @@ -1,177 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:flutter_html/flutter_html.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import '../../../widgets/kr_app_text_style.dart'; -import '../controllers/kr_webview_controller.dart'; -import '../../../services/api_service/api.dart'; -import '../../../utils/kr_log_util.dart'; - -/// WebView 页面组件 -class KRWebView extends GetView { - const KRWebView({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: Icon( - Icons.arrow_back_ios, - size: 20.sp, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - onPressed: () => Get.back(), - ), - centerTitle: true, - title: Text( - controller.kr_title.value, - style: KrAppTextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - body: _buildBody(), - ); - } - - /// 构建主体内容 - Widget _buildBody() { - return Stack( - children: [ - _buildContent(), - _buildLoadingIndicator(), - ], - ); - } - - /// 构建内容组件 - Widget _buildContent() { - if (controller.kr_url.contains(Api.kr_getSiteTos) || - controller.kr_url.contains(Api.kr_getSitePrivacy)) { - return _buildProtocolContent(); - } else { - return _buildWebView(); - } - } - - /// 构建协议内容 - Widget _buildProtocolContent() { - return Obx(() { - if (controller.kr_isHtml.value) { - return SingleChildScrollView( - padding: EdgeInsets.all(16.w), - child: Html( - data: controller.kr_content.value, - style: { - 'body': Style( - margin: Margins.all(0), - padding: HtmlPaddings.all(0), - fontSize: FontSize(14.sp), - color: Theme.of(Get.context!).textTheme.bodySmall?.color, - fontFamily: 'AlibabaPuHuiTi-Regular', - lineHeight: LineHeight(1.4), - ), - 'p': Style( - margin: Margins.only(bottom: 8.h), - ), - 'b': Style( - fontWeight: FontWeight.bold, - ), - 'i': Style( - fontStyle: FontStyle.italic, - ), - 'a': Style( - color: Colors.blue, - textDecoration: TextDecoration.underline, - ), - }, - shrinkWrap: true, - ), - ); - } else if (controller.kr_isMarkdown.value) { - return SingleChildScrollView( - padding: EdgeInsets.all(16.w), - child: MarkdownBody( - data: controller.kr_content.value, - styleSheet: MarkdownStyleSheet( - p: TextStyle( - fontSize: 14.sp, - color: Theme.of(Get.context!).textTheme.bodySmall?.color, - fontFamily: 'AlibabaPuHuiTi-Regular', - height: 1.4, - ), - strong: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.bold, - color: Theme.of(Get.context!).textTheme.bodySmall?.color, - fontFamily: 'AlibabaPuHuiTi-Regular', - height: 1.4, - ), - em: TextStyle( - fontSize: 14.sp, - fontStyle: FontStyle.italic, - color: Theme.of(Get.context!).textTheme.bodySmall?.color, - fontFamily: 'AlibabaPuHuiTi-Regular', - height: 1.4, - ), - a: TextStyle( - fontSize: 14.sp, - color: Colors.blue, - decoration: TextDecoration.underline, - fontFamily: 'AlibabaPuHuiTi-Regular', - height: 1.4, - ), - ), - ), - ); - } else { - return SingleChildScrollView( - padding: EdgeInsets.all(16.w), - child: Text( - controller.kr_content.value, - style: TextStyle( - fontSize: 14.sp, - color: Theme.of(Get.context!).textTheme.bodySmall?.color, - fontFamily: 'AlibabaPuHuiTi-Regular', - height: 1.4, - ), - ), - ); - } - }); - } - - /// 构建 WebView 组件 - Widget _buildWebView() { - return WebViewWidget( - controller: controller.kr_webViewController, - ); - } - - /// 构建加载指示器 - Widget _buildLoadingIndicator() { - return Obx( - () => controller.kr_isLoading.value - ? const Center( - child: CircularProgressIndicator(), - ) - : const SizedBox.shrink(), - ); - } - - /// 显示错误提示 - void _showErrorSnackbar(String title, String message) { - Get.snackbar( - title, - message, - snackPosition: SnackPosition.BOTTOM, - ); - } -} diff --git a/lib/app/network/http_util.dart b/lib/app/network/http_util.dart index feae3b5..3ccf8d9 100755 --- a/lib/app/network/http_util.dart +++ b/lib/app/network/http_util.dart @@ -14,6 +14,7 @@ import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; import 'package:kaer_with_panels/app/utils/kr_common_util.dart'; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; +import 'package:kaer_with_panels/app/utils/kr_http_adapter_util.dart'; import 'package:kaer_with_panels/singbox/model/singbox_status.dart'; // import 'package:crypto/crypto.dart'; @@ -74,52 +75,11 @@ class HttpUtil { }; // 🔧 配置HttpClientAdapter 优先走本地 sing-box mixed 端口, - // 若代理不可用则回退到直连 + // 若代理不可用则回推到直连 KRLogUtil.kr_i('🔧 配置 HttpClientAdapter...', tag: 'HttpUtil'); - _dio.httpClientAdapter = IOHttpClientAdapter( - createHttpClient: () { - KRLogUtil.kr_i('📱 createHttpClient 回调被调用', tag: 'HttpUtil'); - final client = HttpClient(); - - // ✅ 优化:智能代理回退逻辑 - client.findProxy = (url) { - try { - // 检查 SingBox 是否正在运行 - final singBoxStatus = KRSingBoxImp.instance.kr_status; - final isProxyAvailable = singBoxStatus == SingboxStatus.started(); - - if (!isProxyAvailable) { - // 代理未运行,直接使用直连 - KRLogUtil.kr_i( - '🔄 代理未运行,使用直连模式: $url', - tag: 'HttpUtil', - ); - return 'DIRECT'; - } - - // 代理正在运行,使用代理配置 - final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule(); - KRLogUtil.kr_i( - '✅ 使用代理模式, url: $url, proxy: $proxyConfig', - tag: 'HttpUtil', - ); - return proxyConfig; - } catch (e) { - // 发生异常时回退到直连 - KRLogUtil.kr_w( - '⚠️ 代理配置异常,回退到直连: $e', - tag: 'HttpUtil', - ); - return 'DIRECT'; - } - }; - - // ✅ 优化:设置连接失败时的自动回退 - client.connectionTimeout = const Duration(seconds: 10); - client.badCertificateCallback = (cert, host, port) => true; - - return client; - }, + _dio.httpClientAdapter = KRHttpAdapterUtil.createAdapter( + useSingBoxProxy: true, + timeout: const Duration(seconds: 10), ); KRLogUtil.kr_i('✅ HttpUtil.initDio() 初始化完成', tag: 'HttpUtil'); } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 3fddc08..9235877 100755 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -8,10 +8,6 @@ import 'package:kaer_with_panels/app/modules/hi_node_list/views/hi_page_node_vie import 'package:kaer_with_panels/app/modules/hi_user_info/bindings/hi_user_info_binding.dart'; import 'package:kaer_with_panels/app/modules/hi_user_info/views/hi_user_info_view.dart'; -import '../modules/kr_language_selector/bindings/kr_language_selector_binding.dart'; -import '../modules/kr_language_selector/views/kr_language_selector_view.dart'; -import '../modules/kr_country_selector/bindings/kr_country_selector_binding.dart'; -import '../modules/kr_country_selector/views/kr_country_selector_view.dart'; import '../modules/kr_crisp_chat/bindings/kr_crisp_binding.dart'; import '../modules/kr_crisp_chat/views/kr_crisp_view.dart'; import '../modules/kr_delete_account/bindings/kr_delete_account_binding.dart'; @@ -26,16 +22,10 @@ import '../modules/kr_message/bindings/kr_message_binding.dart'; import '../modules/kr_message/views/kr_message_view.dart'; import '../modules/kr_purchase_membership/bindings/kr_purchase_membership_binding.dart'; import '../modules/kr_purchase_membership/views/kr_purchase_membership_view.dart'; -import '../modules/kr_setting/bindings/kr_setting_binding.dart'; -import '../modules/kr_setting/views/kr_setting_view.dart'; -import '../modules/kr_webview/bindings/kr_webview_binding.dart'; -import '../modules/kr_webview/views/kr_webview_view.dart'; import '../modules/kr_order_status/bindings/kr_order_status_binding.dart'; import '../modules/kr_order_status/views/kr_order_status_view.dart'; import '../modules/kr_splash/bindings/kr_splash_binding.dart'; import '../modules/kr_splash/views/kr_splash_view.dart'; -import '../modules/kr_device_management/bindings/kr_device_management_binding.dart'; -import '../modules/kr_device_management/views/kr_device_management_view.dart'; import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart'; import 'package:kaer_with_panels/app/widgets/swipe/swipe_wrapper.dart'; @@ -84,30 +74,12 @@ class AppPages { popGesture: false, arguments: {'showSubscriptionButton': true}, // 显示购买按钮 ), - GetPage( - name: _Paths.KR_SETTING, - page: () => SwipeWrapper.detect(() => const KRSettingView()), - binding: KRSettingBinding(), - popGesture: false, - ), GetPage( name: _Paths.KR_INVITE, page: () => SwipeWrapper.detect(() => const KRInviteView()), binding: KRInviteBinding(), popGesture: false, ), - GetPage( - name: _Paths.KR_LANGUAGE_SELECTOR, - page: () => SwipeWrapper.detect(() => const KRLanguageSelectorView()), - binding: KRLanguageSelectorBinding(), - popGesture: false, - ), - GetPage( - name: _Paths.KR_COUNTRY_SELECTOR, - page: () => SwipeWrapper.detect(() => const KRCountrySelectorView()), - binding: KRCountrySelectorBinding(), - popGesture: false, - ), GetPage( name: _Paths.KR_PURCHASE_MEMBERSHIP, page: () => SwipeWrapper.detect(() => const KRPurchaseMembershipView()), @@ -127,12 +99,6 @@ class AppPages { binding: KrDeleteAccountBinding(), popGesture: false, ), - GetPage( - name: Routes.KR_WEBVIEW, - page: () => SwipeWrapper.detect(() => const KRWebView()), - binding: KRWebViewBinding(), - popGesture: false, - ), GetPage( name: Routes.KR_ORDER_STATUS, page: () => SwipeWrapper.detect(() => const KROrderStatusView()), @@ -145,12 +111,6 @@ class AppPages { binding: KRCrispBinding(), popGesture: false, ), - GetPage( - name: _Paths.KR_DEVICE_MANAGEMENT, - page: () => SwipeWrapper.detect(() => const KRDeviceManagementView()), - binding: KRDeviceManagementBinding(), - popGesture: false, - ), GetPage( name: _Paths.HI_NODE_LIST, page: () => SwipeWrapper.detect(() => const HINodePageView()), diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 009a2f8..51610d2 100755 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -17,7 +17,6 @@ abstract class Routes { static const KR_PURCHASE_MEMBERSHIP = _Paths.KR_PURCHASE_MEMBERSHIP; static const KR_MESSAGE = _Paths.KR_MESSAGE; static const KR_DELETE_ACCOUNT = _Paths.KR_DELETE_ACCOUNT; - static const KR_WEBVIEW = _Paths.KR_WEBVIEW; static const KR_ORDER_STATUS = '/kr-order-status'; static const KR_CRISP = _Paths.KR_CRISP; static const KR_DEVICE_MANAGEMENT = _Paths.KR_DEVICE_MANAGEMENT; @@ -43,7 +42,6 @@ abstract class _Paths { static const KR_PURCHASE_MEMBERSHIP = '/kr-purchase-membership'; static const KR_MESSAGE = '/kr-message'; static const KR_DELETE_ACCOUNT = '/kr-delete-account'; - static const KR_WEBVIEW = '/kr_webview'; static const KR_CRISP = '/kr-crisp'; static const KR_DEVICE_MANAGEMENT = '/kr-device-management'; static const HI_MENU = '/hi_menu'; diff --git a/lib/app/services/api_service/kr_auth_api.dart b/lib/app/services/api_service/kr_auth_api.dart index 11fba89..5f0ad0d 100755 --- a/lib/app/services/api_service/kr_auth_api.dart +++ b/lib/app/services/api_service/kr_auth_api.dart @@ -22,6 +22,7 @@ import '../kr_site_config_service.dart'; import '../../common/app_config.dart'; import 'package:dio/dio.dart' as dio; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; +import 'package:kaer_with_panels/app/utils/kr_http_adapter_util.dart'; import 'package:flutter/foundation.dart'; class KRAuthApi { @@ -401,6 +402,13 @@ class KRAuthApi { // 使用 Dio 直接发送请求(因为需要特殊的加密处理) final dioInstance = dio.Dio(); + // ✅ 关键修复:统一使用配置好的 Adapter,包含 SSL 验证绕过和代理处理 + dioInstance.httpClientAdapter = KRHttpAdapterUtil.createAdapter( + useSingBoxProxy: false, // 设备登录建议先直连 + forceDirect: true, + timeout: const Duration(seconds: 10), + ); + final baseUrl = AppConfig.getInstance().baseUrl; final url = '$baseUrl${Api.kr_deviceLogin}'; diff --git a/lib/app/services/api_service/kr_subscribe_api.dart b/lib/app/services/api_service/kr_subscribe_api.dart index ea3085b..0421b67 100755 --- a/lib/app/services/api_service/kr_subscribe_api.dart +++ b/lib/app/services/api_service/kr_subscribe_api.dart @@ -229,8 +229,11 @@ class KRSubscribeApi { /// 获取用户已订阅套餐 Future>> - kr_getAlreadySubscribe() async { + kr_getAlreadySubscribe({String? includeExpired}) async { final Map data = {}; + if (includeExpired != null) { + data['includeExpired'] = includeExpired; + } BaseResponse baseResponse = await HttpUtil.getInstance().request( diff --git a/lib/app/services/global_overlay_service.dart b/lib/app/services/global_overlay_service.dart index 8530f9e..50da1f7 100644 --- a/lib/app/services/global_overlay_service.dart +++ b/lib/app/services/global_overlay_service.dart @@ -3,6 +3,8 @@ import 'package:get/get.dart'; import '../modules/kr_home/views/hi_subscription_corner_button.dart'; import 'package:kaer_with_panels/main.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:kaer_with_panels/app/routes/app_pages.dart'; class GlobalOverlayService extends GetxService { Color? _currentColor; @@ -24,9 +26,14 @@ class GlobalOverlayService extends GetxService { } OverlayEntry? _overlayEntry; final RxBool _isVisible = false.obs; + final RxString _currentRoute = ''.obs; bool get isVisible => _isVisible.value; + void updateCurrentRoute(String route) { + _currentRoute.value = route; + } + @override void onInit() { super.onInit(); @@ -77,20 +84,56 @@ class GlobalOverlayService extends GetxService { // 2️⃣ money-icon,独立定位,固定在屏幕右上角 Positioned( - top: MediaQuery.of(context).padding.top + 8, - right: ((radius - (statusBarHeight / 2)) / 2) + 3, - child: GestureDetector( - behavior: HitTestBehavior.translucent, // 让区域可响应点击 - onTap: () { - // ✅ 这里“代理”点击事件,转发到底层按钮动画逻辑 - print('🔥 money-icon tapped → trigger HISubscriptionCornerButton animation'); - GlobalOverlayService.instance.triggerSubscriptionAnimation(); - }, - child: IgnorePointer( - ignoring: false, // 不阻断事件 - child: KrLocalImage( - imageName: 'money-icon', - imageType: ImageType.svg, + top: MediaQuery.of(context).padding.top, + right: (radius / 2), + child: FractionalTranslation( + // Offset 的第一个参数是 x,第二个是 y + // 0.5 代表向右偏移自身宽度的 50%,-0.5 代表向左偏移自身宽度的 50% + translation: const Offset(0.5, 0), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + print('🔥 money-icon tapped'); + GlobalOverlayService.instance.triggerSubscriptionAnimation(); + }, + child: IgnorePointer( + ignoring: false, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + KrLocalImage( + imageName: 'money-icon', + imageType: ImageType.svg, + width: 32.w, // 建议固定宽度以保证计算准确 + ), + SizedBox(height: 2.w), + Obx(() { + final current = _currentRoute.value; + print('🔥 GlobalOverlayService Obx: currentRoute = $current'); + + // ✅ 结合三种方式判断:1. 显式设为透明 2. 路由名称匹配 3. 包含关键特征 + bool isHidden = _currentColor == Colors.transparent || + current == Routes.KR_PURCHASE_MEMBERSHIP || + current.contains(Routes.KR_PURCHASE_MEMBERSHIP) || + Routes.KR_PURCHASE_MEMBERSHIP.contains(current) || + current == Routes.KR_ORDER_STATUS || + current.contains('purchase-membership'); + + if (isHidden) { + return const SizedBox.shrink(); + } + + return Text( + '购买套餐', + style: TextStyle( + color: Colors.black, + fontSize: 12.sp, + ), + ); + }), + ], + ), ), ), ), diff --git a/lib/app/services/kr_device_info_service.dart b/lib/app/services/kr_device_info_service.dart index 16144cb..50e6107 100644 --- a/lib/app/services/kr_device_info_service.dart +++ b/lib/app/services/kr_device_info_service.dart @@ -225,17 +225,21 @@ class KRDeviceInfoService { } /// Windows设备ID - 使用机器GUID + /// 🔧 修复:不使用 flutter_udid,因为它会调用 wmic 命令弹出黑窗口 Future _getWindowsDeviceId() async { try { final windowsInfo = await _deviceInfo.windowsInfo; - // 优先使用 flutter_udid - String udid = await FlutterUdid.consistentUdid; + // 🔧 修复:不使用 FlutterUdid.consistentUdid + // 因为它在 Windows 上会调用 "cmd.exe /c wmic csproduct get UUID" + // 这个调用没有使用 CREATE_NO_WINDOW 标志,会弹出黑色命令行窗口 + + // 直接使用 device_info_plus 提供的信息构建唯一标识 + // windowsInfo.deviceId 已经是一个稳定的设备标识 // 构建多因子字符串 final factors = [ - udid, - windowsInfo.deviceId, // 设备ID + windowsInfo.deviceId, // 设备ID (最稳定,来自注册表 MachineGuid) windowsInfo.computerName, // 计算机名 windowsInfo.productName, // 产品名 windowsInfo.numberOfCores.toString(), // CPU核心数 diff --git a/lib/app/services/kr_site_config_service.dart b/lib/app/services/kr_site_config_service.dart index 5ea1ff4..3c6908c 100644 --- a/lib/app/services/kr_site_config_service.dart +++ b/lib/app/services/kr_site_config_service.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import '../model/response/kr_site_config.dart'; import '../common/app_config.dart'; import '../utils/kr_log_util.dart'; +import '../utils/kr_http_adapter_util.dart'; import 'singbox_imp/kr_sing_box_imp.dart'; /// 网站配置服务 @@ -25,6 +26,14 @@ class KRSiteConfigService extends ChangeNotifier { '🌐 网站配置服务:使用直连模式(不通过代理)', tag: 'KRSiteConfigService', ); + + // 🔧 关键修复:添加 SSL 证书验证跳过,强制直连 + _dio.httpClientAdapter = KRHttpAdapterUtil.createAdapter( + useSingBoxProxy: false, + forceDirect: true, + timeout: const Duration(seconds: 20), + ); + if (kDebugMode) { print('🌐 网站配置服务:使用直连模式,避免 SingBox 未初始化问题'); } diff --git a/lib/app/utils/kr_http_adapter_util.dart b/lib/app/utils/kr_http_adapter_util.dart new file mode 100644 index 0000000..1193bf5 --- /dev/null +++ b/lib/app/utils/kr_http_adapter_util.dart @@ -0,0 +1,47 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; +import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; +import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; + +class KRHttpAdapterUtil { + /// 创建标准配置的 HttpClientAdapter + /// + /// [useSingBoxProxy] 是否集成 sing-box 代理规则,默认 true + /// [forceDirect] 是否强制直连(不使用任何代理),默认 false + static HttpClientAdapter createAdapter({ + bool useSingBoxProxy = true, + bool forceDirect = false, + Duration timeout = const Duration(seconds: 10), + }) { + return IOHttpClientAdapter( + createHttpClient: () { + final client = HttpClient(); + client.connectionTimeout = timeout; + + // ✅ 统一修复:允许所有 SSL 证书 + client.badCertificateCallback = (cert, host, port) => true; + + if (forceDirect) { + client.findProxy = (url) { + KRLogUtil.kr_i('🔍 请求使用直连: $url', tag: 'KRHttpAdapterUtil'); + return 'DIRECT'; + }; + } else if (useSingBoxProxy) { + client.findProxy = (url) { + try { + final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule(); + KRLogUtil.kr_i('🔍 请求使用代理: $proxyConfig, url: $url', tag: 'KRHttpAdapterUtil'); + return proxyConfig; + } catch (e) { + KRLogUtil.kr_w('⚠️ 获取代理配置异常,回退到 DIRECT: $e', tag: 'KRHttpAdapterUtil'); + return 'DIRECT'; + } + }; + } + + return client; + }, + ); + } +} diff --git a/lib/app/utils/kr_window_manager.dart b/lib/app/utils/kr_window_manager.dart index 00fa4d2..9d6f3dc 100755 --- a/lib/app/utils/kr_window_manager.dart +++ b/lib/app/utils/kr_window_manager.dart @@ -25,8 +25,9 @@ class KRWindowManager with WindowListener, TrayListener { KRLogUtil.kr_i('kr_initWindowManager: 窗口管理器已初始化'); const WindowOptions windowOptions = WindowOptions( - size: Size(800, 668), - minimumSize: Size(400, 334), + size: Size(420, 800), + minimumSize: Size(420, 800), + maximumSize: Size(420, 800), center: true, backgroundColor: Colors.white, skipTaskbar: false, @@ -46,16 +47,20 @@ class KRWindowManager with WindowListener, TrayListener { if (Platform.isWindows) { await windowManager.setTitleBarStyle(TitleBarStyle.normal); await windowManager.setTitle('HiFastVPN'); - await windowManager.setSize(const Size(800, 668)); - await windowManager.setMinimumSize(const Size(400, 334)); + await windowManager.setSize(const Size(420, 800)); + await windowManager.setMinimumSize(const Size(420, 800)); + await windowManager.setMaximumSize(const Size(420, 800)); + await windowManager.setResizable(false); await windowManager.center(); await windowManager.show(); // 阻止窗口关闭 await windowManager.setPreventClose(true); } else { await windowManager.setTitle('HiFastVPN'); - await windowManager.setSize(const Size(800, 668)); - await windowManager.setMinimumSize(const Size(400, 334)); + await windowManager.setSize(const Size(420, 800)); + await windowManager.setMinimumSize(const Size(420, 800)); + await windowManager.setMaximumSize(const Size(420, 800)); + await windowManager.setResizable(false); await windowManager.center(); } diff --git a/lib/app/utils/kr_windows_dns_util.dart b/lib/app/utils/kr_windows_dns_util.dart index d09d37a..64fcfbd 100644 --- a/lib/app/utils/kr_windows_dns_util.dart +++ b/lib/app/utils/kr_windows_dns_util.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; +import 'package:kaer_with_panels/app/utils/kr_windows_process_util.dart'; /// Windows DNS 管理工具类 /// @@ -38,28 +39,48 @@ class KRWindowsDnsUtil { } try { - KRLogUtil.kr_i('📦 开始备份 Windows DNS 设置...', tag: 'WindowsDNS'); + // 🔒 添加5秒超时保护 + return await Future.value(() async { + KRLogUtil.kr_i('📦 开始备份 Windows DNS 设置...', tag: 'WindowsDNS'); - // 1. 获取主网络接口 - final interfaceName = await _kr_getPrimaryNetworkInterface(); - if (interfaceName == null) { - KRLogUtil.kr_e('❌ 无法获取主网络接口', tag: 'WindowsDNS'); - return false; - } - _primaryInterfaceName = interfaceName; - KRLogUtil.kr_i('🔍 主网络接口: $_primaryInterfaceName', tag: 'WindowsDNS'); + // 1. 获取主网络接口 + final interfaceName = await _kr_getPrimaryNetworkInterface(); + if (interfaceName == null) { + KRLogUtil.kr_e('❌ 无法获取主网络接口', tag: 'WindowsDNS'); + return false; + } + _primaryInterfaceName = interfaceName; + KRLogUtil.kr_i('🔍 主网络接口: $_primaryInterfaceName', tag: 'WindowsDNS'); - // 2. 获取当前 DNS 服务器 - final dnsServers = await _kr_getCurrentDnsServers(interfaceName); - if (dnsServers.isEmpty) { - KRLogUtil.kr_w('⚠️ 当前 DNS 为空,可能是自动获取', tag: 'WindowsDNS'); - _originalDnsServers = []; // 空列表表示 DHCP 自动获取 - } else { - _originalDnsServers = dnsServers; - KRLogUtil.kr_i('✅ 已备份 DNS: ${dnsServers.join(", ")}', tag: 'WindowsDNS'); - } + // 2. 获取当前 DNS 服务器 + final dnsServers = await _kr_getCurrentDnsServers(interfaceName); - return true; + // 🔧 P0修复1: 过滤掉 127.0.0.1 (sing-box 的本地 DNS) + // 原因:如果备份了 127.0.0.1,关闭 VPN 后恢复为 127.0.0.1,但 sing-box 已停止,导致 DNS 无法解析 + final validDnsServers = dnsServers.where((dns) => !dns.startsWith('127.')).toList(); + + if (validDnsServers.isEmpty) { + KRLogUtil.kr_w('⚠️ 当前 DNS 为空或全是本地地址,设为 DHCP 自动获取', tag: 'WindowsDNS'); + if (dnsServers.isNotEmpty) { + KRLogUtil.kr_i(' (已过滤的本地DNS: ${dnsServers.join(", ")})', tag: 'WindowsDNS'); + } + _originalDnsServers = []; // 空列表表示 DHCP 自动获取 + } else { + _originalDnsServers = validDnsServers; + KRLogUtil.kr_i('✅ 已备份有效 DNS: ${validDnsServers.join(", ")}', tag: 'WindowsDNS'); + if (dnsServers.length != validDnsServers.length) { + KRLogUtil.kr_i(' (已过滤掉 ${dnsServers.length - validDnsServers.length} 个本地地址)', tag: 'WindowsDNS'); + } + } + + return true; + }()).timeout( + const Duration(seconds: 5), + onTimeout: () { + KRLogUtil.kr_w('⏱️ DNS 备份操作超时(5秒),跳过备份', tag: 'WindowsDNS'); + return false; + }, + ); } catch (e) { KRLogUtil.kr_e('❌ 备份 DNS 设置失败: $e', tag: 'WindowsDNS'); return false; @@ -80,22 +101,33 @@ class KRWindowsDnsUtil { try { KRLogUtil.kr_i('🔄 开始恢复 Windows DNS 设置...', tag: 'WindowsDNS'); - // 1. 检查是否有备份 - if (_primaryInterfaceName == null) { - KRLogUtil.kr_w('⚠️ 没有备份的网络接口,尝试自动检测', tag: 'WindowsDNS'); - _primaryInterfaceName = await _kr_getPrimaryNetworkInterface(); - if (_primaryInterfaceName == null) { - KRLogUtil.kr_e('❌ 无法检测网络接口,执行兜底恢复', tag: 'WindowsDNS'); - return await _kr_fallbackRestoreDns(); - } + // 🔧 P1修复: 恢复时重新检测主接口,防止网络切换导致恢复错误接口 + final currentInterface = await _kr_getPrimaryNetworkInterface(); + if (currentInterface == null) { + KRLogUtil.kr_e('❌ 无法检测当前网络接口,执行兜底恢复', tag: 'WindowsDNS'); + return await _kr_fallbackRestoreDns(); } - // 2. 恢复原始 DNS + // 检查接口是否变化 + if (_primaryInterfaceName != null && _primaryInterfaceName != currentInterface) { + KRLogUtil.kr_w('⚠️ 网络接口已变化: $_primaryInterfaceName → $currentInterface', tag: 'WindowsDNS'); + KRLogUtil.kr_w(' 执行兜底恢复以确保当前接口DNS正常', tag: 'WindowsDNS'); + _primaryInterfaceName = currentInterface; // 更新为当前接口 + return await _kr_fallbackRestoreDns(); + } + + // 使用当前检测到的接口 + _primaryInterfaceName = currentInterface; + KRLogUtil.kr_i('🔍 当前网络接口: $_primaryInterfaceName', tag: 'WindowsDNS'); + + // 1. 检查是否有备份的DNS if (_originalDnsServers == null) { KRLogUtil.kr_w('⚠️ 没有备份的 DNS,执行兜底恢复', tag: 'WindowsDNS'); return await _kr_fallbackRestoreDns(); } + // 2. 恢复原始 DNS + if (_originalDnsServers!.isEmpty) { // 原本是 DHCP 自动获取 KRLogUtil.kr_i('🔄 恢复为 DHCP 自动获取 DNS', tag: 'WindowsDNS'); @@ -130,6 +162,15 @@ class KRWindowsDnsUtil { return await _kr_fallbackRestoreDns(); } + // 🔧 P2优化: 测试 DNS 解析是否真正可用 + KRLogUtil.kr_i('🧪 测试 DNS 解析功能...', tag: 'WindowsDNS'); + final canResolve = await _kr_testDnsResolution(); + if (!canResolve) { + KRLogUtil.kr_w('⚠️ DNS 解析测试失败,执行兜底恢复', tag: 'WindowsDNS'); + return await _kr_fallbackRestoreDns(); + } + KRLogUtil.kr_i('✅ DNS 解析测试通过', tag: 'WindowsDNS'); + return true; } catch (e) { KRLogUtil.kr_e('❌ 恢复 DNS 设置失败: $e', tag: 'WindowsDNS'); @@ -187,7 +228,7 @@ class KRWindowsDnsUtil { Future _kr_getPrimaryNetworkInterface() async { try { // 使用 netsh 获取接口列表 - final result = await Process.run('netsh', ['interface', 'show', 'interface']); + final result = await KRWindowsProcessUtil.runHidden('netsh', ['interface', 'show', 'interface']); if (result.exitCode != 0) { KRLogUtil.kr_e('❌ 获取网络接口失败: ${result.stderr}', tag: 'WindowsDNS'); @@ -271,7 +312,7 @@ class KRWindowsDnsUtil { /// 返回:DNS 服务器列表 Future> _kr_getCurrentDnsServers(String interfaceName) async { try { - final result = await Process.run('netsh', [ + final result = await KRWindowsProcessUtil.runHidden('netsh', [ 'interface', 'ipv4', 'show', @@ -294,10 +335,9 @@ class KRWindowsDnsUtil { final ipMatch = RegExp(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b').firstMatch(line); if (ipMatch != null) { final ip = ipMatch.group(0)!; - // 排除本地回环地址 - if (!ip.startsWith('127.')) { - dnsServers.add(ip); - } + // 🔧 关键修复:不过滤127.0.0.1,以便正确检测DNS是否还在使用sing-box的本地DNS + // 这样在恢复DNS时,第126行的验证才能正确检测到127.0.0.1并触发兜底恢复 + dnsServers.add(ip); } } @@ -325,7 +365,7 @@ class KRWindowsDnsUtil { // 1. 设置主 DNS KRLogUtil.kr_i('🔧 设置主 DNS: ${dnsServers[0]}', tag: 'WindowsDNS'); - var result = await Process.run('netsh', [ + var result = await KRWindowsProcessUtil.runHidden('netsh', [ 'interface', 'ipv4', 'set', @@ -345,7 +385,7 @@ class KRWindowsDnsUtil { if (dnsServers.length > 1) { for (int i = 1; i < dnsServers.length; i++) { KRLogUtil.kr_i('🔧 设置备用 DNS ${i}: ${dnsServers[i]}', tag: 'WindowsDNS'); - result = await Process.run('netsh', [ + result = await KRWindowsProcessUtil.runHidden('netsh', [ 'interface', 'ipv4', 'add', @@ -384,7 +424,7 @@ class KRWindowsDnsUtil { try { KRLogUtil.kr_i('🔧 设置 DNS 为自动获取 (DHCP)', tag: 'WindowsDNS'); - final result = await Process.run('netsh', [ + final result = await KRWindowsProcessUtil.runHidden('netsh', [ 'interface', 'ipv4', 'set', @@ -416,7 +456,7 @@ class KRWindowsDnsUtil { try { KRLogUtil.kr_i('🔄 刷新 DNS 缓存...', tag: 'WindowsDNS'); - final result = await Process.run('ipconfig', ['/flushdns']); + final result = await KRWindowsProcessUtil.runHidden('ipconfig', ['/flushdns']); if (result.exitCode == 0) { KRLogUtil.kr_i('✅ DNS 缓存已刷新', tag: 'WindowsDNS'); @@ -428,6 +468,51 @@ class KRWindowsDnsUtil { } } + /// 🔧 P2优化: 测试 DNS 解析是否真正可用 + /// + /// 通过 nslookup 测试常见域名解析 + /// 返回:true 表示 DNS 可用,false 表示 DNS 不可用 + Future _kr_testDnsResolution() async { + try { + // 测试多个常见域名,提高成功率 + final testDomains = ['www.baidu.com', 'www.qq.com', 'dns.alidns.com']; + + for (var domain in testDomains) { + try { + // 使用 nslookup 测试 DNS 解析,设置 2 秒超时 + final result = await KRWindowsProcessUtil.runHidden( + 'nslookup', + [domain], + ).timeout( + const Duration(seconds: 2), + onTimeout: () { + return ProcessResult(0, 1, '', 'Timeout'); + }, + ); + + if (result.exitCode == 0) { + final output = result.stdout.toString(); + // 检查输出是否包含 IP 地址(简单验证) + if (output.contains('Address:') || output.contains('地址:')) { + KRLogUtil.kr_i('✅ DNS 解析测试通过: $domain', tag: 'WindowsDNS'); + return true; + } + } + } catch (e) { + // 单个域名失败,继续测试下一个 + continue; + } + } + + // 所有域名都解析失败 + KRLogUtil.kr_w('⚠️ 所有测试域名解析均失败', tag: 'WindowsDNS'); + return false; + } catch (e) { + KRLogUtil.kr_e('❌ DNS 解析测试异常: $e', tag: 'WindowsDNS'); + return false; + } + } + /// 清除备份数据 /// /// 在应用退出或不需要时调用 diff --git a/lib/app/utils/kr_windows_process_util.dart b/lib/app/utils/kr_windows_process_util.dart new file mode 100644 index 0000000..00e95d8 --- /dev/null +++ b/lib/app/utils/kr_windows_process_util.dart @@ -0,0 +1,594 @@ +import 'dart:convert'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:ffi/ffi.dart'; + +class KRWindowsProcessUtil { + /// 🔍 调试标志:设为 true 可以追踪所有命令执行(用于排查黑窗问题) + static const bool _debugCommandExecution = true; // ← 开启调试 + + static Future runHidden(String executable, List arguments) async { + if (!Platform.isWindows) { + return Process.run(executable, arguments); + } + + final result = await _runHiddenWindows(executable, arguments); + return result; + } + + static Future startHidden(String executable, List arguments) async { + if (!Platform.isWindows) { + final process = await Process.start(executable, arguments); + return process.pid; + } + return _startHiddenWindows(executable, arguments); + } + + static String _buildCommandLine(String executable, List arguments) { + final parts = [_quoteArgument(executable)]; + for (final arg in arguments) { + parts.add(_quoteArgument(arg)); + } + return parts.join(' '); + } + + static bool _shouldSearchPath(String executable) { + if (executable.isEmpty) { + return true; + } + return !(executable.contains('\\') || executable.contains('/') || executable.contains(':')); + } + + static String _quoteArgument(String value) { + if (value.isEmpty) { + return '""'; + } + final needsQuotes = value.contains(' ') || value.contains('\t') || value.contains('"'); + if (!needsQuotes) { + return value; + } + final buffer = StringBuffer('"'); + var backslashes = 0; + for (var i = 0; i < value.length; i++) { + final char = value[i]; + if (char == '\\') { + backslashes++; + continue; + } + if (char == '"') { + buffer.write('\\' * (backslashes * 2 + 1)); + buffer.write('"'); + backslashes = 0; + continue; + } + if (backslashes > 0) { + buffer.write('\\' * backslashes); + backslashes = 0; + } + buffer.write(char); + } + if (backslashes > 0) { + buffer.write('\\' * (backslashes * 2)); + } + buffer.write('"'); + return buffer.toString(); + } + + static Future _runHiddenWindows(String executable, List arguments) async { + final stdoutPipe = _createPipe(); + final stderrPipe = _createPipe(); + + final startupInfo = calloc(); + final processInfo = calloc(); + final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16(); + final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16(); + final stdInput = _getStdInputHandle(); + + startupInfo.ref + ..cb = sizeOf() + ..dwFlags = STARTF_USESTDHANDLES + ..hStdInput = stdInput + ..hStdOutput = stdoutPipe.write + ..hStdError = stderrPipe.write; + + final created = _CreateProcessW( + applicationName, + commandLine, + nullptr, + nullptr, + TRUE, + CREATE_NO_WINDOW, + nullptr, + nullptr, + startupInfo, + processInfo, + ); + + calloc.free(commandLine); + if (applicationName != nullptr) { + calloc.free(applicationName); + } + + if (created == 0) { + _closeHandle(stdoutPipe.read); + _closeHandle(stdoutPipe.write); + _closeHandle(stderrPipe.read); + _closeHandle(stderrPipe.write); + calloc.free(startupInfo); + calloc.free(processInfo); + throw Exception('CreateProcessW failed: ${_GetLastError()}'); + } + + _closeHandle(stdoutPipe.write); + _closeHandle(stderrPipe.write); + + final output = await _collectOutput(processInfo.ref.hProcess, stdoutPipe.read, stderrPipe.read); + final exitCode = _getExitCode(processInfo.ref.hProcess); + + _closeHandle(stdoutPipe.read); + _closeHandle(stderrPipe.read); + _closeHandle(processInfo.ref.hThread); + _closeHandle(processInfo.ref.hProcess); + + final pid = processInfo.ref.dwProcessId; + calloc.free(startupInfo); + calloc.free(processInfo); + + return ProcessResult(pid, exitCode, output.stdout, output.stderr); + } + + static Future _startHiddenWindows(String executable, List arguments) async { + final startupInfo = calloc(); + final processInfo = calloc(); + final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16(); + final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16(); + + startupInfo.ref.cb = sizeOf(); + + final created = _CreateProcessW( + applicationName, + commandLine, + nullptr, + nullptr, + FALSE, + CREATE_NO_WINDOW, + nullptr, + nullptr, + startupInfo, + processInfo, + ); + + calloc.free(commandLine); + if (applicationName != nullptr) { + calloc.free(applicationName); + } + + if (created == 0) { + calloc.free(startupInfo); + calloc.free(processInfo); + throw Exception('CreateProcessW failed: ${_GetLastError()}'); + } + + final pid = processInfo.ref.dwProcessId; + + _closeHandle(processInfo.ref.hThread); + _closeHandle(processInfo.ref.hProcess); + calloc.free(startupInfo); + calloc.free(processInfo); + + return pid; + } + + static _Pipe _createPipe() { + final readHandle = calloc>(); + final writeHandle = calloc>(); + final securityAttributes = calloc(); + securityAttributes.ref + ..nLength = sizeOf() + ..bInheritHandle = TRUE + ..lpSecurityDescriptor = nullptr; + + final created = _CreatePipe(readHandle, writeHandle, securityAttributes, 0); + calloc.free(securityAttributes); + if (created == 0) { + calloc.free(readHandle); + calloc.free(writeHandle); + throw Exception('CreatePipe failed: ${_GetLastError()}'); + } + + final readValue = readHandle.value; + final writeValue = writeHandle.value; + calloc.free(readHandle); + calloc.free(writeHandle); + + final infoResult = _SetHandleInformation(readValue, HANDLE_FLAG_INHERIT, 0); + if (infoResult == 0) { + _closeHandle(readValue); + _closeHandle(writeValue); + throw Exception('SetHandleInformation failed: ${_GetLastError()}'); + } + + return _Pipe(readValue, writeValue); + } + + static Pointer _getStdInputHandle() { + final handle = _GetStdHandle(STD_INPUT_HANDLE); + if (handle == INVALID_HANDLE_VALUE || handle == 0) { + return nullptr; + } + return Pointer.fromAddress(handle); + } + + static Future<_ProcessOutput> _collectOutput( + Pointer process, + Pointer stdoutHandle, + Pointer stderrHandle, + ) async { + final stdoutBuilder = BytesBuilder(); + final stderrBuilder = BytesBuilder(); + + while (true) { + final stdoutRead = _drainPipe(stdoutHandle, stdoutBuilder); + final stderrRead = _drainPipe(stderrHandle, stderrBuilder); + final waitResult = _WaitForSingleObject(process, 0); + if (waitResult == WAIT_OBJECT_0) { + break; + } + if (waitResult == WAIT_FAILED) { + throw Exception('WaitForSingleObject failed: ${_GetLastError()}'); + } + if (!stdoutRead && !stderrRead) { + await Future.delayed(const Duration(milliseconds: 10)); + } else { + await Future.delayed(Duration.zero); + } + } + + while (_drainPipe(stdoutHandle, stdoutBuilder) || _drainPipe(stderrHandle, stderrBuilder)) { + await Future.delayed(Duration.zero); + } + + return _ProcessOutput( + _decodeOutput(stdoutBuilder), + _decodeOutput(stderrBuilder), + ); + } + + static bool _drainPipe(Pointer handle, BytesBuilder builder) { + final buffer = calloc(4096); + final bytesRead = calloc(); + final available = calloc(); + var didRead = false; + + while (true) { + final peekOk = _PeekNamedPipe(handle, nullptr, 0, nullptr, available, nullptr); + if (peekOk == 0 || available.value == 0) { + break; + } + final toRead = available.value < 4096 ? available.value : 4096; + final ok = _ReadFile(handle, buffer.cast(), toRead, bytesRead, nullptr); + final read = ok == 0 ? 0 : bytesRead.value; + if (read == 0) { + break; + } + builder.add(buffer.asTypedList(read)); + didRead = true; + } + + calloc.free(buffer); + calloc.free(bytesRead); + calloc.free(available); + + return didRead; + } + + static String _decodeOutput(BytesBuilder builder) { + if (builder.length == 0) { + return ''; + } + final bytes = builder.toBytes(); + try { + return systemEncoding.decode(bytes); + } catch (_) { + return utf8.decode(bytes, allowMalformed: true); + } + } + + static int _getExitCode(Pointer process) { + final exitCode = calloc(); + final ok = _GetExitCodeProcess(process, exitCode); + final code = ok == 0 ? -1 : exitCode.value; + calloc.free(exitCode); + return code; + } + + static void _closeHandle(Pointer handle) { + if (handle == nullptr) { + return; + } + _CloseHandle(handle); + } + + // 🔧 WinINet API helpers for proxy settings + /// 查询当前系统代理设置 + static String? queryWindowsProxyServer() { + if (!Platform.isWindows) return null; + + try { + final bufferSize = calloc(); + bufferSize.value = sizeOf(); + + final proxyInfo = calloc(); + + final result = _InternetQueryOptionW(nullptr, INTERNET_OPTION_PROXY, proxyInfo.cast(), bufferSize); + if (result == 0) { + calloc.free(bufferSize); + calloc.free(proxyInfo); + return null; + } + + final proxyServer = proxyInfo.ref.lpszProxy.toDartString(); + calloc.free(bufferSize); + calloc.free(proxyInfo); + + return proxyServer.isEmpty ? null : proxyServer; + } catch (e) { + return null; + } + } + + /// 设置系统代理 + static bool setWindowsProxyServer(String? server) { + if (!Platform.isWindows) return false; + + try { + final proxyInfo = calloc(); + + if (server != null && server.isNotEmpty) { + // 设置代理模式 + proxyInfo.ref.dwAccessType = INTERNET_OPEN_TYPE_PROXY; + proxyInfo.ref.lpszProxy = server.toNativeUtf16(); + proxyInfo.ref.lpszProxyBypass = ''.toNativeUtf16(); + } else { + // 禁用代理 + proxyInfo.ref.dwAccessType = INTERNET_OPEN_TYPE_DIRECT; + proxyInfo.ref.lpszProxy = nullptr; + proxyInfo.ref.lpszProxyBypass = nullptr; + } + + final result = _InternetSetOptionW( + nullptr, + INTERNET_OPTION_PROXY, + proxyInfo.cast(), + sizeOf(), + ); + + if (server != null && server.isNotEmpty) { + calloc.free(proxyInfo.ref.lpszProxy); + calloc.free(proxyInfo.ref.lpszProxyBypass); + } + calloc.free(proxyInfo); + + return result != 0; + } catch (e) { + return false; + } + } + + /// 禁用系统代理 + static bool disableWindowsProxy() { + return setWindowsProxyServer(null); + } +} + +class _Pipe { + final Pointer read; + final Pointer write; + + _Pipe(this.read, this.write); +} + +class _ProcessOutput { + final String stdout; + final String stderr; + + _ProcessOutput(this.stdout, this.stderr); +} + +const int TRUE = 1; +const int FALSE = 0; + +const int STARTF_USESTDHANDLES = 0x00000100; +const int CREATE_NO_WINDOW = 0x08000000; +const int HANDLE_FLAG_INHERIT = 0x00000001; +const int WAIT_OBJECT_0 = 0x00000000; +const int WAIT_FAILED = 0xFFFFFFFF; +const int STD_INPUT_HANDLE = -10; +const int INVALID_HANDLE_VALUE = -1; + +final class SECURITY_ATTRIBUTES extends Struct { + @Uint32() + external int nLength; + + external Pointer lpSecurityDescriptor; + + @Int32() + external int bInheritHandle; +} + +final class STARTUPINFO extends Struct { + @Uint32() + external int cb; + + external Pointer lpReserved; + + external Pointer lpDesktop; + + external Pointer lpTitle; + + @Uint32() + external int dwX; + + @Uint32() + external int dwY; + + @Uint32() + external int dwXSize; + + @Uint32() + external int dwYSize; + + @Uint32() + external int dwXCountChars; + + @Uint32() + external int dwYCountChars; + + @Uint32() + external int dwFillAttribute; + + @Uint32() + external int dwFlags; + + @Uint16() + external int wShowWindow; + + @Uint16() + external int cbReserved2; + + external Pointer lpReserved2; + + external Pointer hStdInput; + + external Pointer hStdOutput; + + external Pointer hStdError; +} + +final class PROCESS_INFORMATION extends Struct { + external Pointer hProcess; + + external Pointer hThread; + + @Uint32() + external int dwProcessId; + + @Uint32() + external int dwThreadId; +} + +final DynamicLibrary _kernel32 = DynamicLibrary.open('kernel32.dll'); + +final _CreatePipe = _kernel32.lookupFunction< + Int32 Function(Pointer>, Pointer>, Pointer, Uint32), + int Function(Pointer>, Pointer>, Pointer, int)>( + 'CreatePipe', +); + +final _SetHandleInformation = _kernel32.lookupFunction< + Int32 Function(Pointer, Uint32, Uint32), + int Function(Pointer, int, int)>( + 'SetHandleInformation', +); + +final _CreateProcessW = _kernel32.lookupFunction< + Int32 Function( + Pointer, + Pointer, + Pointer, + Pointer, + Int32, + Uint32, + Pointer, + Pointer, + Pointer, + Pointer, + ), + int Function( + Pointer, + Pointer, + Pointer, + Pointer, + int, + int, + Pointer, + Pointer, + Pointer, + Pointer, + )>( + 'CreateProcessW', +); + +final _PeekNamedPipe = _kernel32.lookupFunction< + Int32 Function(Pointer, Pointer, Uint32, Pointer, Pointer, Pointer), + int Function(Pointer, Pointer, int, Pointer, Pointer, Pointer)>( + 'PeekNamedPipe', +); + +final _ReadFile = _kernel32.lookupFunction< + Int32 Function(Pointer, Pointer, Uint32, Pointer, Pointer), + int Function(Pointer, Pointer, int, Pointer, Pointer)>( + 'ReadFile', +); + +final _CloseHandle = _kernel32.lookupFunction< + Int32 Function(Pointer), + int Function(Pointer)>( + 'CloseHandle', +); + +final _WaitForSingleObject = _kernel32.lookupFunction< + Uint32 Function(Pointer, Uint32), + int Function(Pointer, int)>( + 'WaitForSingleObject', +); + +final _GetStdHandle = _kernel32.lookupFunction< + IntPtr Function(Int32), + int Function(int)>( + 'GetStdHandle', +); + +final _GetExitCodeProcess = _kernel32.lookupFunction< + Int32 Function(Pointer, Pointer), + int Function(Pointer, Pointer)>( + 'GetExitCodeProcess', +); + +final _GetLastError = _kernel32.lookupFunction< + Uint32 Function(), + int Function()>( + 'GetLastError', +); + +// 🔧 WinINet API for proxy settings - 用于替代 reg 命令,消除黑屏 +final DynamicLibrary _wininet = DynamicLibrary.open('wininet.dll'); + +const int INTERNET_OPTION_PROXY = 38; +const int INTERNET_OPEN_TYPE_PROXY = 3; +const int INTERNET_OPEN_TYPE_DIRECT = 1; + +final class INTERNET_PROXY_INFO extends Struct { + @Int32() + external int dwAccessType; + + external Pointer lpszProxy; + + external Pointer lpszProxyBypass; +} + +/// WinINet InternetSetOption API - 用于设置系统代理 +final _InternetSetOptionW = _wininet.lookupFunction< + Int32 Function(Pointer, Uint32, Pointer, Uint32), + int Function(Pointer, int, Pointer, int)>( + 'InternetSetOptionW', +); + +/// WinINet InternetQueryOption API - 用于查询系统代理 +final _InternetQueryOptionW = _wininet.lookupFunction< + Int32 Function(Pointer, Uint32, Pointer, Pointer), + int Function(Pointer, int, Pointer, Pointer)>( + 'InternetQueryOptionW', +); diff --git a/lib/app/widgets/dialogs/hi_dialog.dart b/lib/app/widgets/dialogs/hi_dialog.dart index 583bdb0..915cc26 100755 --- a/lib/app/widgets/dialogs/hi_dialog.dart +++ b/lib/app/widgets/dialogs/hi_dialog.dart @@ -106,7 +106,7 @@ class _HIDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(23.r), ), - minimumSize: Size.fromHeight(40.w), + minimumSize: Size(85.w, 40.w), padding: EdgeInsets.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), @@ -120,7 +120,7 @@ class _HIDialogState extends State { ), ) : Text( - widget.confirmText ?? AppTranslations.kr_dialog.kr_confirm, + widget.confirmText ?? (widget.cancelText == null ? '好的' : AppTranslations.kr_dialog.kr_confirm), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, @@ -136,96 +136,106 @@ class _HIDialogState extends State { final dialog = Dialog( backgroundColor: Colors.transparent, insetPadding: EdgeInsets.all(20.w), - child: ClipRRect( - borderRadius: BorderRadius.circular(34.r), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 26, sigmaY: 26), // 毛玻璃模糊 - child: Container( - width: 245.w, - padding: EdgeInsets.fromLTRB(30.w, 16.w, 30.w, 16.w), - decoration: BoxDecoration( - color: const Color(0xF5F5F5).withOpacity(0.6), // 半透明底色 - borderRadius: BorderRadius.circular(34.r), - border: Border.all( - color: Colors.white.withOpacity(0.2), // 高光边框 - width: 1.5, + child: Transform.translate( + offset: Offset(0, -30.w), + child: ClipRRect( + borderRadius: BorderRadius.circular(34.r), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 26, sigmaY: 26), // 毛玻璃模糊 + child: Container( + width: 245.w, + padding: EdgeInsets.fromLTRB(30.w, 16.w, 30.w, 16.w), + decoration: BoxDecoration( + color: const Color(0xFFDEDEDE), // 半透明底色 + borderRadius: BorderRadius.circular(34.r), + border: Border.all( + color: Colors.white.withOpacity(0.2), // 高光边框 + width: 1.5, + ), ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.title != null) ...[ - Align( - alignment: Alignment.centerLeft, - child: Text( - widget.title!, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w600, - color: Colors.black, - fontFamily: 'AlibabaPuHuiTi-Medium', - height: 1.3, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.title != null) ...[ + Align( + alignment: Alignment.centerLeft, + child: Text( + widget.title!, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + height: 1.3, + ), ), ), - ), - SizedBox(height: 4.w), - ], - if (widget.message != null || widget.customMessageWidget != null) ...[ - Container( - constraints: BoxConstraints(maxHeight: 200.h), - child: SingleChildScrollView( - child: widget.customMessageWidget ?? - Text( - widget.message!, - textAlign: TextAlign.left, - style: TextStyle( - fontSize: 14.sp, - color: Colors.black, - height: 1.4, - fontFamily: 'AlibabaPuHuiTi-Medium', - ), - ), - ), - ), - ], - if (widget.confirmText != null || widget.cancelText != null) ...[ - SizedBox(height: 28.w), - Row( - children: [ - if (widget.confirmText != null) Expanded(child: _buildConfirmButton()), - if (widget.cancelText != null && widget.confirmText != null) - SizedBox(width: 12.w), - if (widget.cancelText != null) - Expanded( - child: TextButton( - onPressed: () { - Get.back(); - widget.onCancel?.call(); - }, - style: TextButton.styleFrom( - backgroundColor: const Color(0xFFFF00B7), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(23.r), - ), - minimumSize: Size.fromHeight(40.w), - padding: EdgeInsets.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text( - widget.cancelText!, + SizedBox(height: 4.w), + ], + if (widget.message != null || widget.customMessageWidget != null) ...[ + Container( + constraints: BoxConstraints(maxHeight: 200.h), + child: SingleChildScrollView( + child: widget.customMessageWidget ?? + Text( + widget.message!, + textAlign: TextAlign.left, style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, + fontSize: 14.sp, color: Colors.black, + height: 1.4, fontFamily: 'AlibabaPuHuiTi-Medium', ), ), - ), + ), + ), + ], + if (widget.confirmText != null || widget.cancelText != null || (widget.confirmText == null && widget.cancelText == null)) ...[ + SizedBox(height: 12.w), + if (widget.confirmText == null && widget.cancelText == null) + Center( + child: SizedBox( + width: 85.w, + child: _buildConfirmButton(), ), - ], - ), - ] - ], + ) + else + Row( + children: [ + if (widget.confirmText != null) Expanded(child: _buildConfirmButton()), + if (widget.cancelText != null && widget.confirmText != null) + SizedBox(width: 12.w), + if (widget.cancelText != null) + Expanded( + child: TextButton( + onPressed: () { + Get.back(); + widget.onCancel?.call(); + }, + style: TextButton.styleFrom( + backgroundColor: const Color(0xFFFF00B7), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(23.r), + ), + minimumSize: Size(85.w, 40.w), + padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text( + widget.cancelText!, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + fontFamily: 'AlibabaPuHuiTi-Medium', + ), + ), + ), + ), + ], + ), + ] + ], + ), ), ), ), diff --git a/lib/app/widgets/hi_collapsible_list.dart b/lib/app/widgets/hi_collapsible_list.dart index b77fe3b..d8d2d71 100644 --- a/lib/app/widgets/hi_collapsible_list.dart +++ b/lib/app/widgets/hi_collapsible_list.dart @@ -6,7 +6,8 @@ import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import 'dart:math' as math; // 导入 math 库以使用 pi import 'package:flutter/gestures.dart'; import 'package:get/get.dart'; -import '../routes/app_pages.dart'; +import 'package:kaer_with_panels/app/routes/app_pages.dart'; +import 'package:url_launcher/url_launcher.dart'; /// 可折叠列表项的数据模型 class HICollapsibleItem { @@ -61,15 +62,11 @@ class _HICollapsibleItemWidgetState extends State { color: const Color(0xFFADFF5B), // 链接颜色 ), recognizer: TapGestureRecognizer() - ..onTap = () { - // 在这里处理点击事件,例如打开一个网页 - // 注意:您需要添加 url_launcher 依赖 - Get.toNamed( - Routes.KR_WEBVIEW, - arguments: { - 'url': 'https://www.baidu.com', - }, - ); + ..onTap = () async { + final Uri url = Uri.parse('https://hifastvpn.com/help'); + if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { + Get.snackbar('错误', '无法打开链接: $url'); + } }, ), ); diff --git a/lib/app/widgets/kr_language_switch_dialog.dart b/lib/app/widgets/kr_language_switch_dialog.dart deleted file mode 100755 index 5d9c49b..0000000 --- a/lib/app/widgets/kr_language_switch_dialog.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get/get.dart'; -import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; -import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; -import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; - -/// 语言切换弹框组件 -class KRLanguageSwitchDialog extends StatelessWidget { - const KRLanguageSwitchDialog({super.key}); - - /// 显示语言切换弹框的静态方法 - static Future kr_show() async { - final isChineseRegion = await KRLanguageUtils.checkInitialLanguage(); - if (isChineseRegion) { - await Get.dialog( - const KRLanguageSwitchDialog(), - barrierDismissible: false, - ); - } - } - - @override - Widget build(BuildContext context) { - return Dialog( - backgroundColor: Colors.transparent, - child: Container( - width: 280.w, - padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(24.r), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 图标 - KrLocalImage( - imageName: 'language_switch', - width: 120.w, - height: 120.h, - ), - SizedBox(height: 16.h), - // 标题 - Text( - '根据您所在地区以及您的语言设置是否切换到中文语言?', - textAlign: TextAlign.center, - style: KrAppTextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - SizedBox(height: 24.h), - // 切换按钮 - _kr_buildButton( - context: context, - text: '切换', - isPrimary: true, - onTap: () async { - final zhLanguage = KRLanguage.values.firstWhere( - (lang) => lang.countryCode == 'zh', - orElse: () => KRLanguage.zh, - ); - await KRLanguageUtils.switchLanguage(zhLanguage); - Get.back(); - }, - ), - SizedBox(height: 12.h), - // 不切换按钮 - _kr_buildButton( - context: context, - text: '不切换', - isPrimary: false, - onTap: () => Get.back(), - ), - ], - ), - ), - ); - } - - /// 构建按钮 - Widget _kr_buildButton({ - required BuildContext context, - required String text, - required bool isPrimary, - required VoidCallback onTap, - }) { - return InkWell( - onTap: onTap, - child: Container( - width: double.infinity, - height: 44.h, - decoration: BoxDecoration( - color: isPrimary ? Colors.blue : Colors.transparent, - borderRadius: BorderRadius.circular(22.r), - border: isPrimary - ? null - : Border.all( - color: Colors.blue, - width: 1, - ), - ), - alignment: Alignment.center, - child: Text( - text, - style: KrAppTextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: isPrimary ? Colors.white : Colors.blue, - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/widgets/kr_subscription_expiry_text.dart b/lib/app/widgets/kr_subscription_expiry_text.dart index 4820217..a4ddf10 100644 --- a/lib/app/widgets/kr_subscription_expiry_text.dart +++ b/lib/app/widgets/kr_subscription_expiry_text.dart @@ -42,9 +42,8 @@ class KRSubscriptionExpiryText extends StatelessWidget { 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'; + final formattedDateTime = '$year/$month/$day $hour:$minute'; expiryText = '到期时间:$formattedDateTime'; } } diff --git a/lib/main.dart b/lib/main.dart index c4631bc..61e4ece 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -110,41 +110,28 @@ Widget _myApp(GetxTranslations translations, Locale initialLocale) { initialRoute: Routes.KR_SPLASH, getPages: AppPages.routes, builder: (context, child) { - if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - /// 屏幕适配 - ScreenUtil.init(context, - designSize: const Size(868, 668), minTextAdapt: true); - } else { - /// 屏幕适配 - ScreenUtil.init(context, - designSize: const Size(375, 667), minTextAdapt: true); - } + /// 屏幕适配 - 统一使用手机端设计尺寸 + ScreenUtil.init( + context, + designSize: const Size(375, 812), + minTextAdapt: true, + ); // child = FlutterEasyLoading(child: child); // 已替换为自定义组件 // 添加生命周期监听 Widget wrappedChild = Listener( onPointerDown: (_) async { - // 确保地图缓存已初始化 - // try { - // final store = FMTCStore(KRFMTC.kr_storeName); - // if (!await store.manage.ready) { - // await store.manage.create(); - // await KRFMTC.kr_initMapCache(); - // } - // } catch (e) { - // print('地图缓存初始化失败: $e'); - // } }, child: child, ); - // 如果是 Mac 平台,添加顶部安全区域 - if (Platform.isMacOS) { + // 如果是桌面平台,添加顶部安全区域 + if (Platform.isMacOS || Platform.isWindows) { wrappedChild = MediaQuery( data: MediaQuery.of(context).copyWith( padding: MediaQuery.of(context).padding.copyWith( - top: 10.w, // Mac 平台顶部安全区域 + top: 24.w, // 桌面端顶部安全区域,避开窗口控件 ), ), child: wrappedChild, @@ -181,9 +168,15 @@ Widget _myApp(GetxTranslations translations, Locale initialLocale) { transitionDuration: TransitionConfig.defaultDuration, // 设置动画持续时间 customTransition: TransitionConfig.createDefaultTransition(), routingCallback: (routing) { - if (routing == null) return; - if(Routes.KR_PURCHASE_MEMBERSHIP.contains(routing.current)) return; - // 需要显示订阅按钮的路由列表 + if (routing == null || routing.current == null) return; + + final String current = routing.current!; + print('🔥 routing.current: $current'); + + // ✅ 同步路由状态到服务,用于内部 UI 响应(如隐藏/显示文字) + GlobalOverlayService.instance.updateCurrentRoute(current); + + // ✅ 显式列表,用于容错匹配 const showButtonRoutes = [ Routes.MR_LOGIN, Routes.HI_MENU, @@ -191,11 +184,22 @@ Widget _myApp(GetxTranslations translations, Locale initialLocale) { Routes.HI_USER_INFO, Routes.KR_ORDER_STATUS, ]; - print('routing.current${routing.current}'); - GlobalOverlayService.instance.updateSubscriptionButtonColor(null); - if (showButtonRoutes.contains(routing.current)) { + + // 检查是否是购买页(使用多种匹配方式确保成功) + bool isPurchasePage = current == Routes.KR_PURCHASE_MEMBERSHIP || + current.contains(Routes.KR_PURCHASE_MEMBERSHIP) || + Routes.KR_PURCHASE_MEMBERSHIP.contains(current); + + if (isPurchasePage) { + // ✅ 购买页:必须可见但透明,解决返回时的遮挡问题 + GlobalOverlayService.instance.updateSubscriptionButtonColor(Colors.transparent); + GlobalOverlayService.instance.safeShowSubscriptionButton(); + } else if (showButtonRoutes.any((r) => current == r || current.contains(r) || r.contains(current))) { + // ✅ 其他白名单页面:可见并恢复默认主色 + GlobalOverlayService.instance.updateSubscriptionButtonColor(null); GlobalOverlayService.instance.safeShowSubscriptionButton(); } else { + // ✅ 默认:隐藏 GlobalOverlayService.instance.hideSubscriptionButton(); } }, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 290f7de..a408754 100755 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -16,7 +16,6 @@ import screen_retriever_macos import share_plus import tray_manager import url_launcher_macos -import webview_flutter_wkwebview import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -31,6 +30,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 8c2feb6..d64a561 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.6.0" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce url: "https://pub.dev" source: hosted - version: "0.11.3" + version: "0.13.4" ansicolor: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.7" args: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: @@ -101,26 +101,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" built_collection: dependency: transitive description: @@ -293,34 +293,42 @@ packages: dependency: transitive description: name: custom_lint_core - sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" url: "https://pub.dev" source: hosted - version: "0.6.3" + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.7.0" dart_mappable: dependency: "direct main" description: name: dart_mappable - sha256: "6eda273146ed930c1f161d0b29f4bc9ef9e87ecfb9341607833bf76b008fb7d5" + sha256: "2255b2c00e328a65fef5a8df2dabfc0dc9c2e518c33a50051a4519b1c7a28c48" url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "4.5.0" dart_mappable_builder: dependency: "direct dev" description: name: dart_mappable_builder - sha256: b3673a6d190f2ea766b39ea298d4c55d1caca9382a536cf164ffe7e2f955c501 + sha256: adea8c55aac73c8254aa14a8272b788eb0f72799dd8e4810a9b664ec9b4e353c url: "https://pub.dev" source: hosted - version: "4.3.1+1" + version: "4.5.0" dart_style: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "3.0.1" dartx: dependency: "direct main" description: @@ -381,18 +389,18 @@ packages: dependency: transitive description: name: drift - sha256: df027d168a2985a2e9da900adeba2ab0136f0d84436592cf3cd5135f82c8579c + sha256: "540cf382a3bfa99b76e51514db5b0ebcd81ce3679b7c1c9cb9478ff3735e47a1" url: "https://pub.dev" source: hosted - version: "2.21.0" + version: "2.28.2" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "623649abe932fc17bd32e578e7e05f7ac5e7dd0b33e6c8669a0634105d1389bf" + sha256: "68c138e884527d2bd61df2ade276c3a144df84d1adeb0ab8f3196b5afe021bd4" url: "https://pub.dev" source: hosted - version: "2.21.2" + version: "2.28.0" easy_refresh: dependency: "direct main" description: @@ -474,18 +482,18 @@ packages: dependency: transitive description: name: flutter_gen_core - sha256: "53890b653738f34363d9f0d40f82104c261716bd551d3ba65f648770b6764c21" + sha256: b6bafbbd981da2f964eb45bcb8b8a7676a281084f8922c0c75de4cfbaa849311 url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.12.0" flutter_gen_runner: dependency: "direct dev" description: name: flutter_gen_runner - sha256: de70b42eb5329f712c8b041069d081ad5fb5109f32d6d1ea9c1b39596786215d + sha256: c99b10af9d404e3f46fd1927e7d90099779e935e86022674c4c2a9e6c2a93b29 url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.12.0" flutter_hooks: dependency: transitive description: @@ -652,10 +660,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -692,10 +700,10 @@ packages: dependency: "direct dev" description: name: go_router_builder - sha256: "3425b72dea69209754ac6b71b4da34165dcd4d4a2934713029945709a246427a" + sha256: "7f6f4bfb97cadc3d25378a0237fe4ddd98b54d6094b5a5c158b775a2cc30843e" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.9.0" google_identity_services_web: dependency: transitive description: @@ -724,10 +732,10 @@ packages: dependency: "direct main" description: name: grpc - sha256: e93ee3bce45c134bf44e9728119102358c7cd69de7832d9a874e2e74eb8cab40 + sha256: "2dde469ddd8bbd7a33a0765da417abe1ad2142813efce3a86c512041294e2b26" url: "https://pub.dev" source: hosted - version: "3.2.4" + version: "4.1.0" hashcodes: dependency: transitive description: @@ -808,6 +816,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" + url: "https://pub.dev" + source: hosted + version: "4.7.2" image_size_getter: dependency: transitive description: @@ -908,10 +924,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.5" leak_tracker: dependency: transitive description: @@ -1208,6 +1224,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" protobuf: dependency: "direct main" description: @@ -1292,10 +1316,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" + sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.9" riverpod_annotation: dependency: "direct main" description: @@ -1308,10 +1332,10 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22 + sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.6.4" rxdart: dependency: "direct main" description: @@ -1433,10 +1457,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: @@ -1473,10 +1497,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: d77749237609784e337ec36c979d41f6f38a7b279df98622ae23929c8eb954a4 + sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67" url: "https://pub.dev" source: hosted - version: "0.39.2" + version: "0.41.2" stack_trace: dependency: transitive description: @@ -1725,38 +1749,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - webview_flutter: - dependency: "direct main" - description: - name: webview_flutter - sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec" - url: "https://pub.dev" - source: hosted - version: "4.10.0" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - sha256: "512c26ccc5b8a571fd5d13ec994b7509f142ff6faf85835e243dde3538fdc713" - url: "https://pub.dev" - source: hosted - version: "4.3.2" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - sha256: "7cb32b21825bd65569665c32bb00a34ded5779786d6201f5350979d2d529940d" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: a3d461fe3467014e05f3ac4962e5fdde2a4bf44c561cb53e9ae5c586600fdbc3 - url: "https://pub.dev" - source: hosted - version: "3.22.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cc68979..928bb68 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,7 @@ dependencies: # 网络和数据处理 dio: ^5.4.1 - grpc: ^3.2.4 + grpc: ^4.1.0 protobuf: ^3.1.0 json_annotation: ^4.9.0 dart_mappable: ^4.2.1 @@ -71,7 +71,6 @@ dependencies: # 平台集成 window_manager: ^0.4.3 - webview_flutter: ^4.7.0 url_launcher: ^6.3.1 flutter_inappwebview: ^6.1.5 # 最新稳定版本 crisp_sdk: ^1.1.0 # 使用 crisp_sdk,配合最新的 flutter_inappwebview @@ -129,7 +128,7 @@ dev_dependencies: drift_dev: ^2.16.0 ffigen: ^8.0.2 slang_build_runner: ^3.30.0 - flutter_gen_runner: ^5.4.0 + flutter_gen_runner: ^5.12.0 go_router_builder: ^2.4.1 dart_mappable_builder: ^4.2.1