Add message clear option

This commit is contained in:
Janez T
2026-03-05 11:04:41 +01:00
parent 2d9cb0ddb9
commit 234edf5bb0
14 changed files with 1075 additions and 319 deletions

View File

@@ -5,8 +5,9 @@
Image mode mirrors the voice on-demand architecture exactly:
- **Control plane (text messages):**
- `IE1:` image envelope announces image availability in chat.
- `IR1:` direct fetch request asks sender to stream image fragments.
- `IE2:` image envelope announces image availability in chat.
- **Control plane (raw binary request):**
- Binary image fetch request (same raw route as image fragments).
- **Data plane (raw binary packets):**
- `ImagePacket` binary payload streamed via `cmdSendRawData` / `pushRawData`.
@@ -17,8 +18,8 @@ pixels are fetched on demand when the user taps the image bubble.
- `lib/utils/image_message_parser.dart`
- `ImagePacket` (binary fragment format)
- `ImageEnvelope` (`IE1`)
- `ImageFetchRequest` (`IR1`)
- `ImageEnvelope` (`IE2`)
- `ImageFetchRequest` (binary)
- `fragmentImage()` — split compressed bytes into packets
- `reassembleImage()` — join received fragments into bytes
- `lib/screens/messages_tab.dart`
@@ -27,7 +28,7 @@ pixels are fetched on demand when the user taps the image bubble.
- Reassembly sessions, outgoing cache, deferred serving
- Outgoing sessions also registered as complete incoming sessions for immediate local display
- `lib/providers/app_provider.dart`
- Incoming routing for `IE1`, `IR1`, binary `0x49` packets
- Incoming routing for `IE2`, binary image fetch requests, binary `0x49` packets
- `lib/widgets/messages/image_message_bubble.dart`
- Square cover thumbnail (up to 256 px); tap-to-load for received images;
progress ring during fetch; full-screen `InteractiveViewer` on tap
@@ -39,52 +40,52 @@ pixels are fetched on demand when the user taps the image bubble.
## 3. Wire Formats
### 3.1 Image Envelope (`IE1`)
### 3.1 Image Envelope (`IE2`)
Prefix: `IE1:` + colon-delimited payload
Prefix: `IE2:` + colon-delimited compact payload (base36 numeric fields)
Fields:
| Field | Type | Description |
|--------------|--------|------------------------------------------------|
| `sid` | string | 8 hex chars (4 bytes), session ID |
| `fmt` | int | `ImageFormat.id` (0 = AVIF, 1 = JPEG) |
| `total` | int | Fragment count (1..255) |
| `w` | int | Actual image width after compression (pixels) |
| `h` | int | Actual image height after compression (pixels) |
| `bytes` | int | Total compressed size in bytes |
| `sid` | string | base36 token for 32-bit session ID |
| `fmt` | base36 | `ImageFormat.id` (0 = AVIF, 1 = JPEG) |
| `total` | base36 | Fragment count (1..255) |
| `w` | base36 | Actual image width after compression (pixels) |
| `h` | base36 | Actual image height after compression (pixels) |
| `bytes` | base36 | Total compressed size in bytes |
| `senderKey6` | string | 12 hex chars (6 bytes sender prefix) |
| `ts` | int | Unix timestamp (seconds) |
| `ver` | int | Protocol version (currently `1`) |
| `ts` | base36 | Unix timestamp (seconds) |
Compact format:
```text
IE1:{sid}:{fmt}:{total}:{w}:{h}:{bytes}:{senderKey6}:{ts}:{ver}
IE2:{sid}:{fmt}:{total}:{w}:{h}:{bytes}:{senderKey6}:{ts}
```
Example (256×171 landscape image, 14 fragments):
```text
IE1:deadbeef:0:14:256:171:2100:aabbccddeeff:1700000000:1
IE2:a:0:e:74:4r:1mc:aabbccddeeff:s44we8
```
Note: `w` and `h` reflect the actual post-compression dimensions, which preserve
Note: `sid` is base36 on wire and expands to 8-hex internally.
`w` and `h` reflect the actual post-compression dimensions, which preserve
the source aspect ratio (contain within the configured max size).
### 3.2 Image Fetch Request (`IR1`)
### 3.2 Image Fetch Request (binary)
Same structure as `VR1`:
Binary payload format:
```text
IR1:{sid}:{want}:{requesterKey6}:{ts}:{ver}
[magic=0x69][sid:4B][flags:1B][requesterKey6:6B][ts:4B][missingCount:1B][missingIndices...]
```
| Field | Value |
|------------------|--------------------------|
| `want` | `a` (= "all fragments") |
| `requesterKey6` | 12 hex chars |
| `ver` | `1` |
| `flags` | bit0=1 => request missing indices, else all |
| `requesterKey6` | 6-byte requester key prefix |
| `ts` | unix timestamp seconds (u32) |
### 3.3 Raw Image Packet (data plane)
@@ -151,16 +152,16 @@ only the shorter axis is padded — no cropping occurs.
7. Envelope sent via normal message path:
- Channel: `sendChannelMessage`
- Direct: `sendTextMessage`
8. Local placeholder message added (`IE1:` text, `deliveryStatus.sending`).
8. Local placeholder message added (`IE2:` text, `deliveryStatus.sending`).
## 6. Incoming Flow (Receive)
### 6.1 `IE1` envelope received
### 6.1 `IE2` envelope received
`AppProvider` calls `imageProvider.registerEnvelope()` and adds the message to
chat. The bubble shows a grey square placeholder with a download icon.
### 6.2 `IR1` request received
### 6.2 Binary image fetch request received
`AppProvider` treats it as control-plane only (not added to chat):
@@ -194,7 +195,7 @@ When `cacheOutgoingSession()` is called it also writes all fragments into
- **Complete session**: `AspectRatio(1.0)``AvifImage.memory(fit: cover)`
square thumbnail; tap → full-screen `InteractiveViewer` with fade transition.
- **Incomplete/missing**: grey square placeholder with download icon;
tap → sends `IR1` fetch request.
tap → sends binary fetch request.
- **Loading**: circular progress indicator showing `received/total` count.
- **Error**: broken-image icon.
@@ -212,7 +213,8 @@ Image bubbles and Message Technical Details show an **estimated transmit time**
The estimate is airtime-based (LoRa packet model), not just compressed image size:
- Source inputs:
- `total` fragments and `bytes` from `IE1` envelope
- `total` fragments and `bytes` from `IE2` envelope
- all numeric envelope values are decoded from base36
- `pathLen` from message metadata
- current radio params from `deviceInfo`: `radioBw`, `radioSf`, `radioCr`
- Per-fragment payload model:
@@ -270,10 +272,10 @@ sequenceDiagram
A->>A: Compress: contain resize → grayscale → PNG → AVIF
A->>A: Fragment into ≤152B packets
A->>A: Cache outgoing + populate local session (immediate display)
A->>M: Send IE1 envelope (actual w×h, fragment count)
M->>B: Deliver IE1
A->>M: Send IE2 envelope (actual w×h, fragment count)
M->>B: Deliver IE2
B->>B: Render grey placeholder bubble
B->>A: Tap → send IR1 fetch request
B->>A: Tap → send binary fetch request
A->>B: Stream binary ImagePackets
B->>B: Reassemble fragments
B->>B: Display AVIF image (cover thumbnail)

