启动页

This commit is contained in:
NIGGER SLAYER 2026-04-20 14:26:12 +08:00
parent 7d9893b4ef
commit f78c099a47
17 changed files with 834 additions and 84 deletions

View File

@ -84,6 +84,7 @@
"like": "إعجاب",
"updateNow": "تحديث الآن",
"skip2": "تخطي",
"skipCountdown": "تخطي {1}ث",
"importantReminder": "تذكير مهم",
"discard": "تجاهل",
"deleteCommentTips": "هل أنت متأكد أنك تريد حذف هذا التعليق؟",

View File

@ -96,6 +96,7 @@
"ownerIncomeCoins": "মালিকের আয়:{1} কয়েন",
"game": "গেম",
"skip2": "স্কিপ করুন",
"skipCountdown": "{1}সে স্কিপ",
"coins4": "কয়েন",
"claim": "গ্রহণ",
"complete": "সম্পূর্ণ",

View File

@ -97,6 +97,7 @@
"ownerIncomeCoins": "Owner's income: {1} coins",
"game": "Game",
"skip2": "Skip",
"skipCountdown": "Skip {1}s",
"coins4": "Coins",
"weekStart": "Week-Start",
"forMoreRewardsPleaseCheckTheTaskCenter": "For more rewards, please check the task center",

View File

@ -87,6 +87,7 @@
"ownerIncomeCoins": "Sahibin Geliri:{1} jetton",
"game": "Oyun",
"skip2": "Atla",
"skipCountdown": "{1} sn geç",
"coins4": "Jetton",
"weekStart": "Hafta Başlangıcı",
"forMoreRewardsPleaseCheckTheTaskCenter": "Daha fazla ödül için lütfen görev merkezini kontrol edin",

View File

