222 lines
6.7 KiB
Dart
222 lines
6.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'models.dart';
|
|
import 'paywall_view_model.dart';
|
|
|
|
/// 付费墙:拉取套餐价格 → 选套餐 → 微信/支付宝支付 → 解锁进入相机
|
|
class PaywallScreen extends StatefulWidget {
|
|
const PaywallScreen({super.key});
|
|
|
|
@override
|
|
State<PaywallScreen> createState() => _PaywallScreenState();
|
|
}
|
|
|
|
class _PaywallScreenState extends State<PaywallScreen> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
context.read<PaywallViewModel>().loadPlans();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final vm = context.watch<PaywallViewModel>();
|
|
final loading = vm.state.state == PayState.loading;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('视野 · 会员'), centerTitle: true),
|
|
body: SafeArea(
|
|
child: Column(
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.fromLTRB(24, 16, 24, 16),
|
|
child: Text(
|
|
'开通会员解锁完整功能,按自然日计费,到期自动失效',
|
|
style: TextStyle(color: Colors.grey, fontSize: 13),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: ListView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
children: [
|
|
_PlansSection(vm: vm, loading: loading),
|
|
const SizedBox(height: 24),
|
|
_PayButton(
|
|
label: '微信支付',
|
|
icon: Icons.wechat,
|
|
color: const Color(0xFF07C160),
|
|
enabled: !loading && vm.state.selectedPlan != null,
|
|
onTap: loading
|
|
? null
|
|
: () async {
|
|
final license = await vm.pay(PayChannel.wechat);
|
|
if (license != null && context.mounted) {
|
|
_onPaid(context);
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
_PayButton(
|
|
label: '支付宝支付',
|
|
icon: Icons.account_balance_wallet,
|
|
color: const Color(0xFF1677FF),
|
|
enabled: !loading && vm.state.selectedPlan != null,
|
|
onTap: loading
|
|
? null
|
|
: () async {
|
|
final license = await vm.pay(PayChannel.alipay);
|
|
if (license != null && context.mounted) {
|
|
_onPaid(context);
|
|
}
|
|
},
|
|
),
|
|
if (vm.state.state == PayState.failed)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 16),
|
|
child: Text(
|
|
vm.state.message ?? '支付失败',
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(color: Colors.red),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 支付成功:返回主界面(主界面刷新展示新到期时间)
|
|
void _onPaid(BuildContext context) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
}
|
|
|
|
/// 套餐区三态:加载中 / 失败可重试 / 套餐卡片列表
|
|
class _PlansSection extends StatelessWidget {
|
|
final PaywallViewModel vm;
|
|
final bool loading;
|
|
|
|
const _PlansSection({required this.vm, required this.loading});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (vm.plansLoading && vm.plans.isEmpty) {
|
|
return const Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 48),
|
|
child: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
if (vm.plansError != null && vm.plans.isEmpty) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 32),
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
vm.plansError!,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(color: Colors.red),
|
|
),
|
|
const SizedBox(height: 12),
|
|
OutlinedButton(
|
|
onPressed: () => context.read<PaywallViewModel>().loadPlans(),
|
|
child: const Text('重试'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
return Column(
|
|
children: [
|
|
...vm.plans.map((p) => _PlanCard(
|
|
plan: p,
|
|
selected: vm.state.selectedPlan?.id == p.id,
|
|
enabled: !loading,
|
|
onTap: () => vm.selectPlan(p),
|
|
)),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PlanCard extends StatelessWidget {
|
|
final Plan plan;
|
|
final bool selected;
|
|
final bool enabled;
|
|
final VoidCallback onTap;
|
|
|
|
const _PlanCard({
|
|
required this.plan,
|
|
required this.selected,
|
|
required this.enabled,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
elevation: selected ? 3 : 1,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(
|
|
color: selected ? Colors.orange : Colors.grey.shade300,
|
|
width: selected ? 2 : 1,
|
|
),
|
|
),
|
|
child: ListTile(
|
|
onTap: enabled ? onTap : null,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
|
title: Text(
|
|
plan.label,
|
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 17),
|
|
),
|
|
subtitle: Text('${plan.days} 天 · 自然日'),
|
|
trailing: Text(
|
|
'¥${plan.priceYuan}',
|
|
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PayButton extends StatelessWidget {
|
|
final String label;
|
|
final IconData icon;
|
|
final Color color;
|
|
final bool enabled;
|
|
final VoidCallback? onTap;
|
|
|
|
const _PayButton({
|
|
required this.label,
|
|
required this.icon,
|
|
required this.color,
|
|
required this.enabled,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: 48,
|
|
child: FilledButton.icon(
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: color,
|
|
disabledBackgroundColor: Colors.grey.shade300,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
|
),
|
|
onPressed: enabled ? onTap : null,
|
|
icon: Icon(icon, size: 20),
|
|
label: Text(label, style: const TextStyle(fontSize: 16)),
|
|
),
|
|
);
|
|
}
|
|
}
|