This commit is contained in:
NIGGER SLAYER 2026-04-21 15:58:29 +08:00
parent 1b0dd6f3ea
commit 7e136f4d7d
11 changed files with 2543 additions and 2407 deletions

View File

@ -502,7 +502,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = S9X2AJ2US9; DEVELOPMENT_TEAM = S9X2AJ2US9;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -511,7 +511,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.0; MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty; PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -693,7 +693,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = F33K8VUZ62; DEVELOPMENT_TEAM = F33K8VUZ62;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -702,7 +702,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.0; MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty; PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -722,7 +722,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = F33K8VUZ62; DEVELOPMENT_TEAM = F33K8VUZ62;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -731,7 +731,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.0; MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty; PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -410,7 +410,8 @@ class _YumiApplicationState extends State<YumiApplication> {
if (SCGlobalConfig.allowsHighCostAnimations) if (SCGlobalConfig.allowsHighCostAnimations)
Consumer<RtcProvider>( Consumer<RtcProvider>(
builder: (context, rtcProvider, _) { builder: (context, rtcProvider, _) {
if (!rtcProvider.roomVisualEffectsEnabled) { if (!rtcProvider
.shouldShowRoomVisualEffects) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return const Positioned.fill( return const Positioned.fill(
@ -420,15 +421,24 @@ class _YumiApplicationState extends State<YumiApplication> {
); );
}, },
), ),
Positioned.fill( Consumer<RtcProvider>(
child: RoomGiftSeatFlightOverlay( builder: (context, rtcProvider, _) {
controller: RoomGiftSeatFlightController(), if (!rtcProvider
resolveTargetKey: .shouldShowRoomVisualEffects) {
(userId) => Provider.of<RtcProvider>( return const SizedBox.shrink();
context, }
listen: false, return Positioned.fill(
).getSeatGlobalKeyByIndex(userId), child: RoomGiftSeatFlightOverlay(
), controller:
RoomGiftSeatFlightController(),
resolveTargetKey:
(userId) => Provider.of<RtcProvider>(
context,
listen: false,
).getSeatGlobalKeyByIndex(userId),
),
);
},
), ),
], ],
); );

View File

