Chat notifications (production-ready) - ChatDeeplinkService: resolves client chat ID from incoming push/WS payloads using the (senderId, chatId) matrix and switches selectedDeviceProvider when the payload references a different watch (multi-device deeplink) - IncomingChatResolver in domain layer with full unit coverage of the 4-case matrix - ChatContext provider (sealed: outsideChat / list / conversation) wired into ChatListScreen and ChatConversationScreen via initState/dispose to enable WhatsApp-style suppression - notifications_init refactor: foreground CHAT_MESSAGE notifications are suppressed on chat list and matching conversation; tap navigation goes through ChatDeeplinkService - ChatSyncService.subscribeToReconnect: reconciles from REST when the WebSocket comes back from a disconnect (recovers messages missed in background) - 5s message-id dedup window in the WS listener (mitigates server-side duplicate chat-message-received events) - Reconcile from remote on conversation mount (covers cached controller from background) - AppBar refresh button + inverted pull-to-refresh in the conversation (overscroll either edge of the reverse list) WS event parser fix - chat-message-received normalises to chat_message_received; parser now accepts both that and chat_message so the conversation reactively refreshes when a watch sends a message Chat application layer - Split the conversation controller into services: chat_send_service, chat_sync_service, chat_participants_service, chat_permission_flow_service, chat_media_cleanup_service - chat_conversation_config centralises page size, polling interval, dedup window - chat_bubble_shell extracted; input bar split into smaller widgets - emoji picker sheet + emoji blocking input formatter + watch_emoji_catalog - Multipart upload header race fixed via synchronized.Lock around the shared Dio instance Videocall (carryover from earlier work in this branch) - Application services: incoming, outgoing, session - Domain entities: VideocallIncomingArgs, VideocallRoom, VideocallUserId, parseDeviceIdFromRoom helper - Views split: idle, incoming, active call, group call - Widgets: picture_in_picture_video, remote_or_fallback_video, video_call_header - videocall_config centralises timeouts, ringing duration, battery threshold - Incoming via push (channel mode) with full-screen notification + ringtone - Hangup-on-remote-left moved to controller; redirect on participant update documented - treezor_token_interceptor: distinguish session expiry from operation-denied 401s Localization & misc - New keys for chat conversation, refresh, errors across en/es/de/fr/it/pt - Location map: dispose ref-after-unmount fix; route history layer cleanup - Legacy device view model: position update event handling - AndroidManifest: notification channel + permissions for incoming-call full-screen intent
3.7 KiB
Videocall — Implemented JCMediaChannel.join for Android Watches
Following your confirmation that Android watches require JCMediaChannel.join (not JCCall.call), we have refactored the mobile side. Summary of changes:
1. We now detect the watch type before starting a call
We use device.capabilities.system from our self-hosted server, which returns "android" or "rtos". Based on that flag, the app picks one of two flows.
2. RTOS watches — flow unchanged
For RTOS watches we keep the original 1:1 P2P flow: JCCall.call(userID: "w_<imei>", isVideo: true, callParam). This continues to work as before.
3. Android watches — new room-based flow
For Android watches, when the user taps the device card on the idle screen, the app now does the following sequence:
- Builds the room number using your TCP protocol convention:
<deviceIdentificator>_<sanitizedAppAccount>(the same format the watch firmware uses when reportingUPRYROOMCOUNT). - Sends the
PRYVCALLsignaling to the watch via our backend (VIDEO_CALL_REQUESTwithchatType=0for single chat,chatType=1for group). - Calls
JCMediaChannel.enableUploadAudioStream(true)andJCMediaChannel.enableUploadVideoStream(true). - Calls
JCMediaChannel.join(roomNumber, joinParam). ThejoinParamwe use:capacity=6,heartbeatTime=20,heartbeatTimeout=60,framerate=24,videoRatio=1.78,smooth=true,maxResolution=0. - Listens for the
onJoincallback for the local side andonParticipantJoinfor the remote (watch) side. - When
onParticipantJoinfires, callsrequestVideo(participant, PICTURESIZE_LARGE)to request the watch's video stream and renders it.
4. Group calls — same room flow as Android 1:1, just with a different room number
For the group/family chat we use room number <deviceIdentificator>_group, also via JCMediaChannel.join. This was already working but now uses the same shared private method as the Android 1:1 flow, so behavior is consistent.
5. SDK initialization is unchanged
We still call JCClient.create(appKey, ...), login("p_<sanitizedEmail>", password), and on _configureDevice we still apply MediaConfig.MODE_RTOS for RTOS watches and MediaConfig.MODE_INTELLIGENT_HARDWARE for Android watches.
6. UI behavior
For both Android 1:1 and group calls, the app shows the channel/conference UI (screenMode = groupCall) which displays the local preview plus a grid of remote participants. For RTOS 1:1 we keep the previous P2P UI driven by onCallItemUpdate(STATE_TALKING).
Expected effect
The previous bug was that the mobile waited forever in STATE_PENDING because the watch was joining a room while the mobile was waiting for a P2P answer. With the new flow, when the user accepts on the watch and the watch joins room <deviceIdentificator>_<appAccount>, the mobile (already in the same room) receives onParticipantJoin with the watch as participant, which is the correct "call connected" event for room mode.
Could you please confirm
- That the room number format
<deviceIdentificator>_<sanitizedAppAccount>(e.g.1106971865_p_testapps_savefamily_gmail_com) is the right one for Android single-chat rooms. - That the
JoinParamvalues above are reasonable for a 1-to-1 watch call (we copied them from our group-call flow). - That nothing else is needed on the mobile side besides
JCMediaChannel.join+enableUploadAudioStream+enableUploadVideoStream+requestVideoafteronParticipantJoin.
We'll test on a real Android watch (1106971865) and share logs once we have results. Thanks for the clarification — the RTC1.0 Flutter quickstart PDF you shared made it very clear once we cross-referenced with the connection process Rev2 document.