Contacts & QR Share¶
Purpose¶
Contacts helps a user manage trusted people they can call. QR share lets a user safely share a temporary contact link without exposing private keys or long-lived identity data.
The flow is intentionally simple:
Discover person -> send request -> accept/decline -> sync contact list -> warm trust data for future calls
Main Concepts¶
| Concept | Meaning |
|---|---|
| Contact | A person already connected to the current user. |
| Friend request | A pending relationship request between two users. |
| Share token | An opaque server-issued value embedded in a QR/link. |
| Trust warm-up | Background verification of peer device keys after a request is sent or accepted. |
| Numeric fallback | Direct add flow from call history when the app already has a participant identifier. |
Friend Request Lifecycle¶
User chooses a person
-> app sends request by known ID or share token
-> server creates pending request
-> receiver gets notified
-> receiver accepts, declines, or sender cancels
-> contact lists and badges refresh
Important behavior:
- Sending by ID is used when the app already knows the target from call history or an internal record.
- Sending by token is used for QR/universal-link sharing.
- Accepting a request creates the contact relationship.
- Declining or canceling removes pending relationship state and clears related local trust cache.
- Successful send/accept can return peer key hashes; the app verifies and caches those in the background so future calls start faster and with fewer warnings.
QR Share Lifecycle¶
User opens own QR
-> app asks server for an opaque token
-> app builds a share link from that token
-> app renders the link as QR
-> another user scans/taps/imports the link
-> app resolves the token
-> app shows the resolved display name
-> user confirms Add Friend
The client treats token lifetime, usage count, and rate limits as server policy. The client only displays the expiration hint returned by the server, with a local fallback when the server omits that hint.
Accepted Input Sources¶
| Source | Behavior |
|---|---|
| Camera scan | Reads QR payload, resolves token if it is a contact link. |
| Photo import | Detects QR payload from the selected image, then follows the same scan path. |
| Universal link | Opens the Add Friend sheet and resolves the embedded token. |
| App deep link | Same as universal link, but through the app URL scheme. |
| Call history action | Uses the known participant identifier directly when no share token is involved. |
Error Handling¶
The UI does not hardcode token-specific branches. It displays the localized server error when available, otherwise a generic validation or network message.
Common user-facing states:
- invalid QR/link
- token cannot be resolved
- request already sent or no longer valid
- network/session failure
- duplicate send prevented while a request is already in progress
State & Concurrency Intent¶
The Contacts and QR screens keep UI state on the main actor and push network work into asynchronous service calls. Loading flags prevent duplicate user actions while a request is in flight.
Design intent:
- one visible source of truth for contact list and request badge
- no business logic inside visual rows/buttons
- no client-side token generation or token signing
- no sensitive data in QR payload beyond the opaque token
- predictable recovery: refresh lists after send/accept/decline/cancel