chore: sync local changes

This commit is contained in:
NIGGER SLAYER 2026-04-16 19:23:54 +08:00
parent 526a498de6
commit 2eff091bf3
17 changed files with 1374 additions and 1257 deletions

View File

@ -2,9 +2,12 @@ import 'package:yumi/app/config/app_config.dart';
import 'package:yumi/app/config/business_logic_strategy.dart'; import 'package:yumi/app/config/business_logic_strategy.dart';
class SCGlobalConfig { class SCGlobalConfig {
static const Set<String> _knownLowPerformanceModels = {"redmi 14c"};
static String get apiHost => AppConfig.current.apiHost; static String get apiHost => AppConfig.current.apiHost;
static String get imgHost => AppConfig.current.imgHost; static String get imgHost => AppConfig.current.imgHost;
static String get privacyAgreementUrl => AppConfig.current.privacyAgreementUrl; static String get privacyAgreementUrl =>
AppConfig.current.privacyAgreementUrl;
static String get userAgreementUrl => AppConfig.current.userAgreementUrl; static String get userAgreementUrl => AppConfig.current.userAgreementUrl;
// URL从AppConfig获取 // URL从AppConfig获取
@ -18,8 +21,10 @@ class SCGlobalConfig {
static String get gamesKingUrl => AppConfig.current.gamesKingUrl; static String get gamesKingUrl => AppConfig.current.gamesKingUrl;
// Flavor调整 // Flavor调整
static String get appDownloadUrlGoogle => AppConfig.current.appDownloadUrlGoogle; static String get appDownloadUrlGoogle =>
static String get appDownloadUrlApple => AppConfig.current.appDownloadUrlApple; AppConfig.current.appDownloadUrlGoogle;
static String get appDownloadUrlApple =>
AppConfig.current.appDownloadUrlApple;
/// ///
static String lang = "en"; static String lang = "en";
@ -33,14 +38,17 @@ class SCGlobalConfig {
static String model = "SM-G9550"; static String model = "SM-G9550";
static String sysVersion = "9"; static String sysVersion = "9";
static int sdkInt = 28; static int sdkInt = 28;
static int processorCount = 0;
static bool isLowRamDevice = false;
static bool _isLowPerformanceDevice = false;
/// ///
static int maxSdkNoAnim = 27; static int maxSdkNoAnim = 27;
///SocialChat ///SocialChat
static String channel = "Google"; static String channel = "Google";
static String origin = "LIKEI"; static String origin = "LIKEI";
static String originChild = "LIKEI"; static String originChild = "LIKEI";
static String get tencentImAppid => AppConfig.current.tencentImAppid; static String get tencentImAppid => AppConfig.current.tencentImAppid;
static String get agoraRtcAppid => AppConfig.current.agoraRtcAppid; static String get agoraRtcAppid => AppConfig.current.agoraRtcAppid;
@ -48,54 +56,55 @@ class SCGlobalConfig {
static num get gameAppid => AppConfig.current.gameAppid; static num get gameAppid => AppConfig.current.gameAppid;
static String get gameAppChannel => AppConfig.current.gameAppChannel; static String get gameAppChannel => AppConfig.current.gameAppChannel;
///广 ///广
static String get bigBroadcastGroup => AppConfig.current.bigBroadcastGroup; static String get bigBroadcastGroup => AppConfig.current.bigBroadcastGroup;
static String get imAdmin => AppConfig.current.imAdmin; static String get imAdmin => AppConfig.current.imAdmin;
static const Set<String> _extraSystemUserIds = {"yuminotice"}; static const Set<String> _extraSystemUserIds = {"yuminotice"};
static String _normalizeConversationUserId(String conversationId) { static String _normalizeConversationUserId(String conversationId) {
return conversationId.startsWith("c2c_") return conversationId.startsWith("c2c_")
? conversationId.replaceFirst("c2c_", "") ? conversationId.replaceFirst("c2c_", "")
: conversationId; : conversationId;
} }
static Set<String> get systemUserIds { static Set<String> get systemUserIds {
return { return {
"administrator", "administrator",
_normalizeConversationUserId(imAdmin), _normalizeConversationUserId(imAdmin),
..._extraSystemUserIds, ..._extraSystemUserIds,
}.where((element) => element.isNotEmpty).toSet(); }.where((element) => element.isNotEmpty).toSet();
} }
static Set<String> get systemConversationIds { static Set<String> get systemConversationIds {
return { return {
"administrator", "administrator",
...systemUserIds ...systemUserIds
.where((element) => element != "administrator") .where((element) => element != "administrator")
.map((element) => "c2c_$element"), .map((element) => "c2c_$element"),
}; };
} }
static String get primarySystemUserId => _normalizeConversationUserId(imAdmin); static String get primarySystemUserId =>
static String get primarySystemConversationId => "c2c_$primarySystemUserId"; _normalizeConversationUserId(imAdmin);
static String get primarySystemConversationId => "c2c_$primarySystemUserId";
static bool isSystemConversationId(String? conversationId) {
if (conversationId == null || conversationId.isEmpty) { static bool isSystemConversationId(String? conversationId) {
return false; if (conversationId == null || conversationId.isEmpty) {
} return false;
return systemConversationIds.contains(conversationId); }
} return systemConversationIds.contains(conversationId);
}
static bool isSystemUserId(String? userId) {
if (userId == null || userId.isEmpty) { static bool isSystemUserId(String? userId) {
return false; if (userId == null || userId.isEmpty) {
} return false;
return systemUserIds.contains(userId); }
} return systemUserIds.contains(userId);
}
///
static String get wealthRankUrl => AppConfig.current.wealthRankUrl; ///
static String get wealthRankUrl => AppConfig.current.wealthRankUrl;
/// ///
static String get charmRankUrl => AppConfig.current.charmRankUrl; static String get charmRankUrl => AppConfig.current.charmRankUrl;
@ -119,17 +128,60 @@ class SCGlobalConfig {
/// ///
static bool _isEntryVehicleAnimation = true; static bool _isEntryVehicleAnimation = true;
static bool get isEntryVehicleAnimation => _isEntryVehicleAnimation; static bool get isEntryVehicleAnimation => _isEntryVehicleAnimation;
static set isEntryVehicleAnimation(bool value) => _isEntryVehicleAnimation = value; static set isEntryVehicleAnimation(bool value) =>
_isEntryVehicleAnimation = value;
/// ///
static bool _isFloatingAnimationInGlobal = true; static bool _isFloatingAnimationInGlobal = true;
static bool get isFloatingAnimationInGlobal => _isFloatingAnimationInGlobal; static bool get isFloatingAnimationInGlobal => _isFloatingAnimationInGlobal;
static set isFloatingAnimationInGlobal(bool value) => _isFloatingAnimationInGlobal = value; static set isFloatingAnimationInGlobal(bool value) =>
_isFloatingAnimationInGlobal = value;
/// ///
static bool _isLuckGiftSpecialEffects = true; static bool _isLuckGiftSpecialEffects = true;
static bool get isLuckGiftSpecialEffects => _isLuckGiftSpecialEffects; static bool get isLuckGiftSpecialEffects => _isLuckGiftSpecialEffects;
static set isLuckGiftSpecialEffects(bool value) => _isLuckGiftSpecialEffects = value; static set isLuckGiftSpecialEffects(bool value) =>
_isLuckGiftSpecialEffects = value;
static bool get isLowPerformanceDevice => _isLowPerformanceDevice;
static bool get allowsHighCostAnimations =>
!_isLowPerformanceDevice && sdkInt > maxSdkNoAnim;
static int get recommendedImageCacheBytes =>
_isLowPerformanceDevice ? 40 * 1024 * 1024 : 100 * 1024 * 1024;
static int get recommendedImageCacheEntries =>
_isLowPerformanceDevice ? 80 : 200;
static void applyDevicePerformanceProfile({
required bool isLowRamDevice,
required int processorCount,
}) {
final normalizedModel = model.toLowerCase().replaceAll('-', ' ');
final matchesKnownLowPerformanceModel = _knownLowPerformanceModels.any(
normalizedModel.contains,
);
SCGlobalConfig.isLowRamDevice = isLowRamDevice;
SCGlobalConfig.processorCount = processorCount;
_isLowPerformanceDevice =
isLowRamDevice ||
processorCount <= 6 ||
matchesKnownLowPerformanceModel;
resetVisualEffectSwitchesToRecommendedDefaults();
}
static bool clampVisualEffectPreference(bool enabled) {
return !_isLowPerformanceDevice && enabled;
}
static void resetVisualEffectSwitchesToRecommendedDefaults() {
final enabled = !_isLowPerformanceDevice;
isEntryVehicleAnimation = enabled;
isGiftSpecialEffects = enabled;
isFloatingAnimationInGlobal = enabled;
isLuckGiftSpecialEffects = enabled;
}
/// ///
static BusinessLogicStrategy get businessLogicStrategy { static BusinessLogicStrategy get businessLogicStrategy {

View File

@ -92,6 +92,14 @@ Future<void> _prepareAppShell() async {
SCKeybordUtil.init(); SCKeybordUtil.init();
// 使 // 使
await _initStore(); await _initStore();
await SCDeviceIdUtils.initDeviceinfo();
_configureImageCache();
}
void _configureImageCache() {
final imageCache = PaintingBinding.instance.imageCache;
imageCache.maximumSizeBytes = SCGlobalConfig.recommendedImageCacheBytes;
imageCache.maximumSize = SCGlobalConfig.recommendedImageCacheEntries;
} }
void _installErrorHandlers() { void _installErrorHandlers() {
@ -286,7 +294,6 @@ class _YumiApplicationState extends State<YumiApplication> {
// //
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_initRouter(); _initRouter();
SCDeviceIdUtils.initDeviceinfo();
unawaited(_initLink()); unawaited(_initLink());
}); });
} }
@ -344,9 +351,9 @@ class _YumiApplicationState extends State<YumiApplication> {
style: TextStyle(fontSize: 12.sp, color: Color(0xff999999)), style: TextStyle(fontSize: 12.sp, color: Color(0xff999999)),
); );
} }
return Container( return SizedBox(
height: kTextTabBarHeight + ScreenUtil().statusBarHeight, height: kTextTabBarHeight + ScreenUtil().statusBarHeight,
child: Center(child: body), child: SizedBox.expand(child: Center(child: body)),
); );
}, },
); );
@ -399,9 +406,10 @@ class _YumiApplicationState extends State<YumiApplication> {
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
child ?? const SizedBox.shrink(), child ?? const SizedBox.shrink(),
const Positioned.fill( if (SCGlobalConfig.allowsHighCostAnimations)
child: VapPlusSvgaPlayer(tag: "room_gift"), const Positioned.fill(
), child: VapPlusSvgaPlayer(tag: "room_gift"),
),
], ],
); );
}, },

View File

