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

336 lines
12 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.
- 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 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)`.
- 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`.
## Recommended Send Path
### Recommended payload shape
The smallest-change plan is to reuse the existing room custom message structure:
```dart
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.
## 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 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