初始化提交
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/kr_purchase_membership_controller.dart';
|
||||
|
||||
class KRPurchaseMembershipBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<KRPurchaseMembershipController>(
|
||||
() => KRPurchaseMembershipController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+506
@@ -0,0 +1,506 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/services/api_service/kr_subscribe_api.dart';
|
||||
import 'package:kaer_with_panels/app/model/response/kr_package_list.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
|
||||
import '../../../common/app_run_data.dart';
|
||||
import '../../../model/response/kr_already_subscribe.dart';
|
||||
import '../../../model/response/kr_payment_methods.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../../../services/api_service/kr_api.user.dart';
|
||||
import '../../../utils/kr_event_bus.dart';
|
||||
|
||||
/// 会员购买控制器
|
||||
/// 负责处理会员套餐选择、支付方式选择和订阅流程
|
||||
class KRPurchaseMembershipController extends GetxController {
|
||||
// 注入的服务
|
||||
final KRSubscribeApi _kr_subscribeApi = KRSubscribeApi();
|
||||
|
||||
// 事件监听器
|
||||
Worker? _kr_eventWorker;
|
||||
|
||||
// UI 状态
|
||||
final RxBool kr_isLoading = false.obs;
|
||||
final RxString kr_errorMessage = ''.obs;
|
||||
final RxString kr_userEmail = ''.obs;
|
||||
final RxBool kr_showPlanSelector = false.obs; // 是否显示套餐选择器
|
||||
|
||||
// 数据状态
|
||||
final RxList<KRPackageListItem> kr_plans = <KRPackageListItem>[].obs;
|
||||
final RxList<KRPaymentMethod> kr_paymentMethods = <KRPaymentMethod>[].obs;
|
||||
final RxInt kr_selectedPlanIndex = 0.obs;
|
||||
final RxInt kr_selectedPaymentMethodIndex = (-1).obs;
|
||||
final RxInt kr_selectedDiscountIndex = (-1).obs;
|
||||
|
||||
// 已订阅套餐列表
|
||||
var _kr_alreadySubscribe = <KRAlreadySubscribe>[];
|
||||
|
||||
/// 描述是否展开
|
||||
final kr_isDescriptionExpanded = false.obs;
|
||||
|
||||
/// 当前余额
|
||||
var _kr_balance = 0;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
kr_initializeData();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_kr_eventWorker?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/// 初始化数据
|
||||
Future<void> kr_initializeData() async {
|
||||
kr_userEmail.value = KRAppRunData.getInstance().kr_account.toString();
|
||||
await kr_getPackageList();
|
||||
|
||||
// 监听所有支付相关消息
|
||||
_kr_eventWorker = KREventBus().kr_listenMessages(
|
||||
[KRMessageType.kr_payment, KRMessageType.kr_subscribe_update],
|
||||
_kr_handleMessage,
|
||||
);
|
||||
}
|
||||
|
||||
/// 处理消息
|
||||
Future<void> _kr_handleMessage(KRMessageData message) async {
|
||||
switch (message.kr_type) {
|
||||
case KRMessageType.kr_payment:
|
||||
await _iniUserInfo();
|
||||
// 只更新支付方式显示,因为支付方式标题中包含余额信息
|
||||
if (kr_paymentMethods.isNotEmpty) {
|
||||
final balanceMethodIndex = kr_paymentMethods
|
||||
.indexWhere((method) => method.platform == 'balance');
|
||||
if (balanceMethodIndex != -1) {
|
||||
// 触发支付方式列表更新
|
||||
kr_paymentMethods.refresh();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KRMessageType.kr_subscribe_update:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取套餐列表和支付方式
|
||||
Future<void> kr_getPackageList() async {
|
||||
kr_isLoading.value = true;
|
||||
kr_selectedPlanIndex.value = 0; // 重置套餐选择
|
||||
kr_selectedDiscountIndex.value = -1; // 重置折扣选择
|
||||
kr_selectedPaymentMethodIndex.value = -1; // 重置支付方式选择
|
||||
|
||||
await _iniUserInfo();
|
||||
await kr_getAlreadySubscribe();
|
||||
await kr_fetchPackages();
|
||||
await kr_fetchPaymentMethods();
|
||||
|
||||
// 根据套餐数量决定是否显示套餐选择器
|
||||
kr_showPlanSelector.value = kr_plans.length > 1;
|
||||
|
||||
kr_isLoading.value = false;
|
||||
}
|
||||
|
||||
/// 初始化用户信息
|
||||
Future<void> _iniUserInfo() async {
|
||||
final either0 = await KRUserApi().kr_getUserInfo();
|
||||
either0.fold(
|
||||
(error) {
|
||||
KRLogUtil.kr_e(error.msg, tag: 'AppRunData');
|
||||
},
|
||||
(userInfo) async {
|
||||
_kr_balance = userInfo.balance;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取用户已订阅套餐
|
||||
Future<void> kr_getAlreadySubscribe() async {
|
||||
final either = await _kr_subscribeApi.kr_getAlreadySubscribe();
|
||||
either.fold(
|
||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
||||
(alreadySubscribe) {
|
||||
_kr_alreadySubscribe = alreadySubscribe;
|
||||
KRLogUtil.kr_i(
|
||||
'已订阅套餐: ${_kr_alreadySubscribe.map((e) => e.subscribeId).toList()}',
|
||||
tag: 'PurchaseMembershipController');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取套餐列表
|
||||
Future<void> kr_fetchPackages() async {
|
||||
final either = await _kr_subscribeApi.kr_getPackageListList();
|
||||
either.fold(
|
||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
||||
(packageList) {
|
||||
kr_plans.value = packageList.kr_list;
|
||||
// 默认选择第一个套餐
|
||||
if (kr_plans.isNotEmpty) {
|
||||
kr_selectedPlanIndex.value = 0;
|
||||
kr_initializeSelection(kr_plans.first);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取支付方式列表
|
||||
Future<void> kr_fetchPaymentMethods() async {
|
||||
final either = await _kr_subscribeApi.kr_getPaymentMethods();
|
||||
either.fold(
|
||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
||||
(paymentMethods) {
|
||||
kr_paymentMethods.value = paymentMethods;
|
||||
|
||||
// 检查当前选择的套餐价格是否小于等于余额
|
||||
if (kr_plans.isNotEmpty) {
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
final selectedPrice = kr_getPlanPrice(selectedPlan,
|
||||
discountIndex: kr_selectedDiscountIndex.value);
|
||||
|
||||
// 查找余额支付方式的索引
|
||||
final balanceMethodIndex = paymentMethods
|
||||
.indexWhere((method) => method.platform == 'balance');
|
||||
|
||||
// 如果找到余额支付方式且余额足够,默认选择余额支付
|
||||
if (balanceMethodIndex != -1 && selectedPrice <= _kr_balance / 100) {
|
||||
kr_selectPaymentMethod(balanceMethodIndex);
|
||||
} else {
|
||||
// 查找第一个非余额支付方式
|
||||
final nonBalanceMethodIndex = paymentMethods
|
||||
.indexWhere((method) => method.platform != 'balance');
|
||||
if (nonBalanceMethodIndex != -1) {
|
||||
kr_selectPaymentMethod(nonBalanceMethodIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取支付方式显示标题
|
||||
String kr_getPaymentMethodTitle(KRPaymentMethod method) {
|
||||
if (method.platform == 'balance') {
|
||||
return '${method.name}(¥${(_kr_balance / 100).toStringAsFixed(2)})';
|
||||
}
|
||||
return method.name;
|
||||
}
|
||||
|
||||
/// 选择套餐
|
||||
void kr_selectPlan(int planIndex, {int? discountIndex}) {
|
||||
if (planIndex >= 0 && planIndex < kr_plans.length) {
|
||||
kr_selectedPlanIndex.value = planIndex;
|
||||
|
||||
// 确保折扣索引有效
|
||||
if (discountIndex != null) {
|
||||
final plan = kr_plans[planIndex];
|
||||
if (discountIndex >= 0 && discountIndex < plan.kr_discount.length) {
|
||||
kr_selectedDiscountIndex.value = discountIndex;
|
||||
} else {
|
||||
// 如果传入的折扣索引无效,但有折扣选项,则默认选择第一个
|
||||
if (plan.kr_discount.isNotEmpty) {
|
||||
kr_selectedDiscountIndex.value = 0;
|
||||
} else {
|
||||
kr_selectedDiscountIndex.value = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没有传入折扣索引,但有折扣选项,则默认选择第一个
|
||||
final plan = kr_plans[planIndex];
|
||||
if (plan.kr_discount.isNotEmpty) {
|
||||
kr_selectedDiscountIndex.value = 0;
|
||||
} else {
|
||||
kr_selectedDiscountIndex.value = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置支付方式选择
|
||||
kr_selectedPaymentMethodIndex.value = -1;
|
||||
|
||||
// 重新判断应该选择的支付方式
|
||||
_kr_updatePaymentMethodSelection();
|
||||
|
||||
// 更新UI状态
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新支付方式选择
|
||||
void _kr_updatePaymentMethodSelection() {
|
||||
if (kr_plans.isEmpty || kr_paymentMethods.isEmpty) return;
|
||||
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
final selectedPrice = kr_getPlanPrice(selectedPlan,
|
||||
discountIndex: kr_selectedDiscountIndex.value);
|
||||
|
||||
// 查找余额支付方式的索引
|
||||
final balanceMethodIndex =
|
||||
kr_paymentMethods.indexWhere((method) => method.platform == 'balance');
|
||||
|
||||
// 如果找到余额支付方式且余额足够,选择余额支付
|
||||
if (balanceMethodIndex != -1 && selectedPrice <= _kr_balance / 100) {
|
||||
kr_selectPaymentMethod(balanceMethodIndex);
|
||||
} else {
|
||||
// 查找第一个非余额支付方式
|
||||
final nonBalanceMethodIndex = kr_paymentMethods
|
||||
.indexWhere((method) => method.platform != 'balance');
|
||||
if (nonBalanceMethodIndex != -1) {
|
||||
kr_selectPaymentMethod(nonBalanceMethodIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 选择支付方式
|
||||
void kr_selectPaymentMethod(int index) {
|
||||
if (index >= 0 && index < kr_paymentMethods.length) {
|
||||
kr_selectedPaymentMethodIndex.value = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前选中的数量
|
||||
int kr_getSelectedQuantity() {
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
if (kr_selectedDiscountIndex.value >= 0 &&
|
||||
kr_selectedDiscountIndex.value < selectedPlan.kr_discount.length) {
|
||||
return selectedPlan
|
||||
.kr_discount[kr_selectedDiscountIndex.value].kr_quantity;
|
||||
}
|
||||
return 1; // 默认数量为1
|
||||
}
|
||||
|
||||
/// 开始订阅流程
|
||||
Future<void> kr_startSubscription() async {
|
||||
if (!kr_validateSubscriptionData()) return;
|
||||
|
||||
kr_errorMessage.value = '';
|
||||
|
||||
try {
|
||||
await kr_processPurchaseAndCheckout();
|
||||
} catch (e) {
|
||||
kr_errorMessage.value = '订阅失败: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
||||
/// 验证订阅数据
|
||||
bool kr_validateSubscriptionData() {
|
||||
if (kr_plans.isEmpty) {
|
||||
KRCommonUtil.kr_showToast('没有可用的套餐');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kr_selectedPaymentMethodIndex.value < 0 ||
|
||||
kr_selectedPaymentMethodIndex.value >= kr_paymentMethods.length) {
|
||||
KRCommonUtil.kr_showToast('请选择支付方式');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 处理购买和结账流程
|
||||
Future<void> kr_processPurchaseAndCheckout() async {
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
final selectedPaymentMethod =
|
||||
kr_paymentMethods[kr_selectedPaymentMethodIndex.value];
|
||||
|
||||
// 获取选中的数量
|
||||
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)
|
||||
.userSubscribeId
|
||||
: 0;
|
||||
|
||||
// 根据判断结果调用不同的接口
|
||||
final purchaseEither = isRenewal
|
||||
? await _kr_subscribeApi.kr_renewal(
|
||||
subscribeId,
|
||||
quantity,
|
||||
selectedPaymentMethod.id,
|
||||
'',
|
||||
)
|
||||
: await _kr_subscribeApi.kr_purchase(
|
||||
selectedPlan.kr_id,
|
||||
quantity,
|
||||
selectedPaymentMethod.id,
|
||||
'',
|
||||
);
|
||||
|
||||
purchaseEither.fold(
|
||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
||||
(order) async {
|
||||
// 所有支付方式都需要调用 checkout 接口
|
||||
final checkoutEither = await _kr_subscribeApi.kr_checkout(order);
|
||||
checkoutEither.fold(
|
||||
(error) => KRCommonUtil.kr_showToast(error.msg),
|
||||
(uri) => Get.toNamed(
|
||||
Routes.KR_ORDER_STATUS,
|
||||
arguments: {
|
||||
'url': uri,
|
||||
'order': order,
|
||||
'payment_type': selectedPaymentMethod.platform,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取套餐价格
|
||||
double kr_getPlanPrice(KRPackageListItem plan, {int? discountIndex}) {
|
||||
if (discountIndex != null &&
|
||||
discountIndex >= 0 &&
|
||||
discountIndex < plan.kr_discount.length) {
|
||||
// 计算折扣价格
|
||||
final discount = plan.kr_discount[discountIndex];
|
||||
return (plan.kr_unitPrice / 100) *
|
||||
discount.kr_quantity *
|
||||
(discount.kr_discount / 100);
|
||||
}
|
||||
return plan.kr_unitPrice / 100;
|
||||
}
|
||||
|
||||
/// 获取时间字符串
|
||||
String kr_getTimeStr(KRPackageListItem plan, {int? discountIndex}) {
|
||||
final quantity = discountIndex != null &&
|
||||
discountIndex >= 0 &&
|
||||
discountIndex < plan.kr_discount.length
|
||||
? plan.kr_discount[discountIndex].kr_quantity
|
||||
: 1;
|
||||
|
||||
if (plan.kr_unitTime == 'Month') {
|
||||
return AppTranslations.kr_purchaseMembership.month(quantity);
|
||||
} else if (plan.kr_unitTime == 'Year') {
|
||||
return AppTranslations.kr_purchaseMembership.year(quantity);
|
||||
} else if (plan.kr_unitTime == 'Day') {
|
||||
return AppTranslations.kr_purchaseMembership.day(quantity);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/// 获取折扣文本
|
||||
String kr_getDiscountText(KRPackageListItem plan, int discountIndex) {
|
||||
if (discountIndex >= 0 && discountIndex < plan.kr_discount.length) {
|
||||
final discount = plan.kr_discount[discountIndex];
|
||||
// 折扣值为 100 表示原价,不需要显示折扣
|
||||
if (discount.kr_discount == 100) {
|
||||
return '';
|
||||
}
|
||||
// 计算折扣百分比(例如:95% 显示为 -5%)
|
||||
final discountPercent = 100 - discount.kr_discount;
|
||||
return '-${discountPercent}%';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/// 获取套餐总选项数
|
||||
int kr_getTotalOptionsCount(KRPackageListItem plan) {
|
||||
// 确保折扣列表不为空
|
||||
if (plan.kr_discount.isEmpty) {
|
||||
return 1; // 如果没有折扣选项,至少返回1个选项
|
||||
}
|
||||
return plan.kr_discount.length;
|
||||
}
|
||||
|
||||
/// 初始化选择
|
||||
void kr_initializeSelection(KRPackageListItem plan) {
|
||||
if (plan.kr_discount.isNotEmpty) {
|
||||
// 默认选择第一个选项
|
||||
kr_selectedDiscountIndex.value = 0;
|
||||
} else {
|
||||
// 如果没有选项,设置为 -1
|
||||
kr_selectedDiscountIndex.value = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前选中套餐的描述
|
||||
String kr_getSelectedPlanDescription() {
|
||||
if (kr_selectedPlanIndex.value >= kr_plans.length) return '';
|
||||
final plan = kr_plans[kr_selectedPlanIndex.value];
|
||||
return plan.kr_description.kr_features
|
||||
.map((feature) => feature.kr_label)
|
||||
.join('、');
|
||||
}
|
||||
|
||||
/// 获取当前选中套餐的特性标题列表
|
||||
List<String> kr_getSelectedPlanFeatureLabels() {
|
||||
if (kr_selectedPlanIndex.value >= kr_plans.length) return [];
|
||||
final plan = kr_plans[kr_selectedPlanIndex.value];
|
||||
return plan.kr_description.kr_features
|
||||
.map((feature) => feature.kr_label)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 获取当前选中套餐的详细信息
|
||||
List<KRFeature> kr_getSelectedPlanFeatures() {
|
||||
if (kr_selectedPlanIndex.value >= kr_plans.length) return [];
|
||||
final plan = kr_plans[kr_selectedPlanIndex.value];
|
||||
return plan.kr_description.kr_features;
|
||||
}
|
||||
|
||||
/// 判断当前选中的套餐是否是续订
|
||||
bool kr_isRenewal() {
|
||||
if (kr_plans.isEmpty || _kr_alreadySubscribe.isEmpty) return false;
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
return _kr_alreadySubscribe
|
||||
.any((subscribe) => subscribe.subscribeId == selectedPlan.kr_id);
|
||||
}
|
||||
|
||||
/// 获取当前选中套餐的订阅按钮文字
|
||||
String kr_getSubscribeButtonText() {
|
||||
if (kr_plans.isEmpty) return '';
|
||||
|
||||
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
|
||||
final isRenewal = _kr_alreadySubscribe
|
||||
.any((subscribe) => subscribe.subscribeId == selectedPlan.kr_id);
|
||||
|
||||
return isRenewal
|
||||
? AppTranslations.kr_purchaseMembership.renewNow
|
||||
: AppTranslations.kr_purchaseMembership.startSubscription;
|
||||
}
|
||||
|
||||
/// 切换描述展开状态
|
||||
void kr_toggleDescriptionExpanded() {
|
||||
kr_isDescriptionExpanded.value = !kr_isDescriptionExpanded.value;
|
||||
}
|
||||
|
||||
/// 获取流量限制显示文本
|
||||
String kr_getTrafficLimitText(KRPackageListItem plan) {
|
||||
KRLogUtil.kr_i('原始流量值: ${plan.kr_traffic}', tag: 'TrafficLimit');
|
||||
if (plan.kr_traffic == 0) {
|
||||
return AppTranslations.kr_purchaseMembership.unlimitedTraffic;
|
||||
}
|
||||
// 将字节转换为GB
|
||||
final trafficInGB = plan.kr_traffic / (1024 * 1024 * 1024);
|
||||
KRLogUtil.kr_i('转换为GB后的值: $trafficInGB', tag: 'TrafficLimit');
|
||||
|
||||
if (trafficInGB < 1) {
|
||||
return '${(trafficInGB * 1024).toStringAsFixed(0)}MB';
|
||||
} else if (trafficInGB < 1024) {
|
||||
return '${trafficInGB.toStringAsFixed(0)}GB';
|
||||
} else {
|
||||
return '${(trafficInGB / 1024).toStringAsFixed(1)}TB';
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取设备限制显示文本
|
||||
String kr_getDeviceLimitText(KRPackageListItem plan) {
|
||||
if (plan.kr_deviceLimit == 0) {
|
||||
return AppTranslations.kr_purchaseMembership.unlimitedDevices;
|
||||
}
|
||||
return AppTranslations.kr_purchaseMembership
|
||||
.devices(plan.kr_deviceLimit.toString());
|
||||
}
|
||||
}
|
||||
+796
@@ -0,0 +1,796 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
import 'package:kaer_with_panels/app/model/response/kr_package_list.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||
import '../controllers/kr_purchase_membership_controller.dart';
|
||||
import '../../../widgets/kr_simple_loading.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import '../../../widgets/kr_network_image.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
|
||||
|
||||
|
||||
|
||||
/// 购买会员页面视图
|
||||
class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
|
||||
const KRPurchaseMembershipView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
body: Obx(() {
|
||||
return 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_purchaseMembership.purchasePackage,
|
||||
style: KrAppTextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_kr_buildAccountSection(context),
|
||||
if (controller.kr_isLoading.value)
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Center(
|
||||
child: KRSimpleLoading(
|
||||
color: Colors.blue,
|
||||
size: 50.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (controller.kr_plans.isEmpty)
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppTranslations.kr_purchaseMembership.noData,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16.r),
|
||||
child: Column(
|
||||
children: [
|
||||
// 套餐选择部分
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_purchaseMembership.selectPackage,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
if (controller.kr_plans.length > 1)
|
||||
Container(
|
||||
height: 32.h,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: controller.kr_plans.length,
|
||||
itemBuilder: (context, index) {
|
||||
final plan = controller.kr_plans[index];
|
||||
final isSelected = index == controller.kr_selectedPlanIndex.value;
|
||||
return GestureDetector(
|
||||
onTap: () => controller.kr_selectPlan(index),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: 8.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? Colors.blue : Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.grey.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
plan.kr_name,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected ? Colors.white : Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
GridView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: (Platform.isWindows || Platform.isMacOS || Platform.isLinux) ? 2.0 : 0.85,
|
||||
crossAxisSpacing: 8.w,
|
||||
mainAxisSpacing: 8.h,
|
||||
),
|
||||
itemCount: controller.kr_getTotalOptionsCount(controller.kr_plans[controller.kr_selectedPlanIndex.value]),
|
||||
itemBuilder: (context, index) {
|
||||
final plan = controller.kr_plans[controller.kr_selectedPlanIndex.value];
|
||||
final discountIndex = plan.kr_discount.isEmpty ? null : index;
|
||||
return _kr_buildPlanOptionCard(
|
||||
plan,
|
||||
controller.kr_selectedPlanIndex.value,
|
||||
discountIndex,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
// 套餐描述部分
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_purchaseMembership.packageDescription,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Obx(() {
|
||||
final featureLabels = controller.kr_getSelectedPlanFeatureLabels();
|
||||
final features = controller.kr_getSelectedPlanFeatures();
|
||||
final isExpanded = controller.kr_isDescriptionExpanded.value;
|
||||
final selectedPlan = controller.kr_plans[controller.kr_selectedPlanIndex.value];
|
||||
|
||||
// 添加流量和设备限制信息
|
||||
final trafficAndDeviceInfo = Padding(
|
||||
padding: EdgeInsets.only(bottom: 8.h),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12.w,
|
||||
vertical: 8.h,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${AppTranslations.kr_purchaseMembership.trafficLimit}:${controller.kr_getTrafficLimitText(selectedPlan)}',
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
'${AppTranslations.kr_purchaseMembership.deviceLimit}:${controller.kr_getDeviceLimitText(selectedPlan)}',
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// if (featureLabels.isEmpty) {
|
||||
// return Column(
|
||||
// children: [
|
||||
// trafficAndDeviceInfo,
|
||||
// Center(
|
||||
// child: Padding(
|
||||
// padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||
// child: Text(
|
||||
// AppTranslations.kr_purchaseMembership.noData,
|
||||
// style: KrAppTextStyle(
|
||||
// fontSize: 14,
|
||||
// color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
|
||||
final displayCount = isExpanded ? featureLabels.length : (featureLabels.length > 3 ? 3 : featureLabels.length);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
trafficAndDeviceInfo,
|
||||
...List.generate(displayCount, (index) {
|
||||
final feature = features[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 8.h),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
backgroundColor: Theme.of(Get.context!).cardColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
feature.kr_label,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
if (feature.kr_details.isNotEmpty)
|
||||
...feature.kr_details.map((detail) => Padding(
|
||||
padding: EdgeInsets.only(bottom: 8.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (detail.kr_label.isNotEmpty) ...[
|
||||
Text(
|
||||
detail.kr_label,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
],
|
||||
Text(
|
||||
detail.kr_description,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
SizedBox(height: 16.h),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
AppTranslations.kr_dialog.kr_ok,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12.w,
|
||||
vertical: 8.h,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(Get.context!).cardColor,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
featureLabels[index],
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(4.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.info_outline,
|
||||
size: 16.r,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
if (featureLabels.length > 3)
|
||||
GestureDetector(
|
||||
onTap: () => controller.kr_toggleDescriptionExpanded(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.h),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
isExpanded
|
||||
? AppTranslations.kr_purchaseMembership.collapse
|
||||
: AppTranslations.kr_purchaseMembership.expand,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
Icon(
|
||||
isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
|
||||
size: 16.r,
|
||||
color: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
// 支付方式选择部分
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_purchaseMembership.paymentMethod,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Obx(() => ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: controller.kr_paymentMethods.length,
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1.w,
|
||||
indent: 44.w,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final paymentMethod = controller.kr_paymentMethods[index];
|
||||
return InkWell(
|
||||
onTap: () => controller.kr_selectPaymentMethod(index),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 12.r, horizontal: 16.r),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: paymentMethod.icon.isNotEmpty
|
||||
? KRNetworkImage(
|
||||
kr_imageUrl: paymentMethod.icon,
|
||||
kr_width: 20.w,
|
||||
kr_height: 20.w,
|
||||
kr_placeholder: SizedBox(
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
|
||||
),
|
||||
),
|
||||
kr_errorWidget: Icon(
|
||||
Icons.payment_rounded,
|
||||
size: 20.w,
|
||||
color: Colors.blue,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.payment_rounded,
|
||||
size: 20.w,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.kr_getPaymentMethodTitle(paymentMethod),
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
final isSelected = index == controller.kr_selectedPaymentMethodIndex.value;
|
||||
return Container(
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.grey.withOpacity(0.3),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
size: 16.r,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 80.h), // 为底部按钮留出空间
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: _kr_buildBottomSection(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 账号部分
|
||||
Widget _kr_buildAccountSection(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(16.r),
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
AppTranslations.kr_purchaseMembership.myAccount,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Obx(() => Text(
|
||||
controller.kr_userEmail.value,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 套餐选项卡片
|
||||
Widget _kr_buildPlanOptionCard(
|
||||
KRPackageListItem plan,
|
||||
int planIndex,
|
||||
int? discountIndex,
|
||||
BuildContext context) {
|
||||
return Obx(() {
|
||||
bool isSelected = planIndex == controller.kr_selectedPlanIndex.value &&
|
||||
discountIndex == controller.kr_selectedDiscountIndex.value;
|
||||
return GestureDetector(
|
||||
onTap: () => controller.kr_selectPlan(planIndex, discountIndex: discountIndex),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.h),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Colors.blue.withOpacity(0.08)
|
||||
: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue.withOpacity(0.3) : Colors.grey.withOpacity(0.15),
|
||||
width: 1,
|
||||
),
|
||||
boxShadow: isSelected ? [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.08),
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
] : [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.03),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
controller.kr_getTimeStr(plan, discountIndex: discountIndex),
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isSelected
|
||||
? Colors.blue
|
||||
: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(
|
||||
'¥',
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 12,
|
||||
color: isSelected
|
||||
? Colors.blue.withOpacity(0.8)
|
||||
: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
controller.kr_getPlanPrice(plan, discountIndex: discountIndex)
|
||||
.toStringAsFixed(2),
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected
|
||||
? Colors.blue
|
||||
: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
if (discountIndex != null && plan.kr_discount.isNotEmpty) ...[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8.w,
|
||||
vertical: 2.h
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: plan.kr_discount[discountIndex].kr_discount < 100
|
||||
? Colors.red.withOpacity(0.08)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(4.r),
|
||||
border: plan.kr_discount[discountIndex].kr_discount < 100
|
||||
? Border.all(
|
||||
color: Colors.red.withOpacity(0.2),
|
||||
width: 1,
|
||||
)
|
||||
: null,
|
||||
boxShadow: plan.kr_discount[discountIndex].kr_discount < 100
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.red.withOpacity(0.05),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
controller.kr_getDiscountText(plan, discountIndex),
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: plan.kr_discount[discountIndex].kr_discount < 100
|
||||
? Colors.red.withOpacity(0.9)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 底部部分
|
||||
Widget _kr_buildBottomSection(BuildContext context) {
|
||||
// 如果正在加载或没有数据,不显示底部按钮
|
||||
if (controller.kr_isLoading.value || controller.kr_plans.isEmpty || controller.kr_paymentMethods.isEmpty) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
offset: Offset(0, -2),
|
||||
blurRadius: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
KRDialog.show(
|
||||
title: AppTranslations.kr_purchaseMembership.confirmPurchase,
|
||||
message: AppTranslations.kr_purchaseMembership.confirmPurchaseDesc,
|
||||
cancelText: AppTranslations.kr_dialog.kr_cancel,
|
||||
confirmText: AppTranslations.kr_dialog.kr_confirm,
|
||||
onConfirm: () => controller.kr_startSubscription(),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(vertical: 12.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
controller.kr_getSubscribeButtonText(),
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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<KRFeature> 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),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user