@ -15,6 +15,7 @@ import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app/config/business_logic_strategy.dart'; import 'package:yumi/app/config/business_logic_strategy.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart'; import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/main.dart'; import 'package:yumi/main.dart';
@ -101,6 +102,74 @@ class _GiftPageState extends State<GiftPage>
debugPrint('[GiftFX][Send] $message'); debugPrint('[GiftFX][Send] $message');
} }
String _describeGiftSendError(Object error) {
if (error is DioException) {
final requestPath = error.requestOptions.path;
final statusCode = error.response?.statusCode;
final responseData = error.response?.data;
return 'dioType=${error.type} '
'statusCode=$statusCode '
'path=$requestPath '
'message=${error.message} '
'error=${error.error} '
'response=$responseData';
}
if (error is NotSuccessException) {
return 'notSuccess message=${error.message}';
}
return 'type=${error.runtimeType} error=$error';
}
void _showLocalLuckyGiftFeedback(
List<MicRes> acceptUsers, {
required SocialChatGiftRes gift,
required int quantity,
}) {
final rtmProvider = Provider.of<RtmProvider>(
navigatorKey.currentState!.context,
listen: false,
);
final currentUser = AccountStorage().getCurrentUser()?.userProfile;
if (currentUser == null) {
_giftFxLog(
'local lucky feedback skipped reason=no_current_user giftId=${gift.id}',
);
return;
}
for (final acceptUser in acceptUsers) {
final targetUser = acceptUser.user;
if (targetUser == null) {
_giftFxLog(
'local lucky feedback skipped reason=no_target_user giftId=${gift.id}',
);
continue;
}
_giftFxLog(
'local lucky feedback trigger '
'giftId=${gift.id} '
'giftName=${gift.giftName} '
'toUserId=${targetUser.id} '
'toUserName=${targetUser.userNickname} '
'quantity=$quantity',
);
rtmProvider.msgFloatingGiftListener?.call(
Msg(
groupId:
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount,
msg: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
type: SCRoomMsgType.luckGiftAnimOther,
gift: gift,
user: currentUser,
toUser: targetUser,
number: quantity,
),
);
}
}
void _applyGiftSelection(SocialChatGiftRes? gift, {bool notify = true}) { void _applyGiftSelection(SocialChatGiftRes? gift, {bool notify = true}) {
checkedGift = gift; checkedGift = gift;
if (gift != null && if (gift != null &&
@ -331,6 +400,80 @@ class _GiftPageState extends State<GiftPage>
} }
} }
bool _usesLuckyGiftEndpoint(SocialChatGiftRes? gift) {
final giftTab = (gift?.giftTab ?? '').trim();
return giftTab == "LUCK" ||
giftTab == SCGiftType.LUCKY_GIFT.name ||
giftTab == SCGiftType.MAGIC.name;
}
bool _hasValidLuckyGiftStandardId(SocialChatGiftRes gift) {
final standardId = (gift.standardId ?? '').trim();
return standardId.isNotEmpty && standardId != '0';
}
String _resolveGiftSendErrorMessage(Object error) {
String sanitize(String message) {
var value = message.trim();
const prefixes = <String>[
'Exception: ',
'DioException: ',
'DioException [unknown]: ',
];
for (final prefix in prefixes) {
if (value.startsWith(prefix)) {
value = value.substring(prefix.length).trim();
}
}
return value;
}
if (error is DioException) {
final responseData = error.response?.data;
if (responseData is Map<String, dynamic>) {
final responseMessage = sanitize(
responseData['errorMsg']?.toString() ?? '',
);
if (responseMessage.isNotEmpty) {
return responseMessage;
}
}
if (error.error is NotSuccessException) {
final responseMessage = sanitize(
(error.error as NotSuccessException).message,
);
if (responseMessage.isNotEmpty) {
return responseMessage;
}
}
final dioMessage = sanitize(error.message ?? '');
if (dioMessage.isNotEmpty) {
return dioMessage;
}
final nestedMessage = sanitize(error.error?.toString() ?? '');
if (nestedMessage.isNotEmpty) {
return nestedMessage;
}
}
if (error is NotSuccessException) {
final responseMessage = sanitize(error.message);
if (responseMessage.isNotEmpty) {
return responseMessage;
}
}
final fallbackMessage = sanitize(error.toString());
if (fallbackMessage.isNotEmpty) {
return fallbackMessage;
}
return 'Gift sending failed, please try again.';
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<SCAppGeneralManager>( return Consumer<SCAppGeneralManager>(
@ -926,7 +1069,7 @@ class _GiftPageState extends State<GiftPage>
} }
/// ///
void giveGifts() { void giveGifts() async {
List<String> acceptUserIds = []; List<String> acceptUserIds = [];
List<MicRes> acceptUsers = []; List<MicRes> acceptUsers = [];
@ -951,60 +1094,164 @@ class _GiftPageState extends State<GiftPage>
} }
} }
if (acceptUserIds.isEmpty) { if (acceptUserIds.isEmpty) {
_giftFxLog(
'tap send aborted reason=no_accept_users '
'giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName} '
'giftTab=${checkedGift?.giftTab} '
'giveType=$giveType',
);
SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectTheRecipient); SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectTheRecipient);
return; return;
} }
if (checkedGift == null) { final selectedGift = checkedGift;
if (selectedGift == null) {
_giftFxLog(
'tap send aborted reason=no_selected_gift '
'acceptUserIds=${acceptUserIds.join(",")} '
'giveType=$giveType',
);
return; return;
} }
SCChatRoomRepository() final selectedNumber = number;
.giveGift( final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "";
acceptUserIds, final roomAccount =
checkedGift!.id ?? "", rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
number, final isLuckyGiftRequest = _usesLuckyGiftEndpoint(selectedGift);
false, final requestName = isLuckyGiftRequest ? 'giveLuckyGift' : 'giveGift';
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", final senderId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
) final senderName =
.then((result) { AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? "";
_giftFxLog( final stopwatch = Stopwatch()..start();
'giveGift success giftId=${checkedGift?.id} '
'giftName=${checkedGift?.giftName} ' _giftFxLog(
'giftSourceUrl=${checkedGift?.giftSourceUrl} ' 'tap send start request=$requestName '
'special=${checkedGift?.special} ' 'senderId=$senderId '
'giftTab=${checkedGift?.giftTab} ' 'senderName=$senderName '
'number=$number ' 'giftId=${selectedGift.id} '
'acceptUserIds=${acceptUserIds.join(",")} ' 'giftName=${selectedGift.giftName} '
'roomId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id}', 'giftTab=${selectedGift.giftTab} '
); 'special=${selectedGift.special} '
// SCTts.show(SCAppLocalizations.of(context)!.giftGivingSuccessful); 'standardId=${selectedGift.standardId} '
sendGiftMsg(acceptUsers); 'giftCandy=${selectedGift.giftCandy} '
Provider.of<SocialChatUserProfileManager>( 'number=$selectedNumber '
context, 'acceptCount=${acceptUserIds.length} '
listen: false, 'acceptUserIds=${acceptUserIds.join(",")} '
).updateBalance(result); 'roomId=$roomId '
}) 'roomAccount=$roomAccount '
.catchError((e) { 'giveType=$giveType '
_giftFxLog( 'giftType=$giftType',
'giveGift failed giftId=${checkedGift?.id} ' );
'giftName=${checkedGift?.giftName} '
'error=$e', if (isLuckyGiftRequest && !_hasValidLuckyGiftStandardId(selectedGift)) {
); const configError =
}); 'Gift configuration unavailable, please try another gift.';
_giftFxLog(
'$requestName skipped giftId=${selectedGift.id} '
'giftName=${selectedGift.giftName} '
'giftTab=${selectedGift.giftTab} '
'standardId=${selectedGift.standardId} '
'reason=invalid_standard_id',
);
SCTts.show(configError);
return;
}
try {
final repository = SCChatRoomRepository();
_giftFxLog(
'calling repository.$requestName '
'giftId=${selectedGift.id} '
'roomId=$roomId '
'acceptUserIds=${acceptUserIds.join(",")} '
'quantity=$selectedNumber',
);
final result =
isLuckyGiftRequest
? await repository.giveLuckyGift(
acceptUserIds,
selectedGift.id ?? "",
selectedNumber,
false,
roomId: roomId,
)
: await repository.giveGift(
acceptUserIds,
selectedGift.id ?? "",
selectedNumber,
false,
roomId: roomId,
);
_giftFxLog(
'$requestName success giftId=${selectedGift.id} '
'giftName=${selectedGift.giftName} '
'giftSourceUrl=${selectedGift.giftSourceUrl} '
'special=${selectedGift.special} '
'giftTab=${selectedGift.giftTab} '
'standardId=${selectedGift.standardId} '
'number=$selectedNumber '
'acceptUserIds=${acceptUserIds.join(",")} '
'roomId=$roomId '
'balance=$result '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
// SCTts.show(SCAppLocalizations.of(context)!.giftGivingSuccessful);
if (isLuckyGiftRequest) {
_showLocalLuckyGiftFeedback(
acceptUsers,
gift: selectedGift,
quantity: selectedNumber,
);
await sendLuckGiftAnimOtherMsg(
acceptUsers,
gift: selectedGift,
quantity: selectedNumber,
);
} else {
sendGiftMsg(acceptUsers, gift: selectedGift, quantity: selectedNumber);
}
if (mounted) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).updateBalance(result);
}
} catch (e) {
final errorMessage = _resolveGiftSendErrorMessage(e);
final errorDetails = _describeGiftSendError(e);
_giftFxLog(
'$requestName failed giftId=${selectedGift.id} '
'giftName=${selectedGift.giftName} '
'giftTab=${selectedGift.giftTab} '
'standardId=${selectedGift.standardId} '
'error=$e '
'resolvedError=$errorMessage '
'details={$errorDetails} '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
if (mounted) {
SCTts.show(errorMessage);
}
}
} }
void sendGiftMsg(List<MicRes> acceptUsers) { void sendGiftMsg(
List<MicRes> acceptUsers, {
required SocialChatGiftRes gift,
required int quantity,
}) {
///IM消息 ///IM消息
for (var u in acceptUsers) { for (var u in acceptUsers) {
final special = checkedGift?.special ?? ""; final special = gift.special ?? "";
final giftSourceUrl = checkedGift?.giftSourceUrl ?? ""; final giftSourceUrl = gift.giftSourceUrl ?? "";
final hasSource = giftSourceUrl.isNotEmpty; final hasSource = giftSourceUrl.isNotEmpty;
final hasAnimation = scGiftHasAnimationSpecial(special); final hasAnimation = scGiftHasAnimationSpecial(special);
final hasGlobalGift = special.contains(SCGiftType.GLOBAL_GIFT.name); final hasGlobalGift = special.contains(SCGiftType.GLOBAL_GIFT.name);
final hasFullScreenEffect = scGiftHasFullScreenEffect(special); final hasFullScreenEffect = scGiftHasFullScreenEffect(special);
_giftFxLog( _giftFxLog(
'dispatch gift msg ' 'dispatch gift msg '
'giftId=${checkedGift?.id} ' 'giftId=${gift.id} '
'giftName=${checkedGift?.giftName} ' 'giftName=${gift.giftName} '
'toUserId=${u.user?.id} ' 'toUserId=${u.user?.id} '
'toUserName=${u.user?.userNickname} ' 'toUserName=${u.user?.userNickname} '
'giftSourceUrl=$giftSourceUrl ' 'giftSourceUrl=$giftSourceUrl '
@ -1022,10 +1269,10 @@ class _GiftPageState extends State<GiftPage>
Msg( Msg(
groupId: groupId:
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount,
gift: checkedGift, gift: gift,
user: AccountStorage().getCurrentUser()?.userProfile, user: AccountStorage().getCurrentUser()?.userProfile,
toUser: u.user, toUser: u.user,
number: number, number: quantity,
type: SCRoomMsgType.gift, type: SCRoomMsgType.gift,
role: role:
Provider.of<RtcProvider>( Provider.of<RtcProvider>(
@ -1049,7 +1296,7 @@ class _GiftPageState extends State<GiftPage>
}, },
); );
} }
num coins = checkedGift!.giftCandy! * number; num coins = (gift.giftCandy ?? 0) * quantity;
if (coins > 9999) { if (coins > 9999) {
var fMsg = SCFloatingMessage( var fMsg = SCFloatingMessage(
type: 1, type: 1,
@ -1059,42 +1306,42 @@ class _GiftPageState extends State<GiftPage>
AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? AccountStorage().getCurrentUser()?.userProfile?.userNickname ??
"", "",
toUserName: u.user?.userNickname ?? "", toUserName: u.user?.userNickname ?? "",
giftUrl: checkedGift?.giftPhoto ?? "", giftUrl: gift.giftPhoto ?? "",
roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
coins: coins, coins: coins,
number: number, number: quantity,
); );
OverlayManager().addMessage(fMsg); OverlayManager().addMessage(fMsg);
} }
if (checkedGift!.giftSourceUrl != null && checkedGift!.special != null) { if (gift.giftSourceUrl != null && gift.special != null) {
if (scGiftHasFullScreenEffect(checkedGift!.special)) { if (scGiftHasFullScreenEffect(gift.special)) {
if (SCGlobalConfig.isGiftSpecialEffects) { if (SCGlobalConfig.isGiftSpecialEffects) {
_giftFxLog( _giftFxLog(
'local trigger player play ' 'local trigger player play '
'path=${checkedGift!.giftSourceUrl} ' 'path=${gift.giftSourceUrl} '
'giftId=${checkedGift?.id} ' 'giftId=${gift.id} '
'giftName=${checkedGift?.giftName}', 'giftName=${gift.giftName}',
); );
SCGiftVapSvgaManager().play(checkedGift!.giftSourceUrl!); SCGiftVapSvgaManager().play(gift.giftSourceUrl!);
} else { } else {
_giftFxLog( _giftFxLog(
'skip local play because isGiftSpecialEffects=false ' 'skip local play because isGiftSpecialEffects=false '
'giftId=${checkedGift?.id}', 'giftId=${gift.id}',
); );
} }
} else { } else {
_giftFxLog( _giftFxLog(
'skip local play because special does not include ' 'skip local play because special does not include '
'${SCGiftType.ANIMSCION.name}/$kSCGiftAnimationSpecialAlias/${SCGiftType.GLOBAL_GIFT.name} ' '${SCGiftType.ANIMSCION.name}/$kSCGiftAnimationSpecialAlias/${SCGiftType.GLOBAL_GIFT.name} '
'giftId=${checkedGift?.id} special=${checkedGift?.special}', 'giftId=${gift.id} special=${gift.special}',
); );
} }
} else { } else {
_giftFxLog( _giftFxLog(
'skip local play because giftSourceUrl or special is null ' 'skip local play because giftSourceUrl or special is null '
'giftId=${checkedGift?.id} ' 'giftId=${gift.id} '
'giftSourceUrl=${checkedGift?.giftSourceUrl} ' 'giftSourceUrl=${gift.giftSourceUrl} '
'special=${checkedGift?.special}', 'special=${gift.special}',
); );
} }
} }
@ -1152,18 +1399,48 @@ class _GiftPageState extends State<GiftPage>
} }
/// ///
void sendLuckGiftAnimOtherMsg(List<MicRes> acceptUsers) { Future<void> sendLuckGiftAnimOtherMsg(
Provider.of<RtmProvider>( List<MicRes> acceptUsers, {
required SocialChatGiftRes gift,
required int quantity,
}) async {
final targetUserIds =
acceptUsers
.map((u) => u.user?.id ?? "")
.where((id) => id.isNotEmpty)
.toList();
final firstTargetUser =
acceptUsers.isNotEmpty ? acceptUsers.first.user : null;
_giftFxLog(
'dispatch lucky anim msg '
'giftId=${gift.id} '
'giftName=${gift.giftName} '
'giftPhoto=${gift.giftPhoto} '
'quantity=$quantity '
'firstTargetUserId=${firstTargetUser?.id} '
'groupId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount} '
'roomId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id} '
'targetUserIds=${targetUserIds.join(",")}',
);
await Provider.of<RtmProvider>(
navigatorKey.currentState!.context, navigatorKey.currentState!.context,
listen: false, listen: false,
).dispatchMessage( ).dispatchMessage(
Msg( Msg(
groupId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, groupId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount,
gift: checkedGift, gift: gift,
user: AccountStorage().getCurrentUser()?.userProfile,
toUser: firstTargetUser,
number: quantity,
type: SCRoomMsgType.luckGiftAnimOther, type: SCRoomMsgType.luckGiftAnimOther,
msg: jsonEncode(acceptUsers.map((u) => u.user?.id).toList()), msg: jsonEncode(acceptUsers.map((u) => u.user?.id).toList()),
), ),
addLocal: false, addLocal: true,
);
_giftFxLog(
'dispatch lucky anim msg finished '
'giftId=${gift.id} '
'targetUserIds=${targetUserIds.join(",")}',
); );
} }
} }

