feat: 尚未购买的弹窗

This commit is contained in:
speakeloudest 2025-11-27 22:58:42 -08:00
parent adcde623c7
commit 55d7508807
7 changed files with 403 additions and 451 deletions

View File

@ -6,6 +6,7 @@ import 'package:get/get.dart';
import 'package:kaer_with_panels/app/modules/hi_menu/controllers/hi_menu_controller.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
/// Widget
///
@ -67,51 +68,10 @@ class UserInfoCard extends StatelessWidget {
fontWeight: FontWeight.bold,
),
),
Obx(() {
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
String expiryText;
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
KRSubscriptionExpiryText(
expireTimeProvider: () => controller.kr_subscribeService
.kr_currentSubscribe.value?.expireTime,
),
],
),
),

View File

@ -19,13 +19,14 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
class HIUserInfoView extends GetView<HIUserInfoController> {
const HIUserInfoView({super.key});
@override
Widget build(BuildContext context) {
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin();
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin();
return HIBaseScaffold(
child: Stack(
children: [
@ -69,9 +70,14 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() {
final account = KRAppRunData.getInstance().kr_account.value;
final isDeviceLogin = account != null && account.startsWith('9000');
final accountText = isDeviceLogin ? '待绑定' : 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}';
final account = KRAppRunData.getInstance()
.kr_account
.value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}';
return Text(
accountText,
style: TextStyle(
@ -84,51 +90,13 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
overflow: TextOverflow.ellipsis,
);
}),
Obx(() {
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
String expiryText;
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
KRSubscriptionExpiryText(
expireTimeProvider: () => controller
.kr_subscribeService
.kr_currentSubscribe
.value
?.expireTime,
),
],
),
),
@ -138,7 +106,8 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
SizedBox(height: 12.w),
//
Obx(() {
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin();
final isDeviceLogin =
KRAppRunData.getInstance().isDeviceLogin();
if (!isDeviceLogin) return SizedBox.shrink();
return InkWell(
onTap: () {
@ -155,14 +124,18 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
}
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: 0).copyWith(bottom: 10), //
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
margin: EdgeInsets.symmetric(horizontal: 0)
.copyWith(bottom: 10), //
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(color: Colors.white, width: 2),
border:
Border.all(color: Colors.white, width: 2),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
isDeviceLogin ? '绑定邮箱' : '修改密码',
@ -203,7 +176,8 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
// 2. GridView
shrinkWrap: true,
// 3.
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //
crossAxisSpacing: 10, //
mainAxisSpacing: 10, //
@ -226,8 +200,10 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
child: Text(
'请确认是否移除此设备?',
style: KrAppTextStyle(
color:
Theme.of(context).textTheme.bodyMedium?.color,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color,
fontSize: 14,
fontWeight: FontWeight.w600,
),
@ -240,8 +216,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
},
onCancel: () {
controller.deleteDevice(id);
}
);
});
},
);
} else {
@ -261,9 +236,11 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
),
// 👇 3: Expanded
Obx(() {
if((KRAppRunData.getInstance().kr_account.value != null &&
KRAppRunData.getInstance().kr_account.value!.startsWith('9000')))
return SizedBox.shrink();
if ((KRAppRunData.getInstance().kr_account.value != null &&
KRAppRunData.getInstance()
.kr_account
.value!
.startsWith('9000'))) return SizedBox.shrink();
return Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Column(
@ -278,7 +255,10 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
'注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1.
textAlign: TextAlign.left, //
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
@ -292,8 +272,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
Get.toNamed(Routes.KR_DELETE_ACCOUNT);
},
// 4. onConfirm
onConfirm: () {
},
onConfirm: () {},
);
},
child: Container(
@ -301,7 +280,8 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2),
border: Border.all(
color: const Color(0xFFFF2ED1), width: 2),
),
alignment: Alignment.center,
child: Text(
@ -324,7 +304,10 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
'确认要退出您的账号?', // 1.
textAlign: TextAlign.center, //
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
color: Theme.of(context)
.textTheme
.bodyMedium
?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
@ -333,15 +316,14 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
cancelText: '确认',
confirmText: '返回',
onCancel: () async {
final currentDevice = controller.devices.firstWhere(
(device) => device['is_current'] == true,
final currentDevice =
controller.devices.firstWhere(
(device) => device['is_current'] == true,
);
final deviceId = currentDevice['id'] as String;
await controller.deleteDevice(deviceId);
},
onConfirm: () {
},
onConfirm: () {},
);
},
child: Container(
@ -376,6 +358,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
),
);
}
///
Widget _buildDeviceCard({
required BuildContext context,
@ -396,7 +379,8 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
final deviceType = deviceInfo['type'] as String;
final iconName = deviceInfo['icon'] as String;
return Stack( // 使 Stack
return Stack(
// 使 Stack
children: [
//
Container(
@ -486,31 +470,31 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
//
final device_limit = AppConfig.getInstance().device_limit;
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w, bottom: 16.w),
child: Column(
mainAxisSize: MainAxisSize.min, // Column
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 2. Text
Text(
'请使用绑定邮箱登录新设备',
style: KrAppTextStyle(
color: Colors.black, // 使
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w, bottom: 16.w),
child: Column(
mainAxisSize: MainAxisSize.min, // Column
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 2. Text
Text(
'请使用绑定邮箱登录新设备',
style: KrAppTextStyle(
color: Colors.black, // 使
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
SizedBox(height: 20.w),
Text(
"每个账号最多允许同时使用${device_limit}台设备同时在线",
style: KrAppTextStyle(
color: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
)
/*Obx(() {
),
SizedBox(height: 20.w),
Text(
"每个账号最多允许同时使用${device_limit}台设备同时在线",
style: KrAppTextStyle(
color: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
)
/*Obx(() {
// final current = controller.kr_subscribeService.kr_currentSubscribe.value;
// current null 0
@ -518,9 +502,9 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
return
}),*/
],
),
],
),
),
confirmText: KRAppRunData.getInstance().isDeviceLogin() ? '前往' : null,
cancelText: '取消',
onConfirm: () {
@ -591,7 +575,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
final RegExp regExp = RegExp(r'\((.*?)\)');
final Match? match = regExp.firstMatch(deviceName);
if (match != null && match.groupCount >= 1 ) {
if (match != null && match.groupCount >= 1) {
//
final inside = match.group(1)!; // "Android; google Pixel 9; 15"

View File

@ -4,11 +4,11 @@ import 'package:get/get.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
import '../controllers/kr_delete_account_controller.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
const KRDeleteAccountView({super.key});
@ -46,7 +46,8 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2),
border:
Border.all(color: const Color(0xFFFF2ED1), width: 2),
),
alignment: Alignment.center,
child: Text(
@ -87,7 +88,6 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
);
}
Widget _buildUserInfoSection() {
return Row(
children: [
@ -129,12 +129,16 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
);
}),
Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value;
final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString();
final deviceId = KRAppRunData.getInstance().deviceId ?? '';
final account = KRAppRunData.getInstance()
.kr_account
.value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}';
return Text(
'ID: ${userId.isNotEmpty ? userId : deviceId}',
accountText,
style: TextStyle(
color: Colors.white.withOpacity(0.85),
fontSize: 14.sp,
@ -142,51 +146,14 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
),
);
}),
Obx(() {
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
String expiryText;
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
expiryText = '到期时间:${expireDateTime}';final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
KRSubscriptionExpiryText(
expireTimeProvider: () => controller
.kr_subscribeService.kr_currentSubscribe.value?.expireTime,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
),
],
),
),
@ -213,7 +180,8 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
color: const Color(0xFFA6A6A6),
fontWeight: FontWeight.w600,
),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
contentPadding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2),
@ -233,14 +201,15 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
suffixIcon: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
child: Obx(() {
return GestureDetector(
onTap: controller.kr_canSendCode.value ? controller.kr_sendCode : null,
onTap: controller.kr_canSendCode.value
? controller.kr_sendCode
: null,
child: Container(
width: 100.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: controller.kr_canSendCode.value
color: controller.kr_canSendCode.value
? Theme.of(Get.context!).primaryColor
: const Color(0xFFD5D5D5),
borderRadius: BorderRadius.circular(100.r), //
@ -254,7 +223,7 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
fontWeight: FontWeight.w600,
color: controller.kr_canSendCode.value
? Colors.black
: const Color(0xFF464655) ,
: const Color(0xFF464655),
),
),
),

View File

@ -86,8 +86,17 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
print('🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
return;
}
final hasValidSubscription =
controller.kr_subscribeService.kr_availableSubscribes.isNotEmpty;
final current = controller.kr_subscribeService.kr_currentSubscribe.value;
bool hasValidSubscription = false;
if (current != null) {
DateTime? expire;
try {
expire = DateTime.parse(current.expireTime);
} catch (_) {
expire = null;
}
hasValidSubscription = expire != null && expire.isAfter(DateTime.now());
}
if (hasValidSubscription) {
controller.kr_toggleSwitch(!controller.kr_isConnected.value);
} else {
@ -291,4 +300,3 @@ class _ContinuousRippleEffectState extends State<ContinuousRippleEffect>
);
}
}

