初始化提交
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user