609 lines
18 KiB
Dart
609 lines
18 KiB
Dart
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<CountryMode> countryModeList = [];
|
|
final Map<String, List<Country>> _countryMap = {};
|
|
final Map<String, Country> _countryByNameMap = {};
|
|
bool _isFetchingCountryList = false;
|
|
|
|
///礼物
|
|
List<SocialChatGiftRes> giftResList = [];
|
|
final Map<String, SocialChatGiftRes> _giftByIdMap = {};
|
|
final Map<String, SocialChatGiftRes> _giftByStandardIdMap = {};
|
|
final Set<String> _warmedGiftCoverUrls = <String>{};
|
|
final Set<String> _warmingGiftCoverUrls = <String>{};
|
|
bool _isFetchingGiftList = false;
|
|
bool _hasGiftListLoaded = false;
|
|
bool _isFetchingGiftBackpack = false;
|
|
bool _hasGiftBackpackLoaded = false;
|
|
bool _isFetchingFirstRechargeRewardHome = false;
|
|
bool _hasFirstRechargeRewardHomeLoaded = false;
|
|
SCFirstRechargeRewardHomeRes? _firstRechargeRewardHome;
|
|
|
|
Map<String, List<SocialChatGiftRes>> giftByTab = {};
|
|
Map<String, List<SocialChatGiftRes>> activityGiftByTab = {};
|
|
List<SocialChatGiftBackpackRes> giftBackpackResList = [];
|
|
|
|
///选中的国家
|
|
Country? selectCountryInfo;
|
|
|
|
List<SocialChatGiftRes> 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<SCFirstRechargeRewardHomeRes?> 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<void> 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 <Country>[]) {
|
|
final prefix = c.aliasName?.substring(0, 1) ?? "";
|
|
if (prefix.isNotEmpty) {
|
|
final countryList = _countryMap[prefix] ?? <Country>[];
|
|
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<void> 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<void> warmGiftPageCovers(
|
|
String type, {
|
|
required int pageIndex,
|
|
int pageCount = 1,
|
|
int pageSize = 8,
|
|
}) async {
|
|
final gifts = giftByTab[type] ?? <SocialChatGiftRes>[];
|
|
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<void> warmGiftCoverImages(
|
|
Iterable<SocialChatGiftRes> gifts, {
|
|
int maxConcurrency = 4,
|
|
}) async {
|
|
final pending = <Future<void>>[];
|
|
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<void> 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 = <SocialChatGiftRes>[];
|
|
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<SocialChatGiftRes> giftResList) {
|
|
final scheduledPaths = <String>{};
|
|
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<SCIndexBannerRes> homeBanners = [];
|
|
List<SCIndexBannerRes> exploreBanners = [];
|
|
List<SCIndexBannerRes> roomBanners = [];
|
|
List<SCIndexBannerRes> gameBanners = [];
|
|
|
|
///首页banner
|
|
Future<void> 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<String, List<Emojis>> emojiByTab = {};
|
|
List<SCRoomEmojiRes> 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<void> 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<void> appLeaderboard() async {
|
|
try {
|
|
appLeaderResult = await SCChatRoomRepository().appLeaderboard();
|
|
notifyListeners();
|
|
} catch (_) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _roomEmojiDebugLog(String message) {
|
|
debugPrint('[RoomEmoji] $message');
|
|
}
|
|
|
|
int _countRoomEmojiItems(List<SCRoomEmojiRes> groups) {
|
|
return groups.fold<int>(
|
|
0,
|
|
(total, group) => total + (group.emojis?.length ?? 0),
|
|
);
|
|
}
|
|
|
|
String _roomEmojiGroupSample(List<SCRoomEmojiRes> groups) {
|
|
if (groups.isEmpty) {
|
|
return '[]';
|
|
}
|
|
return groups
|
|
.take(3)
|
|
.map((group) {
|
|
final emojis = group.emojis ?? const <Emojis>[];
|
|
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(', ');
|
|
}
|