UI/UX Part 10 — Files, Media Gallery & Transfer UX Architecture
Reusable P2P Communication Platform
Status: UI/UX architecture specification
UI Series: Part 10
Desktop UI: Dioxus
Android UI: Kotlin + Jetpack Compose
Core runtime: Rust
Primary purpose: define the complete files, shared-media, gallery, preview, upload/download, transfer-center, storage-management, platform file integration, and large-file UX across desktop and Android.
1. Purpose
Files are a first-class communication surface. Users will exchange:
images
videos
documents
audio
voice notes
archives
code
PDFs
links
large binary files
The UX must remain understandable when a file is remote-only, partially downloaded, queued, paused, waiting for a peer, verifying, locally deleted, unavailable, or present only on another device.
The UI represents logical file and transfer state; Rust owns storage, encryption, integrity, availability, transfer scheduling, and authoritative lifecycle.
2. Architectural Position
Rust File / Blob Core
│
├── Blob identity
├── Metadata
├── Encryption
├── Local availability
├── Remote availability
├── Transfer state
├── Integrity verification
├── Thumbnail state
└── Storage policy
│
▼
File Presentation Service
│
┌────┴─────┐
│ │
Dioxus Compose
Desktop Android
3. Primary File UX Surfaces
Files destination
Conversation shared media
Message attachment cards
Transfer center
Media viewer
File details
Storage management
4. Files Destination
Recommended categories:
Recent
Images
Videos
Documents
Audio
Links
Transfers
Optional later:
Favorites
Downloaded
Large Files
5. Stable File Identity
Use a logical identity such as:
#![allow(unused)] fn main() { pub struct BlobId([u8; 32]); }
Never use a filename, local path, URI, or list index as authoritative file identity.
The same blob may appear in multiple messages or conversations while physical storage remains deduplicated.
6. File Summary DTO
#![allow(unused)] fn main() { pub struct FileSummaryView { pub blob: BlobId, pub source_message: Option<MessageId>, pub conversation: ConversationId, pub sender: AccountId, pub display_name: String, pub kind: FileKind, pub size: u64, pub created_at: Timestamp, pub availability: FileAvailability, pub transfer: Option<TransferSummaryView>, pub thumbnail: Option<ThumbnailRef>, } }
7. File Kinds
#![allow(unused)] fn main() { pub enum FileKind { Image, Video, Document, Audio, Archive, Code, Other, Link, } }
Use semantic kinds rather than relying only on MIME strings.
8. File Availability
#![allow(unused)] fn main() { pub enum FileAvailability { RemoteOnly, PartiallyAvailable, LocalVerified, LocalUnverified, Missing, Removed, } }
LocalVerified means the content exists locally and integrity verification succeeded.
RemoteOnly means metadata is known but the device does not currently possess the complete file.
PartiallyAvailable means resumable local transfer data exists.
9. Device-Local Availability
A fundamental rule:
message/file metadata may sync account-wide
but local downloaded bytes are device-specific
If the phone downloads a 2 GB video, the desktop must not claim that it is locally available unless the desktop also has it.
10. Desktop Files Layout
Recommended:
+---------------------------------------------------------------+
| Files Search Filter Sort View |
+-----------------------+---------------------------------------+
| Categories | Content |
| Recent | |
| Images | Grid / list / details |
| Videos | |
| Documents | |
| Audio | |
| Links | |
| Transfers | |
+-----------------------+---------------------------------------+
Dioxus should support richer desktop density, resizing, keyboard navigation, multi-select, context menus, and optional preview/details pane.
11. Android Files Layout
Recommended:
TopAppBar
Files
Search
Sort / Filter
Category chips or secondary navigation
LazyGrid / LazyColumn
Use a grid for visual media and list presentation for documents/audio.
12. Tablet and Foldable
Expanded Android can use:
NavigationRail / category pane
│
File grid/list
│
Optional preview/detail pane
Respect hinges and adaptive window size.
13. Recent Files
Sort by meaningful message/file activity, usually sent/received timestamp.
Do not reorder because a thumbnail finished generating or a local cache changed.
14. Image Gallery
Grid item:
thumbnail
optional download-state overlay
optional multi-select state
Support date grouping such as:
Today
Yesterday
This Month
Older
if useful.
15. Video Gallery
Grid item:
thumbnail
duration
remote/download indicator
Do not decode full video for list presentation.
16. Documents
Prefer list/details presentation:
filename
semantic type
size
conversation/sender
date
availability
17. Audio
List:
name/type
duration if known
sender
date
availability
Voice notes may appear here while remaining primarily accessible through the timeline.
18. Links
Use Part 32 local indexing.
Display:
title if available
domain
source conversation
date
Opening a link must pass through safe external URL handling.
19. Search
File search can include:
filename
caption
conversation
sender
metadata
optional locally extracted document text
Search remains local-first.
20. Filtering
Potential filters:
Conversation
Sender
Type
Date
Downloaded
Remote-only
Keep the normal UI simple; expose combinations through filter sheets/popovers.
21. Sorting
Recommended:
Newest
Oldest
Name
Size
22. View Modes
Desktop:
Grid
List
Details
Android:
Grid for media
List for docs/audio/transfers
Persist view preference per device/category.
23. Shared Media per Conversation
Contact/group details should link to a scoped media area:
Media
Files
Audio
Links
Each item retains its source MessageId when available.
Action:
Show in conversation
jumps to the source message.
24. Source Message Missing
If the message was deleted but a retained local file still exists:
Source message unavailable
The file view must not crash or navigate to a nonexistent row.
25. Thumbnail Architecture
Thumbnail generation is derived state.
#![allow(unused)] fn main() { pub enum ThumbnailState { None, Generating, Ready(ThumbnailRef), Failed, } }
The UI receives handles/references, never generates authoritative file state.
26. Thumbnail Rules
Use:
stable placeholder immediately
known aspect ratio where available
small decoded thumbnail for grid/list
full content only in viewer
Thumbnail failure must not make the original file unusable.
27. Layout Stability
If image/video dimensions are known before thumbnail decode, reserve the expected aspect ratio to avoid large layout shifts.
28. Media Viewer
Opening supported media launches a dedicated viewer.
Desktop:
main workspace
or detached media window
Android:
full-screen destination
29. Viewer Actions
Common:
Back/Close
Previous
Next
Save
Share
Forward
Info
Show in Conversation
Video adds:
Play/Pause
Seek
30. Viewer Navigation
Previous/next operates within a logical collection:
conversation images
recent videos
search result set
not the device filesystem directory.
31. Image Viewer
Support:
zoom
pan
fit-to-screen
actual-size option on desktop
Android uses pinch-to-zoom.
Desktop may use mouse wheel/keyboard controls.
32. Video Playback
Use native/platform-appropriate media rendering.
Hard rule:
raw decoded video frames do not flow through Compose/Dioxus state
UI receives renderer/player state and handles.
33. Audio Playback
UI consumes:
playback state
position
duration
speed if applicable
The audio subsystem owns samples/buffers.
34. Document Preview
Initial recommendation:
preview only formats that can be handled safely and cheaply
otherwise open externally
Do not build a complex office/PDF renderer merely for Part 10.
35. Text/Code Preview
Small text/code files may use a read-only viewer with:
size cap
syntax highlighting optional
copy
open externally
36. Archives
Do not automatically extract untrusted archives.
Show:
archive filename
size
type
open externally / save
37. Transfer Identity
#![allow(unused)] fn main() { pub struct TransferId([u8; 16]); }
A TransferId is distinct from BlobId.
The same blob can participate in multiple transfer attempts over time.
38. Transfer Direction
#![allow(unused)] fn main() { pub enum TransferDirection { Upload, Download, } }
39. Transfer States
#![allow(unused)] fn main() { pub enum TransferState { Queued, WaitingForPeer, Connecting, Transferring, Paused, Verifying, Complete, Failed, Cancelled, } }
40. Transfer Summary
#![allow(unused)] fn main() { pub struct TransferSummaryView { pub id: TransferId, pub blob: BlobId, pub direction: TransferDirection, pub state: TransferState, pub completed_bytes: u64, pub total_bytes: u64, pub rate: Option<TransferRate>, pub eta: Option<Duration>, pub failure: Option<TransferFailureView>, pub capabilities: TransferCapabilities, } }
41. Queued Is Not Failure
A transfer may be queued because of:
offline state
scheduler priority
battery policy
metered policy
active call priority
peer unavailable temporarily
Use neutral language.
42. Waiting for Peer
User-facing examples:
Waiting for sender
Waiting for recipient
Waiting for device
depending direction/context.
43. Transferring
Show when meaningful:
percentage
completed / total bytes
speed
ETA when stable
Do not show unstable ETA values.
44. Progress Update Rate
Visible transfer progress should be coalesced to roughly:
5–10 updates/sec maximum
Background/invisible transfers may update much less frequently.
45. Pause / Resume
Only show if Rust reports capability.
Do not infer support from UI state alone.
46. Cancel
Cancel means:
stop this transfer
It does not automatically mean:
delete message
delete file metadata
block peer
47. Retry
Retry a failed transfer without duplicating the logical file.
Use same BlobId and backend retry semantics.
48. Verification Phase
After all bytes arrive, show:
Verifying…
A transfer becomes complete only after integrity verification succeeds.
49. Verification Failure
This is security-sensitive.
Show:
File failed integrity verification
Do not allow normal Open action.
Potential actions:
Retry
Delete partial data
Details
50. Failure Taxonomy
#![allow(unused)] fn main() { pub enum TransferFailureKind { PeerUnavailable, RouteUnavailable, StorageFull, SourceMissing, PermissionDenied, VerificationFailed, CancelledByPeer, Unsupported, } }
Presentation maps these to understandable language.
51. Transfer Center
Recommended sections:
Active
Queued
Paused
Failed
Completed Recently
52. Desktop Transfer Center
Provide a dedicated destination plus an optional small utility panel showing active transfers.
Do not permanently occupy large screen space when nothing is happening.
53. Android Transfer Center
Files → Transfers screen.
Long user-visible transfers may also expose foreground-service notifications when justified by Android policy.
54. Transfer Row
thumbnail/icon
filename
peer/conversation
state
progress
rate / ETA when useful
actions
55. Android Transfer Notification
Possible:
File transfer in progress
42%
Pause / Cancel
Strict privacy mode can hide file/contact names.
56. Desktop Background Transfer
If the core/daemon remains active after window close:
transfer continues
The tray may show a compact count.
57. Offline Transfers
If no usable route:
Waiting for connection
Do not immediately mark failed.
58. Reconnection
When connectivity returns, Rust resumes/restarts according to transfer semantics.
UI keeps one logical row instead of creating a new visible transfer for every route attempt.
59. Direct / Relay / LAN Changes
Normal UI should not care.
A path switch does not change TransferId merely because the network path changes.
Advanced diagnostics may expose transport details.
60. Resumable Transfers
If supported, transfer state/checkpoints survive restart.
On UI restart:
request fresh transfer snapshot
Never reconstruct byte progress from stale UI state.
61. Message Delivery vs Attachment Completion
These are distinct concepts.
message delivered
≠
recipient has downloaded full attachment
The timeline can show the message while attachment transfer continues.
62. Upload Source Durability
Once an outbound attachment message is durably committed, Rust must have a source that is durable enough to satisfy the outbox contract.
UI should not assume the original Android URI or desktop path will remain forever.
63. Android Source Lifetime
Use:
persisted URI permission
or
secure app staging
or
owned FD/stream lifecycle
according to provider capability.
64. Desktop Source Lifetime
For durable queued sends, stage/copy/reference according to Part 05/06 file subsystem policy.
If source disappears, show a semantic failure rather than an OS path exception.
65. Large Files
Hard UX requirements:
no full-file UI memory buffering
non-blocking preparation
cancelability
progress when meaningful
storage preflight
66. Large File Preparation
Normal UI can show:
Preparing…
Advanced details can show:
Hashing
Staging
Encrypting metadata
if useful.
67. Memory Rule
Never transfer multi-gigabyte content through:
Kotlin ByteArray
Compose state
Dioxus component Vec<u8>
Use handles, streams, descriptors, and Rust-owned buffers.
68. Auto-Download
User policy may include:
Never
Wi-Fi only
Small files only
Always
Later per-type policy:
Images
Video
Documents
Audio
69. Android Auto-Download Factors
Policy can incorporate:
metered network
battery saver
storage pressure
background limits
70. Desktop Auto-Download
Desktop may use a more permissive default but should still obey storage/resource policy.
71. Unknown Request Attachments
Default:
no automatic download
Possible UI:
Attachment from unknown sender
Accept request to download
or explicit manual download if security policy allows.
72. Storage Architecture UX
Users should understand:
app-managed internal copy
vs
exported external copy
The internal blob store remains authoritative for application availability.
73. Internal Storage First
Received files should normally reside in app-managed storage first.
Benefits:
encryption
integrity
deduplication
cleanup
consistent references
Do not automatically scatter all received files into public Downloads.
74. Save / Export
User explicitly chooses:
Save As
Save to device
Share outside app
The exported copy is separate from the internal app copy.
75. Delete Local Copy
Action wording:
Remove from this device
This preserves message/file metadata if the content can be reacquired.
76. Delete Everywhere
Only offer if the message/file protocol explicitly supports it and user has permission.
Never combine with local cleanup.
77. Storage Management Screen
Recommended sections:
Media
Documents
Audio
Partial Transfers
Thumbnail / Derived Cache
Per Conversation
Large Files
Search index storage can be shown separately as derived/rebuildable data.
78. Storage by Conversation
Example:
School ERP Group 4.2 GB
Alice 820 MB
Family 510 MB
This helps users clean data intentionally.
79. Large Files Cleanup
List largest local blobs.
Potential actions:
Remove from device
Show source conversation
Save externally first
80. Cache Cleanup
Safe derived data:
thumbnails
temporary previews
Can be removed without losing authoritative user content.
81. Partial Transfer Cleanup
Show total space.
Cancel/remove partial state only with clear consequence:
Download progress will be lost
if true.
82. Storage Pressure
If Rust reports low space:
Storage is almost full
Manage Storage
The app should remain readable even if new files cannot be stored.
83. Save As — Desktop
Use native file dialog.
Flow:
Choose destination
→ Rust prepares verified export stream/handle
→ write
→ success
Never expose internal CAS paths.
84. Open — Desktop
Depending type:
built-in viewer
or
system default app
85. Reveal in Folder
Only for exported/user-visible filesystem copies.
Do not reveal private internal content-addressed storage.
86. Android Save
Use platform mechanisms such as:
Storage Access Framework
MediaStore
CreateDocument
according to content type.
87. Android Open
Use a secure content URI/provider.
Do not expose raw app-private paths.
88. Android Share
Use the system share sheet with scoped temporary read permission.
89. Android Content Provider Security
Only expose explicitly authorized file handles/BlobIds.
Never create a generic provider that makes the entire internal blob store browsable.
90. URI Permission Lifetime
Grant only what the receiving app needs and revoke/expire appropriately.
91. Desktop Drag-In
Handled by Part 06 composer.
Dropped files become Rust-owned draft attachment sources.
92. Desktop Drag-Out
Future enhancement.
Could export a temporary file/handle to the OS drag system.
Not required for v1.
93. Share vs Forward
Use distinct terminology:
Forward → another conversation inside the app
Share → another application through the OS
94. Forwarding
Rust owns re-encryption/reference semantics.
UI only chooses destination conversation(s).
95. Filenames
Support Unicode.
Sanitize export paths.
Prevent:
../
absolute paths
reserved names
path separators used maliciously
96. Duplicate Filenames
Export/save should use collision-safe naming or ask user.
Logical identity remains BlobId.
97. File Type Safety
MIME is a hint, not sole authority.
Potentially dangerous files such as executables can show a warning before external open.
Do not claim malware detection unless actually implemented.
98. Missing File
Display:
File unavailable
Possible reason/details:
source no longer online
local copy removed
source missing
99. Personal / Archive Node
If a trusted archive source can supply a file later, UI may show:
Download from archive
without exposing unnecessary storage topology.
100. Deduplication
Physical content deduplication is invisible.
If two messages reference identical bytes, each still appears in its own conversation context.
101. EXIF and Metadata
Do not surface sensitive photo location metadata by default.
Detailed metadata view can be added only if useful and privacy-safe.
102. Thumbnail Privacy
Thumbnails are sensitive derived data and follow app-lock/local-storage security policy.
103. Locked App
Files/gallery should not bypass app lock.
104. Sensitive Media
Optional setting:
blur until opened
High-security Android mode may apply secure-window policy to selected viewer screens.
105. Search Privacy
File names, extracted text, and content stay local by default according to Part 32.
106. Accessibility — File Grid
Example screen-reader output:
Photo from Alice, yesterday, downloaded
107. Accessibility — Document Row
PDF, timetable.pdf, 2.4 megabytes, received from Alice, downloaded
108. Accessibility — Transfer
timetable.pdf, downloading, 42 percent
Do not announce every percentage update automatically.
109. Media Viewer Accessibility
Expose labeled:
Close
Previous
Next
Save
Share
Info
110. Large Font
Rows/cards must expand without clipping important actions or filename/state.
111. RTL
Layouts mirror correctly.
Technical identifiers, hashes, and canonical filenames may need controlled bidi handling.
112. Color Independence
Transfer state requires text/icon/progress semantics, not color alone.
113. Reduced Motion
Gallery and viewer transitions can be reduced/disabled.
114. Desktop Keyboard UX
Potential:
Enter Open
Space Quick Preview
Ctrl/Cmd+S Save As
Ctrl/Cmd+F Search
Shift+F10 Context Menu
Deletion should never be a surprising single-key destructive action.
115. Desktop Multi-Select
Support for files:
Ctrl/Cmd click
Shift click
Batch actions:
Download
Save
Forward
Remove Local Copies
as capabilities allow.
116. Android Selection Mode
Long press enters contextual multi-selection.
Back exits selection before leaving screen.
117. Batch Download
Selected remote files can be queued.
Transfer center remains authoritative for progress.
118. Batch Cleanup
Show approximate freed space before confirmation.
119. Auto-Cleanup
Optional policy:
Keep forever
30 days
90 days
custom
This is device-local storage policy unless explicitly synced.
120. Retention Integration
Part 33 may remove local blobs according to policy.
UI can show:
Removed to save space
when metadata remains.
121. Backup Integration
Backup policy determines whether full file bytes, metadata only, recent files, or no files are included.
Part 10 only displays backup coverage where useful; it does not own backup truth.
122. Search Index Integration
If a local downloaded document was indexed and local content is later removed, Part 32 decides whether extracted content/vector state remains or is deleted.
123. File Lifecycle
Conceptually:
message metadata known
↓
remote available
↓
partial local
↓
local verified
↓
optional external export
↓
local app copy may later be removed
The UI must represent this logical lifecycle, not merely path.exists().
124. Rust File Presentation API
#![allow(unused)] fn main() { pub trait FilePresentation { async fn list( &self, query: FileListQuery, ) -> Result<FilePage, UiError>; async fn details( &self, blob: BlobId, ) -> Result<FileDetailView, UiError>; async fn download( &self, blob: BlobId, ) -> Result<TransferId, UiError>; async fn remove_local( &self, blob: BlobId, ) -> Result<(), UiError>; async fn source_message( &self, blob: BlobId, ) -> Result<Option<MessageId>, UiError>; } }
125. Transfer Presentation API
#![allow(unused)] fn main() { pub trait TransferPresentation { async fn list( &self, filter: TransferFilter, ) -> Result<Vec<TransferSummaryView>, UiError>; async fn pause( &self, transfer: TransferId, ) -> Result<(), UiError>; async fn resume( &self, transfer: TransferId, ) -> Result<(), UiError>; async fn cancel( &self, transfer: TransferId, ) -> Result<(), UiError>; async fn retry( &self, transfer: TransferId, ) -> Result<(), UiError>; } }
126. Transfer Capabilities
#![allow(unused)] fn main() { pub struct TransferCapabilities { pub can_pause: bool, pub can_resume: bool, pub can_cancel: bool, pub can_retry: bool, } }
The UI renders actions from these capabilities instead of hardcoded assumptions.
127. Export Presentation API
#![allow(unused)] fn main() { pub trait FileExportPresentation { async fn prepare_export( &self, blob: BlobId, ) -> Result<FileExportHandle, UiError>; } }
The platform UI then chooses destination/share mechanism.
128. Thumbnail Presentation API
#![allow(unused)] fn main() { pub trait ThumbnailPresentation { async fn thumbnail( &self, blob: BlobId, size: ThumbnailSize, ) -> Result<Option<ThumbnailRef>, UiError>; } }
129. File Events
#![allow(unused)] fn main() { pub enum FileUiEvent { FileUpdated(FileSummaryView), FileRemoved(BlobId), ThumbnailChanged { blob: BlobId, thumbnail: Option<ThumbnailRef>, }, StoragePressureChanged(StoragePressureView), } }
130. Transfer Events
#![allow(unused)] fn main() { pub enum TransferUiEvent { TransferAdded(TransferSummaryView), TransferUpdated(TransferSummaryView), TransferRemoved(TransferId), } }
Only affected rows/items update.
131. Android ViewModel Ownership
Owns presentation state such as:
selected category
filter/sort sheet
selection mode
viewer position
save/share one-off effects
Rust owns file/transfer truth.
132. Dioxus Presenter Ownership
Owns:
view mode
selection
sidebar category
preview pane
platform dialog effects
Rust owns file/transfer truth.
133. No Raw File Bytes in UI DTOs
Hard rule.
UI receives:
BlobId
ThumbnailRef
secure viewer/export handles
metadata
progress
not file contents.
134. Android Platform Effects
Examples:
OpenCreateDocument
LaunchShareSheet
OpenExternalViewer
OpenMediaStoreExport
135. Desktop Platform Effects
Examples:
OpenSaveDialog
OpenExternalApplication
RevealExportedFile
CreateMediaViewerWindow
136. Android Permission Philosophy
Prefer SAF/MediaStore/system picker/content URIs to broad storage permissions.
Request broad media access only if a real feature needs device-wide browsing.
137. Empty States
Examples:
No shared files yet
No shared images
No active transfers
No downloaded files
Use one relevant action where possible.
138. Offline UX
Local verified files open normally.
Remote-only file shows:
Waiting for connection
or a Download action that queues the request.
139. Loading UX
Local file metadata should render immediately.
Thumbnail generation and remote availability checks remain incremental.
140. Performance Requirements
The UI should support thousands of file records through:
paging
virtualization
bounded thumbnail cache
incremental events
141. Image Grid Performance
Decode only visible and near-visible thumbnails.
142. Video Thumbnail Performance
Generate asynchronously and cache.
143. Large File Metadata
Do not synchronously inspect/hash huge content on the UI thread.
144. Stable Keys
Use:
BlobId
TransferId
for grid/list identity.
145. Multi-Device Semantics
Example:
Phone:
Blob X = LocalVerified
Desktop:
Blob X = RemoteOnly
Both know the same logical message/file metadata.
This is correct.
146. Transfer State Is Device-Local
Do not synchronize byte progress from one device into another device's transfer UI.
Each device may start its own download.
147. Shared Metadata
Can synchronize:
BlobId
filename
size
type
message association
sender
subject to encryption/privacy model.
148. Notification Semantics
Do not notify for every tiny automatic thumbnail/image download.
Notify only for meaningful user-visible transfers or failures.
149. Screenshot Test States
Required fixtures:
empty Files
image gallery
document list
remote-only media
downloading
paused
waiting for peer
verifying
failed verification
storage full
transfer center
media viewer
dark mode
large font
RTL
150. Android Test Matrix
Include:
SAF save
MediaStore save
share URI
external viewer
process death
foreground transfer notification
metered network policy
storage full
foldable layout
TalkBack
151. Desktop Test Matrix
Include:
grid/list/details
keyboard navigation
multi-select
Save As
external open
reveal exported file
detached viewer
daemon transfer continuity
152. Transfer Tests
offline queue
pause/resume
peer disappears
route switch
verification
storage full
cancel
retry
restart
153. Security Tests
A verification failure must never transition to openable/complete UI state.
154. Path Safety Tests
Malicious filenames cannot escape export destinations or create unintended paths.
155. Large File Test
Multi-gigabyte files must not cause UI memory proportional to file size.
156. Thumbnail Failure Test
File remains accessible through metadata/open/download even when thumbnail generation fails.
157. Local Delete Test
Removing the local copy does not delete its message or peer copy unless user explicitly invokes a separate supported destructive action.
158. Initial Production Recommendation
Ship first:
Recent files
Image gallery
Video gallery
Document list
Audio list
Links
Conversation shared media
Download
Transfer center
Pause/resume/cancel/retry when supported
Media viewer
Save / share / open
Remove local copy
Low-storage warning
Defer:
cloud-like folders
collaborative editing
built-in office suite
advanced PDF editor
complex tags
favorites
remote media-streaming optimization
filesystem synchronization
159. Definition of Done
UI/UX Part 10 is complete when:
- files use stable logical
BlobIdidentity rather than paths - device-local availability is explicitly separated from synced logical metadata
- Recent/Images/Videos/Documents/Audio/Links/Transfers surfaces are defined
- conversation-scoped shared media is defined
- transfer states, actions, progress, and retry semantics are explicit
- queued/offline/waiting states are not treated as immediate errors
- Complete means integrity verification succeeded
- progress updates are throttled and capability-driven
- large files never travel through UI memory as giant buffers
- Android uses secure content URIs, FDs, SAF, and MediaStore-style flows
- desktop uses native Save/Open/Reveal semantics without exposing internal CAS paths
- media viewer semantics are defined
- thumbnails are derived, asynchronous, and non-authoritative
- removing a local copy is distinct from deleting the logical message/file everywhere
- storage management and low-storage UX are defined
- accessibility, keyboard/TalkBack, RTL, large font, and reduced motion are covered
- Rust file, transfer, export, thumbnail, and event presentation contracts are defined
- restart, offline, route switch, storage-full, verification-failure, malicious-filename, and multi-device cases are tested
160. Final Architecture
RUST FILE / BLOB CORE
│
┌─────────────────────┼─────────────────────┐
│ │ │
Metadata Local Storage Transfers
│ │ │
└─────────────────────┼─────────────────────┘
│
File Presentation
┌────────┴────────┐
│ │
Dioxus Compose
│ │
Desktop Files Android Files
│ │
└────────┬────────┘
│
BlobId / TransferId
Platform-specific output:
Desktop:
Save As
Open
Reveal
detached viewer
Android:
SAF / MediaStore
secure content URI
share sheet
external viewer
The UI never owns:
raw transfer chunks
encryption keys
internal CAS paths
full large-file buffers
161. Final Principle
The file UX should behave like a reliable communication attachment system, not an accidental filesystem browser.
The right model is:
logical file identity
+
clear local/remote availability
+
resumable verified transfer state
+
platform-native open/save/share
+
bounded thumbnails/previews
+
device-aware storage policy
not:
UI manipulates arbitrary paths and byte buffers directly
This gives Dioxus desktop and Android Compose a safe, fast, native file/media experience while the Rust file/blob and transfer engines remain authoritative.