@ -13,7 +13,7 @@ class SCAppLocalizations {
}
static const LocalizationsDelegate<SCAppLocalizations> delegate =
_SCAppLocalizationsDelegate();
_SCAppLocalizationsDelegate();
Map<String, String>? _localizedStrings;
@ -68,7 +68,8 @@ class SCAppLocalizations {
String get expirationTime => translate('expirationTime');
String get inviteNewUsersToEarnCoins => translate('inviteNewUsersToEarnCoins');
String get inviteNewUsersToEarnCoins =>
translate('inviteNewUsersToEarnCoins');
String get avoidBeingKicked => translate('avoidBeingKicked');
@ -90,7 +91,8 @@ class SCAppLocalizations {
String get numberOfMic => translate('numberOfMic');
String get noHistoricalRecordsAvailable => translate('noHistoricalRecordsAvailable');
String get noHistoricalRecordsAvailable =>
translate('noHistoricalRecordsAvailable');
String get myRoom => translate('myRoom');
@ -106,7 +108,8 @@ class SCAppLocalizations {
String get crateMyRoom => translate('crateMyRoom');
String get exclusiveEmojiWillBeReleasedAfterBecoming => translate('exclusiveEmojiWillBeReleasedAfterBecoming');
String get exclusiveEmojiWillBeReleasedAfterBecoming =>
translate('exclusiveEmojiWillBeReleasedAfterBecoming');
String get socialPrivilege => translate('socialPrivilege');
@ -126,7 +129,8 @@ class SCAppLocalizations {
String get vipBirthdayGift => translate('vipBirthdayGift');
String get pleaseUpgradeYourVipLevel => translate('pleaseUpgradeYourVipLevel');
String get pleaseUpgradeYourVipLevel =>
translate('pleaseUpgradeYourVipLevel');
String get membershipFreeChatSpeak => translate('membershipFreeChatSpeak');
@ -148,25 +152,34 @@ class SCAppLocalizations {
String get enterTheRoom => translate('enterTheRoom');
String get taskNamePersonalGameConsume => translate('taskNamePersonalGameConsume');
String get taskNamePersonalGameConsume =>
translate('taskNamePersonalGameConsume');
String get taskNamePersonalMicInRoom => translate('taskNamePersonalMicInRoom');
String get taskNamePersonalMicInRoom =>
translate('taskNamePersonalMicInRoom');
String get taskNamePersonalActiveInRoom => translate('taskNamePersonalActiveInRoom');
String get taskNamePersonalActiveInRoom =>
translate('taskNamePersonalActiveInRoom');
String get taskNameRoomOwnerMicTime => translate('taskNameRoomOwnerMicTime');
String get taskNamePersonalLuckyGiftGold => translate('taskNamePersonalLuckyGiftGold');
String get taskNamePersonalLuckyGiftGold =>
translate('taskNamePersonalLuckyGiftGold');
String get taskNamePersonalMagicGiftGold => translate('taskNamePersonalMagicGiftGold');
String get taskNamePersonalMagicGiftGold =>
translate('taskNamePersonalMagicGiftGold');
String get taskNameRoomOwnerSendGiftGold => translate('taskNameRoomOwnerSendGiftGold');
String get taskNameRoomOwnerSendGiftGold =>
translate('taskNameRoomOwnerSendGiftGold');
String get taskNameRoomUserSendGiftGold => translate('taskNameRoomUserSendGiftGold');
String get taskNameRoomUserSendGiftGold =>
translate('taskNameRoomUserSendGiftGold');
String get taskNameRoomOwnerInviteMic => translate('taskNameRoomOwnerInviteMic');
String get taskNameRoomOwnerInviteMic =>
translate('taskNameRoomOwnerInviteMic');
String get taskNameRoomOnlineUserCount => translate('taskNameRoomOnlineUserCount');
String get taskNameRoomOnlineUserCount =>
translate('taskNameRoomOnlineUserCount');
String get taskNamePersonalSendGift => translate('taskNamePersonalSendGift');
@ -174,11 +187,14 @@ class SCAppLocalizations {
String get taskNameRoomMicUser60Min => translate('taskNameRoomMicUser60Min');
String get taskNameRoomMicUser120Min => translate('taskNameRoomMicUser120Min');
String get taskNameRoomMicUser120Min =>
translate('taskNameRoomMicUser120Min');
String get taskNameRoomOwnerSendGiftUser => translate('taskNameRoomOwnerSendGiftUser');
String get taskNameRoomOwnerSendGiftUser =>
translate('taskNameRoomOwnerSendGiftUser');
String get taskNameRoomOwnerSendRedPacket => translate('taskNameRoomOwnerSendRedPacket');
String get taskNameRoomOwnerSendRedPacket =>
translate('taskNameRoomOwnerSendRedPacket');
String get taskNameRoomNewMember => translate('taskNameRoomNewMember');
@ -202,7 +218,8 @@ class SCAppLocalizations {
String get dailyCoinBonanzaRules => translate('dailyCoinBonanzaRules');
String get dailyCoinBonanzaRulesDetail => translate('dailyCoinBonanzaRulesDetail');
String get dailyCoinBonanzaRulesDetail =>
translate('dailyCoinBonanzaRulesDetail');
String get personalTasks => translate('personalTasks');
@ -218,7 +235,8 @@ class SCAppLocalizations {
String get operationFail => translate('operationFail');
String get forMoreRewardsPleaseCheckTheTaskCenter => translate('forMoreRewardsPleaseCheckTheTaskCenter');
String get forMoreRewardsPleaseCheckTheTaskCenter =>
translate('forMoreRewardsPleaseCheckTheTaskCenter');
String get likedYourComment => translate('likedYourComment');
@ -250,7 +268,8 @@ class SCAppLocalizations {
String get basicFeatures => translate('basicFeatures');
String get areYouSureYouWantToDeleteYourAccount => translate('areYouSureYouWantToDeleteYourAccount');
String get areYouSureYouWantToDeleteYourAccount =>
translate('areYouSureYouWantToDeleteYourAccount');
String get game => translate('game');
@ -258,7 +277,8 @@ class SCAppLocalizations {
String get deleteAccountTips => translate('deleteAccountTips');
String get thisUserHasBeenBlacklisted => translate('thisUserHasBeenBlacklisted');
String get thisUserHasBeenBlacklisted =>
translate('thisUserHasBeenBlacklisted');
String get comment => translate('comment');
@ -266,7 +286,8 @@ class SCAppLocalizations {
String get customizedGiftRules => translate('customizedGiftRules');
String get customizedGiftRulesContent => translate('customizedGiftRulesContent');
String get customizedGiftRulesContent =>
translate('customizedGiftRulesContent');
String get clearCache => translate('clearCache');
@ -286,9 +307,11 @@ class SCAppLocalizations {
String get enterThisVoiceChatRoom => translate('enterThisVoiceChatRoom');
String get swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt => translate('swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt');
String get swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt =>
translate('swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt');
String get areYouSureYouWantToClearLocalCache => translate('areYouSureYouWantToClearLocalCache');
String get areYouSureYouWantToClearLocalCache =>
translate('areYouSureYouWantToClearLocalCache');
String get multiple => translate('multiple');
@ -300,13 +323,17 @@ class SCAppLocalizations {
String get clearCacheSuccessfully => translate('clearCacheSuccessfully');
String get successfullyAddedToTheBlacklist => translate('successfullyAddedToTheBlacklist');
String get successfullyAddedToTheBlacklist =>
translate('successfullyAddedToTheBlacklist');
String get successfullyRemovedFromTheBlacklist => translate('successfullyRemovedFromTheBlacklist');
String get successfullyRemovedFromTheBlacklist =>
translate('successfullyRemovedFromTheBlacklist');
String get successfullyRemovedFromTheDynamicBlacklist => translate('successfullyRemovedFromTheDynamicBlacklist');
String get successfullyRemovedFromTheDynamicBlacklist =>
translate('successfullyRemovedFromTheDynamicBlacklist');
String get successfullyAddedToTheDynamicBlacklist => translate('successfullyAddedToTheDynamicBlacklist');
String get successfullyAddedToTheDynamicBlacklist =>
translate('successfullyAddedToTheDynamicBlacklist');
String get areYouSureToCancelBlacklist =>
translate('areYouSureToCancelBlacklist');
@ -327,7 +354,8 @@ class SCAppLocalizations {
String get userBlacklist => translate('userBlacklist');
String get areYouSureToCancelDynamicBlacklist => translate('areYouSureToCancelDynamicBlacklist');
String get areYouSureToCancelDynamicBlacklist =>
translate('areYouSureToCancelDynamicBlacklist');
String get thisFeatureIsCurrentlyUnavailable =>
translate('thisFeatureIsCurrentlyUnavailable');
@ -995,7 +1023,6 @@ class SCAppLocalizations {
String get charmGameRulesTips => translate('charmGameRulesTips');
String get enterYourNewPassword => translate('enterYourNewPassword');
String get confirmSwitchMicThemeTips =>
@ -1063,13 +1090,11 @@ class SCAppLocalizations {
String vipEmoticon(String name) =>
translate('vipEmoticon').replaceAll('{1}', name);
String family3(String name) =>
translate('family3').replaceAll('{1}', name);
String family3(String name) => translate('family3').replaceAll('{1}', name);
String onlineUsers(String name1, String name2) =>
translate(
'onlineUsers',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String onlineUsers(String name1, String name2) => translate(
'onlineUsers',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String timeSpentTogether(String name) =>
translate('timeSpentTogether').replaceAll('{1}', name);
@ -1086,10 +1111,9 @@ class SCAppLocalizations {
String win2(String name) => translate('win2').replaceAll('{1}', name);
String numberOfMyCPs(String name1, String name2) =>
translate(
'numberOfMyCPs',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String numberOfMyCPs(String name1, String name2) => translate(
'numberOfMyCPs',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String sendUserId(String name) =>
translate('sendUserId').replaceAll('{1}', name);
@ -1466,6 +1490,9 @@ class SCAppLocalizations {
String get skip2 => translate('skip2');
String skipCountdown(String seconds) =>
translate('skipCountdown').replaceAll('{1}', seconds);
String get updateNow => translate('updateNow');
String get clearMessage => translate('clearMessage');
@ -1505,13 +1532,14 @@ class SCAppLocalizations {
String xxfamily(String name) => translate('xxfamily').replaceAll('{1}', name);
String collectionTimeTips(String time,
String remainCount,
String totalCount,) =>
translate('collectionTimeTips')
.replaceAll('{1}', time)
.replaceAll("{2}", remainCount)
.replaceAll("{3}", totalCount);
String collectionTimeTips(
String time,
String remainCount,
String totalCount,
) => translate('collectionTimeTips')
.replaceAll('{1}', time)
.replaceAll("{2}", remainCount)
.replaceAll("{3}", totalCount);
String yourVipWillExpire(String name) =>
translate('yourVipWillExpire').replaceAll('{1}', name);
@ -1522,10 +1550,9 @@ class SCAppLocalizations {
String privileges(String name) =>
translate('privileges').replaceAll('{1}', name);
String remainingNumberTips(String name1, String name2) =>
translate(
'remainingNumberTips',
).replaceAll('{1}', name1).replaceAll("{2}", name2);
String remainingNumberTips(String name1, String name2) => translate(
'remainingNumberTips',
).replaceAll('{1}', name1).replaceAll("{2}", name2);
String coins2(String name) => translate('coins2').replaceAll('{1}', name);
@ -1548,18 +1575,16 @@ class SCAppLocalizations {
String skip(String name) => translate('skip').replaceAll('{1}', name);
String familyMember2(String name1, String name2) =>
translate(
'familyMember2',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String familyMember2(String name1, String name2) => translate(
'familyMember2',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String familyMember3(String name1) =>
translate('familyMember3').replaceAll('{1}', name1);
String familyAdmin2(String name1, String name2) =>
translate(
'familyAdmin2',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String familyAdmin2(String name1, String name2) => translate(
'familyAdmin2',
).replaceAll('{1}', name1).replaceAll('{2}', name2);
String follow2(String name) => translate('follow2').replaceAll('{1}', name);
@ -1586,7 +1611,7 @@ class _SCAppLocalizationsDelegate
@override
bool isSupported(Locale locale) =>
['en', 'zh', 'ar','bn','tr'].contains(locale.languageCode);
['en', 'zh', 'ar', 'bn', 'tr'].contains(locale.languageCode);
@override
Future<SCAppLocalizations> load(Locale locale) async {

View File

@ -0,0 +1,105 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
class LastWeeklyCPSplashEntry {
const LastWeeklyCPSplashEntry({
required this.rank,
required this.leftAvatarUrl,
required this.rightAvatarUrl,
required this.leftNickname,
required this.rightNickname,
});
final int rank;
final String leftAvatarUrl;
final String rightAvatarUrl;
final String leftNickname;
final String rightNickname;
factory LastWeeklyCPSplashEntry.fromJson(Map<String, dynamic> json) {
return LastWeeklyCPSplashEntry(
rank: (json['rank'] as num?)?.toInt() ?? 0,
leftAvatarUrl: (json['leftAvatarUrl'] as String?) ?? '',
rightAvatarUrl: (json['rightAvatarUrl'] as String?) ?? '',
leftNickname: (json['leftNickname'] as String?) ?? '',
rightNickname: (json['rightNickname'] as String?) ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'rank': rank,
'leftAvatarUrl': leftAvatarUrl,
'rightAvatarUrl': rightAvatarUrl,
'leftNickname': leftNickname,
'rightNickname': rightNickname,
};
}
}
class LastWeeklyCPSplashCache {
static const String _cacheKey = 'last_weekly_cp_splash_cache_v1';
/// `api-ready-launch-splash`
///
/// CP ready
///
static const bool _allowCacheDrivenSplash = false;
static bool get shouldShow {
if (!_allowCacheDrivenSplash) {
return false;
}
return loadCachedEntries().isNotEmpty;
}
static List<LastWeeklyCPSplashEntry> loadDisplayEntries() {
if (!_allowCacheDrivenSplash) {
return const [];
}
return loadCachedEntries();
}
static List<LastWeeklyCPSplashEntry> loadCachedEntries() {
final raw = DataPersistence.getString(_cacheKey);
if (raw.isEmpty) {
return const [];
}
try {
final decoded = jsonDecode(raw);
if (decoded is! List) {
return const [];
}
return decoded
.whereType<Map>()
.map(
(item) => LastWeeklyCPSplashEntry.fromJson(
Map<String, dynamic>.from(item),
),
)
.toList()
..sort((a, b) => a.rank.compareTo(b.rank));
} catch (error, stackTrace) {
debugPrint('LastWeeklyCPSplashCache parse failed: $error\n$stackTrace');
return const [];
}
}
static Future<void> refreshCacheInBackground() async {
// TODO(api-ready-launch-splash): CP ready `_allowCacheDrivenSplash`
// true -> LastWeeklyCPSplashEntry ->
// `_cacheKey`
if (!_allowCacheDrivenSplash) {
return;
}
try {
// 线 CP
} catch (error, stackTrace) {
debugPrint('LastWeeklyCPSplashCache refresh failed: $error\n$stackTrace');
}
}
}

View File

@ -1,13 +1,67 @@
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});
@ -17,15 +71,28 @@ class SplashPage extends StatefulWidget {
}
class _SplashPageState extends State<SplashPage> {
static const Duration _minimumSplashDuration = Duration(milliseconds: 900);
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();
}
@ -38,36 +105,341 @@ class _SplashPageState extends State<SplashPage> {
///
void _startNavigationTimer() {
_timer = Timer(_minimumSplashDuration, () {
if (mounted) {
_goMainPage();
_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(
alignment: Alignment.center,
fit: StackFit.expand,
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,
),
splashContent,
if (showLaunchOverlay) _buildSkipCountdownButton(context),
],
),
);
}
void _goMainPage() async {
_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) {
@ -92,5 +464,83 @@ class _SplashPageState extends State<SplashPage> {
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),
),
),
],
);
}
}

View File

@ -0,0 +1,148 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:yumi/shared/business_logic/models/res/sc_top_four_with_reward_res.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
class WeeklyStarSplashEntry {
const WeeklyStarSplashEntry({
required this.rank,
required this.avatarUrl,
required this.nickname,
});
final int rank;
final String avatarUrl;
final String nickname;
factory WeeklyStarSplashEntry.fromJson(Map<String, dynamic> json) {
return WeeklyStarSplashEntry(
rank: (json['rank'] as num?)?.toInt() ?? 0,
avatarUrl: (json['avatarUrl'] as String?) ?? '',
nickname: (json['nickname'] as String?) ?? '',
);
}
Map<String, dynamic> toJson() {
return {'rank': rank, 'avatarUrl': avatarUrl, 'nickname': nickname};
}
}
class WeeklyStarSplashCache {
static const String _cacheKey = 'weekly_star_splash_cache_v1';
/// `api-ready-launch-splash`
///
/// ready
/// 退
static const bool _allowCacheDrivenSplash = false;
static bool get shouldShow {
if (!_allowCacheDrivenSplash) {
return false;
}
return loadCachedEntries().isNotEmpty;
}
static List<WeeklyStarSplashEntry> loadDisplayEntries() {
if (!_allowCacheDrivenSplash) {
return const [];
}
return loadCachedEntries();
}
static List<WeeklyStarSplashEntry> loadCachedEntries() {
final raw = DataPersistence.getString(_cacheKey);
if (raw.isEmpty) {
return const [];
}
try {
final decoded = jsonDecode(raw);
if (decoded is! List) {
return const [];
}
return decoded
.whereType<Map>()
.map(
(item) =>
WeeklyStarSplashEntry.fromJson(Map<String, dynamic>.from(item)),
)
.toList()
..sort((a, b) => a.rank.compareTo(b.rank));
} catch (error, stackTrace) {
debugPrint('WeeklyStarSplashCache parse failed: $error\n$stackTrace');
return const [];
}
}
static Future<void> refreshCacheInBackground() async {
// TODO(api-ready-launch-splash): ready `_allowCacheDrivenSplash`
// true `_cacheKey`
//
if (!_allowCacheDrivenSplash) {
return;
}
try {
final result = await SCConfigRepositoryImp().topFourWithReward();
final entries = _mapTopThree(result);
if (entries.isEmpty) {
return;
}
await DataPersistence.setString(
_cacheKey,
jsonEncode(entries.map((entry) => entry.toJson()).toList()),
);
} catch (error, stackTrace) {
debugPrint('WeeklyStarSplashCache refresh failed: $error\n$stackTrace');
}
}
static List<WeeklyStarSplashEntry> _mapTopThree(
List<SCTopFourWithRewardRes> result,
) {
final topThree =
result
.where(
(item) =>
(item.avatar ?? '').trim().isNotEmpty ||
(item.nickname ?? '').trim().isNotEmpty,
)
.map(
(item) => WeeklyStarSplashEntry(
rank: (item.rank ?? 0).toInt(),
avatarUrl: (item.avatar ?? '').trim(),
nickname: (item.nickname ?? '').trim(),
),
)
.toList()
..sort((a, b) => a.rank.compareTo(b.rank));
return _ensureThreeEntries(topThree.take(3).toList());
}
static List<WeeklyStarSplashEntry> _ensureThreeEntries(
List<WeeklyStarSplashEntry> entries,
) {
final normalized = List<WeeklyStarSplashEntry>.from(entries)
..sort((a, b) => a.rank.compareTo(b.rank));
for (var rank = 1; rank <= 3; rank++) {
final hasRank = normalized.any((entry) => entry.rank == rank);
if (!hasRank) {
normalized.add(
WeeklyStarSplashEntry(
rank: rank,
avatarUrl: '',
nickname: 'Namename',
),
);
}
}
normalized.sort((a, b) => a.rank.compareTo(b.rank));
return normalized.take(3).toList();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@ -4,7 +4,14 @@
- 控制当前 Flutter Android 发包体积,持续定位冗余组件、超大资源和不合理构建配置,并把每一步处理结果落盘记录。
## 本轮启动优化(非网络)
- 已移除启动页固定 `6` 秒倒计时,改为最短约 `900ms` 展示后继续路由判断,避免人为等待直接拉长入 app 时间。
- 已补回启动页正式展示逻辑:当前 `Weekly Star / Last Weekly CP` 两套自定义启动页都改为“本地有缓存才展示、无缓存不展示”;由于现阶段周榜与 CP 榜接口链路都还未 ready缓存刷新逻辑已先关闭所以当前启动阶段会直接回退到默认 splash不再展示这两套定制视觉稿。相关恢复入口已在缓存类里用 `api-ready-launch-splash` 注释标记,后续接口 ready 后可直接搜索接回。
- 已将启动页展示时间收敛为 `3` 秒,并在右上角新增通用 `skip 倒计时` 按钮:当前按钮会按秒级动态展示剩余时间,点击可立即跳过;文案已补齐 `en/ar/tr/bn` 多语言翻译,并按 locale 输出倒计时文本,便于后续继续做 RTL 语言验收。
- 已按 2026-04-20 最新截图反馈重新校准 `Last Weekly CP` 启动页的头像与昵称区域,并在 `SplashPage` 中单独抽出 `_LastWeeklyCpLayout` 手动微调区;后续如果还要继续挪位置,直接改榜一/榜二/榜三的 `frame/avatar/label Rect` 即可,不用再翻布局层级。
- 已按 2026-04-20 最新确认把 `Last Weekly CP` 启动页临时切到“通用头像占位”模式:由于当前 CP 榜正式接口还未就绪,启动页不再读取本地 `cpList` 或当前用户头像,现统一使用默认头像占位与通用昵称,专门用于先校对双人头像位和昵称区域的 UI 位置;后续接口 ready 后再切回真实数据源。
- 已继续扩展启动页方案:桌面 `启动页_cp榜` 的背景图与 CP 榜一/榜二/榜三素材已导入工程,并新增 `Last Weekly CP` 启动页;当前 `SplashPage` 会在 `Weekly Star``Last Weekly CP` 两套视觉稿之间随机选一张展示,便于一起校对两套 UI 样式。
- 已为 `Last Weekly CP` 启动页补齐本地缓存骨架:新增独立的 CP 榜缓存结构,当前会先把默认占位头像与通用昵称写入本地缓存,后续若改成正式的“有相关缓存才启用”或切到独立榜单来源,只需要替换数据入口,不用重做随机展示和布局层。
- 已按 2026-04-20 新需求在 Flutter 启动页接入 `Weekly Star` 周榜视觉稿:桌面 `启动页_周榜` 的背景图与榜一/榜二/榜三翅膀框素材已导入工程,并替换了原先仅展示静态 logo 的 `SplashPage`,当前会按参考图在启动阶段叠加 3 个头像位和用户名描边文案,便于先校对 UI 样式。
- 已为 `Weekly Star` 启动页补齐本地缓存读写骨架:新增独立缓存辅助类,支持把 `/ranking/top-four-with-reward` 返回的榜单前三头像/昵称写入本地,并在启动时优先读取缓存数据渲染;后续只需要把“当前固定启用”切回“缓存存在时才启用”即可,不用重做布局层。
- 已将 Firebase / Crashlytics 从 `runApp` 前阻塞初始化改为首帧后后台预热,并补充“未就绪时仅本地打印”的异常兜底,减少首屏前平台初始化阻塞。
- 已将 `SocialChatAuthenticationManager``SocialChatUserProfileManager` 改为按需创建,避免 splash 阶段就提前触发 Google Sign-In / Firebase 会话检查。
- 已将 deep link、设备信息、缓存目录创建等非首帧必需动作延后到首帧后执行降低首屏竞争。
@ -162,6 +169,9 @@
- `lib/modules/user/edit/edit_user_info_page2.dart`
- `lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart`
- `lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart`
- `lib/modules/splash/splash_page.dart`
- `lib/modules/splash/last_weekly_cp_splash_cache.dart`
- `lib/modules/splash/weekly_star_splash_cache.dart`
- `lib/shared/tools/sc_room_profile_cache.dart`
- `lib/shared/business_logic/models/res/room_res.dart`
- `lib/shared/business_logic/models/res/follow_room_res.dart`
@ -193,6 +203,14 @@
- `lib/modules/user/my_items/theme/bags_theme_page.dart`
- `lib/ui_kit/widgets/store/store_bag_page_helpers.dart`
- `sc_images/general/sc_no_data.png`
- `sc_images/splash/sc_weekly_star_bg.png`
- `sc_images/splash/sc_icon_weekly_star_rank_1.png`
- `sc_images/splash/sc_icon_weekly_star_rank_2.png`
- `sc_images/splash/sc_icon_weekly_star_rank_3.png`
- `sc_images/splash/sc_last_weekly_cp_bg.png`
- `sc_images/splash/sc_icon_last_weekly_cp_rank_1.png`
- `sc_images/splash/sc_icon_last_weekly_cp_rank_2.png`
- `sc_images/splash/sc_icon_last_weekly_cp_rank_3.png`
- `.gitignore`
- `android/key.properties`
- `pubspec.yaml`