View File

@@ -5,8 +5,9 @@
Voice mode uses a **two-plane architecture**:
- **Control plane (text messages):**
- `VE1:` voice envelope announces voice availability in chat.
- `VR1:` direct fetch request asks sender to stream voice payload.
- `VE2:` voice envelope announces voice availability in chat.
- **Control plane (raw binary request):**
- Binary voice fetch request (same raw route as voice packets).
- **Data plane (raw binary packets):**
- `VoicePacket` payload streamed via `cmdSendRawData` and received through `pushRawData`.
@@ -16,73 +17,58 @@ This design avoids broadcasting full voice payloads to channels/rooms. Chat carr
- `lib/utils/voice_message_parser.dart`
- `VoicePacket` (legacy text + binary packet format)
- `VoiceEnvelope` (`VE1`)
- `VoiceFetchRequest` (`VR1`)
- `VoiceEnvelope` (`VE2`)
- `VoiceFetchRequest` (binary)
- `lib/screens/messages_tab.dart`
- Capture/encode voice, cache encoded packets, send envelope only
- `lib/providers/voice_provider.dart`
- Reassembly/playback sessions
- Outgoing session cache + deferred serving
- `lib/providers/app_provider.dart`
- Incoming routing for `VE1` and `VR1`
- Incoming routing for `VE2` and binary voice fetch requests
- Handles raw packet ingestion
- `lib/widgets/messages/voice_message_bubble.dart`
- Play behavior (immediate play if complete, otherwise fetch + auto-play)
- `lib/providers/messages_provider.dart`
- Message-level voice detection (`VE1` + legacy `V:`)
- Message-level voice detection (`VE2` + legacy `V:`)
- `lib/services/message_storage_service.dart`
- Persists `isVoice` and `voiceId`
## 3. Wire Formats
### 3.1 Voice Envelope (`VE1`)
### 3.1 Voice Envelope (`VE2`)
Prefix: `VE1:` + colon-delimited compact payload
Prefix: `VE2:` + colon-delimited compact payload (base36 numeric fields)
Fields:
- `sid` (string, 8 hex chars): session ID
- `mode` (int): codec mode ID (`VoicePacketMode.id`)
- `total` (int): packet count (1..255)
- `durMs` (int): estimated duration in ms
- `sid` (string): base36 token for 32-bit session ID
- `mode` (base36): codec mode ID (`VoicePacketMode.id`)
- `total` (base36): packet count (1..255)
- `durS` (base36): estimated duration in seconds
- `senderKey6` (string, 12 hex chars): sender public-key prefix (6 bytes)
- `ts` (int): unix timestamp seconds
- `ver` (int): protocol version (currently `1`)
- `ts` (base36): unix timestamp seconds
`sid` is base36 on wire and expands to 8-hex internally.
Compact format:
```text
VE1:{sid}:{mode}:{total}:{durMs}:{senderKey6}:{ts}:{ver}
VE2:{sid}:{mode}:{total}:{durS}:{senderKey6}:{ts}
```
Example:
```text
VE1:deadbeef:1:4:3200:aabbccddeeff:1700000000:1
VE2:a:1:4:4:aabbccddeeff:s44we8
```
### 3.2 Voice Fetch Request (`VR1`)
### 3.2 Voice Fetch Request (binary)
Prefix: `VR1:` + colon-delimited compact payload
Fields:
- `sid` (string, 8 hex chars): requested session
- `want` (string): currently `a` (compact token for `all`)
- `requesterKey6` (string, 12 hex chars): requester key prefix
- `ts` (int): unix timestamp seconds
- `ver` (int): protocol version (`1`)
Compact format:
Binary payload format:
```text
VR1:{sid}:{want}:{requesterKey6}:{ts}:{ver}
```
Example:
```text
VR1:deadbeef:a:112233445566:1700000010:1
[magic=0x72][sid:4B][flags:1B][requesterKey6:6B][ts:4B][missingCount:1B][missingIndices...]
```
### 3.3 Raw Voice Packet (data plane)
@@ -102,18 +88,18 @@ Binary payload structure:
2. Each chunk is codec2-encoded into `VoicePacket` objects.
3. Packets are cached in `VoiceProvider` outgoing cache (TTL 15 min).
4. Sender inserts local voice placeholder message (`isVoice=true`, `voiceId=sessionId`).
5. Sender sends one envelope (`VE1`) through normal message path:
5. Sender sends one envelope (`VE2`) through normal message path:
- channel/room: `sendChannelMessage`
- direct: `sendTextMessage`
6. **No raw audio packets are sent during initial send.**
## 5. Incoming Routing
### 5.1 `VE1` envelope received
### 5.1 `VE2` envelope received
`AppProvider` marks message as voice (`isVoice`, `voiceId`) and adds it to chat.
### 5.2 `VR1` request received
### 5.2 Binary voice fetch request received
`AppProvider` treats it as control-plane only:
@@ -132,8 +118,8 @@ In `VoiceMessageBubble`:
- If session already complete: play immediately.
- If incomplete/missing:
1. Resolve sender contact (message sender prefix or `VE1.senderKey6` fallback)
2. Send direct `VR1` fetch request
1. Resolve sender contact (message sender prefix or `VE2.senderKey6` fallback)
2. Send direct binary fetch request
3. Show requesting state in UI
4. Auto-play when session becomes complete
@@ -170,10 +156,9 @@ Parser validation enforces:
- strict hex lengths for IDs and key prefixes
- valid mode range
- valid packet counts and duration bounds
- fixed protocol version (`ver == 1`)
- `VR1.want` token `a` (internally normalized to `all`)
`VR1` handling verifies sender prefix matches `requesterKey6` to reduce spoofing risk.
- compact base36 numeric fields in envelope/request
- Binary request flags specify `all` or `missing` indices.
- Request payload includes `requesterKey6` to resolve return route.
## 10. Transmit Time Estimate (UI)
@@ -182,7 +167,8 @@ Voice bubbles and Message Technical Details show an **estimated transmit time**
The estimate is airtime-based (LoRa packet model), not file-duration-only:
- Source inputs:
- `packetCount` and `durationMs` from `VE1` envelope, or
- `packetCount` and `durationMs` from `VE2` envelope, or
- numeric envelope values decoded from base36
- actual received `VoicePacket.codec2Data.length` bytes when local session packets exist
- `pathLen` from message metadata
- current radio params from `deviceInfo`: `radioBw`, `radioSf`, `radioCr`
@@ -223,7 +209,7 @@ Fallback defaults are used when radio params are unavailable: `SF10`, `BW250kHz`
## 12. Backward Compatibility
- Legacy `V:` text packet parsing is still supported.
- Message voice detection accepts both new `VE1` and legacy `V:` formats.
- Message voice detection accepts `VE2` and legacy `V:` formats.
## 13. High-Level Sequence
@@ -235,10 +221,10 @@ sequenceDiagram
A->>A: Record + encode voice packets
A->>A: Cache session packets (TTL 15m)
A->>M: Send VE1 envelope
M->>B: Deliver VE1
A->>M: Send VE2 envelope
M->>B: Deliver VE2
B->>B: Render voice bubble (metadata only)
B->>A: Send VR1 request on Play
B->>A: Send binary fetch request on Play
A->>B: Stream raw VoicePacket packets
B->>B: Reassemble session
B->>B: Auto-play when complete