chatapp3-flutter/lib/modules/splash/splash_page.dart
NIGGER SLAYER f78c099a47 启动页
2026-04-20 14:26:12 +08:00

547 lines
17 KiB
Dart
Raw 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 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/shared/tools/sc_version_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/app/routes/sc_routes.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/modules/auth/login_route.dart';
import 'package:yumi/ui_kit/components/sc_compontent.dart';
import 'last_weekly_cp_splash_cache.dart';
import 'weekly_star_splash_cache.dart';
enum _SplashPresentationVariant { weeklyStar, lastWeeklyCp, defaultSplash }
/// CP 启动页手动微调区。
/// 如果后续还要继续对齐设计稿,优先只改这一组 Rect / Size
/// 不要去动 _buildCpRankCard 里的层级和结构。
class _LastWeeklyCpLayout {
static const Rect rank1FrameRect = Rect.fromLTWH(40, 253, 297, 148);
static const Rect rank2FrameRect = Rect.fromLTWH(0, 427, 196, 111);
static const Rect rank3FrameRect = Rect.fromLTWH(179, 427, 196, 111);
static final Rect rank1LeftAvatarRect = Rect.fromCenter(
center: const Offset(151, 353),
width: 58,
height: 58,
);
static final Rect rank1RightAvatarRect = Rect.fromCenter(
center: const Offset(227, 352),
width: 58,
height: 58,
);
static const Rect rank1LabelRect = Rect.fromLTWH(118, 383, 140, 22);
static final Rect rank2LeftAvatarRect = Rect.fromCenter(
center: const Offset(46, 508),
width: 50,
height: 50,
);
static final Rect rank2RightAvatarRect = Rect.fromCenter(
center: const Offset(123, 509),
width: 50,
height: 50,
);
static const Rect rank2LabelRect = Rect.fromLTWH(10, 533, 145, 26);
static final Rect rank3LeftAvatarRect = Rect.fromCenter(
center: const Offset(252, 508),
width: 50,
height: 50,
);
static final Rect rank3RightAvatarRect = Rect.fromCenter(
center: const Offset(329, 509),
width: 50,
height: 50,
);
static const Rect rank3LabelRect = Rect.fromLTWH(218, 533, 145, 26);
}
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
static const Duration _minimumSplashDuration = Duration(seconds: 3);
static const Size _weeklyStarCanvasSize = Size(375, 812);
static const Size _lastWeeklyCpCanvasSize = Size(375, 812);
Timer? _timer;
Timer? _countdownTimer;
late final List<WeeklyStarSplashEntry> _weeklyStarEntries;
late final List<LastWeeklyCPSplashEntry> _lastWeeklyCpEntries;
late final _SplashPresentationVariant _selectedVariant;
int _remainingSeconds = _minimumSplashDuration.inSeconds;
bool _isNavigating = false;
@override
void initState() {
super.initState();
_weeklyStarEntries = WeeklyStarSplashCache.loadDisplayEntries();
_lastWeeklyCpEntries = LastWeeklyCPSplashCache.loadDisplayEntries();
_selectedVariant = _pickSplashVariant();
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(FileCacheManager.getInstance().getFilePath());
unawaited(WeeklyStarSplashCache.refreshCacheInBackground());
unawaited(LastWeeklyCPSplashCache.refreshCacheInBackground());
});
_startNavigationTimer();
}
@override
void dispose() {
_cancelTimer();
super.dispose();
}
/// 启动页最短展示时间
void _startNavigationTimer() {
_remainingSeconds = _minimumSplashDuration.inSeconds;
_timer = Timer(_minimumSplashDuration, _dismissSplash);
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
final nextSeconds = _minimumSplashDuration.inSeconds - timer.tick;
if (nextSeconds <= 0) {
timer.cancel();
return;
}
if (!mounted) {
return;
}
setState(() {
_remainingSeconds = nextSeconds;
});
});
}
@override
Widget build(BuildContext context) {
final splashContent = switch (_selectedVariant) {
_SplashPresentationVariant.weeklyStar => _buildWeeklyStarSplash(),
_SplashPresentationVariant.lastWeeklyCp => _buildLastWeeklyCpSplash(),
_SplashPresentationVariant.defaultSplash => _buildDefaultSplash(),
};
final showLaunchOverlay =
_selectedVariant != _SplashPresentationVariant.defaultSplash;
return Material(
color: Colors.black,
child: Stack(
fit: StackFit.expand,
children: [
splashContent,
if (showLaunchOverlay) _buildSkipCountdownButton(context),
],
),
);
}
_SplashPresentationVariant _pickSplashVariant() {
final variants = <_SplashPresentationVariant>[];
if (WeeklyStarSplashCache.shouldShow) {
variants.add(_SplashPresentationVariant.weeklyStar);
}
if (LastWeeklyCPSplashCache.shouldShow) {
variants.add(_SplashPresentationVariant.lastWeeklyCp);
}
if (variants.isEmpty) {
return _SplashPresentationVariant.defaultSplash;
}
return variants[Random().nextInt(variants.length)];
}
Widget _buildDefaultSplash() {
return Stack(
alignment: Alignment.center,
children: [
Image.asset(
SCGlobalConfig.businessLogicStrategy.getSplashPageBackgroundImage(),
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
fit: BoxFit.cover,
),
Image.asset(
SCGlobalConfig.businessLogicStrategy.getSplashPageIcon(),
width: 107.w,
height: 159.w,
),
],
);
}
Widget _buildSkipCountdownButton(BuildContext context) {
final localizations = SCAppLocalizations.of(context);
final label =
localizations?.skipCountdown(_remainingSeconds.toString()) ??
'Skip ${_remainingSeconds}s';
final borderRadius = BorderRadius.circular(999);
return SafeArea(
child: Align(
alignment: AlignmentDirectional.topEnd,
child: Padding(
padding: EdgeInsetsDirectional.only(top: 12.h, end: 16.w),
child: Material(
color: Colors.transparent,
child: Ink(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0x33FFFFFF), Color(0x18FFFFFF)],
begin: AlignmentDirectional.centerStart,
end: AlignmentDirectional.centerEnd,
),
borderRadius: borderRadius,
border: Border.all(color: Colors.white.withValues(alpha: 0.18)),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10.w,
offset: Offset(0, 3.w),
),
],
),
child: InkWell(
borderRadius: borderRadius,
onTap: _dismissSplash,
splashFactory: NoSplash.splashFactory,
overlayColor: WidgetStateProperty.all(Colors.transparent),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 8.h,
),
child: Text(
label,
maxLines: 1,
softWrap: false,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.96),
fontSize: 12.sp,
height: 1,
fontWeight: FontWeight.w700,
),
),
),
),
),
),
),
),
);
}
Widget _buildWeeklyStarSplash() {
return SizedBox.expand(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: _weeklyStarCanvasSize.width,
height: _weeklyStarCanvasSize.height,
child: Stack(
children: [
Positioned.fill(
child: Image.asset(
'sc_images/splash/sc_weekly_star_bg.png',
fit: BoxFit.cover,
),
),
_buildRankCard(
entry: _entryForRank(1),
frameAsset: 'sc_images/splash/sc_icon_weekly_star_rank_1.png',
frameRect: const Rect.fromLTWH(24, 110, 327, 248),
avatarRect: Rect.fromCenter(
center: Offset(188, 261),
width: 74,
height: 74,
),
labelRect: const Rect.fromLTWH(154, 328, 67, 12),
),
_buildRankCard(
entry: _entryForRank(2),
frameAsset: 'sc_images/splash/sc_icon_weekly_star_rank_2.png',
frameRect: const Rect.fromLTWH(0, 376, 182, 115),
avatarRect: Rect.fromCenter(
center: Offset(77, 437),
width: 62,
height: 62,
),
labelRect: const Rect.fromLTWH(40, 501, 74, 12),
),
_buildRankCard(
entry: _entryForRank(3),
frameAsset: 'sc_images/splash/sc_icon_weekly_star_rank_3.png',
frameRect: const Rect.fromLTWH(180, 376, 195, 117),
avatarRect: Rect.fromCenter(
center: Offset(299, 439),
width: 66,
height: 66,
),
labelRect: const Rect.fromLTWH(260, 501, 78, 12),
),
],
),
),
),
);
}
Widget _buildLastWeeklyCpSplash() {
return SizedBox.expand(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
width: _lastWeeklyCpCanvasSize.width,
height: _lastWeeklyCpCanvasSize.height,
child: Stack(
children: [
Positioned.fill(
child: Image.asset(
'sc_images/splash/sc_last_weekly_cp_bg.png',
fit: BoxFit.cover,
),
),
_buildCpRankCard(
entry: _cpEntryForRank(1),
frameAsset:
'sc_images/splash/sc_icon_last_weekly_cp_rank_1.png',
frameRect: _LastWeeklyCpLayout.rank1FrameRect,
leftAvatarRect: _LastWeeklyCpLayout.rank1LeftAvatarRect,
rightAvatarRect: _LastWeeklyCpLayout.rank1RightAvatarRect,
labelRect: _LastWeeklyCpLayout.rank1LabelRect,
),
_buildCpRankCard(
entry: _cpEntryForRank(2),
frameAsset:
'sc_images/splash/sc_icon_last_weekly_cp_rank_2.png',
frameRect: _LastWeeklyCpLayout.rank2FrameRect,
leftAvatarRect: _LastWeeklyCpLayout.rank2LeftAvatarRect,
rightAvatarRect: _LastWeeklyCpLayout.rank2RightAvatarRect,
labelRect: _LastWeeklyCpLayout.rank2LabelRect,
),
_buildCpRankCard(
entry: _cpEntryForRank(3),
frameAsset:
'sc_images/splash/sc_icon_last_weekly_cp_rank_3.png',
frameRect: _LastWeeklyCpLayout.rank3FrameRect,
leftAvatarRect: _LastWeeklyCpLayout.rank3LeftAvatarRect,
rightAvatarRect: _LastWeeklyCpLayout.rank3RightAvatarRect,
labelRect: _LastWeeklyCpLayout.rank3LabelRect,
),
],
),
),
),
);
}
WeeklyStarSplashEntry _entryForRank(int rank) {
return _weeklyStarEntries.firstWhere(
(entry) => entry.rank == rank,
orElse:
() => WeeklyStarSplashEntry(
rank: rank,
avatarUrl: '',
nickname: 'Namename',
),
);
}
LastWeeklyCPSplashEntry _cpEntryForRank(int rank) {
return _lastWeeklyCpEntries.firstWhere(
(entry) => entry.rank == rank,
orElse:
() => LastWeeklyCPSplashEntry(
rank: rank,
leftAvatarUrl: '',
rightAvatarUrl: '',
leftNickname: 'Namename',
rightNickname: 'Namename',
),
);
}
Widget _buildRankCard({
required WeeklyStarSplashEntry entry,
required String frameAsset,
required Rect frameRect,
required Rect avatarRect,
required Rect labelRect,
}) {
return Stack(
children: [
Positioned.fromRect(
rect: avatarRect,
child: head(url: entry.avatarUrl, width: avatarRect.width),
),
Positioned.fromRect(
rect: frameRect,
child: Image.asset(frameAsset, fit: BoxFit.fill),
),
Positioned.fromRect(
rect: labelRect,
child: _SplashOutlinedText(text: entry.nickname),
),
],
);
}
Widget _buildCpRankCard({
required LastWeeklyCPSplashEntry entry,
required String frameAsset,
required Rect frameRect,
required Rect leftAvatarRect,
required Rect rightAvatarRect,
required Rect labelRect,
}) {
return Stack(
children: [
Positioned.fromRect(
rect: frameRect,
child: Image.asset(frameAsset, fit: BoxFit.fill),
),
Positioned.fromRect(
rect: leftAvatarRect,
child: head(url: entry.leftAvatarUrl, width: leftAvatarRect.width),
),
Positioned.fromRect(
rect: rightAvatarRect,
child: head(url: entry.rightAvatarUrl, width: rightAvatarRect.width),
),
Positioned.fromRect(
rect: labelRect,
child: _LastWeeklyCpPairLabel(
leftText: entry.leftNickname,
rightText: entry.rightNickname,
),
),
],
);
}
void _dismissSplash() {
if (_isNavigating) {
return;
}
_isNavigating = true;
_cancelTimer();
unawaited(_goMainPage());
}
Future<void> _goMainPage() async {
try {
await SCVersionUtils.checkReview();
if (!mounted) {
return;
}
var user = AccountStorage().getCurrentUser();
var token = AccountStorage().getToken();
if (user != null && token.isNotEmpty) {
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
} else {
SCNavigatorUtils.push(context, LoginRouter.login, replace: true);
}
} catch (e) {
if (!mounted) {
return;
}
SCNavigatorUtils.push(context, LoginRouter.login, replace: true);
}
}
/// 取消倒计时的计时器。
void _cancelTimer() {
// 计时器(`Timer`)组件的取消(`cancel`)方法,取消计时器。
_timer?.cancel();
_countdownTimer?.cancel();
}
}
class _SplashOutlinedText extends StatelessWidget {
const _SplashOutlinedText({required this.text});
final String text;
static const Color _fillColor = Color(0xFFA11726);
static const Color _strokeColor = Color(0xFFFFEBBA);
@override
Widget build(BuildContext context) {
final displayText = text.trim().isEmpty ? 'Namename' : text.trim();
return FittedBox(
fit: BoxFit.scaleDown,
child: Stack(
alignment: Alignment.center,
children: [
Text(
displayText,
maxLines: 1,
style: TextStyle(
fontSize: 12,
height: 1,
fontWeight: FontWeight.w500,
foreground:
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1
..color = _strokeColor,
),
),
Text(
displayText,
maxLines: 1,
style: const TextStyle(
fontSize: 12,
height: 1,
fontWeight: FontWeight.w500,
color: _fillColor,
),
),
],
),
);
}
}
class _LastWeeklyCpPairLabel extends StatelessWidget {
const _LastWeeklyCpPairLabel({
required this.leftText,
required this.rightText,
});
final String leftText;
final String rightText;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: _SplashOutlinedText(text: leftText),
),
),
const SizedBox(width: 8),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: _SplashOutlinedText(text: rightText),
),
),
],
);
}
}