View File

@ -20,6 +20,7 @@ import 'package:yumi/ui_kit/widgets/room/room_head_widget.dart';
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
import 'package:yumi/ui_kit/widgets/room/room_online_user_widget.dart'; import 'package:yumi/ui_kit/widgets/room/room_online_user_widget.dart';
import 'package:yumi/ui_kit/widgets/room/room_play_widget.dart'; import 'package:yumi/ui_kit/widgets/room/room_play_widget.dart';
import 'package:yumi/shared/data_sources/models/enum/sc_gift_type.dart';
import '../../ui_kit/components/sc_float_ichart.dart'; import '../../ui_kit/components/sc_float_ichart.dart';
import '../../ui_kit/widgets/room/seat/room_seat_widget.dart'; import '../../ui_kit/widgets/room/seat/room_seat_widget.dart';
@ -32,7 +33,7 @@ class VoiceRoomPage extends StatefulWidget {
const VoiceRoomPage({super.key}); const VoiceRoomPage({super.key});
@override @override
_VoiceRoomPageState createState() => _VoiceRoomPageState(); State<VoiceRoomPage> createState() => _VoiceRoomPageState();
} }
class _VoiceRoomPageState extends State<VoiceRoomPage> class _VoiceRoomPageState extends State<VoiceRoomPage>
@ -258,12 +259,15 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
return; return;
} }
var giftModel = LGiftModel(); var giftModel = LGiftModel();
final giftTab = (msg.gift?.giftTab ?? '').trim();
giftModel.labelId = "${msg.gift?.id}${msg.user?.id}${msg.toUser?.id}"; giftModel.labelId = "${msg.gift?.id}${msg.user?.id}${msg.toUser?.id}";
giftModel.sendUserName = msg.user?.userNickname ?? ""; giftModel.sendUserName = msg.user?.userNickname ?? "";
giftModel.sendToUserName = msg.toUser?.userNickname ?? ""; giftModel.sendToUserName = msg.toUser?.userNickname ?? "";
giftModel.sendUserPic = msg.user?.userAvatar ?? ""; giftModel.sendUserPic = msg.user?.userAvatar ?? "";
giftModel.giftPic = msg.gift?.giftPhoto ?? ""; giftModel.giftPic = msg.gift?.giftPhoto ?? "";
giftModel.giftCount = msg.number ?? 0; giftModel.giftCount = msg.number ?? 0;
giftModel.isLuckyGift =
giftTab == "LUCK" || giftTab == SCGiftType.LUCKY_GIFT.name;
Provider.of<GiftAnimationManager>( Provider.of<GiftAnimationManager>(
context, context,
listen: false, listen: false,

View File

@ -430,7 +430,7 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
), ),
child: Container( child: Container(
width: ScreenUtil().screenWidth, width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight * 0.56, height: ScreenUtil().screenHeight * 0.75,
color: const Color(0xFF081915), color: const Color(0xFF081915),
child: Column( child: Column(
children: [ children: [

View File

@ -135,25 +135,25 @@ class RealTimeCommunicationManager extends ChangeNotifier {
onUserOffline: (connection, remoteUid, reason) { onUserOffline: (connection, remoteUid, reason) {
print('rtc用户 $remoteUid 离开了频道 (原因: ${reason})'); print('rtc用户 $remoteUid 离开了频道 (原因: ${reason})');
}, },
onTokenPrivilegeWillExpire: ( onTokenPrivilegeWillExpire: (
RtcConnection connection, RtcConnection connection,
String token, String token,
) async { ) async {
var rtcToken = await SCAccountRepository().getRtcToken( var rtcToken = await SCAccountRepository().getRtcToken(
currenRoom?.roomProfile?.roomProfile?.id ?? "", currenRoom?.roomProfile?.roomProfile?.id ?? "",
AccountStorage().getCurrentUser()?.userProfile?.id ?? "", AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
isPublisher: isOnMai(), isPublisher: isOnMai(),
); );
engine?.renewToken(rtcToken.rtcToken ?? ""); engine?.renewToken(rtcToken.rtcToken ?? "");
}, },
onAudioVolumeIndication: initializeAudioVolumeIndicationCallback, onAudioVolumeIndication: initializeAudioVolumeIndicationCallback,
), ),
); );
var rtcToken = await SCAccountRepository().getRtcToken( var rtcToken = await SCAccountRepository().getRtcToken(
currenRoom?.roomProfile?.roomProfile?.id ?? "", currenRoom?.roomProfile?.roomProfile?.id ?? "",
AccountStorage().getCurrentUser()?.userProfile?.id ?? "", AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
isPublisher: false, isPublisher: false,
); );
await engine?.joinChannel( await engine?.joinChannel(
token: rtcToken.rtcToken ?? "", token: rtcToken.rtcToken ?? "",
channelId: currenRoom?.roomProfile?.roomProfile?.id ?? "", channelId: currenRoom?.roomProfile?.roomProfile?.id ?? "",
@ -635,10 +635,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
DataPersistence.setLastTimeRoomId(""); DataPersistence.setLastTimeRoomId("");
SCGiftVapSvgaManager().stopPlayback(); SCGiftVapSvgaManager().stopPlayback();
SCRoomUtils.closeAllDialogs(); SCRoomUtils.closeAllDialogs();
SCGlobalConfig.isEntryVehicleAnimation = true; SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults();
SCGlobalConfig.isGiftSpecialEffects = true;
SCGlobalConfig.isFloatingAnimationInGlobal = true;
SCGlobalConfig.isLuckGiftSpecialEffects = true;
} }
void toggleRemoteAudioMuteForAllUsers() { void toggleRemoteAudioMuteForAllUsers() {

View File

@ -784,6 +784,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
// //
Future<void> _sendRoomMessage(Msg msg) async { Future<void> _sendRoomMessage(Msg msg) async {
try { try {
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
_giftFxLog(
'send room msg start '
'type=${msg.type} '
'groupId=${msg.groupId} '
'msg=${msg.msg} '
'giftId=${msg.gift?.id} '
'giftPhoto=${msg.gift?.giftPhoto}',
);
}
if (msg.type == SCRoomMsgType.roomSettingUpdate) { if (msg.type == SCRoomMsgType.roomSettingUpdate) {
debugPrint( debugPrint(
"[Room Cover Sync] send roomSettingUpdate groupId=${msg.groupId ?? ""} roomId=${msg.msg ?? ""}", "[Room Cover Sync] send roomSettingUpdate groupId=${msg.groupId ?? ""} roomId=${msg.msg ?? ""}",
@ -809,6 +819,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
.getMessageManager() .getMessageManager()
.createCustomMessage(data: jsonEncode(msg.toJson())); .createCustomMessage(data: jsonEncode(msg.toJson()));
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
_giftFxLog(
'send room msg createCustomMessage '
'type=${msg.type} '
'code=${textMsg.code} '
'id=${textMsg.data?.id} '
'desc=${textMsg.desc}',
);
}
if (textMsg.code != 0) return; if (textMsg.code != 0) return;
final sendResult = await TencentImSDKPlugin.v2TIMManager final sendResult = await TencentImSDKPlugin.v2TIMManager
@ -818,6 +838,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
groupID: msg.groupId!, groupID: msg.groupId!,
receiver: '', receiver: '',
); );
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
_giftFxLog(
'send room msg result '
'type=${msg.type} '
'code=${sendResult.code} '
'msgId=${sendResult.data?.msgID} '
'groupId=${sendResult.data?.groupID} '
'desc=${sendResult.desc}',
);
}
if (sendResult.code == 0) {} if (sendResult.code == 0) {}
} catch (e) { } catch (e) {
throw Exception("create fail: $e"); throw Exception("create fail: $e");
@ -847,14 +877,17 @@ class RealTimeMessagingManager extends ChangeNotifier {
roomChatMsgList.removeAt(roomChatMsgList.length - 1); roomChatMsgList.removeAt(roomChatMsgList.length - 1);
} }
msgChatListener?.call(msg); msgChatListener?.call(msg);
} else if (msg.type == SCRoomMsgType.gift) { } else if (msg.type == SCRoomMsgType.gift ||
msg.type == SCRoomMsgType.luckGiftAnimOther) {
roomGiftMsgList.insert(0, msg); roomGiftMsgList.insert(0, msg);
if (roomGiftMsgList.length > 250) { if (roomGiftMsgList.length > 250) {
print('大于200条消息'); print('大于200条消息');
roomGiftMsgList.removeAt(roomGiftMsgList.length - 1); roomGiftMsgList.removeAt(roomGiftMsgList.length - 1);
} }
msgGiftListener?.call(msg); msgGiftListener?.call(msg);
msgFloatingGiftListener?.call(msg); if (msg.type == SCRoomMsgType.gift) {
msgFloatingGiftListener?.call(msg);
}
} }
} }
@ -1119,6 +1152,17 @@ class RealTimeMessagingManager extends ChangeNotifier {
).fetchOnlineUsersList(); ).fetchOnlineUsersList();
} else if (msg.type == SCRoomMsgType.gameLuckyGift) { } else if (msg.type == SCRoomMsgType.gameLuckyGift) {
var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data); var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data);
_giftFxLog(
'recv GAME_LUCKY_GIFT '
'giftId=${broadCastRes.data?.giftId} '
'roomId=${broadCastRes.data?.roomId} '
'sendUserId=${broadCastRes.data?.sendUserId} '
'acceptUserId=${broadCastRes.data?.acceptUserId} '
'giftQuantity=${broadCastRes.data?.giftQuantity} '
'awardAmount=${broadCastRes.data?.awardAmount} '
'multiple=${broadCastRes.data?.multiple} '
'multipleType=${broadCastRes.data?.multipleType}',
);
msg.gift = SocialChatGiftRes(giftPhoto: broadCastRes.data?.giftCover); msg.gift = SocialChatGiftRes(giftPhoto: broadCastRes.data?.giftCover);
msg.awardAmount = broadCastRes.data?.awardAmount; msg.awardAmount = broadCastRes.data?.awardAmount;
msg.user = SocialChatUserProfile( msg.user = SocialChatUserProfile(
@ -1279,21 +1323,56 @@ class RealTimeMessagingManager extends ChangeNotifier {
); );
} }
} else if (msg.type == SCRoomMsgType.luckGiftAnimOther) { } else if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
if (Provider.of<GiftProvider>( final hideLGiftAnimal =
context!, Provider.of<GiftProvider>(
listen: false, context!,
).hideLGiftAnimal) { listen: false,
return; ).hideLGiftAnimal;
if (hideLGiftAnimal) {
_giftFxLog(
'recv LUCK_GIFT_ANIM_OTHER skipped '
'reason=hideLGiftAnimal '
'giftPhoto=${msg.gift?.giftPhoto}',
);
} else {
final targetUserIds =
(jsonDecode(msg.msg ?? "") as List)
.map((e) => e as String)
.toList();
_giftFxLog(
'recv LUCK_GIFT_ANIM_OTHER '
'giftPhoto=${msg.gift?.giftPhoto} '
'sendUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'quantity=${msg.number} '
'targetUserIds=${targetUserIds.join(",")}',
);
eventBus.fire(
GiveRoomLuckWithOtherEvent(
msg.gift?.giftPhoto ?? "",
targetUserIds,
),
);
if (msg.user != null && msg.toUser != null && msg.gift != null) {
_giftFxLog(
'trigger floating gift listener from LUCK_GIFT_ANIM_OTHER '
'sendUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'quantity=${msg.number} '
'giftId=${msg.gift?.id}',
);
msgFloatingGiftListener?.call(msg);
} else {
_giftFxLog(
'skip floating gift listener from LUCK_GIFT_ANIM_OTHER '
'reason=incomplete_msg '
'sendUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'giftId=${msg.gift?.id} '
'quantity=${msg.number}',
);
}
} }
eventBus.fire(
GiveRoomLuckWithOtherEvent(
msg.gift?.giftPhoto ?? "",
(jsonDecode(msg.msg ?? "") as List)
.map((e) => e as String)
.toList(),
),
);
return;
} else if (msg.type == SCRoomMsgType.roomRoleChange) { } else if (msg.type == SCRoomMsgType.roomRoleChange) {
/// ///
Provider.of<RealTimeCommunicationManager>( Provider.of<RealTimeCommunicationManager>(

View File

@ -1,158 +1,150 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/shared/tools/sc_message_utils.dart';
import 'package:yumi/shared/tools/sc_message_utils.dart'; import 'package:yumi/shared/tools/sc_room_utils.dart';
import 'package:yumi/shared/tools/sc_room_utils.dart'; import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; import 'package:provider/provider.dart';
import 'package:yumi/main.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:provider/provider.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/modules/auth/login_route.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/modules/auth/login_route.dart'; import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart'; import '../../../tools/sc_heartbeat_utils.dart';
import '../../models/enum/sc_props_type.dart';
import '../../../tools/sc_heartbeat_utils.dart';
import '../../models/enum/sc_props_type.dart'; typedef UserManager = AccountStorage;
typedef UserManager = AccountStorage; class AccountStorage {
static AccountStorage? _instance;
class AccountStorage {
static AccountStorage? _instance; AccountStorage._internal();
AccountStorage._internal(); factory AccountStorage() {
return _instance ??= AccountStorage._internal();
factory AccountStorage() { }
return _instance ??= AccountStorage._internal();
} String token = "";
SocialChatLoginRes? _currentUser;
String token = "";
SocialChatLoginRes? _currentUser; ///
SocialChatLoginRes? getCurrentUser() {
/// if (_currentUser != null) {
SocialChatLoginRes? getCurrentUser() { return _currentUser;
if (_currentUser != null) { }
return _currentUser; var userJson = DataPersistence.getCurrentUser();
} if (userJson.isNotEmpty) {
var userJson = DataPersistence.getCurrentUser(); _currentUser = SocialChatLoginRes.fromJson(jsonDecode(userJson));
if (userJson.isNotEmpty) { }
_currentUser = SocialChatLoginRes.fromJson(jsonDecode(userJson)); return _currentUser;
} }
return _currentUser;
} ///
void setCurrentUser(SocialChatLoginRes user) {
/// _currentUser = user;
void setCurrentUser(SocialChatLoginRes user) { setToken(_currentUser!.token ?? "");
_currentUser = user; String userJson = jsonEncode(_currentUser?.toJson());
setToken(_currentUser!.token ?? ""); if (userJson.isNotEmpty) {
String userJson = jsonEncode(_currentUser?.toJson()); DataPersistence.setCurrentUser(userJson);
if (userJson.isNotEmpty) { }
DataPersistence.setCurrentUser(userJson); }
}
} ///
PropsResources? getHeaddress() {
/// PropsResources? pr;
PropsResources? getHeaddress() { _currentUser?.userProfile?.useProps?.forEach((value) {
PropsResources? pr; if (value.propsResources?.type == SCPropsType.AVATAR_FRAME.name) {
_currentUser?.userProfile?.useProps?.forEach((value) { ///
if (value.propsResources?.type == SCPropsType.AVATAR_FRAME.name) { if (int.parse(value.expireTime ?? "0") >
/// DateTime.now().millisecondsSinceEpoch) {
if (int.parse(value.expireTime ?? "0") > pr = value.propsResources;
DateTime }
.now() }
.millisecondsSinceEpoch) { });
pr = value.propsResources; return pr;
} }
}
}); ///
return pr; PropsResources? getChatbox() {
} PropsResources? pr;
_currentUser?.userProfile?.useProps?.forEach((value) {
/// if (value.propsResources?.type == SCPropsType.CHAT_BUBBLE.name) {
PropsResources? getChatbox() { ///
PropsResources? pr; if (int.parse(value.expireTime ?? "0") >
_currentUser?.userProfile?.useProps?.forEach((value) { DateTime.now().millisecondsSinceEpoch) {
if (value.propsResources?.type == SCPropsType.CHAT_BUBBLE.name) { pr = value.propsResources;
/// }
if (int.parse(value.expireTime ?? "0") > }
DateTime });
.now() return pr;
.millisecondsSinceEpoch) { }
pr = value.propsResources;
} ///
} PropsResources? getMountains() {
}); PropsResources? pr;
return pr; _currentUser?.userProfile?.useProps?.forEach((value) {
} if (value.propsResources?.type == SCPropsType.RIDE.name) {
///
/// if (int.parse(value.expireTime ?? "0") >
PropsResources? getMountains() { DateTime.now().millisecondsSinceEpoch) {
PropsResources? pr; pr = value.propsResources;
_currentUser?.userProfile?.useProps?.forEach((value) { }
if (value.propsResources?.type == SCPropsType.RIDE.name) { }
/// });
if (int.parse(value.expireTime ?? "0") > return pr;
DateTime }
.now()
.millisecondsSinceEpoch) { ///
pr = value.propsResources; PropsResources? getDataCard() {
} PropsResources? pr;
} _currentUser?.userProfile?.useProps?.forEach((value) {
}); if (value.propsResources?.type == SCPropsType.DATA_CARD.name) {
return pr; ///
} if (int.parse(value.expireTime ?? "0") >
DateTime.now().millisecondsSinceEpoch) {
/// pr = value.propsResources;
PropsResources? getDataCard() { }
PropsResources? pr; }
_currentUser?.userProfile?.useProps?.forEach((value) { });
if (value.propsResources?.type == SCPropsType.DATA_CARD.name) { return pr;
/// }
if (int.parse(value.expireTime ?? "0") >
DateTime.now().millisecondsSinceEpoch) { String getToken() {
pr = value.propsResources; if (token.isNotEmpty) {
} return token;
} }
}); token = DataPersistence.getToken();
return pr; return token;
} }
String getToken() { void setToken(String tk) {
if (token.isNotEmpty) { token = tk;
return token; DataPersistence.setToken(token);
} }
token = DataPersistence.getToken();
return token; void _cleanUser() {
} token = "";
_currentUser = null;
void setToken(String tk) { DataPersistence.setToken("");
token = tk; DataPersistence.setCurrentUser("");
DataPersistence.setToken(token); }
}
///退
void _cleanUser() { void logout(BuildContext context) {
token = ""; _cleanUser();
_currentUser = null; SCHeartbeatUtils.cancelTimer();
DataPersistence.setToken(""); Provider.of<RtcProvider>(
DataPersistence.setCurrentUser(""); context,
} listen: false,
).exitCurrentVoiceRoomSession(true);
///退 Provider.of<RtmProvider>(context, listen: false).logout();
void logout(BuildContext context) { SCNavigatorUtils.push(context, LoginRouter.login, clearStack: true);
_cleanUser(); SCRoomUtils.closeAllDialogs();
SCHeartbeatUtils.cancelTimer(); SCMessageUtils.redPacketFutureCache.clear();
Provider.of<RtcProvider>(context, listen: false).exitCurrentVoiceRoomSession(true); SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults();
Provider.of<RtmProvider>(context, listen: false).logout(); OverlayManager().dispose();
SCNavigatorUtils.push(context, LoginRouter.login, clearStack: true); }
SCRoomUtils.closeAllDialogs(); }
SCMessageUtils.redPacketFutureCache.clear();
SCGlobalConfig.isEntryVehicleAnimation = true;
SCGlobalConfig.isGiftSpecialEffects = true;
SCGlobalConfig.isFloatingAnimationInGlobal = true;
SCGlobalConfig.isLuckGiftSpecialEffects = true;
OverlayManager().dispose();
}
}

View File

@ -32,6 +32,7 @@ import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.dart';
import 'package:yumi/shared/business_logic/repositories/room_repository.dart'; import 'package:yumi/shared/business_logic/repositories/room_repository.dart';
import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart'; import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart';
import 'package:yumi/shared/tools/sc_room_profile_cache.dart'; import 'package:yumi/shared/tools/sc_room_profile_cache.dart';
@ -44,6 +45,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
return _instance ??= SCChatRoomRepository._internal(); return _instance ??= SCChatRoomRepository._internal();
} }
void _giftRepoLog(String message) {
debugPrint('[GiftFX][Repo] $message');
}
bool _isBrokenLocalMediaUrl(String? url) { bool _isBrokenLocalMediaUrl(String? url) {
return (url ?? "").contains("/external/oss/local/"); return (url ?? "").contains("/external/oss/local/");
} }
@ -314,6 +319,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
final result = await http.post( final result = await http.post(
"daa4f379ff3e429e55c318b6af7291e8", "daa4f379ff3e429e55c318b6af7291e8",
data: params, data: params,
extra: const {BaseNetworkClient.silentErrorToastKey: true},
fromJson: (json) => json as double, fromJson: (json) => json as double,
); );
return result; return result;
@ -617,11 +623,27 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
params["accepts"] = accepts.toJson(); params["accepts"] = accepts.toJson();
} }
_giftRepoLog(
'request giveLuckyGift endpoint=/gift/give/lucky-gift '
'giftId=$giftId '
'roomId=$roomId '
'quantity=$quantity '
'checkCombo=$checkCombo '
'acceptUserIds=${acceptUserIds.join(",")} '
'payload=$params',
);
final result = await http.post( final result = await http.post(
"6ddf7ebecfa97adf23c6303877c7763ce591ff710a7abc4fe1e335d16efee21b", "6ddf7ebecfa97adf23c6303877c7763ce591ff710a7abc4fe1e335d16efee21b",
data: params, data: params,
extra: const {BaseNetworkClient.silentErrorToastKey: true},
fromJson: (json) => json as double, fromJson: (json) => json as double,
); );
_giftRepoLog(
'response giveLuckyGift endpoint=/gift/give/lucky-gift '
'giftId=$giftId '
'roomId=$roomId '
'balance=$result',
);
return result; return result;
} }

