chatapp3-flutter/docs/voice-room-emoji-plan.md
NIGGER SLAYER c4e177dd7e bug fix
2026-04-21 18:25:03 +08:00

12 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.
  • Emoji packages are shown only on mic seats.
  • Emoji packages must not appear in room chat messages, including the All, Chat, and Gift tabs.
  • 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).
  • If the current user is not on mic, the emoji package button can either be disabled or show a toast such as "Take a mic first".

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. Seat-only display

Emoji package messages should not enter room chat lists.

Recommended rule:

  • Text messages still follow the existing room chat path.
  • Emoji package clicks trigger seat animation only.
  • No new item should be inserted into roomAllMsgList, roomChatMsgList, or roomGiftMsgList.

The smallest-change plan is to reuse the existing room custom message structure:

Msg(
  groupId: roomAccount,
  type: SCRoomMsgType.emoticons,
  msg: emoji.sourceUrl,
  number: currentSeatIndex,
  user: currentUserProfile,
  role: currentUserRole,
)

Recommended field meaning:

  • type: EMOTICONS
  • msg: selected emoji resource URL
  • number: current seat index
  • user: sender profile, for consistency with existing room messages

Why not send it as a normal chat message

Because RtmProvider.addMsg(...) always inserts the message into roomAllMsgList first.

Current receive side is already good enough:

  • In _newGroupMsg(...), EMOTICONS goes directly to RtcProvider.starPlayEmoji(msg) and then return.
  • That means remote users will not see emoji package content in room chat.

Current sender side still needs care during implementation:

  • If the sender uses dispatchMessage(...) with default addLocal: true, the local sender will still push one item into roomAllMsgList.
  • That would violate the requirement that emoji packages must not show in chat.

Recommended implementation behavior later:

  • Build the Msg.
  • First do a local optimistic seat display by calling RtcProvider.starPlayEmoji(msg).
  • Then call dispatchMessage(msg, addLocal: false).

This gives the sender instant seat feedback while keeping the chat list clean.

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 returned seat index is -1:

  • Do not send.
  • Show a toast telling the user that only users on mic can use emoji packages.

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 two possible product definitions for emoji package state:

Option A: transient RTM-only state

Behavior:

  • Emoji package is only an instant seat effect.
  • Users who join mid-animation do not 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. This should be the first implementation choice unless product explicitly wants state recovery.

Option B: recoverable seat state

Behavior:

  • If a user joins the room late or reconnects, they can still see the currently active emoji package on the seat during its valid duration.

If product wants this, 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

So if the final backend plan depends on seat polling or room re-entry recovery, this model area will need a small follow-up patch during implementation.

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.

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. Add on-mic gating and resolve seat index at click time.
  5. On emoji package click, do local optimistic seat playback.
  6. Send RTM EMOTICONS with addLocal: false.
  7. Verify that All, Chat, and Gift tabs remain unchanged after sending.
  8. If product wants reconnect recovery, 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.
  • Only users on mic can send emoji packages.
  • Sender sees the emoji package on their own seat immediately.
  • Other users see the emoji package on the sender's seat.
  • Emoji package sending does not create new rows in All, Chat, or Gift.
  • 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.

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 RTM payload
  • Do local optimistic seat playback
  • Do not insert emoji package messages into any chat list
  • Treat backend seat-state recovery as optional, not mandatory