优化火箭奖励展示和素材预加载
This commit is contained in:
parent
4b637d4137
commit
84b68fbb4a
@ -56,6 +56,7 @@ import '../../shared/business_logic/models/res/sc_room_theme_list_res.dart';
|
|||||||
import '../../shared/business_logic/models/res/sc_vip_res.dart';
|
import '../../shared/business_logic/models/res/sc_vip_res.dart';
|
||||||
import '../../shared/tools/sc_room_profile_cache.dart';
|
import '../../shared/tools/sc_room_profile_cache.dart';
|
||||||
import '../../ui_kit/components/sc_float_ichart.dart';
|
import '../../ui_kit/components/sc_float_ichart.dart';
|
||||||
|
import '../../ui_kit/widgets/room/rocket/room_rocket_asset_preloader.dart';
|
||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
import '../../ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
|
import '../../ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
|
||||||
import '../../ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
import '../../ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
||||||
@ -2765,6 +2766,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
.then((res) {
|
.then((res) {
|
||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_scheduleRoomRocketAssetPreload(roomId, res, afterRoomEntry: true);
|
||||||
})
|
})
|
||||||
.catchError((e) {});
|
.catchError((e) {});
|
||||||
_loadRoomRocketRewardPopups(roomId);
|
_loadRoomRocketRewardPopups(roomId);
|
||||||
@ -2825,6 +2827,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||||
|
_scheduleRoomRocketAssetPreload(
|
||||||
|
(res.roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(),
|
||||||
|
res,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///刷新房间火箭信息
|
///刷新房间火箭信息
|
||||||
@ -2843,6 +2849,35 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||||
|
_scheduleRoomRocketAssetPreload(resolvedRoomId, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleRoomRocketAssetPreload(
|
||||||
|
String roomId,
|
||||||
|
SCRoomRocketStatusRes status, {
|
||||||
|
bool afterRoomEntry = false,
|
||||||
|
}) {
|
||||||
|
final resolvedRoomId = roomId.trim();
|
||||||
|
if (resolvedRoomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (afterRoomEntry) {
|
||||||
|
RoomRocketAssetPreloader.preloadAfterRoomEntry(
|
||||||
|
roomId: resolvedRoomId,
|
||||||
|
status: status,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
RoomRocketAssetPreloader.preloadForStatus(
|
||||||
|
roomId: resolvedRoomId,
|
||||||
|
status: status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (RoomRocketAssetPreloader.shouldForceCurrentLaunchPreload(status)) {
|
||||||
|
RoomRocketAssetPreloader.forcePreloadCurrentLaunchAssets(
|
||||||
|
roomId: resolvedRoomId,
|
||||||
|
status: status,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void scheduleRoomRocketStatusRefreshForGift({String? roomId}) {
|
void scheduleRoomRocketStatusRefreshForGift({String? roomId}) {
|
||||||
@ -3646,6 +3681,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomRocketGiftRefreshTimer = null;
|
_roomRocketGiftRefreshTimer = null;
|
||||||
_cancelRoomRocketPostLaunchStatusRefresh();
|
_cancelRoomRocketPostLaunchStatusRefresh();
|
||||||
_lastRoomRocketLaunchNoticeKey = null;
|
_lastRoomRocketLaunchNoticeKey = null;
|
||||||
|
RoomRocketAssetPreloader.cancelCurrentRoom();
|
||||||
_stopRoomRedPacketPresenceHeartbeat();
|
_stopRoomRedPacketPresenceHeartbeat();
|
||||||
_previewRoomSeatCount = null;
|
_previewRoomSeatCount = null;
|
||||||
_finishRoomStartupSeatLoading(executePendingAction: false);
|
_finishRoomStartupSeatLoading(executePendingAction: false);
|
||||||
|
|||||||
@ -231,6 +231,8 @@ abstract class SocialChatRoomRepository {
|
|||||||
String roomId, {
|
String roomId, {
|
||||||
int cursor = 1,
|
int cursor = 1,
|
||||||
int limit = 10,
|
int limit = 10,
|
||||||
|
String? launchNo,
|
||||||
|
bool roomScope = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
///查询火箭规则说明
|
///查询火箭规则说明
|
||||||
|
|||||||
@ -1133,11 +1133,16 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
String roomId, {
|
String roomId, {
|
||||||
int cursor = 1,
|
int cursor = 1,
|
||||||
int limit = 10,
|
int limit = 10,
|
||||||
|
String? launchNo,
|
||||||
|
bool roomScope = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
final resolvedLaunchNo = launchNo?.trim() ?? '';
|
||||||
final queryParams = {
|
final queryParams = {
|
||||||
if (roomId.trim().isNotEmpty) "roomId": roomId,
|
if (roomId.trim().isNotEmpty) "roomId": roomId,
|
||||||
"cursor": cursor,
|
"cursor": cursor,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
|
if (resolvedLaunchNo.isNotEmpty) "launchNo": resolvedLaunchNo,
|
||||||
|
if (roomScope || resolvedLaunchNo.isNotEmpty) "scope": "room",
|
||||||
};
|
};
|
||||||
const path = "/go/app/voice-room/rocket/reward-records";
|
const path = "/go/app/voice-room/rocket/reward-records";
|
||||||
_rocketApiDebug('request GET $path', query: queryParams);
|
_rocketApiDebug('request GET $path', query: queryParams);
|
||||||
|
|||||||
426
lib/ui_kit/widgets/room/rocket/room_rocket_asset_preloader.dart
Normal file
426
lib/ui_kit/widgets/room/rocket/room_rocket_asset_preloader.dart
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_api_res.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||||||
|
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
||||||
|
|
||||||
|
const int _roomRocketAssetPreloadMaxConcurrent = 2;
|
||||||
|
const Duration _roomRocketAssetPreloadDelay = Duration(milliseconds: 350);
|
||||||
|
const Duration _roomRocketAssetPreloadTimeout = Duration(seconds: 12);
|
||||||
|
const double _roomRocketAssetPreloadFallbackThreshold = 95;
|
||||||
|
|
||||||
|
enum _RoomRocketPreloadKind { image, pag }
|
||||||
|
|
||||||
|
class _RoomRocketPreloadAsset {
|
||||||
|
const _RoomRocketPreloadAsset({
|
||||||
|
required this.resource,
|
||||||
|
required this.kind,
|
||||||
|
required this.roomId,
|
||||||
|
required this.generation,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String resource;
|
||||||
|
final _RoomRocketPreloadKind kind;
|
||||||
|
final String roomId;
|
||||||
|
final int generation;
|
||||||
|
|
||||||
|
String get key {
|
||||||
|
final uri = _networkUri(resource);
|
||||||
|
return '${kind.name}:${uri?.toString() ?? resource.trim()}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomRocketAssetPreloader {
|
||||||
|
const RoomRocketAssetPreloader._();
|
||||||
|
|
||||||
|
static final Queue<_RoomRocketPreloadAsset> _queue =
|
||||||
|
Queue<_RoomRocketPreloadAsset>();
|
||||||
|
static final Set<String> _scheduledKeys = <String>{};
|
||||||
|
static int _generation = 0;
|
||||||
|
static int _running = 0;
|
||||||
|
static String _activeRoomId = '';
|
||||||
|
|
||||||
|
static void preloadAfterRoomEntry({
|
||||||
|
required String roomId,
|
||||||
|
required SCRoomRocketStatusRes status,
|
||||||
|
}) {
|
||||||
|
final normalizedRoomId = roomId.trim();
|
||||||
|
if (normalizedRoomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final generation = _syncRoom(normalizedRoomId);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
unawaited(
|
||||||
|
Future<void>.delayed(_roomRocketAssetPreloadDelay, () {
|
||||||
|
if (!_isCurrentRoom(normalizedRoomId, generation)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_enqueueAssets(
|
||||||
|
_assetsForStatus(
|
||||||
|
status,
|
||||||
|
roomId: normalizedRoomId,
|
||||||
|
generation: generation,
|
||||||
|
includeNextLevel: true,
|
||||||
|
includeCurrentRewardCovers: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preloadForStatus({
|
||||||
|
required String roomId,
|
||||||
|
required SCRoomRocketStatusRes status,
|
||||||
|
}) {
|
||||||
|
final normalizedRoomId = roomId.trim();
|
||||||
|
if (normalizedRoomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final generation = _syncRoom(normalizedRoomId);
|
||||||
|
_enqueueAssets(
|
||||||
|
_assetsForStatus(
|
||||||
|
status,
|
||||||
|
roomId: normalizedRoomId,
|
||||||
|
generation: generation,
|
||||||
|
includeNextLevel: true,
|
||||||
|
includeCurrentRewardCovers: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void forcePreloadCurrentLaunchAssets({
|
||||||
|
required String roomId,
|
||||||
|
required SCRoomRocketStatusRes status,
|
||||||
|
}) {
|
||||||
|
final normalizedRoomId = roomId.trim();
|
||||||
|
if (normalizedRoomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final generation = _syncRoom(normalizedRoomId);
|
||||||
|
_enqueueAssets(
|
||||||
|
_assetsForStatus(
|
||||||
|
status,
|
||||||
|
roomId: normalizedRoomId,
|
||||||
|
generation: generation,
|
||||||
|
includeNextLevel: false,
|
||||||
|
includeCurrentRewardCovers: true,
|
||||||
|
onlyCurrentLaunchAssets: true,
|
||||||
|
),
|
||||||
|
priority: true,
|
||||||
|
forceRetry: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cancelCurrentRoom() {
|
||||||
|
_generation += 1;
|
||||||
|
_activeRoomId = '';
|
||||||
|
_queue.clear();
|
||||||
|
_scheduledKeys.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool shouldForceCurrentLaunchPreload(SCRoomRocketStatusRes status) {
|
||||||
|
return _statusPercent(status) >= _forceThreshold(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _syncRoom(String roomId) {
|
||||||
|
if (_activeRoomId != roomId) {
|
||||||
|
_activeRoomId = roomId;
|
||||||
|
_generation += 1;
|
||||||
|
_queue.clear();
|
||||||
|
_scheduledKeys.clear();
|
||||||
|
}
|
||||||
|
return _generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isCurrentRoom(String roomId, int generation) {
|
||||||
|
return _activeRoomId == roomId &&
|
||||||
|
_generation == generation &&
|
||||||
|
_isAppResumed();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isAppResumed() {
|
||||||
|
final state = WidgetsBinding.instance.lifecycleState;
|
||||||
|
return state == null || state == AppLifecycleState.resumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _enqueueAssets(
|
||||||
|
List<_RoomRocketPreloadAsset> assets, {
|
||||||
|
bool priority = false,
|
||||||
|
bool forceRetry = false,
|
||||||
|
}) {
|
||||||
|
if (assets.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final normalizedAssets = priority ? assets.reversed : assets;
|
||||||
|
for (final asset in normalizedAssets) {
|
||||||
|
if (_networkUri(asset.resource) == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (forceRetry) {
|
||||||
|
_queue.removeWhere((item) => item.key == asset.key);
|
||||||
|
_scheduledKeys.remove(asset.key);
|
||||||
|
}
|
||||||
|
if (!_scheduledKeys.add(asset.key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (priority) {
|
||||||
|
_queue.addFirst(asset);
|
||||||
|
} else {
|
||||||
|
_queue.addLast(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pump() {
|
||||||
|
if (!_isAppResumed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (_running < _roomRocketAssetPreloadMaxConcurrent &&
|
||||||
|
_queue.isNotEmpty) {
|
||||||
|
final asset = _queue.removeFirst();
|
||||||
|
if (!_isCurrentRoom(asset.roomId, asset.generation)) {
|
||||||
|
_scheduledKeys.remove(asset.key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_running += 1;
|
||||||
|
unawaited(
|
||||||
|
_preloadAsset(asset)
|
||||||
|
.then((ok) {
|
||||||
|
if (!ok) {
|
||||||
|
_scheduledKeys.remove(asset.key);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.whenComplete(() {
|
||||||
|
_running = (_running - 1).clamp(0, 999).toInt();
|
||||||
|
_pump();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> _preloadAsset(_RoomRocketPreloadAsset asset) async {
|
||||||
|
if (!_isCurrentRoom(asset.roomId, asset.generation)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final uri = _networkUri(asset.resource);
|
||||||
|
if (uri == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final url = uri.toString();
|
||||||
|
try {
|
||||||
|
if (asset.kind == _RoomRocketPreloadKind.pag) {
|
||||||
|
await FileCacheManager.getInstance()
|
||||||
|
.getFile(url: url, headers: buildNetworkImageHeaders(url))
|
||||||
|
.timeout(_roomRocketAssetPreloadTimeout);
|
||||||
|
} else {
|
||||||
|
await DefaultCacheManager()
|
||||||
|
.getSingleFile(url, headers: buildNetworkImageHeaders(url))
|
||||||
|
.timeout(_roomRocketAssetPreloadTimeout);
|
||||||
|
}
|
||||||
|
_debug('preloaded ${asset.kind.name} ${_shortResource(url)}');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
_debug(
|
||||||
|
'preload failed ${asset.kind.name} ${_shortResource(url)}: $error',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<_RoomRocketPreloadAsset> _assetsForStatus(
|
||||||
|
SCRoomRocketStatusRes status, {
|
||||||
|
required String roomId,
|
||||||
|
required int generation,
|
||||||
|
required bool includeNextLevel,
|
||||||
|
required bool includeCurrentRewardCovers,
|
||||||
|
bool onlyCurrentLaunchAssets = false,
|
||||||
|
}) {
|
||||||
|
final currentLevel = _currentLevel(status);
|
||||||
|
final result = <_RoomRocketPreloadAsset>[];
|
||||||
|
final current = _levelFor(status, currentLevel);
|
||||||
|
if (current != null) {
|
||||||
|
if (!onlyCurrentLaunchAssets) {
|
||||||
|
_addImage(result, current.rocketIconUrl, roomId, generation);
|
||||||
|
}
|
||||||
|
_addPag(result, current.rocketAnimationUrl, roomId, generation);
|
||||||
|
}
|
||||||
|
if (!onlyCurrentLaunchAssets && includeNextLevel) {
|
||||||
|
final next = _levelFor(status, currentLevel + 1);
|
||||||
|
if (next != null) {
|
||||||
|
_addImage(result, next.rocketIconUrl, roomId, generation);
|
||||||
|
_addPag(result, next.rocketAnimationUrl, roomId, generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeCurrentRewardCovers) {
|
||||||
|
final preview = status.rewardPreviewForLevel(currentLevel);
|
||||||
|
for (final reward in preview?.all ?? const <SCRoomRocketRewardRes>[]) {
|
||||||
|
_addImage(result, reward.rewardCover, roomId, generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _dedupeAssets(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addImage(
|
||||||
|
List<_RoomRocketPreloadAsset> result,
|
||||||
|
String resource,
|
||||||
|
String roomId,
|
||||||
|
int generation,
|
||||||
|
) {
|
||||||
|
final value = resource.trim();
|
||||||
|
if (value.isEmpty || RoomRocketPagEffectOverlay.isPag(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.add(
|
||||||
|
_RoomRocketPreloadAsset(
|
||||||
|
resource: value,
|
||||||
|
kind: _RoomRocketPreloadKind.image,
|
||||||
|
roomId: roomId,
|
||||||
|
generation: generation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addPag(
|
||||||
|
List<_RoomRocketPreloadAsset> result,
|
||||||
|
String resource,
|
||||||
|
String roomId,
|
||||||
|
int generation,
|
||||||
|
) {
|
||||||
|
final value = resource.trim();
|
||||||
|
if (!RoomRocketPagEffectOverlay.isPag(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.add(
|
||||||
|
_RoomRocketPreloadAsset(
|
||||||
|
resource: value,
|
||||||
|
kind: _RoomRocketPreloadKind.pag,
|
||||||
|
roomId: roomId,
|
||||||
|
generation: generation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<_RoomRocketPreloadAsset> _dedupeAssets(
|
||||||
|
List<_RoomRocketPreloadAsset> assets,
|
||||||
|
) {
|
||||||
|
final seen = <String>{};
|
||||||
|
final result = <_RoomRocketPreloadAsset>[];
|
||||||
|
for (final asset in assets) {
|
||||||
|
final key = asset.key;
|
||||||
|
if (!seen.add(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.add(asset);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _currentLevel(SCRoomRocketStatusRes status) {
|
||||||
|
return (status.currentLevel ?? status.level?.toInt() ?? 1)
|
||||||
|
.clamp(1, 99)
|
||||||
|
.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRoomRocketLevelRes? _levelFor(SCRoomRocketStatusRes status, int level) {
|
||||||
|
final normalizedLevel = level.clamp(1, 99).toInt();
|
||||||
|
for (final item in status.levels ?? const <SCRoomRocketLevelRes>[]) {
|
||||||
|
if (item.level == normalizedLevel) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _statusPercent(SCRoomRocketStatusRes status) {
|
||||||
|
final value = status.displayPercent ?? status.energyPercent;
|
||||||
|
if (value != null) {
|
||||||
|
final raw = value.toDouble();
|
||||||
|
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||||
|
return percent.clamp(0, 100).toDouble();
|
||||||
|
}
|
||||||
|
final currentEnergy = status.currentEnergy ?? 0;
|
||||||
|
final maxEnergy = status.maxEnergy ?? status.needEnergy ?? 0;
|
||||||
|
if (maxEnergy <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _forceThreshold(SCRoomRocketStatusRes status) {
|
||||||
|
final level = _levelFor(status, _currentLevel(status));
|
||||||
|
final threshold = level?.shakeThresholdPercent.toDouble();
|
||||||
|
if (threshold == null || threshold <= 0 || threshold > 100) {
|
||||||
|
return _roomRocketAssetPreloadFallbackThreshold;
|
||||||
|
}
|
||||||
|
return threshold.clamp(80, 99.5).toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri? _networkUri(String resource) {
|
||||||
|
final normalized = normalizeImageResourceUrl(resource.trim());
|
||||||
|
if (normalized.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final direct = Uri.tryParse(normalized);
|
||||||
|
if (_isHttpUri(direct)) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
final encoded = Uri.tryParse(Uri.encodeFull(normalized));
|
||||||
|
if (_isHttpUri(encoded)) {
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isHttpUri(Uri? uri) {
|
||||||
|
if (uri == null || !uri.hasScheme || uri.host.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final scheme = uri.scheme.toLowerCase();
|
||||||
|
return scheme == 'http' || scheme == 'https';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _shortResource(String value) {
|
||||||
|
final text = value.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||||
|
if (text.length <= 120) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return '${text.substring(0, 117)}...';
|
||||||
|
}
|
||||||
|
|
||||||
|
void _debug(String message) {
|
||||||
|
if (!kDebugMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[RoomRocketAssetPreloader] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
List<String> debugRoomRocketAssetPreloadResources(
|
||||||
|
SCRoomRocketStatusRes status, {
|
||||||
|
bool includeNextLevel = true,
|
||||||
|
bool includeCurrentRewardCovers = true,
|
||||||
|
bool onlyCurrentLaunchAssets = false,
|
||||||
|
}) {
|
||||||
|
return _assetsForStatus(
|
||||||
|
status,
|
||||||
|
roomId: 'debug-room',
|
||||||
|
generation: 1,
|
||||||
|
includeNextLevel: includeNextLevel,
|
||||||
|
includeCurrentRewardCovers: includeCurrentRewardCovers,
|
||||||
|
onlyCurrentLaunchAssets: onlyCurrentLaunchAssets,
|
||||||
|
).map((item) => item.resource).toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
bool debugShouldForceRoomRocketAssetPreload(SCRoomRocketStatusRes status) {
|
||||||
|
return RoomRocketAssetPreloader.shouldForceCurrentLaunchPreload(status);
|
||||||
|
}
|
||||||
@ -88,7 +88,13 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
if (useInitialPopupRecords) {
|
if (useInitialPopupRecords) {
|
||||||
_usedInitialPopupRecords = true;
|
_usedInitialPopupRecords = true;
|
||||||
}
|
}
|
||||||
final nextRoomRecords = await _loadRoomRewardRecords(requestCursor);
|
final launchNo = _launchNoFromRecords(
|
||||||
|
nextPopupRecords.isNotEmpty ? nextPopupRecords : _popupRecords,
|
||||||
|
);
|
||||||
|
final nextRoomRecords = await _loadRoomRewardRecords(
|
||||||
|
requestCursor,
|
||||||
|
launchNo: launchNo,
|
||||||
|
);
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -152,8 +158,9 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SCRoomRocketRewardRecordRes>> _loadRoomRewardRecords(
|
Future<List<SCRoomRocketRewardRecordRes>> _loadRoomRewardRecords(
|
||||||
int cursor,
|
int cursor, {
|
||||||
) async {
|
String launchNo = '',
|
||||||
|
}) async {
|
||||||
if (widget.roomId.trim().isEmpty) {
|
if (widget.roomId.trim().isEmpty) {
|
||||||
return const <SCRoomRocketRewardRecordRes>[];
|
return const <SCRoomRocketRewardRecordRes>[];
|
||||||
}
|
}
|
||||||
@ -166,6 +173,8 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
widget.roomId,
|
widget.roomId,
|
||||||
cursor: cursor,
|
cursor: cursor,
|
||||||
limit: _rewardDialogPageSize,
|
limit: _rewardDialogPageSize,
|
||||||
|
launchNo: launchNo,
|
||||||
|
roomScope: true,
|
||||||
);
|
);
|
||||||
return res.records;
|
return res.records;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -208,6 +217,16 @@ class _RoomRocketRewardDialogLoaderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _launchNoFromRecords(List<SCRoomRocketRewardRecordRes> records) {
|
||||||
|
for (final record in records) {
|
||||||
|
final launchNo = record.launchNo.trim();
|
||||||
|
if (launchNo.isNotEmpty) {
|
||||||
|
return launchNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
void _replaceUniqueRewardRecords(
|
void _replaceUniqueRewardRecords(
|
||||||
List<SCRoomRocketRewardRecordRes> target,
|
List<SCRoomRocketRewardRecordRes> target,
|
||||||
List<SCRoomRocketRewardRecordRes> records,
|
List<SCRoomRocketRewardRecordRes> records,
|
||||||
|
|||||||
108
test/room_rocket_asset_preloader_test.dart
Normal file
108
test/room_rocket_asset_preloader_test.dart
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_api_res.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_asset_preloader.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('room rocket asset preloader', () {
|
||||||
|
test('collects current and next level disk preload resources', () {
|
||||||
|
final resources = debugRoomRocketAssetPreloadResources(_status());
|
||||||
|
|
||||||
|
expect(resources, [
|
||||||
|
'https://cdn.example.com/rocket-l2.png',
|
||||||
|
'https://cdn.example.com/rocket-l2.pag',
|
||||||
|
'https://cdn.example.com/rocket-l3.png',
|
||||||
|
'https://cdn.example.com/rocket-l3.pag',
|
||||||
|
'https://cdn.example.com/reward-top1.png',
|
||||||
|
'https://cdn.example.com/reward-in-room.png',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('force mode only collects current launch pag and reward covers', () {
|
||||||
|
final resources = debugRoomRocketAssetPreloadResources(
|
||||||
|
_status(),
|
||||||
|
includeNextLevel: false,
|
||||||
|
onlyCurrentLaunchAssets: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(resources, [
|
||||||
|
'https://cdn.example.com/rocket-l2.pag',
|
||||||
|
'https://cdn.example.com/reward-top1.png',
|
||||||
|
'https://cdn.example.com/reward-in-room.png',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses shake threshold to decide force preload', () {
|
||||||
|
expect(
|
||||||
|
debugShouldForceRoomRocketAssetPreload(_status(percent: 94)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
debugShouldForceRoomRocketAssetPreload(_status(percent: 95)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRoomRocketStatusRes _status({num percent = 95}) {
|
||||||
|
return SCRoomRocketStatusRes(
|
||||||
|
roomId: 'room-1',
|
||||||
|
currentLevel: 2,
|
||||||
|
displayPercent: percent,
|
||||||
|
levels: [
|
||||||
|
_level(
|
||||||
|
level: 2,
|
||||||
|
icon: 'https://cdn.example.com/rocket-l2.png',
|
||||||
|
animation: 'https://cdn.example.com/rocket-l2.pag',
|
||||||
|
rewardPreview: SCRoomRocketRewardPreviewRes(
|
||||||
|
top1: [
|
||||||
|
_reward('https://cdn.example.com/reward-top1.png'),
|
||||||
|
_reward('https://cdn.example.com/reward-top1.png'),
|
||||||
|
],
|
||||||
|
ignite: const <SCRoomRocketRewardRes>[],
|
||||||
|
inRoom: [_reward('https://cdn.example.com/reward-in-room.png')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_level(
|
||||||
|
level: 3,
|
||||||
|
icon: 'https://cdn.example.com/rocket-l3.png',
|
||||||
|
animation: 'https://cdn.example.com/rocket-l3.pag',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRoomRocketLevelRes _level({
|
||||||
|
required int level,
|
||||||
|
required String icon,
|
||||||
|
required String animation,
|
||||||
|
SCRoomRocketRewardPreviewRes? rewardPreview,
|
||||||
|
}) {
|
||||||
|
return SCRoomRocketLevelRes(
|
||||||
|
id: level.toString(),
|
||||||
|
level: level,
|
||||||
|
needEnergy: level * 100,
|
||||||
|
rocketIconUrl: icon,
|
||||||
|
rocketPreviewUrl: '',
|
||||||
|
rocketAnimationUrl: animation,
|
||||||
|
progressBarUrl: '',
|
||||||
|
shakeThresholdPercent: 95,
|
||||||
|
enabled: true,
|
||||||
|
sort: level,
|
||||||
|
rewardPreview: rewardPreview,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRoomRocketRewardRes _reward(String cover) {
|
||||||
|
return SCRoomRocketRewardRes(
|
||||||
|
rewardScene: 'TOP1',
|
||||||
|
rewardType: 'GOLD',
|
||||||
|
rewardItemId: '',
|
||||||
|
rewardName: 'Yumi',
|
||||||
|
rewardCover: cover,
|
||||||
|
rocketIconUrl: '',
|
||||||
|
rewardAmount: 1000,
|
||||||
|
expireDays: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user