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

358 lines
14 KiB
Markdown

# 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 Architecture
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.
## 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:
- `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.
## 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.
```dart
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
### Recommended send branching
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
### 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 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.
## 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/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