14 KiB
Voice Room Emoji Package Plan
Goal
This document only records a feasible implementation plan. No business code is changed in this round.
Requirement summary:
- Add an emoji package button next to the voice-room chat entry.
- The emoji package button and text chat must open the same bottom composer sheet.
- Users can send emoji packages whether they are on mic or not.
- If the sender is on mic, the emoji package is shown on the sender's mic seat and does not appear in room chat.
- If the sender is not on mic, the emoji package is shown in room chat and does not appear on any mic seat.
- Wait for final backend data and UI assets before starting the real implementation.
Current Codebase Status
The current project already has several reusable pieces, so the feature is feasible and does not need a brand-new architecture.
Reusable entry points:
- Bottom area layout:
lib/ui_kit/widgets/room/room_bottom_widget.dart - Chat composer popup:
lib/ui_kit/widgets/room/room_msg_input.dart - Seat emoji display:
lib/modules/room/seat/sc_seat_item.dart - Room RTM send/receive:
lib/services/audio/rtm_manager.dart - Seat transient state:
lib/services/audio/rtc_manager.dart - Seat model:
lib/shared/business_logic/models/res/mic_res.dart - Emoji material API:
lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart - Emoji material model:
lib/shared/business_logic/models/res/sc_room_emoji_res.dart - Existing room message type:
lib/app/constants/sc_room_msg_type.dart
Confirmed reusable behavior in current code:
SCRoomMsgType.emoticonsalready exists.- The room receiver already handles
EMOTICONSseparately and callsRtcProvider.starPlayEmoji(msg). SCSeatItemalready has anEmoticonswidget with queueing, fade animation, and auto-dismiss.- The project already has
/material/emoji/all, and the response model already supports category + emoji list.
Recommended Architecture
Recommended direction: keep one composer sheet and support two entry modes.
Recommended entry modes:
textmode: opened from the existing chat entry, keyboard focused by default.emojiPackagemode: opened from the new emoji package button, keyboard hidden by default, emoji package panel open by default.
Recommended widget strategy:
- Reuse
RoomMsgInputinstead of creating a second bottom sheet. - Add an input parameter such as
initialModeorinitialPanel. - Keep the text input area as the same top row.
- Use the lower content area to switch between text helper emoji and room emoji package content.
Why this is the best fit:
- It matches the requirement that text chat and emoji package use the same bottom popup.
- It keeps interaction and style in one place.
- It avoids future drift between two different room composer implementations.
Recommended Interaction Flow
1. Bottom bar
Add a new emoji package button next to the room chat entry in RoomBottomWidget.
Recommended behavior:
- Tap chat entry: open
RoomMsgInput(initialMode: text). - Tap emoji package button: open
RoomMsgInput(initialMode: emojiPackage). - The emoji package button should always be available. Do not gate it by mic status.
Important layout note:
RoomBottomWidgetcurrently uses hard-coded width math for the bottom bar and for_resolveGiftCenterX(...).- After adding one more action button, the row layout and gift floating button center calculation both need to be updated together.
- If only the row is changed and
_resolveGiftCenterX(...)is not updated, the floating gift button can become visually misaligned.
2. Composer sheet
Recommended sheet structure:
- Top area: existing input field, send button, and keyboard/emoji switch logic.
- Bottom area in
textmode: current text emoji helper can remain for typing unicode emoji. - Bottom area in
emojiPackagemode: category tabs + emoji package grid from backend data.
Recommended send interaction for emoji package:
- Tapping an emoji package item sends immediately.
- The sheet should stay open after sending so the user can send multiple emoji packages continuously.
- If UI later wants tap-to-close, that can be a product choice, but continuous sending is the safer default for room interaction.
3. Split display rule
Emoji package messages should be routed by the sender's current mic status.
Recommended rule:
- Text messages still follow the existing room chat path.
- If the sender is on mic, emoji package clicks trigger seat animation only.
- If the sender is not on mic, emoji package clicks create a room chat message only.
- A single emoji package send should choose one display channel only and must not show in both places at the same time.
Recommended Send Path
Recommended payload shape
The smallest-change plan is still to reuse the existing room custom message structure, but make the payload support both seat display and chat display.
Msg(
groupId: roomAccount,
type: SCRoomMsgType.emoticons,
msg: emoji.sourceUrl,
number: currentSeatIndexOrMinusOne,
user: currentUserProfile,
role: currentUserRole,
)
Recommended field meaning:
type:EMOTICONSmsg: selected emoji resource URLnumber: current seat index if the sender is on mic, otherwise-1user: sender profile, for consistency with existing room messages
Recommended send branching
The current project already has two useful building blocks:
RtcProvider.starPlayEmoji(msg)for seat playbackRtmProvider.addMsg(...)for room chat insertion
Recommended implementation behavior later:
- Resolve the sender's current seat index at click time.
- If the sender is on mic:
- Build the
Msgwith the current seat index. - First do a local optimistic seat display by calling
RtcProvider.starPlayEmoji(msg). - Then call
dispatchMessage(msg, addLocal: false).
- Build the
- If the sender is not on mic:
- Build the
Msgwithnumber: -1. - Insert it as a local chat item.
- Then send the RTM message normally so other users also see the chat item.
- Build the
This gives the correct split behavior:
- on mic: seat only
- off mic: chat only
Recommended receive branching
The current receive logic for EMOTICONS should be adjusted to branch by seat index.
Recommended behavior:
- If
msg.number >= 0, treat it as seat display and callRtcProvider.starPlayEmoji(msg). - If
msg.number < 0, treat it as a normal room chat item and add it into the room chat list.
Without this receive-side branch, the current implementation would still route all EMOTICONS messages to seat display only.
Recommended Seat Resolution Rule
When sending an emoji package, determine the seat index at send time, not when the sheet is opened.
Recommended lookup:
- Use
RtcProvider.userOnMaiInIndex(currentUserId)when the user taps an emoji item.
Reason:
- The user may switch mic seats after opening the popup.
- Using the latest seat index avoids showing the emoji on the wrong seat.
- If the latest seat index is
-1, that means the message should go to room chat instead of seat display.
Backend Dependency Assessment
Material data
The project already has /material/emoji/all and the local model already supports:
- category name
- category cover
- category-level ownership state
- category-level amount
- emoji list
- emoji cover URL
- emoji source URL
This means the current backend structure is already close to the requirement shown in the design.
Backend items to confirm before development:
- Whether
/material/emoji/allis the final interface for this feature - Whether category ordering is controlled by backend
- Whether each category has a tab icon or cover ready for UI
- Whether
coverUrlis the correct image for the grid cell - Whether
sourceUrlis the real image used on the mic seat - Whether ownership is category-level only or item-level
If ownership later becomes item-level, the current model may need extension because it currently looks category-oriented.
No extra send API is strictly required
For the minimal-change version, no new HTTP send API is needed.
Recommended transport:
- Use the existing room RTM custom message channel.
- Reuse
EMOTICONSas the message type.
This keeps the feature aligned with the current room event architecture.
Optional Sync Decision
There are now two display paths, so the sync decision should be split as well.
Seat path
Behavior:
- If the sender is on mic, the emoji package is an instant seat effect.
- Users who join mid-animation do not necessarily need to recover the current emoji state.
Pros:
- Smallest frontend change
- No need for backend seat-state persistence
- Best fit for the current codebase
Recommended default:
- Yes. Treat the seat path as transient unless product explicitly wants recovery after reconnect or late join.
If product later wants seat-state recovery, backend and model changes are needed:
- mic list response needs to include active emoji state
- active emoji state should include at least resource URL and expiration timing
- frontend model parsing needs to be updated
Important current gap:
MicRes.fromJson(...)does not parseemojiPathMicRes.toJson()also does not serializeemojiPath
Chat path
Behavior:
- If the sender is not on mic, the emoji package behaves like a normal room chat message.
Recommended default:
- Reuse the existing room chat message list behavior and message history strategy.
- No special seat-state sync is needed for this path.
Current Code Caveats To Remember
1. RoomMsgInput is text-first today
Current behavior:
- It always autofocuses the text field.
- It only supports the local text emoji helper based on
SCEmojiDatas.smileys.
Meaning for future implementation:
- It needs a mode parameter so opening from the emoji package button can skip keyboard autofocus and show the package panel first.
2. Seat emoji display currently uses an implicit field contract
Current behavior in RtcProvider.starPlayEmoji(msg):
- It writes
emojiPath: msg.msg - It writes
type: msg.type - It writes
number: msg.msg
Current behavior in SCSeatItem.Emoticons:
- For dice and RPS,
numberis treated like a result value - For other types,
numberis treated like an image URL
Meaning:
- The current image emoji path works because the code uses a field-reuse convention.
- Later implementation should stay compatible with this path unless the seat overlay model is cleaned up in one pass.
Additional note after the new requirement:
- The same
EMOTICONStype now needs a second branch for off-mic chat display. - So later implementation should not assume that every
EMOTICONSmessage must callstarPlayEmoji(...).
3. Emoji cache utility exists but is not wired into the room sheet yet
SCAppGeneralManager already has:
emojiByTabemojiAll()
But this is not currently connected to the room composer.
Recommended later choice:
- Either reuse
SCAppGeneralManagerfor caching - Or load emoji package data directly inside the room sheet and cache locally there
Either is feasible. Reusing the manager is better if the same materials are used in multiple room surfaces.
Suggested Development Sequence Later
When backend and UI are ready, the development order should be:
- Add the new emoji package button in
RoomBottomWidgetand adjust layout math together with_resolveGiftCenterX(...). - Refactor
RoomMsgInputinto one shared room composer withtextandemojiPackageentry modes. - Wire the sheet to load emoji categories and emoji items from
/material/emoji/all. - Resolve seat index at click time and branch by on-mic or off-mic state.
- If on mic, do local optimistic seat playback and send RTM
EMOTICONSwithaddLocal: false. - If off mic, insert a local chat item and send RTM
EMOTICONSas a chat-visible message. - Adjust receive logic so on-mic emoji goes to seat display and off-mic emoji goes to room chat.
- Verify that on-mic sends do not pollute chat, while off-mic sends do appear in
AllandChatonly. - If product wants reconnect recovery for seat effects, then patch
MicResparsing and seat sync logic.
Acceptance Checklist
- New emoji package button appears next to the room chat entry.
- Chat entry and emoji package button open the same bottom composer sheet.
- Opening from chat goes to text mode.
- Opening from emoji package goes to emoji package mode.
- Users on and off mic can both send emoji packages.
- If the sender is on mic, the sender sees the emoji package on their own seat immediately.
- If the sender is on mic, other users see the emoji package on the sender's seat.
- If the sender is on mic, the emoji package does not appear in
AllorChat. - If the sender is not on mic, the emoji package appears in
AllandChat. - If the sender is not on mic, the emoji package does not appear on any seat.
- Existing text chat sending is not affected.
- Existing gift floating button remains visually centered after the new button is added.
- Repeated emoji package taps queue and play correctly on the seat path.
Final Recommendation
This feature is feasible with low-to-medium implementation risk because the project already has the key building blocks:
- shared room composer entry
- room RTM custom message channel
EMOTICONSmessage type- seat emoji playback widget
- emoji material API
Recommended final direction:
- Reuse the existing room composer popup
- Reuse
EMOTICONSas the transport type - Use seat-index-based branching in the RTM payload
- On mic: do local optimistic seat playback and keep emoji out of chat
- Off mic: treat emoji as a room chat message and keep it out of seat display
- Treat backend seat-state recovery as optional for the seat path, not mandatory