This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
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('点击重试')),
],
],
),
),
);
}
}