// account_screens.dart — 账户子页(套餐选择 / 设备管理 / 兑换 & 购买 / 联系我们) import 'package:flutter/material.dart'; import '../pangolin_theme.dart'; import 'pangolin_icons.dart'; import 'plan_card.dart'; import 'pangolin_button.dart'; /// 通用:带返回箭头的子页骨架 class _SubScaffold extends StatelessWidget { const _SubScaffold({required this.title, required this.child, this.onBack}); final String title; final Widget child; final VoidCallback? onBack; @override Widget build(BuildContext context) { final c = context.pangolin; return Scaffold( backgroundColor: c.bg, body: SafeArea(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(8, 6, 16, 10), child: Row(children: [ IconButton(onPressed: onBack ?? () => Navigator.of(context).maybePop(), icon: Icon(PangolinIcons.arrowLeft, size: 22, color: c.fg1)), Text(title, style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700)), ]), ), Expanded(child: child), ])), ); } } /// ── 套餐选择 ── class PlansScreen extends StatelessWidget { const PlansScreen({super.key, this.zh = true, this.onChoose, this.onBack}); final bool zh; final ValueChanged? onChoose; final VoidCallback? onBack; @override Widget build(BuildContext context) { final plans = [ ( id: 'free', name: zh ? '免费版' : 'Free', price: '¥0', cta: zh ? '当前' : 'Current', featured: false, current: true, pop: null as String?, feats: zh ? ['3 个基础节点', '每日 1GB 流量', '1 台设备'] : ['3 basic locations', '1GB / day', '1 device'], ), ( id: 'pro', name: zh ? '专业版' : 'Pro', price: '¥25', cta: zh ? '立即升级' : 'Upgrade', featured: true, current: false, pop: zh ? '最受欢迎' : 'Popular', feats: zh ? ['80+ 全球节点', '无限流量 · 极速', '5 台设备同时在线', '流媒体优化'] : ['80+ locations', 'Unlimited · fast', '5 devices', 'Streaming optimized'], ), ( id: 'team', name: zh ? '团队版' : 'Team', price: '¥99', cta: zh ? '选择' : 'Choose', featured: false, current: false, pop: null as String?, feats: zh ? ['Pro 全部功能', '10 个成员席位', '集中计费与管理', '优先客服'] : ['Everything in Pro', '10 seats', 'Central billing', 'Priority support'], ), ]; return _SubScaffold( title: zh ? '选择套餐' : 'Choose plan', onBack: onBack, child: ListView( padding: const EdgeInsets.fromLTRB(20, 14, 20, 24), children: [ for (final p in plans) Padding( padding: EdgeInsets.only(bottom: 14, top: p.pop != null ? 12 : 0), child: PlanCard( name: p.name, price: p.price, period: zh ? '/月' : '/mo', features: p.feats, ctaLabel: p.cta, featured: p.featured, isCurrent: p.current, popularLabel: p.pop, onPressed: () => onChoose?.call(p.id), ), ), ], ), ); } } /// ── 设备管理 ── class DeviceItem { const DeviceItem({required this.id, required this.icon, required this.name, required this.os, required this.active, this.me = false}); final String id, name, os, active; final IconData icon; final bool me; } class DevicesScreen extends StatefulWidget { const DevicesScreen({super.key, this.zh = true, this.onBack}); final bool zh; final VoidCallback? onBack; @override State createState() => _DevicesScreenState(); } class _DevicesScreenState extends State { late List _devices = [ DeviceItem(id: '1', icon: PangolinIcons.laptop, name: 'MacBook Pro', os: 'macOS 26', active: widget.zh ? '当前设备' : 'This device', me: true), DeviceItem(id: '2', icon: PangolinIcons.smartphone, name: 'iPhone 17', os: 'iOS 26', active: '2 min'), DeviceItem(id: '3', icon: PangolinIcons.monitorSmartphone, name: 'iPad Air', os: 'iPadOS 26', active: widget.zh ? '3 小时前' : '3h ago'), ]; @override Widget build(BuildContext context) { final c = context.pangolin; return _SubScaffold( title: widget.zh ? '我的设备' : 'My devices', onBack: widget.onBack, child: ListView(padding: const EdgeInsets.fromLTRB(20, 4, 20, 24), children: [ Text(widget.zh ? 'PRO 套餐最多 5 台设备同时在线' : 'Up to 5 devices on PRO', style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400)), const SizedBox(height: 12), Container( decoration: BoxDecoration(color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: c.border), boxShadow: PangolinShadow.sm), clipBehavior: Clip.antiAlias, child: Column(children: [ for (var i = 0; i < _devices.length; i++) _row(c, _devices[i], i < _devices.length - 1), ]), ), ]), ); } Widget _row(PangolinScheme c, DeviceItem d, bool divider) { return Container( decoration: BoxDecoration(border: divider ? Border(bottom: BorderSide(color: c.border)) : null), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row(children: [ Container(width: 38, height: 38, decoration: BoxDecoration(color: c.accentSubtle, borderRadius: BorderRadius.circular(PangolinRadius.md)), child: Icon(d.icon, size: 19, color: c.accent)), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Flexible(child: Text(d.name, style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w600, fontSize: 14.5))), if (d.me) Container(margin: const EdgeInsets.only(left: 7), padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 1), decoration: BoxDecoration(color: c.successSubtle, borderRadius: BorderRadius.circular(PangolinRadius.full)), child: Text(d.active, style: PangolinText.caption.copyWith(color: c.success, fontWeight: FontWeight.w600, fontSize: 10.5))), ]), const SizedBox(height: 1), Text(d.me ? d.os : '${d.os} · ${d.active}', style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400)), ])), if (!d.me) OutlinedButton( onPressed: () => setState(() => _devices.removeWhere((x) => x.id == d.id)), style: OutlinedButton.styleFrom(foregroundColor: c.fg2, side: BorderSide(color: c.borderStrong, width: 1.5), shape: const StadiumBorder(), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), minimumSize: Size.zero), child: Text(widget.zh ? '移除' : 'Remove', style: PangolinText.caption.copyWith(fontWeight: FontWeight.w600)), ), ]), ); } } /// ── 兑换 & 购买 ── class RedeemScreen extends StatefulWidget { const RedeemScreen({super.key, this.zh = true, this.onBack}); final bool zh; final VoidCallback? onBack; @override State createState() => _RedeemScreenState(); } class _RedeemScreenState extends State { final _code = TextEditingController(); bool _ok = false; @override void dispose() { _code.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final c = context.pangolin; final zh = widget.zh; final channels = [ (icon: PangolinIcons.shoppingBag, name: zh ? '自助发卡商店' : 'Self-serve store', sub: 'shop.pangolin.vpn', accent: true), (icon: PangolinIcons.send, name: 'Telegram', sub: '@PangolinVPN_bot', accent: false), (icon: PangolinIcons.messageCircle, name: 'LINE', sub: '@pangolinvpn', accent: false), (icon: PangolinIcons.mail, name: zh ? '邮箱客服' : 'Email support', sub: 'buy@pangolin.vpn', accent: false), ]; return _SubScaffold( title: zh ? '兑换 & 购买' : 'Redeem & buy', onBack: widget.onBack, child: ListView(padding: const EdgeInsets.fromLTRB(20, 4, 20, 24), children: [ Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration(color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.xl), border: Border.all(color: c.border), boxShadow: PangolinShadow.sm), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(zh ? '兑换激活码' : 'Redeem a code', style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w700, fontSize: 14.5)), const SizedBox(height: 12), if (_ok) Row(children: [ Icon(PangolinIcons.checkCircle, size: 20, color: c.success), const SizedBox(width: 9), Text(zh ? '激活成功 · PRO 已开通' : 'Activated · PRO unlocked', style: PangolinText.body.copyWith(color: c.success, fontWeight: FontWeight.w600)), ]) else Row(children: [ Expanded(child: Container( padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration(color: c.bg, borderRadius: BorderRadius.circular(PangolinRadius.md), border: Border.all(color: c.borderStrong, width: 1.5)), child: TextField(controller: _code, textCapitalization: TextCapitalization.characters, style: PangolinText.mono.copyWith(color: c.fg1, letterSpacing: 1.5), decoration: InputDecoration(border: InputBorder.none, isCollapsed: true, contentPadding: const EdgeInsets.symmetric(vertical: 13), hintText: zh ? '输入激活码' : 'Enter code', hintStyle: PangolinText.sm.copyWith(color: c.fg3)), onChanged: (_) => setState(() {})), )), const SizedBox(width: 10), PangolinButton(label: zh ? '激活' : 'Redeem', onPressed: _code.text.trim().length >= 4 ? () => setState(() => _ok = true) : null), ]), ]), ), const SizedBox(height: 22), Text(zh ? '购买渠道' : 'Where to buy', style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w700, fontSize: 14.5)), const SizedBox(height: 6), Text(zh ? 'App 内不支持直接支付。请通过以下渠道获取激活码:' : 'In-app payment is unavailable. Get a code via:', style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400, height: 1.5)), const SizedBox(height: 14), for (final ch in channels) Padding(padding: const EdgeInsets.only(bottom: 10), child: _channel(c, ch.icon, ch.name, ch.sub, ch.accent)), ]), ); } Widget _channel(PangolinScheme c, IconData icon, String name, String sub, bool accent) { return InkWell( borderRadius: BorderRadius.circular(PangolinRadius.lg), onTap: () {}, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: accent ? c.accentSubtle : c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: accent ? c.accentBorder : c.border), boxShadow: PangolinShadow.sm, ), child: Row(children: [ Container(width: 40, height: 40, decoration: BoxDecoration(color: accent ? c.accent : c.bgSubtle, borderRadius: BorderRadius.circular(PangolinRadius.md)), child: Icon(icon, size: 20, color: accent ? PangolinColors.white : c.accent)), const SizedBox(width: 13), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name, style: PangolinText.body.copyWith(color: c.fg1, fontWeight: FontWeight.w600)), Text(sub, style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400)), ])), Icon(PangolinIcons.externalLink, size: 16, color: c.fg3), ]), ), ); } } /// ── 联系我们 ── class ContactScreen extends StatelessWidget { const ContactScreen({super.key, this.zh = true, this.onBack}); final bool zh; final VoidCallback? onBack; @override Widget build(BuildContext context) { final c = context.pangolin; final channels = [ (icon: PangolinIcons.send, name: 'Telegram', sub: '@PangolinVPN_bot', accent: true), (icon: PangolinIcons.messageCircle, name: 'LINE', sub: '@pangolinvpn', accent: false), (icon: PangolinIcons.mail, name: zh ? '邮箱客服' : 'Email support', sub: 'support@pangolin.vpn', accent: false), (icon: PangolinIcons.shoppingBag, name: zh ? '自助发卡商店' : 'Self-serve store', sub: 'shop.pangolin.vpn', accent: false), ]; return _SubScaffold( title: zh ? '联系我们' : 'Contact us', onBack: onBack, child: ListView(padding: const EdgeInsets.fromLTRB(20, 4, 20, 24), children: [ Text(zh ? '遇到问题?通过以下任一渠道联系我们,通常数分钟内回复。' : 'Need help? Reach us via any channel below — usually replies in minutes.', style: PangolinText.sm.copyWith(color: c.fg2, height: 1.6)), const SizedBox(height: 16), for (final ch in channels) Padding(padding: const EdgeInsets.only(bottom: 10), child: _contact(c, ch.icon, ch.name, ch.sub, ch.accent)), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: c.border), boxShadow: PangolinShadow.sm), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(zh ? '服务时间' : 'Support hours', style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w600)), const SizedBox(height: 5), Text(zh ? '每日 9:00 – 24:00 (GMT+8)' : 'Daily 9:00 – 24:00 (GMT+8)', style: PangolinText.sm.copyWith(color: c.fg1, fontWeight: FontWeight.w600)), ]), ), ]), ); } Widget _contact(PangolinScheme c, IconData icon, String name, String sub, bool accent) { return InkWell( borderRadius: BorderRadius.circular(PangolinRadius.lg), onTap: () {}, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: accent ? c.accentSubtle : c.surface, borderRadius: BorderRadius.circular(PangolinRadius.lg), border: Border.all(color: accent ? c.accentBorder : c.border), boxShadow: PangolinShadow.sm, ), child: Row(children: [ Container(width: 40, height: 40, decoration: BoxDecoration(color: accent ? c.accent : c.bgSubtle, borderRadius: BorderRadius.circular(PangolinRadius.md)), child: Icon(icon, size: 20, color: accent ? PangolinColors.white : c.accent)), const SizedBox(width: 13), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name, style: PangolinText.body.copyWith(color: c.fg1, fontWeight: FontWeight.w600)), Text(sub, style: PangolinText.caption.copyWith(color: c.fg3, fontWeight: FontWeight.w400)), ])), Icon(PangolinIcons.externalLink, size: 16, color: c.fg3), ]), ), ); } }