217 lines
6.4 KiB
Dart
217 lines
6.4 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:provider/provider.dart';
|
||
|
||
import '../auth/session_store.dart';
|
||
import 'home_view_model.dart';
|
||
|
||
/// 主界面:当前账号到期时间 + 搜索按钮(强制服务端校验后进相机)+ 充值入口
|
||
class HomeScreen extends StatefulWidget {
|
||
const HomeScreen({super.key});
|
||
|
||
@override
|
||
State<HomeScreen> createState() => _HomeScreenState();
|
||
}
|
||
|
||
class _HomeScreenState extends State<HomeScreen> {
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
context.read<HomeViewModel>().refresh();
|
||
});
|
||
}
|
||
|
||
Future<void> _onSearch() async {
|
||
final vm = context.read<HomeViewModel>();
|
||
final allowed = await vm.verifyForCamera();
|
||
if (!mounted) return;
|
||
if (vm.sessionExpired) {
|
||
_toLogin();
|
||
return;
|
||
}
|
||
if (!allowed) {
|
||
if (vm.error != null) {
|
||
// 网络/服务端异常:无法确认授权状态,提示错误
|
||
_showBlocked(vm.error);
|
||
return;
|
||
}
|
||
// 服务端确认无有效授权:直接进充值页
|
||
await _onRecharge();
|
||
return;
|
||
}
|
||
await Navigator.of(context).pushNamed('/camera');
|
||
if (mounted) vm.refresh();
|
||
}
|
||
|
||
Future<void> _onRecharge() async {
|
||
await Navigator.of(context).pushNamed('/paywall');
|
||
if (mounted) context.read<HomeViewModel>().refresh();
|
||
}
|
||
|
||
Future<void> _toLogin() async {
|
||
final container = context.read<SessionStore>();
|
||
await container.clear();
|
||
if (!mounted) return;
|
||
Navigator.of(context).pushReplacementNamed('/login');
|
||
}
|
||
|
||
void _showBlocked(String? error) {
|
||
showDialog<void>(
|
||
context: context,
|
||
builder: (ctx) => AlertDialog(
|
||
title: const Text('授权不可用'),
|
||
content: Text(error ?? '授权已过期,请充值后续费'),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.of(ctx).pop(),
|
||
child: const Text('取消'),
|
||
),
|
||
FilledButton(
|
||
onPressed: () {
|
||
Navigator.of(ctx).pop();
|
||
_onRecharge();
|
||
},
|
||
child: const Text('去充值'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final vm = context.watch<HomeViewModel>();
|
||
final license = vm.license;
|
||
final active = license?.isActive ?? false;
|
||
final statusText = vm.loading
|
||
? '加载中…'
|
||
: (license == null || license.expiresAt == null
|
||
? '未充值'
|
||
: (active
|
||
? '有效至 ${_fmt(license.expiresAt!)}'
|
||
: '已过期(${_fmt(license.expiresAt!)})'));
|
||
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('视野'),
|
||
centerTitle: true,
|
||
actions: [
|
||
IconButton(
|
||
tooltip: '退出登录',
|
||
icon: const Icon(Icons.logout),
|
||
onPressed: _toLogin,
|
||
),
|
||
],
|
||
),
|
||
body: SafeArea(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
const SizedBox(height: 24),
|
||
_StatusCard(
|
||
licenseText: statusText,
|
||
active: active,
|
||
),
|
||
const SizedBox(height: 40),
|
||
SizedBox(
|
||
height: 88,
|
||
child: FilledButton.icon(
|
||
onPressed: vm.verifying ? null : _onSearch,
|
||
style: FilledButton.styleFrom(
|
||
backgroundColor: Colors.green,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(44),
|
||
),
|
||
),
|
||
icon: vm.verifying
|
||
? const SizedBox(
|
||
width: 24,
|
||
height: 24,
|
||
child: CircularProgressIndicator(
|
||
strokeWidth: 2, color: Colors.white),
|
||
)
|
||
: const Icon(Icons.visibility, size: 32),
|
||
label: Text(
|
||
vm.verifying ? '正在校验授权…' : '打开视野',
|
||
style: const TextStyle(
|
||
fontSize: 22, fontWeight: FontWeight.w600),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
SizedBox(
|
||
height: 52,
|
||
child: OutlinedButton.icon(
|
||
onPressed: _onRecharge,
|
||
icon: const Icon(Icons.payment),
|
||
label: const Text('充值', style: TextStyle(fontSize: 16)),
|
||
),
|
||
),
|
||
if (vm.error != null && vm.license != null) ...[
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
vm.error!,
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(color: Colors.red),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
static String _fmt(DateTime t) {
|
||
String p(int n) => n.toString().padLeft(2, '0');
|
||
return '${t.year}-${p(t.month)}-${p(t.day)} ${p(t.hour)}:${p(t.minute)}';
|
||
}
|
||
}
|
||
|
||
class _StatusCard extends StatelessWidget {
|
||
final String licenseText;
|
||
final bool active;
|
||
|
||
const _StatusCard({
|
||
required this.licenseText,
|
||
required this.active,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Card(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(20),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
active ? Icons.verified_user : Icons.error_outline,
|
||
color: active ? Colors.green : Colors.orange,
|
||
size: 36,
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text('当前账户',
|
||
style: TextStyle(
|
||
color: Colors.grey.shade600, fontSize: 13)),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
licenseText,
|
||
style: const TextStyle(
|
||
fontSize: 18, fontWeight: FontWeight.w600),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|