View File

@ -18,7 +18,6 @@ import 'kr_home_subscription_view.dart';
import './hi_animated_connect_button.dart';
import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
class KRHomeView extends StatefulWidget {
const KRHomeView({super.key});
@ -35,8 +34,6 @@ class _KRHomeViewState extends State<KRHomeView> {
controller = Get.find<KRHomeController>();
}
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -50,174 +47,186 @@ class _KRHomeViewState extends State<KRHomeView> {
children: [
HIBaseScaffold(
showMenuButton: true,
topContentAreaHeight: 80,
child: Stack(
fit: StackFit.expand,
children: [
Align(
alignment: Alignment.topLeft,
child:
SafeArea(
child: Padding(
padding: EdgeInsets.fromLTRB(40.w, 8.w, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Hi快VPN-网在我在,网快我快',
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
//
Obx(() {
// 1.
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
// 2. Widget
Widget content;
// --- ---
final textStyle = TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
height: 1.2, //
);
// 3.
if (currentSubscribe == null) {
// --- 1: ---
content = Text(
'尚未购买套餐',
style: textStyle,
);
} else {
// --- 2: ---
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime = DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
//
}
if (expireDateTime != null && expireDateTime.isBefore(now)) {
// --- 2.1: ---
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
// 使 \n Text
content = Text(
'您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐',
style: textStyle,
);
} else {
// --- 2.2: ---
final difference = expireDateTime?.difference(now);
final remainingDaysText = (difference?.inDays ?? 0) > 0
? '${difference!.inDays}'
: '不足一天';
// 使 \n Text
content = Text(
'套餐剩余:$remainingDaysText\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}',
style: textStyle,
);
}
}
// 4. Container
return Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(top: 8.h),
child: content,
);
}),
SizedBox(height: 8.h), //
// --- Checkbox ---
Obx(() {
return Row(
mainAxisSize: MainAxisSize.min, // Row
children: [
// --- Checkbox ---
GestureDetector(
//
onTap: () => controller.toggleQuickConnect(!controller.isQuickConnectEnabled.value),
child: AbsorbPointer(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 1. Checkbox
Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
color: controller.isQuickConnectEnabled.value
? Theme.of(context).primaryColor
: Colors.transparent,
border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
),
borderRadius: BorderRadius.circular(4.r),
),
child: controller.isQuickConnectEnabled.value
? Icon(
Icons.check,
size: 14.w,
color: Colors.black,
)
: null,
),
SizedBox(width: 8.w),
// 2.
Text(
'闪连',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
],
),
),
topContentAreaHeight: 80,
child: Stack(
fit: StackFit.expand,
children: [
Align(
alignment: Alignment.topLeft,
child: SafeArea(
child: Padding(
padding: EdgeInsets.fromLTRB(40.w, 8.w, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Hi快VPN-网在我在,网快我快',
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
// --- ---
SizedBox(width: 6.w), //
GestureDetector(
onTap: () {
HIDialog.show(
title: '*闪连功能',
message: '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开',
),
//
Obx(() {
// 1.
final currentSubscribe = controller
.kr_subscribeService.kr_currentSubscribe.value;
// 2. Widget
Widget content;
// --- ---
final normalStyle = TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
height: 1.2,
);
final highlightStyle = normalStyle.copyWith(
color: const Color(0xFFFF00B7),
);
// 3.
if (currentSubscribe == null) {
// --- 1: ---
content = Text(
'尚未购买套餐',
style: highlightStyle,
);
} else {
// --- 2: ---
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
//
}
if (expireDateTime != null &&
expireDateTime.isBefore(now)) {
// --- 2.1: ---
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
// 使 \n Text
content = Text(
'您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐',
style: highlightStyle,
);
},
child: KrLocalImage(
imageName: 'question-icon', //
imageType: ImageType.svg, //
width: 24.w,
height: 24.w,
color: Colors.white, //
),
),
],
);
}),
],
} else {
// --- 2.2: ---
final difference =
expireDateTime?.difference(now);
final remainingDaysText =
(difference?.inDays ?? 0) > 0
? '${difference!.inDays}'
: '不足一天';
// 使 \n Text
content = Text(
'套餐剩余:$remainingDaysText\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}',
style: normalStyle,
);
}
}
// 4. Container
return Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(top: 8.h),
child: content,
);
}),
SizedBox(height: 8.h), //
// --- Checkbox ---
Obx(() {
return Row(
mainAxisSize: MainAxisSize.min, // Row
children: [
// --- Checkbox ---
GestureDetector(
//
onTap: () => controller.toggleQuickConnect(
!controller.isQuickConnectEnabled.value),
child: AbsorbPointer(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 1. Checkbox
Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
color: controller
.isQuickConnectEnabled.value
? Theme.of(context).primaryColor
: Colors.transparent,
border: Border.all(
color:
Theme.of(context).primaryColor,
width: 1.5,
),
borderRadius:
BorderRadius.circular(4.r),
),
child: controller
.isQuickConnectEnabled.value
? Icon(
Icons.check,
size: 14.w,
color: Colors.black,
)
: null,
),
SizedBox(width: 8.w),
// 2.
Text(
'闪连',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
// --- ---
SizedBox(width: 6.w), //
GestureDetector(
onTap: () {
HIDialog.show(
title: '*闪连功能',
message:
'开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开',
);
},
child: KrLocalImage(
imageName: 'question-icon', //
imageType: ImageType.svg, //
width: 24.w,
height: 24.w,
color: Colors.white, //
),
),
],
);
}),
],
),
),
),
),
),
],
),
],
),
),
// HIAnimatedConnectButton HIBaseScaffold SafeArea
HIAnimatedConnectButton(),
],
);
),
// HIAnimatedConnectButton HIBaseScaffold SafeArea
HIAnimatedConnectButton(),
],
);
}
}

