From 23e538cd0c2cd87eadee0f63e31df2823d94f1b6 Mon Sep 17 00:00:00 2001 From: roxy Date: Sat, 9 May 2026 13:38:27 +0800 Subject: [PATCH] =?UTF-8?q?bug=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/configs/sc_variant1_config.dart | 2 +- .../auth/edit/sc_edit_profile_page.dart | 1 + lib/modules/index/index_page.dart | 36 ++++++- lib/modules/user/me_page2.dart | 29 ----- lib/modules/user/task/task_page.dart | 37 ++++--- lib/services/audio/rtc_manager.dart | 61 ++++++++--- lib/services/music/room_music_manager.dart | 31 ++++++ .../sources/local/data_persistence.dart | 14 +++ .../tools/sc_entry_popup_coordinator.dart | 65 +++++++++++- ...oating_room_redenvelope_screen_widget.dart | 7 +- .../room_red_packet_claim_record_page.dart | 25 +---- lib/ui_kit/widgets/room/room_msg_item.dart | 100 ++++++++++++------ 需求进度.md | 1 + 13 files changed, 281 insertions(+), 128 deletions(-) diff --git a/lib/app/config/configs/sc_variant1_config.dart b/lib/app/config/configs/sc_variant1_config.dart index cac760a..64e7c6f 100644 --- a/lib/app/config/configs/sc_variant1_config.dart +++ b/lib/app/config/configs/sc_variant1_config.dart @@ -16,7 +16,7 @@ class SCVariant1Config implements AppConfig { @override String get apiHost => const String.fromEnvironment( 'API_HOST', - defaultValue: 'http://192.168.110.64:1100/', + defaultValue: 'https://jvapi.haiyihy.com/', ); // 默认连线上环境,本地调试可通过 --dart-define=API_HOST 覆盖 本地“http://192.168.110.64:1100/”,线上“https://jvapi.haiyihy.com/” @override diff --git a/lib/modules/auth/edit/sc_edit_profile_page.dart b/lib/modules/auth/edit/sc_edit_profile_page.dart index e71d2ca..8575916 100644 --- a/lib/modules/auth/edit/sc_edit_profile_page.dart +++ b/lib/modules/auth/edit/sc_edit_profile_page.dart @@ -715,6 +715,7 @@ class _SCEditProfilePageState extends State { userProvider?.syncCurrentUserProfile(user.userProfile); await DataPersistence.clearPendingRegisterRewardDialog(); await DataPersistence.setAwaitRegisterRewardSocket(true); + await DataPersistence.setPendingFirstRegisterRoomGameEvent(true); if (!mounted) { return; } diff --git a/lib/modules/index/index_page.dart b/lib/modules/index/index_page.dart index 4c44915..5399ada 100644 --- a/lib/modules/index/index_page.dart +++ b/lib/modules/index/index_page.dart @@ -8,6 +8,7 @@ import 'package:yumi/main.dart' show routeObserver; import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart'; import 'package:yumi/modules/home/index_home_page.dart'; import 'package:yumi/modules/chat/message/sc_message_page.dart'; +import 'package:yumi/modules/user/task/task_page.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:provider/provider.dart'; @@ -25,7 +26,6 @@ import '../../shared/data_sources/models/enum/sc_heartbeat_status.dart'; import '../../ui_kit/components/sc_float_ichart.dart'; import '../../ui_kit/widgets/daily_sign_in/daily_sign_in_dialog.dart'; import '../../ui_kit/widgets/register_reward/register_reward_dialog.dart'; -import '../home/popular/event/home_event_page.dart'; import '../user/me_page2.dart'; /// 首页 @@ -50,6 +50,7 @@ class _SCIndexPageState extends State bool _hasShownRegisterRewardDialog = false; bool _isShowingDailySignInDialog = false; bool _showRegisterRewardAfterDailySignIn = false; + bool _isOpeningFirstRegisterRoomGame = false; StreamSubscription? _registerRewardSubscription; Completer? _registerRewardSocketCompleter; PageRoute? _routeObserverRoute; @@ -255,7 +256,7 @@ class _SCIndexPageState extends State } _pages.add(SCIndexHomePage()); - _pages.add(HomeEventPage()); + _pages.add(const TaskPage(showBackButton: false)); _pages.add(SCMessagePage()); _pages.add(MePage2()); } @@ -320,7 +321,7 @@ class _SCIndexPageState extends State svgaPath: "sc_images/index/sc_icon_explore_anim.svga", fallbackPath: "sc_images/index/sc_icon_explore_en.png", ), - label: SCAppLocalizations.of(context)!.explore, + label: SCAppLocalizations.of(context)!.task, ), ); @@ -427,6 +428,7 @@ class _SCIndexPageState extends State debugPrint( '[SignInReward][Home] skip daily sign-in dialog reason=no-data', ); + await _openFirstRegisterRoomGameIfNeeded(); return; } debugPrint( @@ -445,6 +447,10 @@ class _SCIndexPageState extends State _showRegisterRewardAfterDailySignIn = false; await _showRegisterRewardDialogIfNeeded(); } + if (!mounted) { + return; + } + await _openFirstRegisterRoomGameIfNeeded(); } Future _showEntryPopupIfNeeded() async { @@ -542,6 +548,30 @@ class _SCIndexPageState extends State } } + Future _openFirstRegisterRoomGameIfNeeded() async { + if (_isOpeningFirstRegisterRoomGame || + !DataPersistence.getPendingFirstRegisterRoomGameEvent()) { + return; + } + if (!mounted) { + return; + } + + _isOpeningFirstRegisterRoomGame = true; + try { + await DataPersistence.clearPendingFirstRegisterRoomGameEvent(); + if (!mounted) { + return; + } + debugPrint('[FirstRegisterGame] launch first party room random game'); + await SCEntryPopupCoordinator.openFirstPartyRoomRandomGame(context); + } catch (error) { + debugPrint('[FirstRegisterGame] launch failed error=$error'); + } finally { + _isOpeningFirstRegisterRoomGame = false; + } + } + Widget _buildBottomTabIcon({ required bool active, required String svgaPath, diff --git a/lib/modules/user/me_page2.dart b/lib/modules/user/me_page2.dart index 56316df..502369d 100644 --- a/lib/modules/user/me_page2.dart +++ b/lib/modules/user/me_page2.dart @@ -7,7 +7,6 @@ import 'package:yumi/app_localizations.dart'; import 'package:yumi/modules/index/main_route.dart'; import 'package:yumi/modules/store/store_route.dart'; import 'package:yumi/modules/user/settings/settings_route.dart'; -import 'package:yumi/modules/user/task/task_route.dart'; import 'package:yumi/modules/user/vip/vip_route.dart'; import 'package:yumi/modules/wallet/wallet_route.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; @@ -35,7 +34,6 @@ class _MePage2State extends State { Map _counterMap = {}; SCVipStatusRes? _vipStatus; - int _taskClaimableCount = 0; bool _isVipStatusLoading = false; @override @@ -55,7 +53,6 @@ class _MePage2State extends State { profileManager.balance(); _loadCounter(); _loadVipStatus(); - _loadTaskClaimableCount(); } Future _loadCounter() async { @@ -101,17 +98,6 @@ class _MePage2State extends State { } } - Future _loadTaskClaimableCount() async { - try { - final count = await SCAccountRepository().taskClaimableCount(); - if (!mounted) return; - setState(() => _taskClaimableCount = count); - } catch (_) { - if (!mounted) return; - setState(() => _taskClaimableCount = 0); - } - } - Future _loadVipEntryStatus() async { final repository = SCVipRepositoryImp(); try { @@ -335,21 +321,6 @@ class _MePage2State extends State { onTap: () => SCNavigatorUtils.push(context, StoreRoute.list), ), SizedBox(width: 10.w), - _buildEntryItem( - title: SCAppLocalizations.of(context)!.task, - iconPath: 'sc_images/index/sc_icon_task.png', - badgeCount: _taskClaimableCount, - onTap: () async { - await SCNavigatorUtils.push(context, TaskRoute.task); - if (!mounted) return; - _loadTaskClaimableCount(); - Provider.of( - context, - listen: false, - ).balance(); - }, - ), - SizedBox(width: 10.w), _buildEntryItem( title: SCAppLocalizations.of(context)!.level, iconPath: 'sc_images/index/sc_icon_level.png', diff --git a/lib/modules/user/task/task_page.dart b/lib/modules/user/task/task_page.dart index bffb9ef..a2ae916 100644 --- a/lib/modules/user/task/task_page.dart +++ b/lib/modules/user/task/task_page.dart @@ -12,7 +12,9 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository import 'package:yumi/shared/tools/sc_entry_popup_coordinator.dart'; class TaskPage extends StatefulWidget { - const TaskPage({super.key}); + const TaskPage({super.key, this.showBackButton = true}); + + final bool showBackButton; @override State createState() => _TaskPageState(); @@ -201,21 +203,24 @@ class _TaskPageState extends State children: [ Align( alignment: AlignmentDirectional.centerStart, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => SCNavigatorUtils.goBack(context), - child: SizedBox( - width: 44.w, - height: 44.w, - child: Icon( - SCGlobalConfig.lang == 'ar' - ? Icons.keyboard_arrow_right - : Icons.keyboard_arrow_left, - color: Colors.white, - size: 28.w, - ), - ), - ), + child: + widget.showBackButton + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => SCNavigatorUtils.goBack(context), + child: SizedBox( + width: 44.w, + height: 44.w, + child: Icon( + SCGlobalConfig.lang == 'ar' + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + color: Colors.white, + size: 28.w, + ), + ), + ) + : SizedBox(width: 44.w, height: 44.w), ), Text( SCAppLocalizations.of(context)!.task, diff --git a/lib/services/audio/rtc_manager.dart b/lib/services/audio/rtc_manager.dart index 858396f..75cc4b3 100644 --- a/lib/services/audio/rtc_manager.dart +++ b/lib/services/audio/rtc_manager.dart @@ -297,6 +297,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { ); static const Duration _agoraJoinTimeout = Duration(seconds: 8); static const Duration _agoraDisconnectedGracePeriod = Duration(seconds: 12); + static const Duration _roomMusicPublishingStateTtl = Duration(seconds: 15); static const String _roomStartupSeatLoadingTag = 'roomStartupSeatInteractionLoading'; static const String _selfMicGoUpRetryLoadingTag = 'selfMicGoUpRetryLoading'; @@ -366,6 +367,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { final Map _seatEmojiEventVersions = {}; int _emojiPlaybackEventVersion = 0; final Set _roomMusicPublishingUserIds = {}; + final Map _roomMusicPublishingClearTimers = {}; RtcEngine? engine; RtcEngineEventHandler? _rtcEngineEventHandler; @@ -1313,11 +1315,15 @@ class RealTimeCommunicationManager extends ChangeNotifier { .map((seat) => (seat.user?.id ?? "").trim()) .where((userId) => userId.isNotEmpty) .toSet(); - final beforeCount = _roomMusicPublishingUserIds.length; - _roomMusicPublishingUserIds.removeWhere( - (userId) => !seatedUserIds.contains(userId), - ); - return beforeCount != _roomMusicPublishingUserIds.length; + final removedUserIds = + _roomMusicPublishingUserIds + .where((userId) => !seatedUserIds.contains(userId)) + .toList(); + for (final userId in removedUserIds) { + _roomMusicPublishingUserIds.remove(userId); + _roomMusicPublishingClearTimers.remove(userId)?.cancel(); + } + return removedUserIds.isNotEmpty; } int? _tryParseAgoraUid(String? value) { @@ -3364,7 +3370,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { _pendingMicListRefreshNotifyIfUnchanged = false; _seatEmojiEventVersions.clear(); _emojiPlaybackEventVersion = 0; - _roomMusicPublishingUserIds.clear(); + _clearRoomMusicPublishingStates(); roomRocketStatus = null; rtmProvider ?.onNewMessageListenerGroupMap["${currenRoom?.roomProfile?.roomProfile?.roomAccount}"] = @@ -3405,6 +3411,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { @override void dispose() { _stopRoomStatePolling(); + _clearRoomMusicPublishingStates(); unawaited(releaseRtcEngineForAppTermination()); super.dispose(); } @@ -4123,15 +4130,36 @@ class RealTimeCommunicationManager extends ChangeNotifier { if (normalizedUserId.isEmpty) { return; } - final changed = - publishing - ? _roomMusicPublishingUserIds.add(normalizedUserId) - : _roomMusicPublishingUserIds.remove(normalizedUserId); + bool changed = false; + if (publishing) { + changed = _roomMusicPublishingUserIds.add(normalizedUserId); + _roomMusicPublishingClearTimers.remove(normalizedUserId)?.cancel(); + _roomMusicPublishingClearTimers[normalizedUserId] = Timer( + _roomMusicPublishingStateTtl, + () { + _roomMusicPublishingClearTimers.remove(normalizedUserId); + if (_roomMusicPublishingUserIds.remove(normalizedUserId)) { + notifyListeners(); + } + }, + ); + } else { + _roomMusicPublishingClearTimers.remove(normalizedUserId)?.cancel(); + changed = _roomMusicPublishingUserIds.remove(normalizedUserId); + } if (changed && notifyIfChanged) { notifyListeners(); } } + void _clearRoomMusicPublishingStates() { + for (final timer in _roomMusicPublishingClearTimers.values) { + timer.cancel(); + } + _roomMusicPublishingClearTimers.clear(); + _roomMusicPublishingUserIds.clear(); + } + void handleRoomMusicPublishingMessage(Msg msg) { final userId = (msg.role ?? "").trim().isNotEmpty ? msg.role : msg.user?.id ?? ""; @@ -4148,9 +4176,10 @@ class RealTimeCommunicationManager extends ChangeNotifier { return; } + final effectivePublishing = publishing && isOnMai(); updateRoomMusicPublishingState( userId: currentUserId, - publishing: publishing && isOnMai(), + publishing: effectivePublishing, ); final groupId = @@ -4158,14 +4187,18 @@ class RealTimeCommunicationManager extends ChangeNotifier { if (groupId.isEmpty) { return; } + if (rtmProvider == null && context != null && context!.mounted) { + rtmProvider = Provider.of(context!, listen: false); + } + final roomRtmProvider = rtmProvider; unawaited( - rtmProvider?.dispatchMessage( + roomRtmProvider?.dispatchMessage( Msg( groupId: groupId, - msg: publishing && isOnMai() ? "1" : "0", + msg: effectivePublishing ? "1" : "0", type: SCRoomMsgType.roomMusic, role: currentUserId, - number: publishing && isOnMai() ? 1 : 0, + number: effectivePublishing ? 1 : 0, user: currentUser, ), addLocal: false, diff --git a/lib/services/music/room_music_manager.dart b/lib/services/music/room_music_manager.dart index 8243704..efaaeeb 100644 --- a/lib/services/music/room_music_manager.dart +++ b/lib/services/music/room_music_manager.dart @@ -13,6 +13,10 @@ import 'package:yumi/ui_kit/components/sc_tts.dart'; enum RoomMusicPlayMode { list, shuffle, single } class RoomMusicManager extends ChangeNotifier { + static const Duration _publishingStateHeartbeatInterval = Duration( + seconds: 5, + ); + RoomMusicManager({RoomMusicRepository? repository}) : _repository = repository ?? RoomMusicRepository() { _playlist = _repository.loadSongs(); @@ -23,6 +27,7 @@ class RoomMusicManager extends ChangeNotifier { RtcProvider? _rtcProvider; Timer? _positionTimer; + Timer? _publishingStateHeartbeatTimer; List _playlist = []; SCMusicMode? _current; RoomMusicPlayMode _playMode = RoomMusicPlayMode.list; @@ -489,9 +494,11 @@ class RoomMusicManager extends ChangeNotifier { void _syncPublishingState({bool force = false}) { final rtcProvider = _rtcProvider; if (rtcProvider == null) { + _updatePublishingStateHeartbeat(false); return; } final publishing = isPublishingToRoom; + _updatePublishingStateHeartbeat(publishing); if (!force && publishing == _lastPublishedRoomState) { return; } @@ -499,6 +506,29 @@ class RoomMusicManager extends ChangeNotifier { rtcProvider.publishCurrentUserRoomMusicState(publishing); } + void _updatePublishingStateHeartbeat(bool publishing) { + if (!publishing) { + _publishingStateHeartbeatTimer?.cancel(); + _publishingStateHeartbeatTimer = null; + return; + } + if (_publishingStateHeartbeatTimer != null) { + return; + } + _publishingStateHeartbeatTimer = Timer.periodic( + _publishingStateHeartbeatInterval, + (_) { + if (!isPublishingToRoom) { + _publishingStateHeartbeatTimer?.cancel(); + _publishingStateHeartbeatTimer = null; + _syncPublishingState(force: true); + return; + } + _rtcProvider?.publishCurrentUserRoomMusicState(true); + }, + ); + } + void _restoreCurrentAfterPlaylistChange() { final currentId = _current?.id; if (currentId == null) { @@ -522,6 +552,7 @@ class RoomMusicManager extends ChangeNotifier { @override void dispose() { _positionTimer?.cancel(); + _publishingStateHeartbeatTimer?.cancel(); _rtcProvider?.setRoomMusicMixingStateListener(null); _rtcProvider?.setRoomMusicMicRouteChangedListener(null); super.dispose(); diff --git a/lib/shared/data_sources/sources/local/data_persistence.dart b/lib/shared/data_sources/sources/local/data_persistence.dart index cc12f5f..72e452a 100644 --- a/lib/shared/data_sources/sources/local/data_persistence.dart +++ b/lib/shared/data_sources/sources/local/data_persistence.dart @@ -10,6 +10,8 @@ class DataPersistence { "await_register_reward_socket"; static const String _pendingRegisterRewardDialogKey = "pending_register_reward_dialog"; + static const String _pendingFirstRegisterRoomGameEventKey = + "pending_first_register_room_game_event"; static const String _pendingChannelAuthTypeKey = "pending_channel_auth_type"; static const String _pendingChannelAuthOpenIdKey = "pending_channel_auth_open_id"; @@ -218,6 +220,18 @@ class DataPersistence { return await remove(_awaitRegisterRewardSocketKey); } + static Future setPendingFirstRegisterRoomGameEvent(bool value) async { + return await setBool(_pendingFirstRegisterRoomGameEventKey, value); + } + + static bool getPendingFirstRegisterRoomGameEvent() { + return getBool(_pendingFirstRegisterRoomGameEventKey); + } + + static Future clearPendingFirstRegisterRoomGameEvent() async { + return await remove(_pendingFirstRegisterRoomGameEventKey); + } + static Future setPendingChannelAuth( String authType, String openId, diff --git a/lib/shared/tools/sc_entry_popup_coordinator.dart b/lib/shared/tools/sc_entry_popup_coordinator.dart index 35958d5..146d2bf 100644 --- a/lib/shared/tools/sc_entry_popup_coordinator.dart +++ b/lib/shared/tools/sc_entry_popup_coordinator.dart @@ -13,6 +13,8 @@ import 'package:yumi/modules/user/task/task_route.dart'; import 'package:yumi/modules/user/vip/vip_route.dart'; import 'package:yumi/modules/wallet/wallet_route.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; +import 'package:yumi/services/home/home_room_preload_manager.dart'; +import 'package:yumi/shared/business_logic/models/res/room_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_entry_popup_res.dart'; import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; @@ -288,6 +290,7 @@ class SCEntryPopupCoordinator { onSelectRootTab?.call(0); return; case '/tab/explore': + case '/tab/task': onSelectRootTab?.call(1); return; case '/tab/message': @@ -402,12 +405,49 @@ class SCEntryPopupCoordinator { ); } - static Future _enterRoom(BuildContext context, String roomId) async { + static Future openFirstPartyRoomRandomGame(BuildContext context) async { + if (!context.mounted) { + return; + } + + final rooms = await SCHomeRoomPreloadManager.instance.loadPartyRooms(); + final room = _firstRoomWithId(rooms); + final roomId = room?.id?.trim() ?? ''; + if (room == null || roomId.isEmpty || !context.mounted) { + _log('first-register-game skip reason=no-room'); + return; + } + + _log('first-register-game enter roomId=$roomId'); + final ready = await _enterRoom( + context, + roomId, + previewData: RoomEntryPreviewData.fromSocialChatRoom(room), + ); + if (!ready || !context.mounted) { + _log('first-register-game skip reason=room-not-ready roomId=$roomId'); + return; + } + + showBottomInBottomDialog( + context, + RoomGameBottomSheet(roomContext: context, initialGameId: '0'), + barrierColor: Colors.black54, + ); + } + + static Future _enterRoom( + BuildContext context, + String roomId, { + RoomEntryPreviewData? previewData, + }) async { final rtcProvider = Provider.of(context, listen: false); - RoomEntryPreviewData? previewData; + RoomEntryPreviewData? resolvedPreviewData = previewData; try { - final room = await SCChatRoomRepository().specific(roomId); - previewData = RoomEntryPreviewData.fromMyRoom(room); + if (resolvedPreviewData == null) { + final room = await SCChatRoomRepository().specific(roomId); + resolvedPreviewData = RoomEntryPreviewData.fromMyRoom(room); + } } catch (error) { debugPrint( '[EntryPopup] load room preview failed roomId=$roomId: $error', @@ -416,10 +456,25 @@ class SCEntryPopupCoordinator { if (!context.mounted) { return false; } - rtcProvider.joinVoiceRoomSession(context, roomId, previewData: previewData); + rtcProvider.joinVoiceRoomSession( + context, + roomId, + previewData: resolvedPreviewData, + ); return _waitForRoomReady(rtcProvider, roomId); } + static SocialChatRoomRes? _firstRoomWithId( + Iterable rooms, + ) { + for (final room in rooms) { + if ((room.id ?? '').trim().isNotEmpty) { + return room; + } + } + return null; + } + static Future _waitForRoomReady( RtcProvider rtcProvider, String roomId, diff --git a/lib/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart b/lib/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart index 72ce255..dbc21c2 100644 --- a/lib/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart +++ b/lib/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_debouncer/flutter_debouncer.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; -import 'package:yumi/shared/tools/sc_room_utils.dart'; import 'package:yumi/main.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart'; @@ -324,11 +323,7 @@ class _FloatingRoomRedenvelopeScreenWidgetState } return; } - if ((currentRoomId ?? '').trim().isEmpty || currentRoomId == roomId) { - rtcProvider.joinVoiceRoomSession(currentContext, roomId); - return; - } - SCRoomUtils.goRoom(roomId, currentContext, fromFloting: true); + rtcProvider.joinVoiceRoomSession(currentContext, roomId); } String _senderNameForFloating() { diff --git a/lib/ui_kit/widgets/room/red_packet/room_red_packet_claim_record_page.dart b/lib/ui_kit/widgets/room/red_packet/room_red_packet_claim_record_page.dart index 2fe60da..d8c8452 100644 --- a/lib/ui_kit/widgets/room/red_packet/room_red_packet_claim_record_page.dart +++ b/lib/ui_kit/widgets/room/red_packet/room_red_packet_claim_record_page.dart @@ -324,27 +324,10 @@ class _ClaimAvatar extends StatelessWidget { @override Widget build(BuildContext context) { - return ClipOval( - child: Container( - width: 34.w, - height: 34.w, - color: Colors.white.withValues(alpha: 0.08), - alignment: Alignment.center, - child: - url.trim().isNotEmpty - ? netImage( - url: url, - width: 34.w, - height: 34.w, - fit: BoxFit.cover, - ) - : Image.asset( - 'sc_images/room/red_packet/small_packet_icon.png', - width: 24.w, - height: 24.w, - fit: BoxFit.contain, - ), - ), + return SizedBox( + width: 34.w, + height: 34.w, + child: head(url: url, width: 34.w, showDefault: true), ); } } diff --git a/lib/ui_kit/widgets/room/room_msg_item.dart b/lib/ui_kit/widgets/room/room_msg_item.dart index a2d4b1a..0c48265 100644 --- a/lib/ui_kit/widgets/room/room_msg_item.dart +++ b/lib/ui_kit/widgets/room/room_msg_item.dart @@ -197,32 +197,56 @@ class _MsgItemState extends State { Widget _buildTextMessageBubble() { final chatBubble = widget.msg.user?.getChatBox(); final hasChatBubble = _resolveRoomChatBubbleImageUrl(chatBubble).isNotEmpty; - if (hasChatBubble) { - return _buildVipTextMessageBubble(chatBubble); - } + final bubbleStartMargin = 54.w; + return LayoutBuilder( + builder: (context, constraints) { + final rowMaxWidth = + constraints.maxWidth.isFinite + ? constraints.maxWidth + : ScreenUtil().screenWidth; + final availableBubbleWidth = math.max( + 1.0, + rowMaxWidth - bubbleStartMargin, + ); + if (hasChatBubble) { + return _buildVipTextMessageBubble( + chatBubble, + maxBubbleWidth: availableBubbleWidth, + startMargin: bubbleStartMargin, + ); + } - return Container( - constraints: BoxConstraints(maxWidth: ScreenUtil().screenWidth * 0.7), - decoration: BoxDecoration( - color: Colors.black38, - borderRadius: BorderRadius.only( - topRight: Radius.circular(15.w), - bottomLeft: Radius.circular(15.w), - bottomRight: Radius.circular(15.w), - ), - ), - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), - margin: EdgeInsetsDirectional.only(start: 54.w).copyWith(bottom: 8.w), - child: ExtendedText( - widget.msg.msg ?? '', - style: TextStyle( - fontSize: sp(13), - color: Color(_msgColor(widget.msg.type ?? "")), - fontWeight: FontWeight.w500, - ), - softWrap: true, - specialTextSpanBuilder: AtTextSpanBuilder(onTapCall: (userId) {}), - ), + return Container( + constraints: BoxConstraints( + maxWidth: math.min( + ScreenUtil().screenWidth * 0.7, + availableBubbleWidth, + ), + ), + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), + margin: EdgeInsetsDirectional.only( + start: bubbleStartMargin, + ).copyWith(bottom: 8.w), + child: ExtendedText( + widget.msg.msg ?? '', + style: TextStyle( + fontSize: sp(13), + color: Color(_msgColor(widget.msg.type ?? "")), + fontWeight: FontWeight.w500, + ), + softWrap: true, + specialTextSpanBuilder: AtTextSpanBuilder(onTapCall: (userId) {}), + ), + ); + }, ); } @@ -255,7 +279,11 @@ class _MsgItemState extends State { ); } - Widget _buildVipTextMessageBubble(PropsResources? resource) { + Widget _buildVipTextMessageBubble( + PropsResources? resource, { + required double maxBubbleWidth, + required double startMargin, + }) { final messageText = widget.msg.msg ?? ""; final textStyle = TextStyle( fontSize: sp(13), @@ -263,10 +291,7 @@ class _MsgItemState extends State { fontWeight: FontWeight.w500, ); final imageUrl = _resolveRoomChatBubbleImageUrl(resource); - final multilineMaxBubbleWidth = math.min( - ScreenUtil().screenWidth * 0.72, - 300.w, - ); + final multilineMaxBubbleWidth = math.min(maxBubbleWidth, 300.w); final multilineMaxTextWidth = math.max( 82.w, multilineMaxBubbleWidth - 64.w, @@ -282,6 +307,7 @@ class _MsgItemState extends State { imageUrl: imageUrl, textSize: multilineTextPainter.size, maxBubbleWidth: multilineMaxBubbleWidth, + startMargin: startMargin, ); } @@ -289,6 +315,8 @@ class _MsgItemState extends State { messageText: messageText, textStyle: textStyle, imageUrl: imageUrl, + maxBubbleWidth: maxBubbleWidth, + startMargin: startMargin, ); } @@ -298,6 +326,7 @@ class _MsgItemState extends State { required String imageUrl, required Size textSize, required double maxBubbleWidth, + required double startMargin, }) { final leftPadding = 22.w; final rightPadding = 42.w; @@ -323,7 +352,9 @@ class _MsgItemState extends State { return Container( width: bubbleWidth, height: bubbleHeight, - margin: EdgeInsetsDirectional.only(start: 54.w).copyWith(bottom: 8.w), + margin: EdgeInsetsDirectional.only( + start: startMargin, + ).copyWith(bottom: 8.w), child: Stack( fit: StackFit.expand, children: [ @@ -353,6 +384,8 @@ class _MsgItemState extends State { required String messageText, required TextStyle textStyle, required String imageUrl, + required double maxBubbleWidth, + required double startMargin, }) { final leftPadding = 22.w; final rightPadding = 24.w; @@ -361,7 +394,6 @@ class _MsgItemState extends State { final minSkinWidth = 96.w; final minSkinHeight = 58.w; final textWidthSafety = 10.w; - final maxBubbleWidth = ScreenUtil().screenWidth * 0.7; final maxTextWidth = math.max( 1.0, maxBubbleWidth - leftPadding - rightPadding, @@ -390,7 +422,9 @@ class _MsgItemState extends State { return Container( width: bubbleWidth, height: bubbleHeight, - margin: EdgeInsetsDirectional.only(start: 54.w).copyWith(bottom: 8.w), + margin: EdgeInsetsDirectional.only( + start: startMargin, + ).copyWith(bottom: 8.w), child: Stack( clipBehavior: Clip.none, fit: StackFit.expand, diff --git a/需求进度.md b/需求进度.md index a271fa2..f230123 100644 --- a/需求进度.md +++ b/需求进度.md @@ -556,6 +556,7 @@ - 已修正两个红包联调问题:当前房间点击红包飘窗 `GO` 打开领取层时会合并房间缓存中的完整红包 payload,避免限时红包丢失 `claimStartTime/countdown`;领取成功后发送的房内 IM 文本会附带不展示的唯一业务 key,避免同一用户连续领取同一发包人的多个红包时公屏只保留一条领取记录。 - 已收口红包发送记录页状态展示:不再显示 `Pending/等待中`,后端 `ACTIVE/PENDING` 等中间态统一展示为已发出;只保留已退回、已领完、已发出三类状态,并补齐多语言的“已领完”文案。 - 已收口房间外红包领取层触发:红包飘屏 `GO` 只有在目标房间页面当前可见时才直接打开领取层,否则先进房;同语区或房间 IM 收到即时红包时,也只有当前可见房间匹配目标 `roomId` 才自动弹领取层,避免首页、房间列表、我的页直接弹抢红包。 +- 已按最新反馈修正领取记录头像和跨房进房:领取记录空头像不再回退成红包图标,统一走项目默认头像;红包飘窗 `GO` 跨房点击直接进目标房间,不再弹进入房间二次确认。 ## 已知问题 - 已接入语音房表情包素材第一版:桌面 `表情素材` 中的 `fluent_emoji` 与 `monkey` gif 已纳入项目资源,底部聊天入口改为图稿里的短白色胶囊并移除内部输入图标,右侧新增独立表情按钮;同一个房间输入弹层现在可从表情按钮直接打开表情包面板,原 unicode emoji 面板已替换为本地 gif 表情。发送逻辑同步按最新规则分流:用户在麦上发表情时只在自己麦位播放,不进入聊天列表;用户未上麦发表情时进入房间聊天框展示,不触发麦位动画。