@ -17,6 +17,7 @@ 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/tools/sc_network_image_utils.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart'; import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart'; import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart'; import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart';
import 'package:yumi/ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart'; import 'package:yumi/ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_screen.dart'; import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_screen.dart';
@ -140,6 +141,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
RoomEntranceHelper.clearQueue(); RoomEntranceHelper.clearQueue();
_clearLuckyGiftComboSessions(); _clearLuckyGiftComboSessions();
_giftSeatFlightController.clear(); _giftSeatFlightController.clear();
OverlayManager().removeRoom();
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend'); SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
SCGiftVapSvgaManager().stopPlayback(); SCGiftVapSvgaManager().stopPlayback();
} }

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@ class SCAppGeneralManager extends ChangeNotifier {
/// ///
List<SocialChatGiftRes> giftResList = []; List<SocialChatGiftRes> giftResList = [];
final Map<String, SocialChatGiftRes> _giftByIdMap = {}; final Map<String, SocialChatGiftRes> _giftByIdMap = {};
final Map<String, SocialChatGiftRes> _giftByStandardIdMap = {};
final Set<String> _warmedGiftCoverUrls = <String>{}; final Set<String> _warmedGiftCoverUrls = <String>{};
final Set<String> _warmingGiftCoverUrls = <String>{}; final Set<String> _warmingGiftCoverUrls = <String>{};
bool _isFetchingGiftList = false; bool _isFetchingGiftList = false;
@ -169,6 +170,7 @@ class SCAppGeneralManager extends ChangeNotifier {
void _rebuildGiftTabs({required bool includeCustomized}) { void _rebuildGiftTabs({required bool includeCustomized}) {
giftByTab.clear(); giftByTab.clear();
_giftByIdMap.clear(); _giftByIdMap.clear();
_giftByStandardIdMap.clear();
for (var gift in giftResList) { for (var gift in giftResList) {
giftByTab[gift.giftTab]; giftByTab[gift.giftTab];
var gmap = giftByTab[gift.giftTab]; var gmap = giftByTab[gift.giftTab];
@ -187,6 +189,10 @@ class SCAppGeneralManager extends ChangeNotifier {
} }
giftByTab["ALL"] = gAllMap; giftByTab["ALL"] = gAllMap;
_giftByIdMap[gift.id!] = gift; _giftByIdMap[gift.id!] = gift;
final standardId = (gift.standardId ?? "").trim();
if (standardId.isNotEmpty && standardId != "0") {
_giftByStandardIdMap[standardId] = gift;
}
} }
if (includeCustomized) { if (includeCustomized) {
@ -277,6 +283,18 @@ class SCAppGeneralManager extends ChangeNotifier {
return _giftByIdMap[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) { void downLoad(List<SocialChatGiftRes> giftResList) {
final scheduledPaths = <String>{}; final scheduledPaths = <String>{};
var scheduledCount = 0; var scheduledCount = 0;

View File

@ -24,6 +24,7 @@ class OverlayManager {
); );
bool _isPlaying = false; bool _isPlaying = false;
OverlayEntry? _currentOverlayEntry; OverlayEntry? _currentOverlayEntry;
SCFloatingMessage? _currentMessage;
bool _isProcessing = false; bool _isProcessing = false;
bool _isDisposed = false; bool _isDisposed = false;
@ -102,17 +103,10 @@ class OverlayManager {
if (_messageQueue.isEmpty) return; if (_messageQueue.isEmpty) return;
final messageToProcess = _messageQueue.first; final messageToProcess = _messageQueue.first;
if (messageToProcess?.type == 1) { if (!_shouldDisplayMessage(context, messageToProcess)) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>( _messageQueue.removeFirst();
context, _safeScheduleNext();
listen: false, return;
);
if (rtcProvider.currenRoom == null) {
//
_messageQueue.removeFirst();
_safeScheduleNext();
return;
}
} }
// //
@ -127,9 +121,11 @@ class OverlayManager {
void _playMessage(SCFloatingMessage message) { void _playMessage(SCFloatingMessage message) {
_isPlaying = true; _isPlaying = true;
_currentMessage = message;
final context = navigatorKey.currentState?.context; final context = navigatorKey.currentState?.context;
if (context == null || !context.mounted) { if (context == null || !context.mounted) {
_isPlaying = false; _isPlaying = false;
_currentMessage = null;
_safeScheduleNext(); _safeScheduleNext();
return; return;
} }
@ -159,10 +155,12 @@ class OverlayManager {
_currentOverlayEntry?.remove(); _currentOverlayEntry?.remove();
_currentOverlayEntry = null; _currentOverlayEntry = null;
_isPlaying = false; _isPlaying = false;
_currentMessage = null;
_safeScheduleNext(); _safeScheduleNext();
} catch (e) { } catch (e) {
debugPrint('清理悬浮消息出错: $e'); debugPrint('清理悬浮消息出错: $e');
_isPlaying = false; _isPlaying = false;
_currentMessage = null;
_safeScheduleNext(); _safeScheduleNext();
} }
} }
@ -211,14 +209,16 @@ class OverlayManager {
_isDisposed = true; _isDisposed = true;
_currentOverlayEntry?.remove(); _currentOverlayEntry?.remove();
_currentOverlayEntry = null; _currentOverlayEntry = null;
_currentMessage = null;
_messageQueue.clear(); _messageQueue.clear();
_isPlaying = false; _isPlaying = false;
_isProcessing = false; _isProcessing = false;
} }
void removeRoom() { void removeRoom() {
// PriorityQueue _removeActiveRoomMessage();
_removeMessagesByType(1); _removeMessagesByType(1);
_removeMessagesByType(0);
} }
// //
@ -247,4 +247,41 @@ class OverlayManager {
int get queueLength => _messageQueue.length; int get queueLength => _messageQueue.length;
bool get isPlaying => _isPlaying; bool get isPlaying => _isPlaying;
bool _shouldDisplayMessage(BuildContext context, SCFloatingMessage? message) {
if (message == null) {
return false;
}
if (message.type != 0 && message.type != 1) {
return true;
}
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
);
if (!rtcProvider.shouldShowRoomVisualEffects) {
return false;
}
final currentRoomId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
final messageRoomId = (message.roomId ?? "").trim();
if (currentRoomId.isEmpty || messageRoomId.isEmpty) {
return false;
}
return currentRoomId == messageRoomId;
}
void _removeActiveRoomMessage() {
final activeMessage = _currentMessage;
if (activeMessage == null ||
(activeMessage.type != 0 && activeMessage.type != 1)) {
return;
}
_currentOverlayEntry?.remove();
_currentOverlayEntry = null;
_currentMessage = null;
_isPlaying = false;
_safeScheduleNext();
}
} }

View File

@ -223,6 +223,7 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
ImageProvider<Object>? _activeImageProvider; ImageProvider<Object>? _activeImageProvider;
Offset? _activeTargetOffset; Offset? _activeTargetOffset;
bool _isPlaying = false; bool _isPlaying = false;
int _sessionToken = 0;
@override @override
void initState() { void initState() {
@ -279,6 +280,7 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
} }
void _clear() { void _clear() {
_sessionToken += 1;
_queue.clear(); _queue.clear();
_controller.stop(); _controller.stop();
_controller.reset(); _controller.reset();
@ -372,16 +374,22 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
if (_isPlaying || _queue.isEmpty || !mounted) { if (_isPlaying || _queue.isEmpty || !mounted) {
return; return;
} }
final scheduledToken = _sessionToken;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_startNextAnimation(); _startNextAnimation(expectedSessionToken: scheduledToken);
}); });
} }
Future<void> _startNextAnimation() async { Future<void> _startNextAnimation({int? expectedSessionToken}) async {
if (!mounted || _isPlaying || _queue.isEmpty) { if (!mounted ||
_isPlaying ||
_queue.isEmpty ||
(expectedSessionToken != null &&
expectedSessionToken != _sessionToken)) {
return; return;
} }
final activeSessionToken = _sessionToken;
final queuedRequest = _queue.removeFirst(); final queuedRequest = _queue.removeFirst();
final targetOffset = _resolveTargetOffset( final targetOffset = _resolveTargetOffset(
queuedRequest.request.targetUserId, queuedRequest.request.targetUserId,
@ -389,7 +397,7 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
if (targetOffset == null) { if (targetOffset == null) {
if (queuedRequest.retryCount < 6) { if (queuedRequest.retryCount < 6) {
Future.delayed(const Duration(milliseconds: 120), () { Future.delayed(const Duration(milliseconds: 120), () {
if (!mounted) { if (!mounted || activeSessionToken != _sessionToken) {
return; return;
} }
_queue.addFirst( _queue.addFirst(
@ -424,7 +432,9 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
).timeout(const Duration(milliseconds: 250)); ).timeout(const Duration(milliseconds: 250));
} catch (_) {} } catch (_) {}
if (!mounted || _activeRequest != queuedRequest.request) { if (!mounted ||
activeSessionToken != _sessionToken ||
_activeRequest != queuedRequest.request) {
return; return;
} }

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.1.0+2 version: 1.1.1+3
environment: environment:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 KiB

After

Width:  |  Height:  |  Size: 426 KiB

View File

@ -17,6 +17,8 @@
- 已继续修正连击播报条“单点送 1 个却直接跳 `+5`”的问题:顶部礼物播报条现已接入 `customAnimationCount` 的点击粒度信息,数量动画不再只按合并后的总量做补间,而是按“单次点击对应的数量步长”逐步递进;例如连续点击 5 次、每次送 1 个时,会按 `+1 +1 +1 +1 +1` 的视觉节奏补齐,而不是直接把右侧数量跳成 `+5` - 已继续修正连击播报条“单点送 1 个却直接跳 `+5`”的问题:顶部礼物播报条现已接入 `customAnimationCount` 的点击粒度信息,数量动画不再只按合并后的总量做补间,而是按“单次点击对应的数量步长”逐步递进;例如连续点击 5 次、每次送 1 个时,会按 `+1 +1 +1 +1 +1` 的视觉节奏补齐,而不是直接把右侧数量跳成 `+5`
- 已继续修正连击档位特效偶发“不播”的问题:当前连击 `SVGA` 触发已改为只命中仓库里真实存在资源的档位集合,不再把 `20/88/200...` 这类当前工程里没有对应特效文件的档位当成可播放资源;同时命中判断会按“本次新增后跨过的最高有效资源档位”来触发,避免因为本地批量窗口把多次点击合成一包后,先命中到空资源路径而看起来像整段特效都没播。 - 已继续修正连击档位特效偶发“不播”的问题:当前连击 `SVGA` 触发已改为只命中仓库里真实存在资源的档位集合,不再把 `20/88/200...` 这类当前工程里没有对应特效文件的档位当成可播放资源;同时命中判断会按“本次新增后跨过的最高有效资源档位”来触发,避免因为本地批量窗口把多次点击合成一包后,先命中到空资源路径而看起来像整段特效都没播。
- 已按 2026-04-21 最新回归继续修正“飞向麦位的自制动画”缺失问题:座位飞屏现已从房间统一调度器的延后队列中移出,恢复为收到礼物消息后直接入队到全局 `RoomGiftSeatFlightOverlay`;保留既有的图片预热、同会话队列上限和 `customAnimationCount` 逐个飞行逻辑,只撤回此前对这一核心反馈动画的延迟调度,避免用户在礼物命中全屏特效或调度状态未及时清空时误以为飞屏动画消失。 - 已按 2026-04-21 最新回归继续修正“飞向麦位的自制动画”缺失问题:座位飞屏现已从房间统一调度器的延后队列中移出,恢复为收到礼物消息后直接入队到全局 `RoomGiftSeatFlightOverlay`;保留既有的图片预热、同会话队列上限和 `customAnimationCount` 逐个飞行逻辑,只撤回此前对这一核心反馈动画的延迟调度,避免用户在礼物命中全屏特效或调度状态未及时清空时误以为飞屏动画消失。
- 已继续加固座位飞屏退出房间后的残留风险:全局 `RoomGiftSeatFlightOverlay` 现已和房间可视特效开关绑定,离开房间后不会再继续挂在首页/探索/消息/我的等页面上显示;同时 overlay 内部的 `postFrame / delayed retry / image precache` 回调已补上会话 token 校验,避免房间退出后旧动画任务把屏幕中央的静态礼物图再次拉起并长期悬挂。
- 已继续修正“房间飘屏在首页/探索/消息/我的也会刷到”的问题:根层房间礼物特效层现已统一改为依据 `RtcProvider.shouldShowRoomVisualEffects` 判断显示,而不再只看偏松的 `roomVisualEffectsEnabled`;同时 `OverlayManager` 在播放房间礼物/幸运礼物飘屏前会再次校验“当前仍在房间可视态且消息房间号与当前房间一致”,房间最小化或退出时也会主动清掉正在播和排队中的房间飘屏,避免房间内的 `5 times`、礼物飘屏等效果溢出到首页等非房间页面。
## 本轮启动优化(非网络) ## 本轮启动优化(非网络)
- 已补回启动页正式展示逻辑:当前 `Weekly Star / Last Weekly CP` 两套自定义启动页都改为“本地有缓存才展示、无缓存不展示”;由于现阶段周榜与 CP 榜接口链路都还未 ready缓存刷新逻辑已先关闭所以当前启动阶段会直接回退到默认 splash不再展示这两套定制视觉稿。相关恢复入口已在缓存类里用 `api-ready-launch-splash` 注释标记,后续接口 ready 后可直接搜索接回。 - 已补回启动页正式展示逻辑:当前 `Weekly Star / Last Weekly CP` 两套自定义启动页都改为“本地有缓存才展示、无缓存不展示”;由于现阶段周榜与 CP 榜接口链路都还未 ready缓存刷新逻辑已先关闭所以当前启动阶段会直接回退到默认 splash不再展示这两套定制视觉稿。相关恢复入口已在缓存类里用 `api-ready-launch-splash` 注释标记,后续接口 ready 后可直接搜索接回。
@ -58,6 +60,14 @@
- 已继续收口幸运礼物 `burst` 金额文案与 `svga` 本体的时机同步:此前中央金币文本直接跟外层中奖数据显隐,而 `svga` 自身还存在资源加载和单次播放完成的生命周期,所以两者在出现/消失时会有肉眼可见的前后差;当前已给 `SCSvgaAssetWidget` 补上播放开始/结束回调,并让 `burst` 中央金额只在 `svga` 真正开始播时显示、在它播完清帧时一并隐藏。 - 已继续收口幸运礼物 `burst` 金额文案与 `svga` 本体的时机同步:此前中央金币文本直接跟外层中奖数据显隐,而 `svga` 自身还存在资源加载和单次播放完成的生命周期,所以两者在出现/消失时会有肉眼可见的前后差;当前已给 `SCSvgaAssetWidget` 补上播放开始/结束回调,并让 `burst` 中央金额只在 `svga` 真正开始播时显示、在它播完清帧时一并隐藏。
- 已按 2026-04-21 最新联调继续细调幸运礼物 `burst` 中央金币文案:当前已在现有基础上再向上微调 `4` 个单位、向右微调 `5` 个单位,让金额更贴近特效中部的目标槽位;同时 `burst` 的整体展示时长也已再缩短 `1` 秒,避免命中后在屏上停留过久。 - 已按 2026-04-21 最新联调继续细调幸运礼物 `burst` 中央金币文案:当前已在现有基础上再向上微调 `4` 个单位、向右微调 `5` 个单位,让金额更贴近特效中部的目标槽位;同时 `burst` 的整体展示时长也已再缩短 `1` 秒,避免命中后在屏上停留过久。
- 已继续修复幸运礼物顶部横幅 `from` 后礼物图标偶发显示不出来的问题:此前这条紫色 lucky gift 横幅只直接使用 socket 下发的 `giftCover/giftUrl` 渲染礼物图,一旦服务端该字段为空或图片加载失败,就只会退回默认占位;当前已把 `giftId` 一并挂进 `SCFloatingMessage`,并在 `FloatingLuckGiftScreenWidget` 内增加“优先用 `giftUrl`,失败时再回退到本地礼物列表缓存中的 `giftPhoto`”的双重兜底,避免横幅末尾再出现空白占位块。 - 已继续修复幸运礼物顶部横幅 `from` 后礼物图标偶发显示不出来的问题:此前这条紫色 lucky gift 横幅只直接使用 socket 下发的 `giftCover/giftUrl` 渲染礼物图,一旦服务端该字段为空或图片加载失败,就只会退回默认占位;当前已把 `giftId` 一并挂进 `SCFloatingMessage`,并在 `FloatingLuckGiftScreenWidget` 内增加“优先用 `giftUrl`,失败时再回退到本地礼物列表缓存中的 `giftPhoto`”的双重兜底,避免横幅末尾再出现空白占位块。
- 已继续补齐幸运礼物横幅礼物图标的“晚到数据”刷新:此前 `from` 后面的礼物图只在横幅首次 build 时用 `listen: false` 读取一次本地礼物列表,若 `giftList` 之后才加载完成,这条 overlay 生命周期内也不会再更新,最终仍会留下空白图标;当前已改为通过 `Selector<SCAppGeneralManager, String>` 监听 `giftId -> giftPhoto` 回退图,并在 socket 未携带 `giftUrl` 时主动补拉一次 `giftList`,让礼物列表就绪后能把幸运礼物横幅末尾图标自动补出来。
- 已继续修正幸运礼物紫色横幅里 `from` 后礼物图“闪一下又空白”的问题:排查后发现这条横幅把礼物图标塞在 `Text.rich` 末尾,而正文又开启了单行省略,导致尾部 `WidgetSpan` 在最终排版溢出时可能被直接不绘制;当前已把礼物图标从内联文本里拆到正文右侧单独布局,保持正文自己省略、礼物图标固定显示,不再因为文本溢出把尾部礼物图一起吃掉。
- 已按 2026-04-21 最新回归把紫色幸运礼物横幅的礼物图取值收口到和其它飘屏同一来源:当前 `RTM` 在构造幸运礼物 `SCFloatingMessage` 前,会优先按 `giftId/standardId` 从本地礼物列表拿同款 `giftPhoto`,拿不到时才退回服务端下发的 `giftCover`;同时紫色横幅组件本身不再额外做自己的礼物图回退和二次解析,只直接显示已统一好的 `message.giftUrl`,避免和其它飘屏走出两套不同的取值链路。
- 已按 2026-04-21 最新排障请求给紫色幸运礼物横幅补齐定向日志:当前会在 `RTM` 解析 `GAME_LUCKY_GIFT` 时输出 `giftId / giftCover / 本地 id 命中 / 本地 standardId 命中 / resolvedUrl`,同时横幅组件自身也会记录一次 `init``gift icon render``gift icon url empty``gift icon load failed`,方便直接判断是服务端字段为空、本地映射错位,还是图片请求本身失败。
- 已根据 2026-04-21 最新日志结论继续收口幸运礼物紫色横幅:日志已确认这条横幅实际拿到的 `giftUrl` 正常且组件已进入渲染分支,因此当前继续把礼物图标的绘制方式直接对齐到其它飘屏的朴素路径,改为更大的 `netImage(url, width)` 直出并使用 `BoxFit.contain`,不再叠加此前那套更特殊的小图标 `borderRadius/noDefaultImg` 参数,优先排除该组件私有渲染参数导致的异常显示。
- 已继续绕开幸运礼物紫色横幅里可疑的小图 `ExtendedImage` 渲染链:当前礼物图标已改为在 `initState` 里先构建 `buildCachedImageProvider`,再通过基础 `Image(image: provider)` 直接绘制,并额外输出一次 `gift icon frame ready` 日志,用于确认这张图是否真的完成首帧解码显示;这样可以把问题进一步收敛为“图片 provider/解码”还是“组件布局/视觉观感”。
- 已按 2026-04-21 最新联调请求,把紫色幸运礼物横幅 `from` 后的图标临时替换为金币图标:这一步只用于快速验证该图标位本身的布局和可见性是否正常,不再受当前礼物图资源链路影响;如果金币图标能稳定显示,就说明问题仍集中在礼物图渲染链,而不是这个位置被遮挡或根本没画出来。
- 已按 2026-04-21 最新回归把 [floating_luck_gift_screen_widget] 的样式撤回到最初实现,不再继续在幸运礼物飘屏横幅上做偏题调试;同时已定位到真正缺图的是消息栏里的 `gameLuckyGift_5` 高亮消息,这条消息此前只写入了 `awardAmount/user/msg`,没有把 `gift` 一并塞进去,导致 `room_msg_item.dart` 读取 `widget.msg.gift?.giftPhoto` 时天然为空。当前已在 `RTM` 构造高亮 lucky 消息时补回 `giftPhoto`,让消息栏里的紫色 lucky message 能和其它消息一样拿到礼物图。
- 已优化语言房麦位/头像的二次确认交互:普通用户点击可上麦的空麦位时,当前会直接执行上麦,不再先弹出只有 `Take the mic / Cancel` 的确认层;普通用户点击房间头像或已占麦位上的用户头像时,也会直接打开个人卡片,不再额外弹出仅含 `Open user profile card / Cancel` 的底部确认。房主/管理员仍保留原有带禁麦、锁麦、邀请上麦等管理动作的底部菜单,避免误删管理能力。 - 已优化语言房麦位/头像的二次确认交互:普通用户点击可上麦的空麦位时,当前会直接执行上麦,不再先弹出只有 `Take the mic / Cancel` 的确认层;普通用户点击房间头像或已占麦位上的用户头像时,也会直接打开个人卡片,不再额外弹出仅含 `Open user profile card / Cancel` 的底部确认。房主/管理员仍保留原有带禁麦、锁麦、邀请上麦等管理动作的底部菜单,避免误删管理能力。
- 已继续收窄语言房个人卡片前的“确认意义”弹层:当前用户在麦位上点击自己的头像时,也会直接打开自己的个人卡片,不再先弹出仅包含 `Leave the mic / Open user profile card / Cancel` 的底部菜单;同时个人卡片内的“离开麦位”入口已替换为新的 `leave` 视觉素材,和最新房间交互稿保持一致。 - 已继续收窄语言房个人卡片前的“确认意义”弹层:当前用户在麦位上点击自己的头像时,也会直接打开自己的个人卡片,不再先弹出仅包含 `Leave the mic / Open user profile card / Cancel` 的底部菜单;同时个人卡片内的“离开麦位”入口已替换为新的 `leave` 视觉素材,和最新房间交互稿保持一致。
- 已继续微调语言房个人卡片与送礼 UI个人卡片底部动作文案现已支持两行居中展示避免 `Leave the mic` 这类英文按钮被硬截断;房间底部礼物入口也已切换为新的本地 `SVGA` 资源 `room_bottom_gift_button.svga`,保持房间底栏视觉和最新动效稿一致。 - 已继续微调语言房个人卡片与送礼 UI个人卡片底部动作文案现已支持两行居中展示避免 `Leave the mic` 这类英文按钮被硬截断;房间底部礼物入口也已切换为新的本地 `SVGA` 资源 `room_bottom_gift_button.svga`,保持房间底栏视觉和最新动效稿一致。