View File

@ -7,25 +7,33 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class SCDeviceIdUtils { class SCDeviceIdUtils {
static void initDeviceinfo() { static Future<void> initDeviceinfo() async {
PackageInfo.fromPlatform().then((info) { final info = await PackageInfo.fromPlatform();
SCGlobalConfig.version = info.version; SCGlobalConfig.version = info.version;
SCGlobalConfig.build = info.buildNumber; SCGlobalConfig.build = info.buildNumber;
if (Platform.isAndroid) {
SCGlobalConfig.channel = "Google"; final processorCount = Platform.numberOfProcessors;
DeviceInfoPlugin().androidInfo.then((dev) {
SCGlobalConfig.model = dev.model; if (Platform.isAndroid) {
SCGlobalConfig.sysVersion = dev.version.release; SCGlobalConfig.channel = "Google";
SCGlobalConfig.sdkInt = dev.version.sdkInt; final dev = await DeviceInfoPlugin().androidInfo;
}); SCGlobalConfig.model = dev.model;
} else if (Platform.isIOS) { SCGlobalConfig.sysVersion = dev.version.release;
SCGlobalConfig.channel = "AppStore"; SCGlobalConfig.sdkInt = dev.version.sdkInt;
DeviceInfoPlugin().iosInfo.then((dev) { SCGlobalConfig.applyDevicePerformanceProfile(
SCGlobalConfig.model = dev.model; isLowRamDevice: dev.isLowRamDevice,
SCGlobalConfig.sysVersion = dev.systemVersion; processorCount: processorCount,
}); );
} } else if (Platform.isIOS) {
}); SCGlobalConfig.channel = "AppStore";
final dev = await DeviceInfoPlugin().iosInfo;
SCGlobalConfig.model = dev.model;
SCGlobalConfig.sysVersion = dev.systemVersion;
SCGlobalConfig.applyDevicePerformanceProfile(
isLowRamDevice: false,
processorCount: processorCount,
);
}
} }
// ID // ID

View File

@ -286,10 +286,11 @@ class SCGiftVapSvgaManager {
'play request path=$path ext=${SCPathUtils.getFileExtension(path)} ' 'play request path=$path ext=${SCPathUtils.getFileExtension(path)} '
'priority=$priority type=$type ' 'priority=$priority type=$type '
'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} ' 'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} '
'lowPerformance=${SCGlobalConfig.isLowPerformanceDevice} '
'disposed=$_dis playing=$_play queueBefore=${_tq.length} ' 'disposed=$_dis playing=$_play queueBefore=${_tq.length} '
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}', 'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}',
); );
if (SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim) { if (SCGlobalConfig.allowsHighCostAnimations) {
if (_dis) { if (_dis) {
_log('play ignored because manager is disposed path=$path'); _log('play ignored because manager is disposed path=$path');
return; return;
@ -307,8 +308,9 @@ class SCGiftVapSvgaManager {
} }
} else { } else {
_log( _log(
'play ignored because sdkInt <= maxSdkNoAnim ' 'play ignored because performance profile disabled heavy animations '
'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim}', 'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} '
'lowPerformance=${SCGlobalConfig.isLowPerformanceDevice}',
); );
} }
} }

