import 'dart:async'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart'; import 'package:yumi/shared/tools/sc_network_image_utils.dart'; import 'package:yumi/shared/data_sources/models/country_mode.dart'; import 'package:yumi/shared/business_logic/models/res/sc_banner_leaderboard_res.dart'; import 'package:yumi/shared/business_logic/models/res/country_res.dart'; import 'package:yumi/shared/business_logic/models/res/gift_backpack_res.dart'; import 'package:yumi/shared/business_logic/models/res/gift_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_first_recharge_reward_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_index_banner_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart'; import '../../shared/data_sources/models/enum/sc_banner_type.dart'; import '../../shared/data_sources/models/enum/sc_gift_type.dart'; class SCAppGeneralManager extends ChangeNotifier { static const String backpackGiftTab = 'backpack'; static const int _maxInitialFullScreenGiftPreloads = 4; List countryModeList = []; final Map> _countryMap = {}; final Map _countryByNameMap = {}; bool _isFetchingCountryList = false; ///礼物 List giftResList = []; final Map _giftByIdMap = {}; final Map _giftByStandardIdMap = {}; final Set _warmedGiftCoverUrls = {}; final Set _warmingGiftCoverUrls = {}; bool _isFetchingGiftList = false; bool _hasGiftListLoaded = false; bool _isFetchingGiftBackpack = false; bool _hasGiftBackpackLoaded = false; bool _isFetchingFirstRechargeRewardHome = false; bool _hasFirstRechargeRewardHomeLoaded = false; SCFirstRechargeRewardHomeRes? _firstRechargeRewardHome; Map> giftByTab = {}; Map> activityGiftByTab = {}; List giftBackpackResList = []; ///选中的国家 Country? selectCountryInfo; List activityList = []; SCAppGeneralManager() { fetchCountryList(); giftList(); // 使用默认参数 giftActivityList(); // giftBackpack(); // configLevel(); } bool get isGiftListLoading => _isFetchingGiftList && !_hasGiftListLoaded; bool get isGiftBackpackLoading => _isFetchingGiftBackpack && !_hasGiftBackpackLoaded; bool get shouldShowFirstRechargeReward => _firstRechargeRewardHome?.shouldShowRewardDialog ?? false; SCFirstRechargeRewardHomeRes? get firstRechargeRewardHome => _firstRechargeRewardHome; bool isGiftTabLoading(String type) { return type == backpackGiftTab ? isGiftBackpackLoading : isGiftListLoading; } Future refreshFirstRechargeRewardHome({ bool forceRefresh = false, }) async { if (_isFetchingFirstRechargeRewardHome) { return _firstRechargeRewardHome; } if (_hasFirstRechargeRewardHomeLoaded && !forceRefresh) { return _firstRechargeRewardHome; } _isFetchingFirstRechargeRewardHome = true; try { final home = await SCConfigRepositoryImp().firstRechargeRewardHome(); _firstRechargeRewardHome = home; _hasFirstRechargeRewardHomeLoaded = true; return home; } catch (_) { return _firstRechargeRewardHome; } finally { _isFetchingFirstRechargeRewardHome = false; notifyListeners(); } } ///加载国家列表 Future fetchCountryList() async { if (_isFetchingCountryList) { return; } if (countryModeList.isNotEmpty) { clearCountrySelection(); notifyListeners(); return; } _isFetchingCountryList = true; try { await Future.delayed(Duration(milliseconds: 550)); final result = await SCConfigRepositoryImp().loadCountry(); _countryMap.clear(); _countryByNameMap.clear(); countryModeList.clear(); for (final c in result.openCountry ?? const []) { final prefix = c.aliasName?.substring(0, 1) ?? ""; if (prefix.isNotEmpty) { final countryList = _countryMap[prefix] ?? []; countryList.add(c.copyWith()); _countryMap[prefix] = countryList; } if ((c.countryName ?? "").isNotEmpty) { _countryByNameMap[c.countryName!] = c; } } _countryMap.forEach((k, v) { countryModeList.add(CountryMode(k, v)); }); ///排序 countryModeList.sort((a, b) => a.prefix.compareTo(b.prefix)); notifyListeners(); } catch (_) { notifyListeners(); } finally { _isFetchingCountryList = false; } } ///国家国家名称获取国家 Country? findCountryByName(String countryName) { return _countryByNameMap[countryName]; } ///选择国家 Country? chooseCountry(int subIndex, int cIndex) { var selectCm = countryModeList[subIndex].prefixCountrys[cIndex]; if (selectCountryInfo != null) { selectCountryInfo = null; } else { selectCountryInfo = selectCm; } return selectCountryInfo; } void updateCurrentCountry(Country? countryInfo) { selectCountryInfo = countryInfo; notifyListeners(); } void clearCountrySelection() { selectCountryInfo = null; } void openDrawer(BuildContext context) { Scaffold.of(context).openDrawer(); } void closeDrawer(BuildContext context) { Scaffold.of(context).closeDrawer(); } Future giftList({ bool includeCustomized = true, bool forceRefresh = false, }) async { if (_isFetchingGiftList) { return; } if (_hasGiftListLoaded && giftResList.isNotEmpty && !forceRefresh) { _rebuildGiftTabs(includeCustomized: includeCustomized); notifyListeners(); unawaited( warmGiftPageCovers("ALL", pageIndex: 0, pageCount: 2, pageSize: 8), ); return; } _isFetchingGiftList = true; if (!_hasGiftListLoaded) { notifyListeners(); } try { giftResList = await SCChatRoomRepository().giftList(); _hasGiftListLoaded = true; _rebuildGiftTabs(includeCustomized: includeCustomized); notifyListeners(); ///先去预下载 downLoad(giftResList); unawaited( warmGiftPageCovers("ALL", pageIndex: 0, pageCount: 2, pageSize: 8), ); } catch (_) { return; } finally { _isFetchingGiftList = false; notifyListeners(); } } String _displayGiftTab(String? giftTab) { return giftTab == "EXCLUSIVE" ? "CUSTOMIZED" : (giftTab ?? ""); } void _rebuildGiftTabs({required bool includeCustomized}) { giftByTab.clear(); _giftByIdMap.clear(); _giftByStandardIdMap.clear(); for (var gift in giftResList) { final tab = _displayGiftTab(gift.giftTab); var gmap = giftByTab[tab]; gmap ??= []; gmap.add(gift); giftByTab[tab] = gmap; var gAllMap = giftByTab["ALL"]; gAllMap ??= []; if (tab != "NSCIONAL_FLAG" && tab != "ACTIVITY" && tab != "LUCKY_GIFT" && tab != "CP" && tab != "CUSTOMIZED" && tab != "MAGIC") { gAllMap.add(gift); } giftByTab["ALL"] = gAllMap; _giftByIdMap[gift.id!] = gift; final standardId = (gift.standardId ?? "").trim(); if (standardId.isNotEmpty && standardId != "0") { _giftByStandardIdMap[standardId] = gift; } } if (includeCustomized) { giftByTab["CUSTOMIZED"] ??= []; ///定制礼物需要一个占位符,显示规则的 giftByTab["CUSTOMIZED"]?.insert(0, SocialChatGiftRes(id: "-1000")); } else { giftByTab.remove("CUSTOMIZED"); } _mergeBackpackGiftTab(); } Future warmGiftPageCovers( String type, { required int pageIndex, int pageCount = 1, int pageSize = 8, }) async { final gifts = giftByTab[type] ?? []; if (gifts.isEmpty) { return; } final start = pageIndex * pageSize; if (start >= gifts.length) { return; } final end = math.min(gifts.length, start + (pageSize * pageCount)); await warmGiftCoverImages(gifts.sublist(start, end)); } Future warmGiftCoverImages( Iterable gifts, { int maxConcurrency = 4, }) async { final pending = >[]; for (final gift in gifts) { final coverUrl = gift.giftPhoto ?? ""; if (coverUrl.isEmpty || _warmedGiftCoverUrls.contains(coverUrl) || !_warmingGiftCoverUrls.add(coverUrl)) { continue; } pending.add(() async { final warmed = await warmNetworkImage(coverUrl); _warmingGiftCoverUrls.remove(coverUrl); if (warmed) { _warmedGiftCoverUrls.add(coverUrl); } }()); if (pending.length >= maxConcurrency) { await Future.wait(pending); pending.clear(); } } if (pending.isNotEmpty) { await Future.wait(pending); } } void giftActivityList() async { try { activityList.clear(); activityList = await SCChatRoomRepository().giftActivityList(); activityGiftByTab.clear(); for (var gift in activityList) { var gmap = activityGiftByTab["${gift.activityId}"]; gmap ??= []; gmap.add(gift); activityGiftByTab["${gift.activityId}"] = gmap; } notifyListeners(); } catch (_) { return; } } ///礼物背包 Future giftBackpack({bool forceRefresh = false}) async { if (_isFetchingGiftBackpack) { return; } if (_hasGiftBackpackLoaded && !forceRefresh) { _mergeBackpackGiftTab(); notifyListeners(); unawaited( warmGiftPageCovers( backpackGiftTab, pageIndex: 0, pageCount: 2, pageSize: 8, ), ); return; } _isFetchingGiftBackpack = true; if (!_hasGiftBackpackLoaded) { notifyListeners(); } try { giftBackpackResList = await SCChatRoomRepository().giftBackpack(); _hasGiftBackpackLoaded = true; _mergeBackpackGiftTab(); notifyListeners(); unawaited( warmGiftPageCovers( backpackGiftTab, pageIndex: 0, pageCount: 2, pageSize: 8, ), ); } catch (_) { return; } finally { _isFetchingGiftBackpack = false; notifyListeners(); } } void _mergeBackpackGiftTab() { final backpackGifts = []; for (final item in giftBackpackResList) { final giftConfig = item.giftConfig; if (giftConfig == null) { continue; } backpackGifts.add( giftConfig.copyWith( giftTab: backpackGiftTab, quantity: _formatBackpackQuantity(item.quantity), backpackId: item.id, backpackQuantity: item.quantity, ), ); } giftByTab[backpackGiftTab] = backpackGifts; } String _formatBackpackQuantity(num? quantity) { if (quantity == null) { return '0'; } if (quantity % 1 == 0) { return quantity.toInt().toString(); } return quantity.toString(); } ///礼物id获取礼物 SocialChatGiftRes? getGiftById(String id) { return _giftByIdMap[id]; } SocialChatGiftRes? getGiftByStandardId(String standardId) { return _giftByStandardIdMap[standardId]; } SocialChatGiftRes? getGiftByIdOrStandardId(String id) { final normalizedId = id.trim(); if (normalizedId.isEmpty) { return null; } return _giftByIdMap[normalizedId] ?? _giftByStandardIdMap[normalizedId]; } void downLoad(List giftResList) { final scheduledPaths = {}; var scheduledCount = 0; for (var gift in giftResList) { final giftSourceUrl = gift.giftSourceUrl ?? ""; if (giftSourceUrl.isEmpty) { continue; } if (!scGiftHasFullScreenEffect(gift.special)) { continue; } if (!scheduledPaths.add(giftSourceUrl)) { continue; } SCGiftVapSvgaManager().preload(giftSourceUrl); scheduledCount += 1; if (scheduledCount >= _maxInitialFullScreenGiftPreloads) { break; } } } List homeBanners = []; List exploreBanners = []; List roomBanners = []; List gameBanners = []; ///首页banner Future loadMainBanner() async { try { var banners = await SCConfigRepositoryImp().getBanner(); homeBanners.clear(); roomBanners.clear(); gameBanners.clear(); exploreBanners.clear(); for (var v in banners) { if ((v.displayPosition ?? "").contains( SCBannerType.EXPLORE_PAGE.name, )) { exploreBanners.add(v); } if ((v.displayPosition ?? "").contains(SCBannerType.ROOM.name)) { roomBanners.add(v); } if ((v.displayPosition ?? "").contains(SCBannerType.HOME_ALERT.name)) { homeBanners.add(v); } if ((v.displayPosition ?? "").contains(SCBannerType.GAME.name)) { gameBanners.add(v); } } notifyListeners(); } catch (_) { return; } } Map> emojiByTab = {}; List roomEmojiGroups = []; bool _isFetchingRoomEmojis = false; bool _hasRoomEmojisLoaded = false; bool _roomEmojisLoadedFromAll = false; bool get isFetchingRoomEmojis => _isFetchingRoomEmojis; bool get hasRoomEmojisLoaded => _hasRoomEmojisLoaded; bool get roomEmojisLoadedFromAll => _roomEmojisLoadedFromAll; bool get hasRemoteRoomEmojis => roomEmojiGroups.any((group) => (group.emojis ?? const []).isNotEmpty); ///emoji表情 Future emojiAll({bool forceRefresh = false}) async { if (_isFetchingRoomEmojis) { _roomEmojiDebugLog('skip load: request already in flight'); return; } if (_hasRoomEmojisLoaded && !forceRefresh && _roomEmojisLoadedFromAll) { _roomEmojiDebugLog( 'skip load: cached groups=${roomEmojiGroups.length} ' 'tabs=${emojiByTab.length} source=/material/emoji/all', ); return; } if (_hasRoomEmojisLoaded && !forceRefresh && !_roomEmojisLoadedFromAll) { _roomEmojiDebugLog( 'reload: cached data was not loaded from temporary /all source', ); } _isFetchingRoomEmojis = true; _roomEmojiDebugLog('load start forceRefresh=$forceRefresh'); try { final roomEmojis = await SCChatRoomRepository().emojiAll(); _roomEmojiDebugLog( 'load response source=/material/emoji/all ' 'rawGroups=${roomEmojis.length} ' 'rawEmojiTotal=${_countRoomEmojiItems(roomEmojis)}', ); roomEmojiGroups = roomEmojis .where( (group) => group.have == true && (group.emojis ?? const []).isNotEmpty, ) .toList(); final filteredNoAccess = roomEmojis.where((group) => group.have != true).length; final filteredEmptyGroups = roomEmojis .where( (group) => group.have == true && (group.emojis ?? const []).isEmpty, ) .length; emojiByTab.clear(); for (final value in roomEmojiGroups) { final groupName = value.groupName?.trim().isNotEmpty == true ? value.groupName!.trim() : (value.groupCode ?? value.id ?? ""); if (groupName.isEmpty) { continue; } emojiByTab[groupName] = value.emojis ?? []; } _hasRoomEmojisLoaded = true; _roomEmojisLoadedFromAll = true; _roomEmojiDebugLog( 'load success visibleGroups=${roomEmojiGroups.length} ' 'filteredNoAccess=$filteredNoAccess ' 'filteredEmptyGroups=$filteredEmptyGroups ' 'tabs=${emojiByTab.length} ' 'visibleEmojiTotal=${_countRoomEmojiItems(roomEmojiGroups)} ' 'sample=${_roomEmojiGroupSample(roomEmojiGroups)}', ); } catch (error, stackTrace) { _roomEmojiDebugLog('load failed error=$error'); debugPrintStack( label: '[RoomEmoji] load stack', stackTrace: stackTrace, maxFrames: 8, ); return; } finally { _isFetchingRoomEmojis = false; notifyListeners(); } } ///加载等级资源 void configLevel() { SCConfigRepositoryImp().configLevel(); } SCBannerLeaderboardRes? appLeaderResult; ///查询榜单前三名 Future appLeaderboard() async { try { appLeaderResult = await SCChatRoomRepository().appLeaderboard(); notifyListeners(); } catch (_) { return; } } } void _roomEmojiDebugLog(String message) { debugPrint('[RoomEmoji] $message'); } int _countRoomEmojiItems(List groups) { return groups.fold( 0, (total, group) => total + (group.emojis?.length ?? 0), ); } String _roomEmojiGroupSample(List groups) { if (groups.isEmpty) { return '[]'; } return groups .take(3) .map((group) { final emojis = group.emojis ?? const []; final first = emojis.isNotEmpty ? emojis.first : null; return '{id=${group.id}, code=${group.groupCode}, name=${group.groupName}, ' 'have=${group.have}, amount=${group.amount}, count=${emojis.length}, ' 'firstType=${first?.sourceType}, firstSource=${first?.sourceUrl}, ' 'firstCover=${first?.coverUrl}}'; }) .join(', '); }