Files
video/app/lib/screens/splash/splash_screen.dart
2026-10-15 20:36:59 +08:00

210 lines
7.1 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
import '../../core/config/app_config.dart';
import '../../core/network/api_client.dart';
import '../../core/storage/local_store.dart';
import '../../core/theme/app_theme.dart';
import '../../providers/auth_provider.dart';
import '../../services/invite_service.dart';
import '../../services/version_service.dart';
/// 启动页:依次执行 ①设备指纹 ②域名发现+ApiClient 初始化 ③登录态恢复 ④版本/公告检查
class SplashScreen extends ConsumerStatefulWidget {
const SplashScreen({super.key});
@override
ConsumerState<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends ConsumerState<SplashScreen> {
String _step = '正在初始化…';
String? _notice;
@override
void initState() {
super.initState();
_bootstrap();
}
Future<void> _bootstrap() async {
try {
// ① 设备指纹(UUID v4,持久化)
_setStep('初始化设备…');
LocalStore.I.ensureDeviceId(() => const Uuid().v4());
// ② 域名发现(TXT → 兜底)+ 网络客户端初始化
_setStep('连接服务器…');
await ApiClient.init();
// ②.5 推广归因:剪贴板口令(主)→ 无口令则首启模糊匹配上报(辅)
try {
await _attributeInvite();
} catch (_) {
// 归因失败不阻塞启动
}
// ③ 登录态恢复(安全存储 Token → /users/me)
_setStep('恢复登录态…');
await ref.read(authProvider.notifier).restore();
// ④ 版本检查与维护公告(失败不阻塞进入首页)
try {
final info = await VersionService.check();
if (!mounted) return;
// 强制更新(当前 version_code 低于服务端最低要求):阻断使用
if (info.forceUpdate) {
await _showForceUpdateDialog(info);
return;
}
// 非强制新版本:可跳过的提示弹窗
if (info.latestVersionCode > AppConfig.appVersionCode) {
await _showUpdatePrompt(info);
if (!mounted) return;
}
setState(() => _notice = info.notice.isNotEmpty ? info.notice : null);
if (_notice == null) {
_goHome();
return;
}
// 有公告:展示 2 秒后进入
Future.delayed(const Duration(seconds: 2), _goHome);
} catch (_) {
_goHome();
}
} catch (e) {
_setStep('网络异常,请检查后重试');
// 域名发现彻底失败:提供重试入口
}
}
/// 推广归因:
/// - 剪贴板命中分享口令 → 存 pending 邀请码(注册页预填,准确率 100%)
/// - 无口令且未上报过 → 模糊匹配上报(每安装仅一次,命中结果服务端缓存)
Future<void> _attributeInvite() async {
final clip = await Clipboard.getData(Clipboard.kTextPlain);
final code = InviteService.parseInviteCommand(clip?.text);
if (code != null) {
await LocalStore.I.savePendingInviteCode(code);
return;
}
if (LocalStore.I.inviteMatchReported) return;
final matched = await InviteService.reportMatch(LocalStore.I.deviceId);
await LocalStore.I.markInviteMatchReported();
if (matched != null && matched.isNotEmpty) {
await LocalStore.I.savePendingInviteCode(matched);
}
}
void _setStep(String s) {
if (mounted) setState(() => _step = s);
}
void _goHome() {
if (!mounted) return;
// 游客可直接浏览(计费接口按 device_id 走),登录页由路由守卫按需唤起
context.go('/home');
}
/// 跳转下载页(商店/外部浏览器)
Future<void> _openUpdateUrl(String url) async {
if (url.isEmpty) return;
final uri = Uri.tryParse(url);
if (uri == null) return;
try {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (_) {
// 调起失败不阻塞(强制更新场景弹窗仍在,可重试)
}
}
/// 强制更新弹窗:不可关闭、不可返回,仅可跳下载页(《01-开发文档》5.7)
Future<void> _showForceUpdateDialog(VersionInfo info) {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (ctx) => PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: AppColors.surface,
title: const Text('版本过低', style: TextStyle(fontSize: 16)),
content: Text(
'当前版本已停止服务,请更新到 v${info.latestVersion} 后继续使用。',
),
actions: [
ElevatedButton(
onPressed: () => _openUpdateUrl(info.updateUrl),
child: const Text('立即更新'),
),
],
),
),
);
}
/// 非强制更新弹窗:可跳过;点更新跳下载页后继续进入 App
Future<void> _showUpdatePrompt(VersionInfo info) async {
final update = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: AppColors.surface,
title: Text('发现新版本 v${info.latestVersion}',
style: const TextStyle(fontSize: 16)),
content: const Text('建议更新到最新版本以获得更好的体验。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('跳过'),
),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('立即更新'),
),
],
),
);
if (update == true) await _openUpdateUrl(info.updateUrl);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 88,
height: 88,
decoration: BoxDecoration(
gradient: const LinearGradient(colors: [AppColors.primary, AppColors.secondary]),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(Icons.play_arrow_rounded, size: 56, color: Colors.white),
),
const SizedBox(height: 24),
const Text('短视频', style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700)),
const SizedBox(height: 40),
if (_notice != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(_notice!, textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.gold)),
)
else
Text(_step, style: const TextStyle(color: AppColors.textSecondary)),
if (_step.contains('异常')) ...[
const SizedBox(height: 16),
TextButton(onPressed: _bootstrap, child: const Text('点击重试')),
],
],
),
),
);
}
}