import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../../data/models/goods.dart'; import '../../../data/models/user_Info.dart'; import '../controllers/goodsvip_controller.dart'; import '../utils/goods_i18n.dart'; import 'widgets/goods_footer_section.dart'; class GoodsvipView extends GetView { const GoodsvipView({super.key}); @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], appBar: AppBar( backgroundColor: isDarkMode ? Colors.black : Colors.white, foregroundColor: Colors.white, elevation: 0.5, leading: IconButton( icon: Icon( Icons.arrow_back_ios, size: 20.sp, color: isDarkMode ? Colors.white : Colors.black87, ), onPressed: () => Get.back(), ), title: Text( 'aiRecharge'.tr, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), ), centerTitle: true, ), body: SafeArea( top: false, child: Obx(() { final userTick = User.refreshTick.value; Widget content; if (controller.loading.value) { content = const Center(child: CircularProgressIndicator()); } else if (controller.error.isNotEmpty) { content = Center(child: Text(controller.error.value)); } else { final Map> grouped = {}; for (final g in controller.items) { grouped.putIfAbsent(g.usagetype, () => []).add(g); } final order = [UsageType.Vip]; final children = []; children.add(_buildFeaturesSection(isDarkMode)); children.add(SizedBox(height: 16.h)); // children.add(_buildScenesSection(isDarkMode)); // children.add(SizedBox(height: 16.h)); for (int i = 0; i < order.length; i++) { final t = order[i]; final list = grouped[t]; if (list == null || list.isEmpty) continue; children.add(_buildTypeSection(t, list, isDarkMode)); if (i != order.length - 1) { children.add(SizedBox(height: 16.h)); children.add(Divider( color: isDarkMode ? Colors.white24 : Colors.black12)); children.add(SizedBox(height: 16.h)); } } children.add(GoodsFooterSection(isDarkMode: isDarkMode)); content = ListView( key: ValueKey(userTick), padding: EdgeInsets.all(16.w), children: children, ); } return Stack( children: [ content, if (controller.isPaying.value) Positioned.fill( child: AbsorbPointer( absorbing: true, child: Container( color: Colors.black.withOpacity(0.35), child: const Center( child: CircularProgressIndicator(), ), ), ), ), ], ); }), ), ); } Widget _buildFeaturesSection(bool isDarkMode) { final textColor = isDarkMode ? Colors.white : Colors.black; final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'featureDescription'.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: textColor, ), ), SizedBox(height: 10.h), Wrap( spacing: 12.w, runSpacing: 12.h, alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, children: [ _buildFeatureCard( isDarkMode, icon: Icons.workspace_premium, title: 'benefitsOverview'.tr, lines: ['higherQuota'.tr, 'lowerUnitPrice'.tr], colorSeed: Colors.amber, ), _buildFeatureCard( isDarkMode, icon: Icons.support_agent, title: 'priorityService'.tr, lines: ['exclusiveSupportChannel'.tr, 'fastResponse'.tr], colorSeed: Colors.purple, ), _buildFeatureCard( isDarkMode, icon: Icons.local_offer, title: 'eventsAndDiscounts'.tr, lines: ['memberExclusiveEvents'.tr, 'longTermDiscountPlan'.tr], colorSeed: Colors.indigo, ), _buildFeatureCard( isDarkMode, icon: Icons.auto_awesome, title: 'generalPrivileges'.tr, lines: ['earlyAccessNewFeatures'.tr, 'crossDeviceSync'.tr], colorSeed: Colors.teal, ), ], ), SizedBox(height: 12.h), Text( 'vipDisclaimer'.tr, style: TextStyle(fontSize: 12.sp, color: subColor), ), ], ); } Widget _buildFeatureCard( bool isDarkMode, { required IconData icon, required String title, required List lines, required MaterialColor colorSeed, }) { final bgColor = isDarkMode ? Colors.grey[900] : Colors.white; final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!; final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; final width = (Get.width - 16.w * 2 - 12.w) / 2; return Container( width: width, padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 36.w, height: 36.w, decoration: BoxDecoration( color: isDarkMode ? mainColor.withOpacity(0.25) : mainColor.withOpacity(0.15), borderRadius: BorderRadius.circular(18.r), ), child: Icon(icon, color: mainColor), ), SizedBox(width: 8.w), Expanded( child: Text( title, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), ), ), ], ), SizedBox(height: 8.h), ...lines.map((l) => Padding( padding: EdgeInsets.only(bottom: 4.h), child: Text(l, style: TextStyle(fontSize: 12.sp, color: subColor)), )), ], ), ); } Widget _buildTypeHeader(UsageType type, bool isDarkMode) { final titles = { UsageType.AI: 'aiFunction'.tr, UsageType.Meet: 'conferenceAssistant'.tr, UsageType.Translation: 'simultaneousTranslation'.tr, UsageType.Vip: 'memberService'.tr, }; final iconAndColor = _iconColorForType(type, isDarkMode); final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; String titleStr = titles[type] ?? type.name; String? vipSubtitle; if (type == UsageType.Vip) { int viplv = 0; int vipexptime = 0; if (User.isLoggedIn()) { final u = User.instance; viplv = u.viplv; vipexptime = u.vipexptime; } DateTime? expiry; if (vipexptime > 0) { expiry = vipexptime > 1000000000000 ? DateTime.fromMillisecondsSinceEpoch(vipexptime) : DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000); } // titleStr = 'Vip $viplv'; vipSubtitle = expiry != null ? '${'expiryLabel'.tr}${_fmtDate(expiry)}' : 'notActivated'.tr; } return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Container( // width: 44.w, // height: 44.w, // decoration: BoxDecoration( // color: iconAndColor.background, // borderRadius: BorderRadius.circular(22.r), // ), // child: Icon(iconAndColor.icon, color: iconAndColor.foreground), // ), // SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titleStr, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w700, color: isDarkMode ? Colors.white : Colors.black, ), ), SizedBox(height: 8.h), if (type == UsageType.Vip && vipSubtitle != null) Text( vipSubtitle, style: TextStyle(fontSize: 12.sp, color: subColor), ) else _buildMetricsInline(type, isDarkMode), ], ), ), ], ), ); } Widget _buildTypeSection( UsageType type, List list, bool isDarkMode) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTypeHeader(type, isDarkMode), SizedBox(height: 8.h), _buildGoodsGrid(list, type, isDarkMode), ], ); } Widget _buildMetricsInline(UsageType type, bool isDarkMode) { final textColor = isDarkMode ? Colors.white : Colors.black; final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; if (type == UsageType.Vip) { int viplv = 0; int vipexptime = 0; if (User.isLoggedIn()) { final u = User.instance; viplv = u.viplv; vipexptime = u.vipexptime; } DateTime? expiry; if (vipexptime > 0) { expiry = vipexptime > 1000000000000 ? DateTime.fromMillisecondsSinceEpoch(vipexptime) : DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000); } return Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Lv.$viplv', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: textColor, ), ), SizedBox(height: 4.h), Text( expiry != null ? '${'validityPeriod'.tr}${_fmtDate(expiry)}' : 'notBound'.tr, style: TextStyle(fontSize: 12.sp, color: subColor), ), ], ), ), // Container( // padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), // decoration: BoxDecoration( // color: isDarkMode ? Colors.white10 : Colors.black12, // borderRadius: BorderRadius.circular(16.r), // ), // child: Text( // viplv > 0 ? 'enabled'.tr : 'disabled'.tr, // style: TextStyle(fontSize: 11.sp, color: subColor), // ), // ), ], ); } int total = 0; int remain = 0; if (User.isLoggedIn()) { final u = User.instance; if (type == UsageType.AI) { total = u.aichattotalintegral; remain = u.aichatintegral; } else if (type == UsageType.Meet) { total = u.meettotalintegral; remain = u.meetintegral; } else if (type == UsageType.Translation) { total = u.tradetotalintegral; remain = u.tradeintegral; } } final progress = (total > 0) ? (remain / total).clamp(0.0, 1.0) : 0.0; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ LinearProgressIndicator( value: progress, minHeight: 8.h, backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[300], valueColor: AlwaysStoppedAnimation( isDarkMode ? Colors.blue[300]! : Colors.blue[600]!), ), SizedBox(height: 8.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${'totalQuota'.tr}:$total', style: TextStyle(fontSize: 12.sp, color: subColor), ), Text( '${'remaining'.tr}:$remain', style: TextStyle(fontSize: 12.sp, color: subColor), ), ], ), ], ); } Widget _buildGoodsCardByType(DBGoods item, UsageType type, bool isDarkMode) { final iconAndColor = _iconColorForType(type, isDarkMode); return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Container( width: 42.w, height: 42.w, decoration: BoxDecoration( color: iconAndColor.background, borderRadius: BorderRadius.circular(21.r), ), child: Icon(iconAndColor.icon, color: iconAndColor.foreground), ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(localizedGoodsName(item), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, )), SizedBox(height: 4.h), Text(localizedGoodsDesc(item), style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white70 : Colors.grey[600], )), ], ), ), SizedBox(width: 12.w), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(goodsDisplayPrice(item), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, )), SizedBox(height: 8.h), GestureDetector( onTap: () => controller.purchase(item), child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), decoration: BoxDecoration( color: Colors.blue[600], borderRadius: BorderRadius.circular(20.r), ), child: Text( 'purchase'.tr, style: TextStyle( fontSize: 12.sp, color: Colors.white, ), ), ), ), ], ), ], ), ); } Widget _buildMetricsForType(UsageType type, bool isDarkMode) { if (type == UsageType.Vip) { return _buildVipMetricsCard(isDarkMode); } return _buildQuotaCard(type, isDarkMode); } Widget _buildVipMetricsCard(bool isDarkMode) { final textColor = isDarkMode ? Colors.white : Colors.black; final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; int viplv = 0; int vipexptime = 0; if (User.isLoggedIn()) { final u = User.instance; viplv = u.viplv; vipexptime = u.vipexptime; } DateTime? expiry; if (vipexptime > 0) { expiry = vipexptime > 1000000000000 ? DateTime.fromMillisecondsSinceEpoch(vipexptime) : DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000); } final iconColors = _iconColorForType(UsageType.Vip, isDarkMode); return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Container( width: 42.w, height: 42.w, decoration: BoxDecoration( color: iconColors.background, borderRadius: BorderRadius.circular(21.r), ), child: Icon(iconColors.icon, color: iconColors.foreground), ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Lv.$viplv', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: textColor, ), ), SizedBox(height: 4.h), Text( expiry != null ? '${'validityPeriod'.tr}${_fmtDate(expiry)}' : 'notBound'.tr, style: TextStyle(fontSize: 12.sp, color: subColor), ), ], ), ), Container( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), decoration: BoxDecoration( color: isDarkMode ? Colors.white10 : Colors.black12, borderRadius: BorderRadius.circular(16.r), ), child: Text( viplv > 0 ? 'enabled'.tr : 'disabled'.tr, style: TextStyle(fontSize: 11.sp, color: subColor), ), ), ], ), ); } Widget _buildQuotaCard(UsageType type, bool isDarkMode) { int total = 0; int remain = 0; if (User.isLoggedIn()) { final u = User.instance; if (type == UsageType.AI) { total = u.aichattotalintegral; remain = u.aichatintegral; } else if (type == UsageType.Meet) { total = u.meettotalintegral; remain = u.meetintegral; } else if (type == UsageType.Translation) { total = u.tradetotalintegral; remain = u.tradeintegral; } } final progress = (total > 0) ? (remain / total).clamp(0.0, 1.0) : 0.0; final iconColors = _iconColorForType(type, isDarkMode); final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; final textColor = isDarkMode ? Colors.white : Colors.black; return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 36.w, height: 36.w, decoration: BoxDecoration( color: iconColors.background, borderRadius: BorderRadius.circular(18.r), ), child: Icon(iconColors.icon, color: iconColors.foreground), ), SizedBox(width: 8.w), Text( 'remainingQuota'.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: textColor, ), ), ], ), SizedBox(height: 12.h), LinearProgressIndicator( value: progress, minHeight: 8.h, backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[300], valueColor: AlwaysStoppedAnimation( isDarkMode ? Colors.blue[300]! : Colors.blue[600]!), ), SizedBox(height: 8.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${'totalQuota'.tr}:$total', style: TextStyle(fontSize: 12.sp, color: subColor), ), Text( '${'remaining'.tr}:$remain', style: TextStyle(fontSize: 12.sp, color: subColor), ), ], ), ], ), ); } Widget _buildGoodsGrid(List list, UsageType type, bool isDarkMode) { return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 8.h, crossAxisSpacing: 12.w, childAspectRatio: 1.3, ), itemCount: list.length, itemBuilder: (_, i) => _buildGridItemCard(list[i], type, isDarkMode), ); } Widget _buildGridItemCard(DBGoods item, UsageType type, bool isDarkMode) { final iconColors = _iconColorForType(type, isDarkMode); return Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 34.w, height: 20.w, decoration: BoxDecoration( color: iconColors.background, borderRadius: BorderRadius.circular(17.r), ), child: Icon(iconColors.icon, color: iconColors.foreground), ), SizedBox(width: 8.w), Expanded( child: Text( goodsDisplayPrice(item), maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 21.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), ), ), ], ), SizedBox(height: 6.h), Expanded( child: Text( localizedGoodsName(item), maxLines: 3, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 15.sp, color: isDarkMode ? Colors.white70 : Colors.grey[700], ), ), ), // SizedBox(height: 6.h), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: () => controller.purchase(item), child: Container( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), decoration: BoxDecoration( color: Colors.blue[600], borderRadius: BorderRadius.circular(8.r), ), child: Text( 'purchase'.tr, style: TextStyle(fontSize: 12.sp, color: Colors.white), ), ), ), ], ), ], ), ); } String _fmtDate(DateTime d) { final y = d.year.toString().padLeft(4, '0'); final m = d.month.toString().padLeft(2, '0'); final day = d.day.toString().padLeft(2, '0'); return '$y-$m-$day'; } _IconColors _iconColorForType(UsageType type, bool isDarkMode) { switch (type) { case UsageType.AI: return _IconColors( icon: Icons.smart_toy, background: isDarkMode ? Colors.teal[900]!.withOpacity(0.3) : Colors.teal[100]!, foreground: isDarkMode ? Colors.teal[300]! : Colors.teal[600]!, ); case UsageType.Meet: return _IconColors( icon: Icons.people_alt, background: isDarkMode ? Colors.pink[900]!.withOpacity(0.3) : Colors.pink[100]!, foreground: isDarkMode ? Colors.pink[300]! : Colors.pink[600]!, ); case UsageType.Translation: return _IconColors( icon: Icons.translate, background: isDarkMode ? Colors.indigo[900]!.withOpacity(0.3) : Colors.indigo[100]!, foreground: isDarkMode ? Colors.indigo[300]! : Colors.indigo[600]!, ); case UsageType.Vip: case UsageType.UsageNull: return _IconColors( icon: Icons.workspace_premium, background: isDarkMode ? Colors.amber[900]!.withOpacity(0.3) : Colors.amber[100]!, foreground: isDarkMode ? Colors.amber[300]! : Colors.amber[600]!, ); } } Widget _buildScenesSection(bool isDarkMode) { final textColor = isDarkMode ? Colors.white : Colors.black; final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'scene'.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: textColor, ), ), SizedBox(height: 10.h), Wrap( spacing: 12.w, runSpacing: 12.h, children: [ _buildFeatureCard( isDarkMode, icon: Icons.smart_toy, title: 'aiFunction'.tr, lines: ['aiHeaderDesc1'.tr, 'aiHeaderDesc2'.tr], colorSeed: Colors.teal, ), _buildFeatureCard( isDarkMode, icon: Icons.people_alt, title: 'conferenceAssistant'.tr, lines: ['meetingHeaderDesc1'.tr, 'meetingHeaderDesc2'.tr], colorSeed: Colors.pink, ), _buildFeatureCard( isDarkMode, icon: Icons.translate, title: 'simultaneousTranslation'.tr, lines: ['transHeaderDesc1'.tr, 'transHeaderDesc2'.tr], colorSeed: Colors.indigo, ), ], ), SizedBox(height: 12.h), Text( 'vipDisclaimer'.tr, style: TextStyle(fontSize: 12.sp, color: subColor), ), ], ); } } class _IconColors { final IconData icon; final Color background; final Color foreground; const _IconColors({ required this.icon, required this.background, required this.foreground, }); }