chatapp3-flutter/docs/voice-room-emoji-plan.md
2026-04-22 20:08:45 +08:00

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.emoticons already exists.
  • The room receiver already handles EMOTICONS separately and calls RtcProvider.starPlayEmoji(msg).
  • SCSeatItem already has an Emoticons widget with queueing, fade animation, and auto-dismiss.
  • The project already has /material/emoji/all, and the response model already supports category + emoji list.

Recommended direction: keep one composer sheet and support two entry modes.

Recommended entry modes:

  • text mode: opened from the existing chat entry, keyboard focused by default.
  • emojiPackage mode: opened from the new emoji package button, keyboard hidden by default, emoji package panel open by default.

Recommended widget strategy:

  • Reuse RoomMsgInput instead of creating a second bottom sheet.
  • Add an input parameter such as initialMode or initialPanel.
  • 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.

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:

  • RoomBottomWidget currently 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 text mode: current text emoji helper can remain for typing unicode emoji.
  • Bottom area in emojiPackage mode: 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.

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: EMOTICONS
  • msg: selected emoji resource URL
  • number: current seat index if the sender is on mic, otherwise -1
  • user: sender profile, for consistency with existing room messages

The current project already has two useful building blocks:

  • RtcProvider.starPlayEmoji(msg) for seat playback
  • RtmProvider.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 Msg with the current seat index.
    • First do a local optimistic seat display by calling RtcProvider.starPlayEmoji(msg).
    • Then call dispatchMessage(msg, addLocal: false).
  • If the sender is not on mic:
    • Build the Msg with number: -1.
    • Insert it as a local chat item.
    • Then send the RTM message normally so other users also see the chat item.

This gives the correct split behavior:

  • on mic: seat only
  • off mic: chat only

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 call RtcProvider.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.

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/all is 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 coverUrl is the correct image for the grid cell
  • Whether sourceUrl is 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 EMOTICONS as 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 parse emojiPath
  • MicRes.toJson() also does not serialize emojiPath

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, number is treated like a result value
  • For other types, number is 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 EMOTICONS type now needs a second branch for off-mic chat display.
  • So later implementation should not assume that every EMOTICONS message must call starPlayEmoji(...).

3. Emoji cache utility exists but is not wired into the room sheet yet

SCAppGeneralManager already has:

  • emojiByTab
  • emojiAll()

But this is not currently connected to the room composer.

Recommended later choice:

  • Either reuse SCAppGeneralManager for 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:

  1. Add the new emoji package button in RoomBottomWidget and adjust layout math together with _resolveGiftCenterX(...).
  2. Refactor RoomMsgInput into one shared room composer with text and emojiPackage entry modes.
  3. Wire the sheet to load emoji categories and emoji items from /material/emoji/all.
  4. Resolve seat index at click time and branch by on-mic or off-mic state.
  5. If on mic, do local optimistic seat playback and send RTM EMOTICONS with addLocal: false.
  6. If off mic, insert a local chat item and send RTM EMOTICONS as a chat-visible message.
  7. Adjust receive logic so on-mic emoji goes to seat display and off-mic emoji goes to room chat.
  8. Verify that on-mic sends do not pollute chat, while off-mic sends do appear in All and Chat only.
  9. If product wants reconnect recovery for seat effects, then patch MicRes parsing 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 All or Chat.
  • If the sender is not on mic, the emoji package appears in All and Chat.
  • 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
  • EMOTICONS message type
  • seat emoji playback widget
  • emoji material API

Recommended final direction:

  • Reuse the existing room composer popup
  • Reuse EMOTICONS as 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