View File

@ -1,302 +1,279 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart';
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:provider/provider.dart';
import 'package:provider/provider.dart'; import 'package:yumi/app_localizations.dart';
import 'package:yumi/app_localizations.dart'; import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/modules/wallet/wallet_route.dart';
import 'package:yumi/modules/wallet/wallet_route.dart'; import 'package:yumi/modules/room/voice_room_route.dart';
import 'package:yumi/modules/room/voice_room_route.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/ui_kit/components/sc_float_ichart.dart';
import 'package:yumi/ui_kit/components/sc_float_ichart.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
typedef SCRoomUtils = SCChatRoomHelper;
typedef SCRoomUtils = SCChatRoomHelper; class SCChatRoomHelper {
static Map<String, SocialChatUserProfile> roomUsersMap = {};
class SCChatRoomHelper {
static Map<String, SocialChatUserProfile> roomUsersMap = {}; static void goRoom(
String roomId,
static void goRoom(String roomId, BuildContext context, {
BuildContext context, { bool needOpenRedenvelope = false,
bool needOpenRedenvelope = false, bool fromFloting = false,
bool fromFloting = false, String redPackId = "",
String redPackId = "", }) {
}) { if (Provider.of<RealTimeCommunicationManager>(
if (Provider context,
.of<RealTimeCommunicationManager>(context, listen: false) listen: false,
.currenRoom != null) { ).currenRoom !=
if (SCFloatIchart().isShow()) { null) {
/// if (SCFloatIchart().isShow()) {
if (Provider ///
.of<RealTimeCommunicationManager>( if (Provider.of<RealTimeCommunicationManager>(
context, context,
listen: false, listen: false,
) ).currenRoom?.roomProfile?.roomProfile?.id ==
.currenRoom roomId) {
?.roomProfile SCRoomUtils.openCurrentRoom(
?.roomProfile context,
?.id == needOpenRedenvelope: needOpenRedenvelope,
roomId) { redPackId: redPackId,
SCRoomUtils.openCurrentRoom( );
context, } else {
needOpenRedenvelope: needOpenRedenvelope, showEnterRoomConfirm(
redPackId: redPackId, roomId,
); context,
} else { needOpenRedenvelope: needOpenRedenvelope,
showEnterRoomConfirm( fromFloting: fromFloting,
roomId, redPackId: redPackId,
context, );
needOpenRedenvelope: needOpenRedenvelope, }
fromFloting: fromFloting, } else {
redPackId: redPackId, if (Provider.of<RealTimeCommunicationManager>(
); context,
} listen: false,
} else { ).currenRoom?.roomProfile?.roomProfile?.id ==
if (Provider roomId) {
.of<RealTimeCommunicationManager>( } else {
context, showEnterRoomConfirm(
listen: false, roomId,
) context,
.currenRoom needOpenRedenvelope: needOpenRedenvelope,
?.roomProfile fromFloting: fromFloting,
?.roomProfile redPackId: redPackId,
?.id == );
roomId) { }
}
} else { } else {
showEnterRoomConfirm( showEnterRoomConfirm(
roomId, roomId,
context, context,
needOpenRedenvelope: needOpenRedenvelope, needOpenRedenvelope: needOpenRedenvelope,
fromFloting: fromFloting, fromFloting: fromFloting,
redPackId: redPackId, redPackId: redPackId,
); );
} }
} }
} else {
showEnterRoomConfirm( static void openCurrentRoom(
roomId, BuildContext context, {
context, bool needOpenRedenvelope = false,
needOpenRedenvelope: needOpenRedenvelope, String? redPackId,
fromFloting: fromFloting, }) {
redPackId: redPackId, SCFloatIchart().remove();
); Provider.of<RealTimeCommunicationManager>(
} context,
} listen: false,
).loadRoomInfo(
static void openCurrentRoom(BuildContext context, { Provider.of<RealTimeCommunicationManager>(
bool needOpenRedenvelope = false, context,
String? redPackId, listen: false,
}) { ).currenRoom?.roomProfile?.roomProfile?.id ??
SCFloatIchart().remove(); "",
Provider.of<RealTimeCommunicationManager>(context, listen: false) );
.loadRoomInfo( Provider.of<SocialChatUserProfileManager>(
Provider context,
.of<RealTimeCommunicationManager>( listen: false,
context, ).fetchUserProfileData();
listen: false, Provider.of<RealTimeCommunicationManager>(
) context,
.currenRoom listen: false,
?.roomProfile ).retrieveMicrophoneList();
?.roomProfile Provider.of<RealTimeCommunicationManager>(context, listen: false)
?.id ?? .closeFullGame = true;
"", SCNavigatorUtils.push(
); context,
Provider.of<SocialChatUserProfileManager>(context, listen: false) '${VoiceRoomRoute.voiceRoom}?id=${Provider.of<RealTimeCommunicationManager>(context, listen: false).currenRoom?.roomProfile?.roomProfile?.id}',
.fetchUserProfileData(); );
Provider.of<RealTimeCommunicationManager>(context, listen: false) }
.retrieveMicrophoneList();
Provider static double getCurrenProgress(
.of<RealTimeCommunicationManager>(context, listen: false) int currentEnergy,
.closeFullGame = true; int maxEnergy,
SCNavigatorUtils.push( double maxWidth,
context, ) {
'${VoiceRoomRoute.voiceRoom}?id=${Provider var progress = (currentEnergy / maxEnergy);
.of<RealTimeCommunicationManager>(context, listen: false) var curren = progress * maxWidth;
.currenRoom if (curren > maxWidth) {
?.roomProfile return maxWidth;
?.roomProfile } else {
?.id}', return curren;
); }
}
}
static void goRecharge(BuildContext context) {
static double getCurrenProgress(int currentEnergy, SmartDialog.dismiss(tag: "showGoToRecharge");
int maxEnergy, SmartDialog.show(
double maxWidth,) { tag: "showGoToRecharge",
var progress = (currentEnergy / maxEnergy); alignment: Alignment.center,
var curren = progress * maxWidth; animationType: SmartAnimationType.fade,
if (curren > maxWidth) { builder: (_) {
return maxWidth; return Container(
} else { width: ScreenUtil().screenWidth * 0.75,
return curren; height: 120.w,
} padding: EdgeInsets.symmetric(horizontal: 10.w),
} decoration: BoxDecoration(
color: Colors.white,
static void goRecharge(BuildContext context) { borderRadius: BorderRadius.all(Radius.circular(12.w)),
SmartDialog.dismiss(tag: "showGoToRecharge"); ),
SmartDialog.show( child: Column(
tag: "showGoToRecharge", children: [
alignment: Alignment.center, SizedBox(height: 8.w),
animationType: SmartAnimationType.fade, Row(
builder: (_) { children: [
return Container( Spacer(),
width: ScreenUtil().screenWidth * 0.75, GestureDetector(
height: 120.w, child: Icon(Icons.close, color: Colors.black, size: 20.w),
padding: EdgeInsets.symmetric(horizontal: 10.w), onTap: () {
decoration: BoxDecoration( SmartDialog.dismiss(tag: "showGoToRecharge");
color: Colors.white, },
borderRadius: BorderRadius.all(Radius.circular(12.w)), ),
), SizedBox(width: 10.w),
child: Column( ],
children: [ ),
SizedBox(height: 8.w), Row(
Row( children: [
children: [ Expanded(
Spacer(), child: text(
GestureDetector( SCAppLocalizations.of(
child: Icon( context,
Icons.close, )!.insufhcientGoldsGoToRecharge,
color: Colors.black, textColor: Colors.black,
size: 20.w, textAlign: TextAlign.center,
), fontSize: 13.sp,
onTap: () { fontWeight: FontWeight.w600,
SmartDialog.dismiss(tag: "showGoToRecharge"); ),
}, ),
), ],
SizedBox(width: 10.w), ),
], SizedBox(height: 10.w),
), GestureDetector(
Row( child: Container(
children: [ alignment: Alignment.center,
Expanded( height: 38.w,
child: text( width: 125.w,
SCAppLocalizations.of( decoration: BoxDecoration(
context, gradient: LinearGradient(
)!.insufhcientGoldsGoToRecharge, colors: [Color(0xFFFEB219), Color(0xFFFF9326)],
textColor: Colors.black, ),
textAlign: TextAlign.center, borderRadius: BorderRadius.all(Radius.circular(8.w)),
fontSize: 13.sp, ),
fontWeight: FontWeight.w600, child: text(
), SCAppLocalizations.of(context)!.goToRecharge,
), textColor: Colors.white,
], fontSize: 13.sp,
), fontWeight: FontWeight.w600,
SizedBox(height: 10.w), ),
GestureDetector( ),
child: Container( onTap: () {
alignment: Alignment.center, closeAllDialogs();
height: 38.w, SCNavigatorUtils.push(
width: 125.w, context,
decoration: BoxDecoration( WalletRoute.recharge,
gradient: LinearGradient( replace: false,
colors: [ );
Color(0xFFFEB219), },
Color(0xFFFF9326), ),
], ],
), ),
borderRadius: BorderRadius.all(Radius.circular(8.w)), );
), },
child: text( );
SCAppLocalizations.of(context)!.goToRecharge, }
textColor: Colors.white,
fontSize: 13.sp, static int getRandomInt(int min, int max) {
fontWeight: FontWeight.w600, final random = Random();
), return min + random.nextInt(max - min + 1);
), }
onTap: () {
closeAllDialogs(); ///
SCNavigatorUtils.push( static bool touristCanMsg(BuildContext context) {
context, ///
WalletRoute.recharge, if (Provider.of<RealTimeCommunicationManager>(
replace: false, context,
); listen: false,
}, ).currenRoom?.roomProfile?.roomSetting?.touristMsg ??
), false) {
], } else {
), if (Provider.of<RealTimeCommunicationManager>(
); context,
}, listen: false,
); ).isTourists()) {
} SCTts.show(SCAppLocalizations.of(context)!.touristsCannotSendMessages);
return false;
static int getRandomInt(int min, int max) { }
final random = Random(); }
return min + random.nextInt(max - min + 1); return true;
} }
/// static void roomSCGlobalConfig(String roomId) {
static bool touristCanMsg(BuildContext context) { final defaultEffectsEnabled = !SCGlobalConfig.isLowPerformanceDevice;
/// SCGlobalConfig
if (Provider .isGiftSpecialEffects = SCGlobalConfig.clampVisualEffectPreference(
.of<RealTimeCommunicationManager>( DataPersistence.getBool(
context, "${AccountStorage().getCurrentUser()?.userProfile?.account}-GiftSpecialEffects",
listen: false, defaultValue: defaultEffectsEnabled,
) ),
.currenRoom );
?.roomProfile SCGlobalConfig
?.roomSetting .isEntryVehicleAnimation = SCGlobalConfig.clampVisualEffectPreference(
?.touristMsg ?? DataPersistence.getBool(
false) {} else { "${AccountStorage().getCurrentUser()?.userProfile?.account}-EntryVehicleAnimation",
if (Provider.of<RealTimeCommunicationManager>(context, listen: false) defaultValue: defaultEffectsEnabled,
.isTourists()) { ),
SCTts.show(SCAppLocalizations.of(context)!.touristsCannotSendMessages); );
return false; SCGlobalConfig
} .isFloatingAnimationInGlobal = SCGlobalConfig.clampVisualEffectPreference(
} DataPersistence.getBool(
return true; "${AccountStorage().getCurrentUser()?.userProfile?.account}-FloatingAnimationInGlobal",
} defaultValue: defaultEffectsEnabled,
),
static void roomSCGlobalConfig(String roomId) { );
SCGlobalConfig.isGiftSpecialEffects = DataPersistence.getBool( SCGlobalConfig
"${AccountStorage() .isLuckGiftSpecialEffects = SCGlobalConfig.clampVisualEffectPreference(
.getCurrentUser() DataPersistence.getBool(
?.userProfile "${AccountStorage().getCurrentUser()?.userProfile?.account}-LuckGiftSpecialEffects",
?.account}-GiftSpecialEffects", defaultValue: defaultEffectsEnabled,
defaultValue: true, ),
); );
SCGlobalConfig.isEntryVehicleAnimation = DataPersistence.getBool( DataPersistence.setLastTimeRoomId(roomId);
"${AccountStorage() }
.getCurrentUser()
?.userProfile static void closeAllDialogs() async {
?.account}-EntryVehicleAnimation", //
defaultValue: true, while (SmartDialog.checkExist(
); dialogTypes: {SmartAllDialogType.custom, SmartAllDialogType.attach},
SCGlobalConfig.isFloatingAnimationInGlobal = DataPersistence.getBool( )) {
"${AccountStorage() SmartDialog.dismiss();
.getCurrentUser() await Future.delayed(const Duration(milliseconds: 50));
?.userProfile }
?.account}-FloatingAnimationInGlobal", }
defaultValue: true, }
);
SCGlobalConfig.isLuckGiftSpecialEffects = DataPersistence.getBool(
"${AccountStorage()
.getCurrentUser()
?.userProfile
?.account}-LuckGiftSpecialEffects",
defaultValue: true,
);
DataPersistence.setLastTimeRoomId(roomId);
}
static void closeAllDialogs() async {
//
while (SmartDialog.checkExist(
dialogTypes: {SmartAllDialogType.custom, SmartAllDialogType.attach},
)) {
SmartDialog.dismiss();
await Future.delayed(const Duration(milliseconds: 50));
}
}
}