View File

@ -12,6 +12,7 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
import 'package:flutter/foundation.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
class KRLoginView extends GetView<KRLoginController> {
const KRLoginView({super.key});
@ -19,7 +20,8 @@ class KRLoginView extends GetView<KRLoginController> {
@override
Widget build(BuildContext context) {
final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
final isHideBack = (Get.arguments as Map<String, dynamic>?)?['is-back'] ?? false;
final isHideBack =
(Get.arguments as Map<String, dynamic>?)?['is-back'] ?? false;
return HIBaseScaffold(
showBack: !isHideBack,
resizeToAvoidBottomInset: true,
@ -27,7 +29,9 @@ class KRLoginView extends GetView<KRLoginController> {
children: [
SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w,),
padding: EdgeInsets.symmetric(
horizontal: 40.w,
),
child: Column(
children: [
// Text(
@ -43,8 +47,7 @@ class KRLoginView extends GetView<KRLoginController> {
),
),
),
if (!isKeyboardVisible)
const HIHelpEntrance(),
if (!isKeyboardVisible) const HIHelpEntrance(),
],
),
);
@ -76,30 +79,17 @@ class KRLoginView extends GetView<KRLoginController> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() {
final account = KRAppRunData.getInstance().kr_account.value;
final isDeviceLogin = account != null && account.startsWith('9000');
if (isDeviceLogin) return const SizedBox();
final account = KRAppRunData.getInstance()
.kr_account
.value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: 'ID: ${KRAppRunData.getInstance().kr_account.value.toString()}';
return Text(
account ?? '',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.bold,
height: 0.9,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}),
Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value;
final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString();
final deviceId = KRAppRunData.getInstance().deviceId ?? '';
return Text(
'ID: ${userId.isNotEmpty ? userId : deviceId}',
accountText,
style: TextStyle(
color: Colors.white.withOpacity(0.85),
fontSize: 14.sp,
@ -107,51 +97,14 @@ class KRLoginView extends GetView<KRLoginController> {
),
);
}),
Obx(() {
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
String expiryText;
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
expiryText = '到期时间:${expireDateTime}';final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
KRSubscriptionExpiryText(
expireTimeProvider: () => controller
.kr_subscribeService.kr_currentSubscribe.value?.expireTime,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
),
],
),
),
@ -283,7 +236,8 @@ class KRLoginView extends GetView<KRLoginController> {
color: const Color(0xFFA6A6A6),
fontWeight: FontWeight.w600,
),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
contentPadding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w), //
borderSide: BorderSide(color: Colors.white, width: 2),
@ -327,7 +281,8 @@ class KRLoginView extends GetView<KRLoginController> {
color: const Color(0xFFA6A6A6),
fontWeight: FontWeight.w600,
),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
contentPadding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2),
@ -347,14 +302,15 @@ class KRLoginView extends GetView<KRLoginController> {
suffixIcon: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
child: Obx(() {
return GestureDetector(
onTap: controller.kr_canSendCode.value ? () => controller.kr_sendCode() : null,
onTap: controller.kr_canSendCode.value
? () => controller.kr_sendCode()
: null,
child: Container(
width: 100.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: controller.kr_canSendCode.value
color: controller.kr_canSendCode.value
? Theme.of(Get.context!).primaryColor
: const Color(0xFFD5D5D5),
borderRadius: BorderRadius.circular(100.r), //
@ -366,7 +322,7 @@ class KRLoginView extends GetView<KRLoginController> {
fontWeight: FontWeight.w600,
color: controller.kr_canSendCode.value
? Colors.black
: const Color(0xFF464655) ,
: const Color(0xFF464655),
),
),
),
@ -378,7 +334,6 @@ class KRLoginView extends GetView<KRLoginController> {
);
}
Widget _buildSaveButton() {
return GestureDetector(
onTap: () {

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class KRSubscriptionExpiryText extends StatelessWidget {
final String? Function() expireTimeProvider;
final TextStyle? style;
const KRSubscriptionExpiryText({
Key? key,
required this.expireTimeProvider,
this.style,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(() {
final expireTimeStr = expireTimeProvider();
String expiryText;
bool highlight = false;
if (expireTimeStr == null || expireTimeStr.isEmpty) {
expiryText = '尚未购买套餐';
highlight = true;
} else {
DateTime? expireDateTime;
try {
expireDateTime = DateTime.parse(expireTimeStr);
} catch (_) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(DateTime.now())) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
highlight = true;
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
final baseStyle = style ??
const TextStyle(
color: Colors.white,
fontSize: 12,
);
final appliedStyle = highlight
? baseStyle.copyWith(color: const Color(0xFFFF00B7))
: baseStyle;
return Text(
expiryText,
style: appliedStyle,
);
});
}
}