初始化提交

This commit is contained in:
2025-09-23 16:23:15 +08:00
commit 877b18a70f
590 changed files with 53617 additions and 0 deletions
@@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/kr_user_info_controller.dart';
class KRUserInfoBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<KRUserInfoController>(
() => KRUserInfoController(),
);
}
}
@@ -0,0 +1,247 @@
import 'package:get/get.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/app/mixins/kr_app_bar_opacity_mixin.dart';
import '../../../common/app_config.dart';
import '../../../common/app_run_data.dart';
import '../../../services/api_service/kr_api.user.dart';
import '../../../utils/kr_common_util.dart';
import '../../../utils/kr_event_bus.dart';
import '../../../utils/kr_log_util.dart';
/// 网格项类型枚举
/// 用于区分不同的功能入口类型
enum KRGridItemType {
/// VPN官网入口
vpnWebsite,
/// 推特社交入口
telegram,
/// 邮箱联系入口
mail,
/// 电话联系入口
phone,
/// 人工客服支持入口
customerService,
/// 客服人员联系入口
contactService,
}
/// 网格项数据模型
/// 用于统一管理功能入口的展示数据
class KRGridItem {
/// 功能图标
final String icon;
/// 功能标题
final String title;
/// 功能描述
final String subtitle;
/// 功能类型
final KRGridItemType type;
const KRGridItem({
required this.icon,
required this.title,
required this.subtitle,
required this.type,
});
}
/// 用户信息页面的控制器
/// 负责管理用户信息页面的状态和业务逻辑
class KRUserInfoController extends GetxController with KRAppBarOpacityMixin {
/// 广告拦截开关状态
/// true: 开启拦截, false: 关闭拦截
final RxBool kr_isAdBlockEnabled = true.obs;
/// NDS解锁开关状态
/// true: 已解锁, false: 未解锁
final RxBool kr_isNDSUnlockEnabled = true.obs;
/// 订阅状态
/// true: 有效订阅, false: 无效订阅
final RxBool kr_hasValidSubscription = false.obs;
/// 是否显示绑定提示
final RxBool kr_showBindingTip = false.obs;
/// 用户余额
RxDouble kr_balance = 0.0.obs;
/// 功能入口网格项列表
/// 包含所有可用的功能入口配置
final kr_gridItems = <KRGridItem>[
KRGridItem(
icon: "my_net_index",
title: AppTranslations.kr_userInfo.vpnWebsite,
subtitle: AppConfig.getInstance().kr_official_website,
type: KRGridItemType.vpnWebsite,
),
KRGridItem(
icon: "my_telegram",
title: AppTranslations.kr_userInfo.telegram,
subtitle: "telegram",
type: KRGridItemType.telegram,
),
KRGridItem(
icon: "my_email",
title: AppTranslations.kr_userInfo.mail,
subtitle: AppConfig.getInstance().kr_official_email,
type: KRGridItemType.mail,
),
KRGridItem(
icon: "my_phone",
title: AppTranslations.kr_userInfo.phone,
subtitle: AppConfig.getInstance().kr_official_telephone,
type: KRGridItemType.phone,
),
KRGridItem(
icon: "my_kf",
title: AppTranslations.kr_userInfo.customerService,
subtitle: "",
type: KRGridItemType.customerService,
),
KRGridItem(
icon: "my_kf_msg",
title: AppTranslations.kr_userInfo.contactService,
subtitle: "",
type: KRGridItemType.contactService,
),
].obs;
@override
void onInit() {
super.onInit();
kr_initData();
}
/// 页面进入时的处理
void kr_onPageEnter() {
KRLogUtil.kr_i('进入用户信息页面', tag: 'UserInfo');
_loadUserInfo();
}
@override
void onReady() {
super.onReady();
// 每次进入页面时执行
KRLogUtil.kr_i('进入用户信息页面', tag: 'UserInfo');
// 刷新用户信息
_loadUserInfo();
}
@override
void onClose() {
super.onClose();
}
/// 初始化控制器数据
/// 从服务器获取用户配置信息
void kr_initData() {
ever(KRAppRunData.getInstance().kr_isLogin, (bool isLogin) {
if (isLogin) {
_loadUserInfo();
} else {
kr_balance.value = 0.0;
}
});
ever(KRSingBoxImp().kr_blockAds, (bool bl) {
kr_isAdBlockEnabled.value = bl;
});
kr_isAdBlockEnabled.value = KRSingBoxImp().kr_blockAds.value;
// 监听所有支付相关消息
KREventBus().kr_listenMessages(
[KRMessageType.kr_payment, KRMessageType.kr_subscribe_update],
_kr_handleMessage,
);
}
/// 处理消息
void _kr_handleMessage(KRMessageData message) {
switch (message.kr_type) {
case KRMessageType.kr_payment:
_loadUserInfo();
break;
case KRMessageType.kr_subscribe_update:
break;
// TODO: Handle this case.
}
}
/// 处理广告拦截开关状态变化
/// [value] 新的开关状态
void kr_toggleAdBlock(bool value) {
kr_isAdBlockEnabled.value = value;
KRSingBoxImp().kr_updateAdBlockEnabled(value);
}
/// 处理NDS解锁开关状态变化
/// [value] 新的开关状态
void kr_toggleNDSUnlock(bool value) {
kr_isNDSUnlockEnabled.value = value;
// TODO: 实现保存设置到服务器的逻辑
}
/// 初始化用户信息
Future<void> _loadUserInfo() async {
if (!KRAppRunData.getInstance().kr_isLogin.value) {
return;
}
final either0 = await KRUserApi().kr_getUserInfo();
either0.fold(
(error) {
KRLogUtil.kr_e(error.msg, tag: 'AppRunData');
},
(userInfo) async {
kr_balance.value = userInfo.balance.toDouble() / 100;
},
);
}
/// 处理用户退出登录
/// 清理用户数据并返回登录页面
void kr_handleLogout() {
KRAppRunData.getInstance().kr_loginOut();
}
/// 重置流量使用量
/// 调用服务器API重置用户的流量使用量
Future<void> kr_resetTraffic() async {
try {
// TODO: 调用服务器API重置流量
KRCommonUtil.kr_showToast(
AppTranslations.kr_userInfo.resetTrafficSuccess);
} catch (e) {
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.resetTrafficFailed);
}
}
String getTitle(KRGridItemType type) {
switch (type) {
case KRGridItemType.vpnWebsite:
return AppTranslations.kr_userInfo.vpnWebsite;
case KRGridItemType.telegram:
return AppTranslations.kr_userInfo.telegram;
case KRGridItemType.mail:
return AppTranslations.kr_userInfo.mail;
case KRGridItemType.phone:
return AppTranslations.kr_userInfo.phone;
case KRGridItemType.customerService:
return AppTranslations.kr_userInfo.customerService;
case KRGridItemType.contactService:
return AppTranslations.kr_userInfo.contactService;
}
}
}
+897
View File
@@ -0,0 +1,897 @@
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/common/app_config.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/services.dart';
import 'dart:io' show Platform;
import 'package:kaer_with_panels/app/routes/app_pages.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/modules/kr_main/controllers/kr_main_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_home/widgets/kr_subscribe_selector_view.dart';
import '../../../common/app_run_data.dart';
import '../../../model/response/kr_user_available_subscribe.dart';
import '../../../services/kr_subscribe_service.dart';
import '../../../widgets/dialogs/kr_dialog.dart';
import '../controllers/kr_user_info_controller.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
class KRUserInfoView extends GetView<KRUserInfoController> {
const KRUserInfoView({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,
title: Align(
alignment: Alignment.centerLeft,
child: Text(
AppTranslations.kr_userInfo.title,
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
actions: [
Padding(
padding: EdgeInsets.only(right: 8.w),
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).iconTheme.color,
size: 22.w,
),
onPressed: () => Get.toNamed(Routes.KR_SETTING),
),
),
],
),
Expanded(
child: Stack(
children: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_kr_buildBindingTip(context),
_kr_buildSubscriptionCard(context),
_kr_buildShortcutSection(context),
_kr_buildOtherSection(context),
_kr_buildLogoutButton(context),
SizedBox(height: 30.w),
],
),
),
],
),
),
],
),
),
);
}
// 构建绑定提示
Widget _kr_buildBindingTip(BuildContext context) {
return Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.w),
child: Row(
children: [
Icon(
KRAppRunData.getInstance().kr_isLogin.value
? Icons.info
: Icons.info_outline,
color: !KRAppRunData.getInstance().kr_isLogin.value
? Theme.of(context).colorScheme.error
: Theme.of(context).textTheme.bodyMedium?.color,
size: 16.w,
),
SizedBox(width: 8.w),
Text(
KRAppRunData.getInstance().kr_isLogin.value
? "${AppTranslations.kr_userInfo.myAccount} ${KRAppRunData().kr_account}"
: AppTranslations.kr_userInfo.bindingTip,
style: KrAppTextStyle(
color: !KRAppRunData.getInstance().kr_isLogin.value
? Theme.of(context).colorScheme.error
: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 12,
),
),
],
),
),
// 余额信息(写死预览)
Visibility(
visible: KRAppRunData.getInstance().kr_isLogin.value &&
AppConfig.getInstance().kr_is_daytime,
child: Padding(
padding: EdgeInsets.only(left: 16.w, bottom: 16.w),
child: Row(
children: [
Icon(
Icons.account_balance_wallet_outlined,
color: Theme.of(context).textTheme.bodyMedium?.color,
size: 16.w,
),
SizedBox(width: 8.w),
Text(
"${AppTranslations.kr_userInfo.balance} ${controller.kr_balance.value.toString()}",
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
],
),
),
),
],
));
}
// 构建订阅卡片
Widget _kr_buildSubscriptionCard(BuildContext context) {
return Obx(() {
final isLoggedIn = KRAppRunData.getInstance().kr_isLogin.value;
final subscribe = KRSubscribeService().kr_currentSubscribe.value;
if (isLoggedIn && subscribe != null) {
return _kr_buildValidSubscriptionCard(context, subscribe);
} else {
return _kr_buildInvalidSubscriptionCard(context, isLoggedIn);
}
});
}
// 构建有效订阅卡片
Widget _kr_buildValidSubscriptionCard(
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
final bool isExpired =
DateTime.parse(subscribe.expireTime).isBefore(DateTime.now());
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.only(
left: 16.w, right: 16.w, top: 16.w, bottom: AppConfig().kr_is_daytime == false ? 0 : 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: [
Expanded(
child: Text(
subscribe.name,
style: KrAppTextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
),
GestureDetector(
onTap: () {
if (KRSubscribeService().kr_currentStatus.value ==
KRSubscribeServiceStatus.kr_loading) {
return;
}
final homeController = Get.find<KRHomeController>();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
child: KRSubscribeSelectorView(
controller: homeController,
),
),
);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppTranslations.kr_userInfo.switchSubscription,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
SizedBox(width: 4.w),
Icon(
Icons.swap_horiz,
color: Theme.of(context).textTheme.bodySmall?.color,
size: 16.w,
),
],
),
),
),
],
),
SizedBox(height: 16.w),
// 过期时间
Row(
children: [
Icon(
isExpired ? Icons.warning_amber_rounded : Icons.check_circle,
color: isExpired
? Theme.of(context).colorScheme.error
: Colors.green,
size: 16.w,
),
SizedBox(width: 8.w),
Text(
"${AppTranslations.kr_userInfo.expireTime}${subscribe.expireTime}",
style: KrAppTextStyle(
fontSize: 12,
color: isExpired
? Theme.of(context).colorScheme.error
: Theme.of(context).textTheme.bodySmall?.color,
),
),
],
),
SizedBox(height: 16.w),
// 流量进度条
_kr_buildTrafficProgress(context, subscribe),
SizedBox(height: 16.w),
// 操作按钮
_kr_buildSubscriptionActions(context, subscribe),
],
),
);
}
// 构建流量进度条
Widget _kr_buildTrafficProgress(
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
final int totalTraffic = subscribe.traffic;
final int usedTraffic = subscribe.download + subscribe.upload;
// 模拟流量超出
var progress = totalTraffic > 0 ? usedTraffic / totalTraffic.toDouble() : 0;
KRLogUtil.kr_i(
"progress: ${AppTranslations.kr_userInfo.deviceLimit.trParams({
'count': subscribe.deviceLimit.toString()
})}",
tag: "KRUserInfoView");
final bool isTrafficExceeded = progress >= 1; // 模拟流量超出
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
Icons.info_outline,
size: 16.w,
color: Theme.of(context).textTheme.bodySmall?.color,
),
SizedBox(width: 4.w),
Text(
AppTranslations.kr_userInfo.deviceLimit
.trParams({'count': subscribe.deviceLimit.toString()}),
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
],
),
Row(
children: [
if (isTrafficExceeded) ...[
Icon(
Icons.warning_amber_rounded,
size: 14.w,
color: Theme.of(context).colorScheme.error,
),
SizedBox(width: 4.w),
],
LayoutBuilder(
builder: (context, constraints) {
final text = totalTraffic == 0
? AppTranslations.kr_userInfo.trafficProgressUnlimited
: isTrafficExceeded
? KRCommonUtil.kr_formatBytes(usedTraffic)
: "${KRCommonUtil.kr_formatBytes(usedTraffic)} / ${KRCommonUtil.kr_formatBytes(totalTraffic)}";
// 根据文本长度和可用宽度计算合适的字体大小
final baseFontSize = 12.0;
final textLength = text.length;
final availableWidth = constraints.maxWidth;
final calculatedFontSize =
(availableWidth / (textLength * 0.8))
.clamp(8.0, baseFontSize);
return Text(
text,
style: KrAppTextStyle(
fontSize: calculatedFontSize,
color: isTrafficExceeded
? Theme.of(context).colorScheme.error
: Theme.of(context).textTheme.bodySmall?.color,
),
);
},
),
if (isTrafficExceeded) ...[
SizedBox(width: 8.w),
GestureDetector(
onTap: () {
final currentExpireTime =
DateTime.parse(subscribe.expireTime);
final newExpireTime =
currentExpireTime.subtract(const Duration(days: 30));
KRDialog.show(
title: AppTranslations.kr_userInfo.resetTrafficTitle,
message:
AppTranslations.kr_userInfo.resetTrafficMessage(
currentExpireTime.toString().split(' ')[0],
newExpireTime.toString().split(' ')[0],
),
cancelText: AppTranslations.kr_dialog.kr_cancel,
confirmText: AppTranslations.kr_dialog.kr_confirm,
onCancel: () => Get.back(),
onConfirm: () =>
KRSubscribeService().kr_resetSubscribePeriod(),
);
},
child: Container(
padding:
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.w),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.error
.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.refresh,
size: 14.w,
color: Theme.of(context).colorScheme.error,
),
SizedBox(width: 4.w),
Text(
AppTranslations.kr_userInfo.reset.tr,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.error,
),
),
],
),
),
),
],
],
),
],
),
if (totalTraffic > 0) ...[
SizedBox(height: 6.w),
Stack(
children: [
Container(
height: 6.w,
decoration: BoxDecoration(
color: Theme.of(context).dividerColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(3.w),
),
),
Container(
height: 6.w,
width: MediaQuery.of(context).size.width * progress,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: progress > 0.8
? [
const Color(0xFFFF6B6B), // 浅红色
const Color(0xFFFF4757), // 深红色
]
: [
const Color(0xFF00C6FF), // 亮蓝色
const Color(0xFF0072FF), // 深蓝色
],
),
borderRadius: BorderRadius.circular(3.w),
boxShadow: [
BoxShadow(
color: (progress > 0.8
? const Color(0xFFFF4757)
: const Color(0xFF0072FF))
.withOpacity(0.3),
blurRadius: 4.w,
offset: Offset(0, 2.w),
),
],
),
),
],
),
],
],
);
}
// 构建订阅操作按钮
Widget _kr_buildSubscriptionActions(
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
if (!AppConfig.getInstance().kr_is_daytime) {
return SizedBox();
}
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => Get.toNamed(Routes.KR_PURCHASE_MEMBERSHIP),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1797FF),
foregroundColor: Colors.white,
elevation: 0,
padding: EdgeInsets.symmetric(vertical: 12.w),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.w),
),
),
child: Text(
AppTranslations.kr_userInfo.subscribeNow,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
);
}
// 构建无效订阅卡片
Widget _kr_buildInvalidSubscriptionCard(
BuildContext context, bool isLoggedIn) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.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: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Theme.of(context).colorScheme.error,
size: 16.w,
),
SizedBox(width: 8.w),
Expanded(
child: Text(
!isLoggedIn
? AppTranslations.kr_userInfo.pleaseLogin
: AppTranslations.kr_userInfo.noValidSubscription,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
),
SizedBox(width: 12.w),
ElevatedButton(
onPressed: !isLoggedIn
? () => Get.find<KRMainController>().kr_setPage(0)
: () => Get.toNamed(Routes.KR_PURCHASE_MEMBERSHIP),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1797FF),
foregroundColor: Colors.white,
elevation: 0,
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.w),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.w),
),
),
child: Text(
!isLoggedIn
? AppTranslations.kr_userInfo.loginNow
: AppTranslations.kr_userInfo.subscribeNow,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
),
);
}
// 构建快捷键区域
Widget _kr_buildShortcutSection(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(16.w, 24.w, 16.w, 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_userInfo.shortcuts,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 12.w),
Column(
children: [
_kr_buildShortcutContainer(
icon: "my_ads",
title: AppTranslations.kr_userInfo.adBlock,
value: controller.kr_isAdBlockEnabled,
onChanged: controller.kr_toggleAdBlock,
context: context,
),
_kr_buildShortcutContainer(
icon: "my_dns",
title: AppTranslations.kr_userInfo.ndsUnlock,
value: controller.kr_isNDSUnlockEnabled,
onChanged: controller.kr_toggleNDSUnlock,
context: context,
),
_kr_buildShortcutContainer(
icon: "my_cn_us",
title: AppTranslations.kr_userInfo.contactUs,
onTap: () {
Get.toNamed(Routes.KR_CRISP);
},
context: context,
),
],
),
],
),
);
}
// 构建快捷键容器
Widget _kr_buildShortcutContainer({
required String icon,
required String title,
RxBool? value,
Function(bool)? onChanged,
VoidCallback? onTap,
required BuildContext context,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 62.w,
margin: EdgeInsets.symmetric(vertical: 6.w),
padding: EdgeInsets.only(left: 12.w, right: 12.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
KrLocalImage(
imageName: icon,
color: Theme.of(context).textTheme.bodyMedium?.color,
width: 40.w,
height: 40.w,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
title,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodyMedium?.color,
fontWeight: FontWeight.w500,
),
),
),
if (value != null)
Obx(
() => CupertinoSwitch(
value: value.value,
onChanged: onChanged,
activeColor: Colors.blue,
),
)
else
Icon(
Icons.arrow_forward_ios,
color: Theme.of(context).textTheme.bodySmall?.color,
size: 16.w,
),
],
),
),
);
}
// 构建其他区域
Widget _kr_buildOtherSection(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_userInfo.others,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 12.w),
Obx(() => Wrap(
spacing: 12.w,
runSpacing: 12.w,
children: List.generate(
controller.kr_gridItems.length,
(index) => SizedBox(
width: (MediaQuery.of(context).size.width - 44.w) /
2, // 44.w = 左右margin(32.w) + 中间间距(12.w)
child: _kr_buildGridItem(
controller.kr_gridItems[index], index, context),
),
),
)),
],
),
);
}
// 构建网格项
Widget _kr_buildGridItem(KRGridItem item, int index, BuildContext context) {
return InkWell(
onTap: () => _kr_handleGridItemTap(item.type),
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.w),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
KrLocalImage(
imageName: item.icon,
width: 24.w,
height: 24.w,
),
KrLocalImage(
imageName: "my_et",
color: Theme.of(context).textTheme.bodySmall?.color,
width: 16.w,
height: 16.w,
),
],
),
SizedBox(height: 8.w),
Text(
controller.getTitle(item.type),
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
SizedBox(height: 4.w),
Text(
item.subtitle,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
),
);
}
// 构建退出登录按钮
Widget _kr_buildLogoutButton(BuildContext context) {
return Obx(() => Visibility(
visible: KRAppRunData.getInstance().kr_isLogin.value,
child: Container(
width: double.infinity,
margin: EdgeInsets.all(16.w),
child: TextButton(
onPressed: () {
KRDialog.show(
title: AppTranslations.kr_userInfo.logoutConfirmTitle,
message: AppTranslations.kr_userInfo.logoutConfirmMessage,
cancelText: AppTranslations.kr_userInfo.logoutCancel,
onCancel: () => Get.back(),
onConfirm: () => controller.kr_handleLogout(),
);
},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).cardColor,
padding: EdgeInsets.symmetric(vertical: 12.w),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.w),
),
),
child: Text(
AppTranslations.kr_userInfo.logout,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
),
),
));
}
// 处理网格项点击
Future<void> _kr_handleGridItemTap(KRGridItemType type) async {
switch (type) {
case KRGridItemType.vpnWebsite:
final Uri url = Uri.parse(AppConfig.getInstance().kr_official_website);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
break;
case KRGridItemType.telegram:
final String tgUrl = AppConfig.getInstance().kr_official_telegram;
final String inviteCode = tgUrl.split('/').last.replaceAll('+', '');
if (Platform.isAndroid || Platform.isIOS) {
// 尝试多种 URL Scheme
final List<String> schemes = [
'tg://join?invite=$inviteCode', // Android 主要格式
'telegram://join?invite=$inviteCode', // iOS 可能使用的格式
];
bool launched = false;
for (String scheme in schemes) {
try {
final Uri tgAppUrl = Uri.parse(scheme);
if (await canLaunchUrl(tgAppUrl)) {
await launchUrl(tgAppUrl);
launched = true;
break;
}
} catch (e) {
continue;
}
}
if (!launched) {
KRCommonUtil.kr_showToast("尝试使用浏览器打开");
// 降级使用浏览器打开
try {
final Uri webUrl = Uri.parse(tgUrl);
if (await canLaunchUrl(webUrl)) {
await launchUrl(webUrl, mode: LaunchMode.externalApplication);
} else {
KRCommonUtil.kr_showToast("无法打开浏览器");
}
} catch (e) {
KRCommonUtil.kr_showToast("打开链接失败,请稍后重试");
}
}
} else {
// 桌面端处理
try {
final Uri webUrl = Uri.parse(tgUrl);
if (await canLaunchUrl(webUrl)) {
await launchUrl(webUrl);
} else {
KRCommonUtil.kr_showToast("无法打开Telegram链接");
}
} catch (e) {
KRCommonUtil.kr_showToast("打开链接失败,请稍后重试");
}
}
break;
case KRGridItemType.mail:
final String email = AppConfig.getInstance().kr_official_email;
await Clipboard.setData(ClipboardData(text: email));
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.copySuccess);
break;
case KRGridItemType.phone:
final String phone = AppConfig.getInstance().kr_official_telephone;
if (Platform.isAndroid || Platform.isIOS) {
final Uri phoneUri = Uri.parse('tel:$phone');
if (await canLaunchUrl(phoneUri)) {
await launchUrl(phoneUri);
} else {
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.notAvailable);
}
} else {
await Clipboard.setData(ClipboardData(text: phone));
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.copySuccess);
}
break;
case KRGridItemType.customerService:
Get.toNamed(Routes.KR_CRISP);
break;
case KRGridItemType.contactService:
Get.toNamed(Routes.KR_CRISP);
break;
}
}
}