View File

@ -2,6 +2,21 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart'; import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
int? _resolveMemCacheDimension(double? logicalSize) {
if (logicalSize == null || !logicalSize.isFinite || logicalSize <= 0) {
return null;
}
final devicePixelRatio =
WidgetsBinding
.instance
.platformDispatcher
.implicitView
?.devicePixelRatio ??
2.0;
final pixelSize = (logicalSize * devicePixelRatio).round();
return pixelSize.clamp(1, 1920).toInt();
}
class CustomCachedImage extends StatelessWidget { class CustomCachedImage extends StatelessWidget {
final String imageUrl; final String imageUrl;
final double? width; final double? width;
@ -30,6 +45,8 @@ class CustomCachedImage extends StatelessWidget {
imageUrl: imageUrl, imageUrl: imageUrl,
width: width, width: width,
height: height, height: height,
memCacheWidth: _resolveMemCacheDimension(width),
memCacheHeight: _resolveMemCacheDimension(height),
fit: fit, fit: fit,
placeholder: (context, url) => placeholder ?? _defaultPlaceholder(), placeholder: (context, url) => placeholder ?? _defaultPlaceholder(),
errorWidget: errorWidget:

View File

@ -18,6 +18,22 @@ import '../../shared/data_sources/models/enum/sc_room_roles_type.dart';
const String kRoomCoverDefaultImg = "sc_images/general/sc_no_data.png"; const String kRoomCoverDefaultImg = "sc_images/general/sc_no_data.png";
int? _resolveImageCacheDimension(double? logicalSize) {
if (logicalSize == null || !logicalSize.isFinite || logicalSize <= 0) {
return null;
}
final devicePixelRatio =
WidgetsBinding
.instance
.platformDispatcher
.implicitView
?.devicePixelRatio ??
2.0;
final maxDimension = SCGlobalConfig.isLowPerformanceDevice ? 1280 : 1920;
final pixelSize = (logicalSize * devicePixelRatio).round();
return pixelSize.clamp(1, maxDimension).toInt();
}
String resolveRoomCoverUrl(String? roomId, String? roomCover) { String resolveRoomCoverUrl(String? roomId, String? roomCover) {
final cache = SCRoomProfileCache.getRoomProfile(roomId ?? ""); final cache = SCRoomProfileCache.getRoomProfile(roomId ?? "");
return SCRoomProfileCache.preferCachedValue(cache["roomCover"], roomCover) ?? return SCRoomProfileCache.preferCachedValue(cache["roomCover"], roomCover) ??
@ -133,7 +149,7 @@ Widget head({
headdress != null && headdress != null &&
SCPathUtils.getFileExtension(headdress).toLowerCase() == SCPathUtils.getFileExtension(headdress).toLowerCase() ==
".svga" && ".svga" &&
SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim SCGlobalConfig.allowsHighCostAnimations
? IgnorePointer( ? IgnorePointer(
child: SVGAHeadwearWidget( child: SVGAHeadwearWidget(
width: width, width: width,
@ -145,7 +161,7 @@ Widget head({
headdress != null && headdress != null &&
SCPathUtils.getFileExtension(headdress).toLowerCase() == SCPathUtils.getFileExtension(headdress).toLowerCase() ==
".mp4" && ".mp4" &&
SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim SCGlobalConfig.allowsHighCostAnimations
? SizedBox( ? SizedBox(
width: width, width: width,
height: width, height: width,
@ -187,6 +203,8 @@ Widget netImage({
url, url,
width: width, width: width,
height: height ?? width, height: height ?? width,
cacheWidth: _resolveImageCacheDimension(width),
cacheHeight: _resolveImageCacheDimension(height ?? width),
headers: buildNetworkImageHeaders(url), headers: buildNetworkImageHeaders(url),
fit: fit ?? BoxFit.cover, fit: fit ?? BoxFit.cover,
cache: true, cache: true,
@ -195,6 +213,7 @@ Widget netImage({
clearMemoryCacheWhenDispose: false, clearMemoryCacheWhenDispose: false,
clearMemoryCacheIfFailed: true, clearMemoryCacheIfFailed: true,
border: border, border: border,
filterQuality: FilterQuality.low,
gaplessPlayback: true, gaplessPlayback: true,
loadStateChanged: (ExtendedImageState state) { loadStateChanged: (ExtendedImageState state) {
if (state.extendedImageLoadState == LoadState.completed) { if (state.extendedImageLoadState == LoadState.completed) {

View File

@ -7,9 +7,10 @@ import 'package:provider/provider.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart';
import 'package:yumi/services/gift/gift_animation_manager.dart'; import 'package:yumi/services/gift/gift_animation_manager.dart';
/// ///
class LGiftAnimalPage extends StatefulWidget { class LGiftAnimalPage extends StatefulWidget {
const LGiftAnimalPage({super.key});
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
return _GiftAnimalPageState(); return _GiftAnimalPageState();
@ -18,632 +19,286 @@ class LGiftAnimalPage extends StatefulWidget {
class _GiftAnimalPageState extends State<LGiftAnimalPage> class _GiftAnimalPageState extends State<LGiftAnimalPage>
with TickerProviderStateMixin { with TickerProviderStateMixin {
static const Set<int> _luckyGiftMilestones = <int>{10, 20, 50, 100, 200, 300};
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return SizedBox(
height: 305.w, height: 332.w,
child: Consumer<GiftAnimationManager>(
builder: (context, ref, child) {
return Stack(
clipBehavior: Clip.none,
children: List.generate(
4,
(index) => _buildGiftTickerItem(context, ref, index),
),
);
},
),
);
}
Widget _buildGiftTickerItem(
BuildContext context,
GiftAnimationManager ref,
int index,
) {
final gift = ref.giftMap[index];
if (gift == null || ref.animationControllerList.length <= index) {
return const SizedBox.shrink();
}
final bean = ref.animationControllerList[index];
return AnimatedBuilder(
animation: bean.controller,
builder: (context, child) {
return Container(
margin: bean.verticalAnimation.value,
width: ScreenUtil().screenWidth * 0.78,
child: _buildGiftTickerCard(context, gift, bean.sizeAnimation.value),
);
},
);
}
Widget _buildGiftTickerCard(
BuildContext context,
LGiftModel gift,
double animatedSize,
) {
return SizedBox(
height: 52.w,
child: Stack( child: Stack(
alignment: AlignmentDirectional.centerStart,
clipBehavior: Clip.none,
children: [ children: [
Consumer<GiftAnimationManager>( Container(
builder: (context, ref, child) { width: ScreenUtil().screenWidth * 0.67,
return ref.giftMap[0] != null height: 42.w,
? AnimatedBuilder( padding: EdgeInsetsDirectional.only(
animation: ref.animationControllerList[0].controller, start: 3.w,
builder: (context, child) { end: 76.w,
return Container( top: 3.w,
margin: ref.animationControllerList[0].verticalAnimation.value, bottom: 3.w,
width: ScreenUtil().screenWidth * 0.75, ),
child: Stack( decoration: BoxDecoration(
alignment: AlignmentDirectional.centerStart, image: DecorationImage(
clipBehavior: Clip.none, image: AssetImage(getBg("")),
children: [ fit: BoxFit.fill,
Container( ),
padding: EdgeInsetsDirectional.only( ),
top: 5.w, child: Row(
), children: <Widget>[
width: ScreenUtil().screenWidth * 0.65, netImage(
height: url: gift.sendUserPic,
35.w, shape: BoxShape.circle,
decoration: BoxDecoration( width: 26.w,
image: DecorationImage( height: 26.w,
image: AssetImage( ),
getBg(""), SizedBox(width: 4.w),
), Expanded(
fit: BoxFit.fill, child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
socialchatNickNameText(
maxWidth: 88.w,
gift.sendUserName,
fontSize: 12.sp,
fontWeight: FontWeight.w500,
type: "",
needScroll: gift.sendUserName.characters.length > 8,
),
SizedBox(height: 1.w),
Row(
children: [
Text(
"${SCAppLocalizations.of(context)!.sendTo} ",
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
height: 1.0,
), ),
), ),
child: Row( Flexible(
mainAxisSize: MainAxisSize.min, child: Text(
children: <Widget>[ gift.sendToUserName,
SizedBox(width: 3.w), maxLines: 1,
netImage( overflow: TextOverflow.ellipsis,
url: ref.giftMap[0]!.sendUserPic, style: TextStyle(
shape: BoxShape.circle, fontSize: 10.sp,
width: 25.w, color: const Color(0xFFFFD400),
height: 25.w, height: 1.0,
), ),
SizedBox(width: 4.w), ),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
socialchatNickNameText(
maxWidth: 82.w,
ref.giftMap[0]!.sendUserName ?? "",
fontSize: 12.sp,
fontWeight: FontWeight.w500,
type: "",
needScroll:
(ref
.giftMap[0]!
.sendUserName
.characters
.length ??
0) >
8,
),
//SizedBox(width: 2.w,),
Row(
children: [
Text(
"${SCAppLocalizations.of(context)!.sendTo} ",
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
),
),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 62.w,
),
child: Text(
"${ref.giftMap[0]?.sendToUserName}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 10.sp,
color:Color(0xFFFFD400),
),
),
),
],
),
],
),
SizedBox(width: 4.w),
],
), ),
), ],
Positioned( ),
left: 155.w, ],
child: Row( ),
children: <Widget>[ ),
netImage( ],
url: ref.giftMap[0]!.giftPic, ),
fit: BoxFit.cover,
borderRadius: BorderRadius.circular(4.w),
width: 32.w,
height: 32.w,
),
SizedBox(width: 12.w),
Text.rich(
TextSpan(
children: [
TextSpan(
text: "x",
style: TextStyle(
fontSize:
ref
.animationControllerList[0]
.sizeAnimation
.value
.w,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
TextSpan(
text:
"${ref.giftMap[0]!.giftCount}",
style: TextStyle(
fontSize:
ref
.animationControllerList[0]
.sizeAnimation
.value,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
),
);
},
)
: Container();
},
), ),
Consumer<GiftAnimationManager>( PositionedDirectional(
builder: (context, ref, child) { end: 0,
return ref.giftMap[1] != null child: ConstrainedBox(
? AnimatedBuilder( constraints: BoxConstraints(
animation: ref.animationControllerList[1].controller, maxWidth: ScreenUtil().screenWidth * 0.30,
builder: (context, child) { minHeight: 40.w,
return Container( ),
margin: ref.animationControllerList[1].verticalAnimation.value, child: Row(
width: ScreenUtil().screenWidth * 0.75, mainAxisSize: MainAxisSize.min,
child: Stack( crossAxisAlignment: CrossAxisAlignment.center,
alignment: AlignmentDirectional.centerStart, children: <Widget>[
clipBehavior: Clip.none, netImage(
children: [ url: gift.giftPic,
Container( fit: BoxFit.cover,
padding: EdgeInsetsDirectional.only( borderRadius: BorderRadius.circular(4.w),
top: width: 34.w,
6.w, height: 34.w,
), ),
width: ScreenUtil().screenWidth * 0.65, SizedBox(width: 8.w),
height: Flexible(
35.w, child: FittedBox(
decoration: BoxDecoration( fit: BoxFit.scaleDown,
// borderRadius: BorderRadius.circular(52), alignment: Alignment.centerLeft,
// gradient: LinearGradient( child: _buildGiftCountLabel(gift, animatedSize),
// colors: [
// Color(0xffFFE400),
// Color(0xffFFE400).withOpacity(0.7),
// Color(0xffFFE400).withOpacity(0),
// ]
// ),
image: DecorationImage(
image: AssetImage(
getBg(""),
),
fit: BoxFit.fill,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(width: 3.w),
netImage(
url: ref.giftMap[1]!.sendUserPic,
shape: BoxShape.circle,
width: 25.w,
height: 25.w,
),
SizedBox(width: 4.w),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
socialchatNickNameText(
maxWidth: 82.w,
ref.giftMap[1]!.sendUserName ?? "",
fontSize: 12.sp,
fontWeight: FontWeight.w500,
type: "",
needScroll:
(ref
.giftMap[1]!
.sendUserName
.characters
.length ??
0) >
8,
),
//SizedBox(width: 2.w,),
Row(
children: [
Text(
"${SCAppLocalizations.of(context)!.sendTo} ",
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
),
),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 62.w,
),
child: Text(
"${ref.giftMap[1]?.sendToUserName}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 10.sp,
color:Color(0xFFFFD400),
),
),
),
],
),
],
),
SizedBox(width: 4.w),
],
),
),
Positioned(
left: 155.w,
child: Row(
children: <Widget>[
netImage(
url: ref.giftMap[1]!.giftPic,
fit: BoxFit.cover,
borderRadius: BorderRadius.circular(4.w),
width: 32.w,
height: 32.w,
),
SizedBox(width: 12.w),
Text.rich(
TextSpan(
children: [
TextSpan(
text: "x",
style: TextStyle(
fontSize:
ref
.animationControllerList[1]
.sizeAnimation
.value
.w,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
TextSpan(
text:
"${ref.giftMap[1]!.giftCount}",
style: TextStyle(
fontSize:
ref
.animationControllerList[1]
.sizeAnimation
.value,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
), ),
); ),
}, ],
) ),
: Container(); ),
},
),
Consumer<GiftAnimationManager>(
builder: (context, ref, child) {
return ref.giftMap[2] != null
? AnimatedBuilder(
animation: ref.animationControllerList[2].controller,
builder: (context, child) {
return Container(
margin: ref.animationControllerList[2].verticalAnimation.value,
width: ScreenUtil().screenWidth * 0.75,
child: Stack(
alignment: AlignmentDirectional.centerStart,
clipBehavior: Clip.none,
children: [
Container(
padding: EdgeInsetsDirectional.only(
top:
6.w,
),
width: ScreenUtil().screenWidth * 0.65,
height:
35.w,
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(52),
// gradient: LinearGradient(
// colors: [
// Color(0xffFFE400),
// Color(0xffFFE400).withOpacity(0.7),
// Color(0xffFFE400).withOpacity(0),
// ]
// ),
image: DecorationImage(
image: AssetImage(
getBg(""),
),
fit: BoxFit.fill,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(width: 3.w),
netImage(
url: ref.giftMap[2]!.sendUserPic,
shape: BoxShape.circle,
width: 25.w,
height: 25.w,
),
SizedBox(width: 4.w),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
socialchatNickNameText(
maxWidth: 82.w,
ref.giftMap[2]!.sendUserName ?? "",
fontSize: 12.sp,
fontWeight: FontWeight.w500,
type: "",
needScroll:
(ref
.giftMap[2]!
.sendUserName
.characters
.length ??
0) >
8,
),
//SizedBox(width: 2.w,),
Row(
children: [
Text(
"${SCAppLocalizations.of(context)!.sendTo} ",
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
),
),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 62.w,
),
child: Text(
"${ref.giftMap[2]?.sendToUserName}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 10.sp,
color:Color(0xFFFFD400),
),
),
),
],
),
],
),
SizedBox(width: 4.w),
],
),
),
Positioned(
left: 155.w,
child: Row(
children: <Widget>[
netImage(
url: ref.giftMap[2]!.giftPic,
fit: BoxFit.cover,
borderRadius: BorderRadius.circular(4.w),
width: 32.w,
height: 32.w,
),
SizedBox(width: 12.w),
Text.rich(
TextSpan(
children: [
TextSpan(
text: "x",
style: TextStyle(
fontSize:
ref
.animationControllerList[2]
.sizeAnimation
.value
.w,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
TextSpan(
text:
"${ref.giftMap[2]!.giftCount}",
style: TextStyle(
fontSize:
ref
.animationControllerList[2]
.sizeAnimation
.value,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
),
);
},
)
: Container();
},
),
Consumer<GiftAnimationManager>(
builder: (context, ref, child) {
return ref.giftMap[3] != null
? AnimatedBuilder(
animation: ref.animationControllerList[3].controller,
builder: (context, child) {
return Container(
margin: ref.animationControllerList[3].verticalAnimation.value,
width: ScreenUtil().screenWidth * 0.75,
child: Stack(
alignment: AlignmentDirectional.centerStart,
clipBehavior: Clip.none,
children: [
Container(
padding: EdgeInsetsDirectional.only(
top:
6.w,
),
width: ScreenUtil().screenWidth * 0.65,
height:
35.w,
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(52),
// gradient: LinearGradient(
// colors: [
// Color(0xffFFE400),
// Color(0xffFFE400).withOpacity(0.7),
// Color(0xffFFE400).withOpacity(0),
// ]
// ),
image: DecorationImage(
image: AssetImage(
getBg(""),
),
fit: BoxFit.fill,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(width: 3.w),
netImage(
url: ref.giftMap[3]!.sendUserPic,
shape: BoxShape.circle,
width: 25.w,
height: 25.w,
),
SizedBox(width: 4.w),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
socialchatNickNameText(
maxWidth: 82.w,
ref.giftMap[3]!.sendUserName ?? "",
fontSize: 12.sp,
fontWeight: FontWeight.w500,
type: "",
needScroll:
(ref
.giftMap[3]!
.sendUserName
.characters
.length ??
0) >
8,
),
//SizedBox(width: 2.w,),
Row(
children: [
Text(
"${SCAppLocalizations.of(context)!.sendTo} ",
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
),
),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 62.w,
),
child: Text(
"${ref.giftMap[3]?.sendToUserName}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 10.sp,
color:Color(0xFFFFD400),
),
),
),
],
),
],
),
SizedBox(width: 4.w),
],
),
),
Positioned(
left: 155.w,
child: Row(
children: <Widget>[
netImage(
url: ref.giftMap[3]!.giftPic,
fit: BoxFit.cover,
borderRadius: BorderRadius.circular(4.w),
width: 32.w,
height: 32.w,
),
SizedBox(width: 12.w),
Text.rich(
TextSpan(
children: [
TextSpan(
text: "x",
style: TextStyle(
fontSize:
ref
.animationControllerList[3]
.sizeAnimation
.value
.w,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
TextSpan(
text:
"${ref.giftMap[3]!.giftCount}",
style: TextStyle(
fontSize:
ref
.animationControllerList[3]
.sizeAnimation
.value,
fontStyle: FontStyle.italic,
color: Color(0xFFFFD400),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
),
);
},
)
: Container();
},
), ),
], ],
), ),
); );
} }
Widget _buildGiftCountLabel(LGiftModel gift, double animatedSize) {
final isMilestone = _isLuckyGiftMilestone(gift);
final xFontSize = animatedSize * (isMilestone ? 1.1 : 1.0);
final countFontSize = animatedSize * (isMilestone ? 1.45 : 1.0);
return Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"x",
style: TextStyle(
fontSize: xFontSize,
fontStyle: FontStyle.italic,
color:
isMilestone
? const Color(0xFFFFE08A)
: const Color(0xFFFFD400),
fontWeight: FontWeight.bold,
height: 1,
shadows:
isMilestone
? const [
Shadow(
color: Color(0xCC7A3E00),
blurRadius: 8,
offset: Offset(0, 2),
),
]
: null,
),
),
SizedBox(width: 2.w),
isMilestone
? _buildMilestoneGiftCountText(
_giftCountText(gift.giftCount),
fontSize: countFontSize,
)
: Text(
_giftCountText(gift.giftCount),
style: TextStyle(
fontSize: countFontSize,
fontStyle: FontStyle.italic,
color: const Color(0xFFFFD400),
fontWeight: FontWeight.bold,
height: 1,
),
),
],
),
);
}
Widget _buildMilestoneGiftCountText(
String countText, {
required double fontSize,
}) {
return Stack(
children: [
Text(
countText,
style: TextStyle(
fontSize: fontSize,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w900,
height: 1,
foreground:
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.8
..color = const Color(0xFF7A3E00),
),
),
ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback:
(bounds) => const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Color(0xFFFFF6C7),
Color(0xFFFFE07A),
Color(0xFFFFB800),
],
).createShader(bounds),
child: Text(
countText,
style: TextStyle(
fontSize: fontSize,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w900,
color: Colors.white,
height: 1,
shadows: const [
Shadow(
color: Color(0xCC6A3700),
blurRadius: 10,
offset: Offset(0, 3),
),
],
),
),
),
],
);
}
bool _isLuckyGiftMilestone(LGiftModel gift) {
if (!gift.isLuckyGift) {
return false;
}
final count = gift.giftCount;
if (count % 1 != 0) {
return false;
}
return _luckyGiftMilestones.contains(count.toInt());
}
String _giftCountText(num count) {
return count % 1 == 0 ? count.toInt().toString() : count.toString();
}
@override @override
void dispose() { void dispose() {
Provider.of<GiftAnimationManager>( Provider.of<GiftAnimationManager>(
@ -770,7 +425,9 @@ class LGiftModel {
// //
num giftCount = 0; num giftCount = 0;
//
bool isLuckyGift = false;
//id //id
String labelId = ""; String labelId = "";
} }

View File

@ -58,7 +58,8 @@ class _MsgItemState extends State<MsgItem> {
} }
/// ///
if (widget.msg.type == SCRoomMsgType.gift) { if (widget.msg.type == SCRoomMsgType.gift ||
widget.msg.type == SCRoomMsgType.luckGiftAnimOther) {
return _buildGiftMsg(context); return _buildGiftMsg(context);
} }

View File

@ -13,6 +13,10 @@
- 本轮按需求暂未处理网络链路上的启动等待,例如审核态检查或远端启动页配置请求。 - 本轮按需求暂未处理网络链路上的启动等待,例如审核态检查或远端启动页配置请求。
## 已完成模块 ## 已完成模块
- 已修复礼物页幸运礼物发送链路:当前 `gift_page` 会按礼物类型分流,普通礼物继续走 `/gift/batch``LUCKY_GIFT/LUCK/MAGIC` 改走已存在的 `/gift/give/lucky-gift`;幸运/魔法礼物成功后不再本地伪造普通 `GIFT` 消息,而是改发房间内 `LUCK_GIFT_ANIM_OTHER` 动画消息,避免和后端幸运礼物开奖/补发逻辑打架;同时送礼失败已补用户提示,且对 `standardId` 缺失的幸运礼物增加了前置拦截,避免继续点发送却只有日志没有反馈。
- 已继续补齐幸运礼物前端可见反馈:根据真机日志确认 `giveLuckyGift` 和腾讯 IM 房间消息发送都已成功,当前问题收敛为发送端没有本地回显、且 `LUCK_GIFT_ANIM_OTHER` 收到后未真正接入房间礼物动画;现已在发送成功后先本地触发一轮房间礼物上飘反馈,并把收到的 `LUCK_GIFT_ANIM_OTHER` 直接接入现有房间礼物动画 listener确保发送端和房间内其它端都能看到即时反应。排障期间临时加过的发送并发锁已在后续需求确认后撤回不再限制幸运礼物连点。
- 已按最新确认调整幸运礼物点击策略:撤回前一轮为排障临时加上的发送并发锁,恢复幸运礼物可连续点击;当前连点时每次请求仍走 `/gift/give/lucky-gift`,并继续通过本地回显 + `LUCK_GIFT_ANIM_OTHER` 接入现有房间礼物动画 listener 的方式提供与普通礼物一致的即时播报体验。
- 已继续优化房间礼物播报 UI礼物上飘条目高度与内部约束已重新收紧修复底部 `bottom overflow`;同时为幸运礼物新增 10/20/50/100/200/300 连击特殊样式,这些档位的连击数字会以更大的“霸气金”渐变字显示,普通礼物与非里程碑连击保持原样式。
- 已修复 `Me -> Friends/Fans 列表 -> 他人主页 -> 返回 Me` 后个人资料串号的问题:`MePage2` 现只读取当前登录用户资料,`SocialChatUserProfileManager.userProfile` 继续只承载“当前查看中的详情页用户”,同时移除了 `syncCurrentUserProfile()` 对详情态的覆盖,避免查看他人资料后把 `Me` 页错误渲染成对方信息。 - 已修复 `Me -> Friends/Fans 列表 -> 他人主页 -> 返回 Me` 后个人资料串号的问题:`MePage2` 现只读取当前登录用户资料,`SocialChatUserProfileManager.userProfile` 继续只承载“当前查看中的详情页用户”,同时移除了 `syncCurrentUserProfile()` 对详情态的覆盖,避免查看他人资料后把 `Me` 页错误渲染成对方信息。
- 已按本轮动效替换需求完成首页底部 tab 动效接入,并同步将本轮新增本地 `.svga` 资源统一改成项目命名风格:当前 `Home / Explore / Message / Me` 已分别使用 `sc_icon_home_anim.svga / sc_icon_explore_anim.svga / sc_icon_message_anim.svga / sc_icon_me_anim.svga`,并继续保留原有 png 作为失败兜底;后续新增本地动效资源默认也按 `sc_icon_*_anim` 规则命名。 - 已按本轮动效替换需求完成首页底部 tab 动效接入,并同步将本轮新增本地 `.svga` 资源统一改成项目命名风格:当前 `Home / Explore / Message / Me` 已分别使用 `sc_icon_home_anim.svga / sc_icon_explore_anim.svga / sc_icon_message_anim.svga / sc_icon_me_anim.svga`,并继续保留原有 png 作为失败兜底;后续新增本地动效资源默认也按 `sc_icon_*_anim` 规则命名。
- 已将语言房右侧 `game` 悬浮入口从 emoji 占位替换为本地动效资源,并按最新反馈移除外层圆形 `container`:当前直接使用 `sc_icon_room_game_entry_anim.svga` 本体作为入口展示,尺寸与原入口占位一致;若 SVGA 加载失败,会自动回退到项目原有 `sc_icon_botton_game.png` - 已将语言房右侧 `game` 悬浮入口从 emoji 占位替换为本地动效资源,并按最新反馈移除外层圆形 `container`:当前直接使用 `sc_icon_room_game_entry_anim.svga` 本体作为入口展示,尺寸与原入口占位一致;若 SVGA 加载失败,会自动回退到项目原有 `sc_icon_botton_game.png`
@ -47,6 +51,7 @@
- 已继续定位“游戏画面可见但触摸无响应”的 Flutter 侧原因:当前实现此前一直把 BAISHUN H5 页面放在 `showGeneralDialog` 底部弹层里承载,同时 App 启动时又全局锁定了 `portraitUp`;对于 `FishingStar` 这类横版游戏,这会叠加出“平台视图在弹层容器中的命中不稳定 + iPhone 端实际不支持横屏”的问题。现已改为用独立路由承载 BAISHUN 游戏页,并按启动数据里的 `orientation` 动态切换横竖屏;同时 iOS `Info.plist` 已补充手机横屏支持,便于继续验证横版游戏触摸是否恢复。上述调整仍属于本轮 BAISHUN 联调修正,不是新的正式产品交互。 - 已继续定位“游戏画面可见但触摸无响应”的 Flutter 侧原因:当前实现此前一直把 BAISHUN H5 页面放在 `showGeneralDialog` 底部弹层里承载,同时 App 启动时又全局锁定了 `portraitUp`;对于 `FishingStar` 这类横版游戏,这会叠加出“平台视图在弹层容器中的命中不稳定 + iPhone 端实际不支持横屏”的问题。现已改为用独立路由承载 BAISHUN 游戏页,并按启动数据里的 `orientation` 动态切换横竖屏;同时 iOS `Info.plist` 已补充手机横屏支持,便于继续验证横版游戏触摸是否恢复。上述调整仍属于本轮 BAISHUN 联调修正,不是新的正式产品交互。
- 已按最新联调反馈回撤 BAISHUN 页面的横屏适配:当前后续不会接横屏游戏,因此已去掉游戏页按 `orientation` 切换系统方向的逻辑,并撤回 iPhone 端新增的横屏声明,只保留独立路由承载 BAISHUN 页面这一项 Flutter 容器修正。同时已继续增强临时调试,在 H5 侧补充 `pointerdown / touchstart / mousedown / click` 事件回传,用于下一轮真机直接确认“点击是否真的进到了 H5 DOM”这些输入事件日志依旧只用于本轮排障后续需要删除。 - 已按最新联调反馈回撤 BAISHUN 页面的横屏适配:当前后续不会接横屏游戏,因此已去掉游戏页按 `orientation` 切换系统方向的逻辑,并撤回 iPhone 端新增的横屏声明,只保留独立路由承载 BAISHUN 页面这一项 Flutter 容器修正。同时已继续增强临时调试,在 H5 侧补充 `pointerdown / touchstart / mousedown / click` 事件回传,用于下一轮真机直接确认“点击是否真的进到了 H5 DOM”这些输入事件日志依旧只用于本轮排障后续需要删除。
- 已按当前收尾需求移除 BAISHUN 临时调试面板:`BS DEBUG` 组件和相关调试入口现已从游戏页删除,不再作为正式功能保留;同时游戏页展示高度已恢复为接近半屏的底部弹出样式,回到最初设定的房内游戏视觉形态。 - 已按当前收尾需求移除 BAISHUN 临时调试面板:`BS DEBUG` 组件和相关调试入口现已从游戏页删除,不再作为正式功能保留;同时游戏页展示高度已恢复为接近半屏的底部弹出样式,回到最初设定的房内游戏视觉形态。
- 已继续微调 BAISHUN 游戏页高度:由于真实 H5 内容本身就是接近半屏,且顶部还需要预留拖拽条和标题栏空间,当前底部游戏页容器高度已从上一版的 `0.56` 屏高上调到 `0.62`,避免视觉上看起来比预期半屏更矮。
- 创建并持续维护进度跟踪文件。 - 创建并持续维护进度跟踪文件。
- 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。 - 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。
- 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。 - 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。