class CharacterPack { const CharacterPack({ required this.filename, required this.name, required this.characterType, required this.renderType, this.live2dModel, }); final String filename; final String name; final String characterType; final String renderType; final String? live2dModel; bool get isLive2d => renderType.toLowerCase() == 'live2d'; String get typeLabel { switch (characterType.toLowerCase()) { case 'human': return '数字人'; case 'singer': return '歌姬'; case 'live2d': return 'Live2D'; case 'pet': default: return '宠物'; } } IconDataLike get iconHint { switch (characterType.toLowerCase()) { case 'human': return IconDataLike.person; case 'singer': return IconDataLike.mic; case 'live2d': return IconDataLike.sparkle; default: return IconDataLike.pets; } } factory CharacterPack.fromJson(Map json) { final character = json['character']; final Map charMap = character is Map ? Map.from(character) : const {}; final filename = json['filename']?.toString() ?? ''; return CharacterPack( filename: filename, name: charMap['name']?.toString().isNotEmpty == true ? charMap['name'].toString() : filename, characterType: charMap['character_type']?.toString() ?? 'pet', renderType: charMap['render_type']?.toString() ?? 'video', live2dModel: charMap['live2d_model']?.toString(), ); } } /// Avoid importing Flutter material in pure model unit tests. enum IconDataLike { pets, person, mic, sparkle } class AvailableConfigs { const AvailableConfigs({ required this.configs, required this.active, }); final List configs; final String active; factory AvailableConfigs.fromJson(Map json) { final raw = json['configs']; final list = []; if (raw is List) { for (final item in raw) { if (item is Map) { list.add(CharacterPack.fromJson(item)); } else if (item is Map) { list.add(CharacterPack.fromJson(Map.from(item))); } } } return AvailableConfigs( configs: list, active: json['active']?.toString() ?? '', ); } }