自动释放资源
This commit is contained in:
parent
a122935747
commit
539edffcfe
@ -10,6 +10,7 @@ class SCRoomRocketLevelRes {
|
||||
required this.shakeThresholdPercent,
|
||||
required this.enabled,
|
||||
required this.sort,
|
||||
this.rewards = const <SCRoomRocketRewardRes>[],
|
||||
this.rewardPreview,
|
||||
this.rocketKings = const <SCRoomRocketKingUserRes>[],
|
||||
});
|
||||
@ -194,6 +195,7 @@ class SCRoomRocketLevelRes {
|
||||
95,
|
||||
enabled: _boolValue(data['enabled'], fallback: true),
|
||||
sort: _intValue(data['sort']),
|
||||
rewards: SCRoomRocketRewardPreviewRes._rewardList(data['rewards']),
|
||||
rewardPreview: SCRoomRocketRewardPreviewRes.tryFromJson(
|
||||
_firstPayload([
|
||||
data['rewardPreview'],
|
||||
@ -241,6 +243,7 @@ class SCRoomRocketLevelRes {
|
||||
final num shakeThresholdPercent;
|
||||
final bool enabled;
|
||||
final int sort;
|
||||
final List<SCRoomRocketRewardRes> rewards;
|
||||
final SCRoomRocketRewardPreviewRes? rewardPreview;
|
||||
final List<SCRoomRocketKingUserRes> rocketKings;
|
||||
|
||||
@ -256,6 +259,7 @@ class SCRoomRocketLevelRes {
|
||||
'shakeThresholdPercent': shakeThresholdPercent,
|
||||
'enabled': enabled,
|
||||
'sort': sort,
|
||||
'rewards': rewards.map((item) => item.toJson()).toList(),
|
||||
'rewardPreview':
|
||||
rewardPreview == null
|
||||
? null
|
||||
@ -270,6 +274,71 @@ class SCRoomRocketLevelRes {
|
||||
'rocketKings': rocketKings.map((item) => item.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
static List<SCRoomRocketLevelRes> listFromJson(dynamic value) {
|
||||
if (value is! Iterable || value is String) {
|
||||
return const <SCRoomRocketLevelRes>[];
|
||||
}
|
||||
return value.map(SCRoomRocketLevelRes.fromJson).toList(growable: false);
|
||||
}
|
||||
}
|
||||
|
||||
class SCRoomRocketLevelContentsRes {
|
||||
SCRoomRocketLevelContentsRes({
|
||||
required this.configured,
|
||||
required this.sysOrigin,
|
||||
required this.enabled,
|
||||
required this.timezone,
|
||||
required this.maxLevel,
|
||||
required this.levels,
|
||||
required this.updateTime,
|
||||
});
|
||||
|
||||
factory SCRoomRocketLevelContentsRes.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
return SCRoomRocketLevelContentsRes(
|
||||
configured: _boolValue(data['configured']),
|
||||
sysOrigin: _stringValue(data['sysOrigin']),
|
||||
enabled: _boolValue(data['enabled']),
|
||||
timezone: _stringValue(data['timezone'] ?? data['timeZone']),
|
||||
maxLevel: _intValue(data['maxLevel']),
|
||||
levels: SCRoomRocketLevelRes.listFromJson(data['levels']),
|
||||
updateTime: _stringValue(data['updateTime']),
|
||||
);
|
||||
}
|
||||
|
||||
final bool configured;
|
||||
final String sysOrigin;
|
||||
final bool enabled;
|
||||
final String timezone;
|
||||
final int maxLevel;
|
||||
final List<SCRoomRocketLevelRes> levels;
|
||||
final String updateTime;
|
||||
|
||||
SCRoomRocketLevelRes? levelFor(int level) {
|
||||
for (final item in levels) {
|
||||
if (item.level == level) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SCRoomRocketRewardPreviewRes? rewardPreviewForLevel(int level) {
|
||||
return levelFor(level)?.rewardPreview;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'configured': configured,
|
||||
'sysOrigin': sysOrigin,
|
||||
'enabled': enabled,
|
||||
'timezone': timezone,
|
||||
'maxLevel': maxLevel,
|
||||
'levels': levels.map((item) => item.toJson()).toList(),
|
||||
'updateTime': updateTime,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class SCRoomRocketRewardRes {
|
||||
|
||||
@ -202,7 +202,10 @@ abstract class SocialChatRoomRepository {
|
||||
});
|
||||
|
||||
///查询房间火箭状态
|
||||
Future<SCRoomRocketStatusRes> rocketStatus(String roomId, {int? level});
|
||||
Future<SCRoomRocketStatusRes> rocketStatus(String roomId, {int? level});
|
||||
|
||||
///查询火箭等级内容
|
||||
Future<SCRoomRocketLevelContentsRes> roomRocketLevelContents();
|
||||
|
||||
///查询火箭王榜单
|
||||
Future<SCRoomRocketKingRes> roomRocketKing(
|
||||
|
||||
@ -1027,6 +1027,23 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
///go/app/voice-room/rocket/level-contents
|
||||
@override
|
||||
Future<SCRoomRocketLevelContentsRes> roomRocketLevelContents() async {
|
||||
const path = "/go/app/voice-room/rocket/level-contents";
|
||||
_rocketApiDebug('request GET $path');
|
||||
final result = await http.get<SCRoomRocketLevelContentsRes>(
|
||||
path,
|
||||
extra: {BaseNetworkClient.silentErrorToastKey: true},
|
||||
fromJson: (json) {
|
||||
_rocketApiDebug('GET $path', raw: json);
|
||||
return SCRoomRocketLevelContentsRes.fromJson(json);
|
||||
},
|
||||
);
|
||||
_rocketApiDebug('parsed $path', result: result);
|
||||
return result;
|
||||
}
|
||||
|
||||
///go/app/voice-room/rocket/king
|
||||
@override
|
||||
Future<SCRoomRocketKingRes> roomRocketKing(
|
||||
|
||||
@ -13,7 +13,18 @@ class RoomRocketApiMapper {
|
||||
static List<RoomRocketLevelItem>? levelsFromStatus(
|
||||
SCRoomRocketStatusRes? status,
|
||||
) {
|
||||
final levels = status?.levels;
|
||||
return levelsFromApiLevels(status?.levels);
|
||||
}
|
||||
|
||||
static List<RoomRocketLevelItem>? levelsFromLevelContents(
|
||||
SCRoomRocketLevelContentsRes? contents,
|
||||
) {
|
||||
return levelsFromApiLevels(contents?.levels);
|
||||
}
|
||||
|
||||
static List<RoomRocketLevelItem>? levelsFromApiLevels(
|
||||
List<SCRoomRocketLevelRes>? levels,
|
||||
) {
|
||||
if (levels == null || levels.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
@ -65,6 +76,26 @@ class RoomRocketApiMapper {
|
||||
return rewardGroupsFromPreview(preview);
|
||||
}
|
||||
|
||||
static RoomRocketRewardGroups? rewardGroupsFromLevelContents(
|
||||
SCRoomRocketLevelContentsRes? contents, {
|
||||
required int level,
|
||||
}) {
|
||||
return rewardGroupsFromPreview(contents?.rewardPreviewForLevel(level));
|
||||
}
|
||||
|
||||
static Map<int, RoomRocketRewardGroups> rewardGroupsByLevelFromLevelContents(
|
||||
SCRoomRocketLevelContentsRes? contents,
|
||||
) {
|
||||
final result = <int, RoomRocketRewardGroups>{};
|
||||
for (final item in contents?.levels ?? const <SCRoomRocketLevelRes>[]) {
|
||||
final groups = rewardGroupsFromPreview(item.rewardPreview);
|
||||
if (groups != null) {
|
||||
result[item.level] = groups;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static RoomRocketRewardGroups? rewardGroupsFromPreview(
|
||||
SCRoomRocketRewardPreviewRes? preview,
|
||||
) {
|
||||
|
||||
@ -350,6 +350,7 @@ class _RoomRocketDialogLoader extends StatefulWidget {
|
||||
|
||||
class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
||||
SCRoomRocketStatusRes? _status;
|
||||
SCRoomRocketLevelContentsRes? _levelContents;
|
||||
List<RoomRocketNoteSection>? _noteSections;
|
||||
Map<int, RoomRocketRewardGroups> _configRewardGroupsByLevel = {};
|
||||
final Map<int, RoomRocketRewardGroups> _statusRewardGroupsByLevel = {};
|
||||
@ -421,7 +422,31 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
||||
|
||||
Future<void> _loadConfigs() async {
|
||||
try {
|
||||
_rocketUiDebug('request enabled rocket configs');
|
||||
_rocketUiDebug('request rocket level contents');
|
||||
final contents = await SCChatRoomRepository().roomRocketLevelContents();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final groups = RoomRocketApiMapper.rewardGroupsByLevelFromLevelContents(
|
||||
contents,
|
||||
);
|
||||
setState(() {
|
||||
_levelContents = contents;
|
||||
_configRewardGroupsByLevel = groups;
|
||||
});
|
||||
_rocketUiDebug(
|
||||
'rocket level contents loaded levels=${contents.levels.length} '
|
||||
'rewardLevels=${groups.keys.toList()}',
|
||||
);
|
||||
} catch (error) {
|
||||
_rocketUiDebug('rocket level contents request failed: $error');
|
||||
unawaited(_loadLegacyConfigs());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadLegacyConfigs() async {
|
||||
try {
|
||||
_rocketUiDebug('request legacy enabled rocket configs');
|
||||
final configs = await SCChatRoomRepository().rocketConfigEnabled();
|
||||
if (!mounted) {
|
||||
return;
|
||||
@ -433,10 +458,10 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
||||
_configRewardGroupsByLevel = groups;
|
||||
});
|
||||
_rocketUiDebug(
|
||||
'rocket configs loaded rewardLevels=${groups.keys.toList()}',
|
||||
'legacy rocket configs loaded rewardLevels=${groups.keys.toList()}',
|
||||
);
|
||||
} catch (error) {
|
||||
_rocketUiDebug('rocket configs request failed: $error');
|
||||
_rocketUiDebug('legacy rocket configs request failed: $error');
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,10 +481,15 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
||||
final rewardGroups =
|
||||
selectedLevelNumber == currentLevelNumber
|
||||
? _mergeRewardGroups(rewardGroupsFromStatus, configRewardGroups)
|
||||
: rewardGroupsFromStatus ?? const RoomRocketRewardGroups();
|
||||
: rewardGroupsFromStatus ??
|
||||
configRewardGroups ??
|
||||
const RoomRocketRewardGroups();
|
||||
final levelItems =
|
||||
RoomRocketApiMapper.levelsFromLevelContents(_levelContents) ??
|
||||
RoomRocketApiMapper.levelsFromStatus(status);
|
||||
return RoomRocketDialog(
|
||||
status: status,
|
||||
levels: RoomRocketApiMapper.levelsFromStatus(status),
|
||||
levels: levelItems,
|
||||
selectedLevel: selectedLevel,
|
||||
onLevelSelected: _onLevelSelected,
|
||||
rewards:
|
||||
@ -555,6 +585,7 @@ class _RoomRocketDialogLoaderState extends State<_RoomRocketDialogLoader> {
|
||||
Future<void> _loadRewardForLevel(int level) async {
|
||||
final normalizedLevel = level.clamp(1, 99).toInt();
|
||||
if (_statusRewardGroupsByLevel.containsKey(normalizedLevel) ||
|
||||
_configRewardGroupsByLevel.containsKey(normalizedLevel) ||
|
||||
_loadingRewardLevels.contains(normalizedLevel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -493,10 +493,7 @@ class _RoomRocketPagViewState extends State<_RoomRocketPagView> {
|
||||
|
||||
void _scheduleMemoryRelease(String path) {
|
||||
_memoryReleaseTimer?.cancel();
|
||||
if (widget.memoryTtl <= Duration.zero) {
|
||||
return;
|
||||
}
|
||||
_memoryReleaseTimer = Timer(widget.memoryTtl, () {
|
||||
_memoryReleaseTimer = _schedulePagMemoryReleaseTimer(widget.memoryTtl, () {
|
||||
if (!mounted || widget.resource.trim() != path || _pagBytes == null) {
|
||||
return;
|
||||
}
|
||||
@ -572,6 +569,24 @@ Duration debugRoomRocketPagMemoryTtl() {
|
||||
return _pagMemoryTtl;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Timer? debugScheduleRoomRocketPagMemoryRelease({
|
||||
Duration memoryTtl = _pagMemoryTtl,
|
||||
required VoidCallback onRelease,
|
||||
}) {
|
||||
return _schedulePagMemoryReleaseTimer(memoryTtl, onRelease);
|
||||
}
|
||||
|
||||
Timer? _schedulePagMemoryReleaseTimer(
|
||||
Duration memoryTtl,
|
||||
VoidCallback onRelease,
|
||||
) {
|
||||
if (memoryTtl <= Duration.zero) {
|
||||
return null;
|
||||
}
|
||||
return Timer(memoryTtl, onRelease);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
String debugNormalizeRoomRocketPagResource(String resource) {
|
||||
return _normalizePagResource(resource);
|
||||
|
||||
@ -45,4 +45,17 @@ void main() {
|
||||
test('keeps pag bytes in memory for five minutes by default', () {
|
||||
expect(debugRoomRocketPagMemoryTtl(), const Duration(minutes: 5));
|
||||
});
|
||||
|
||||
test('releases pag bytes when the configured ttl expires', () async {
|
||||
var released = false;
|
||||
final timer = debugScheduleRoomRocketPagMemoryRelease(
|
||||
memoryTtl: const Duration(milliseconds: 10),
|
||||
onRelease: () => released = true,
|
||||
);
|
||||
|
||||
expect(released, isFalse);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 30));
|
||||
expect(released, isTrue);
|
||||
timer?.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
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_config_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_api_mapper.dart';
|
||||
@ -137,4 +138,74 @@ void main() {
|
||||
expect(groups[2]?.inRoom.single.imageUrl, 'cover-2');
|
||||
expect(groups[2]?.inRoom.single.expireDays, 7);
|
||||
});
|
||||
|
||||
test('parses rocket level contents response from go api', () {
|
||||
final contents = SCRoomRocketLevelContentsRes.fromJson({
|
||||
'configured': true,
|
||||
'sysOrigin': 'LIKEI',
|
||||
'enabled': true,
|
||||
'timezone': 'Asia/Riyadh',
|
||||
'maxLevel': 6,
|
||||
'levels': [
|
||||
{
|
||||
'level': 1,
|
||||
'needEnergy': 100,
|
||||
'rocketIconUrl': 'icon-1',
|
||||
'previewRocketUrl': 'preview-1',
|
||||
'rocketAnimationUrl': 'animation-1',
|
||||
'rewardPreview': {
|
||||
'top1': [
|
||||
{
|
||||
'rewardScene': 'TOP1',
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
'rewardName': 'Lv1 top1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
'level': 2,
|
||||
'needEnergy': 200,
|
||||
'rocketIconUrl': 'icon-2',
|
||||
'previewRocketUrl': 'preview-2',
|
||||
'rocketAnimationUrl': 'animation-2',
|
||||
'rewards': [
|
||||
{
|
||||
'rewardScene': 'IGNITE',
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 200,
|
||||
'rewardName': 'Lv2 ignite',
|
||||
},
|
||||
{
|
||||
'rewardScene': 'IN_ROOM',
|
||||
'rewardType': 'NONE',
|
||||
'rewardName': '轮空',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(contents.enabled, isTrue);
|
||||
expect(contents.levels, hasLength(2));
|
||||
expect(contents.levelFor(1)?.rocketIconUrl, 'icon-1');
|
||||
expect(contents.rewardPreviewForLevel(1)?.top1.single.rewardAmount, 100);
|
||||
expect(contents.rewardPreviewForLevel(2)?.ignite.single.rewardAmount, 200);
|
||||
expect(contents.levelFor(2)?.rewards, hasLength(2));
|
||||
|
||||
final levels = RoomRocketApiMapper.levelsFromLevelContents(contents);
|
||||
final allGroups = RoomRocketApiMapper.rewardGroupsByLevelFromLevelContents(
|
||||
contents,
|
||||
);
|
||||
final groups = RoomRocketApiMapper.rewardGroupsFromLevelContents(
|
||||
contents,
|
||||
level: 2,
|
||||
);
|
||||
|
||||
expect(levels?.first.imageUrl, 'icon-1');
|
||||
expect(allGroups[1]?.top1.single.amount, '100');
|
||||
expect(allGroups[2]?.setOff.single.amount, '200');
|
||||
expect(groups?.setOff.single.amount, '200');
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user