启动页
@ -84,6 +84,7 @@
|
||||
"like": "إعجاب",
|
||||
"updateNow": "تحديث الآن",
|
||||
"skip2": "تخطي",
|
||||
"skipCountdown": "تخطي {1}ث",
|
||||
"importantReminder": "تذكير مهم",
|
||||
"discard": "تجاهل",
|
||||
"deleteCommentTips": "هل أنت متأكد أنك تريد حذف هذا التعليق؟",
|
||||
|
||||
@ -96,6 +96,7 @@
|
||||
"ownerIncomeCoins": "মালিকের আয়:{1} কয়েন",
|
||||
"game": "গেম",
|
||||
"skip2": "স্কিপ করুন",
|
||||
"skipCountdown": "{1}সে স্কিপ",
|
||||
"coins4": "কয়েন",
|
||||
"claim": "গ্রহণ",
|
||||
"complete": "সম্পূর্ণ",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,11 +1090,9 @@ 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(
|
||||
String onlineUsers(String name1, String name2) => translate(
|
||||
'onlineUsers',
|
||||
).replaceAll('{1}', name1).replaceAll('{2}', name2);
|
||||
|
||||
@ -1086,8 +1111,7 @@ class SCAppLocalizations {
|
||||
|
||||
String win2(String name) => translate('win2').replaceAll('{1}', name);
|
||||
|
||||
String numberOfMyCPs(String name1, String name2) =>
|
||||
translate(
|
||||
String numberOfMyCPs(String name1, String name2) => translate(
|
||||
'numberOfMyCPs',
|
||||
).replaceAll('{1}', name1).replaceAll('{2}', name2);
|
||||
|
||||
@ -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,10 +1532,11 @@ class SCAppLocalizations {
|
||||
|
||||
String xxfamily(String name) => translate('xxfamily').replaceAll('{1}', name);
|
||||
|
||||
String collectionTimeTips(String time,
|
||||
String collectionTimeTips(
|
||||
String time,
|
||||
String remainCount,
|
||||
String totalCount,) =>
|
||||
translate('collectionTimeTips')
|
||||
String totalCount,
|
||||
) => translate('collectionTimeTips')
|
||||
.replaceAll('{1}', time)
|
||||
.replaceAll("{2}", remainCount)
|
||||
.replaceAll("{3}", totalCount);
|
||||
@ -1522,8 +1550,7 @@ class SCAppLocalizations {
|
||||
String privileges(String name) =>
|
||||
translate('privileges').replaceAll('{1}', name);
|
||||
|
||||
String remainingNumberTips(String name1, String name2) =>
|
||||
translate(
|
||||
String remainingNumberTips(String name1, String name2) => translate(
|
||||
'remainingNumberTips',
|
||||
).replaceAll('{1}', name1).replaceAll("{2}", name2);
|
||||
|
||||
@ -1548,16 +1575,14 @@ class SCAppLocalizations {
|
||||
|
||||
String skip(String name) => translate('skip').replaceAll('{1}', name);
|
||||
|
||||
String familyMember2(String name1, String name2) =>
|
||||
translate(
|
||||
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(
|
||||
String familyAdmin2(String name1, String name2) => translate(
|
||||
'familyAdmin2',
|
||||
).replaceAll('{1}', name1).replaceAll('{2}', name2);
|
||||
|
||||
@ -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 {
|
||||
|
||||
105
lib/modules/splash/last_weekly_cp_splash_cache.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,17 +105,61 @@ 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(
|
||||
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(
|
||||
@ -63,11 +174,272 @@ class _SplashPageState extends State<SplashPage> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _goMainPage() async {
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
148
lib/modules/splash/weekly_star_splash_cache.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
BIN
sc_images/splash/sc_icon_last_weekly_cp_rank_1.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
sc_images/splash/sc_icon_last_weekly_cp_rank_2.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
sc_images/splash/sc_icon_last_weekly_cp_rank_3.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
sc_images/splash/sc_icon_weekly_star_rank_1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
sc_images/splash/sc_icon_weekly_star_rank_2.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
sc_images/splash/sc_icon_weekly_star_rank_3.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
sc_images/splash/sc_last_weekly_cp_bg.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
sc_images/splash/sc_weekly_star_bg.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
20
需求进度.md
@ -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`
|
||||
|
||||