feat(direct-call): call history, recent contacts, deregister button
Persistent JSON-backed call history for the direct-call screen so users
can see what they've placed / received / missed and dial back with one
click. Also fixes two small latent UX issues reported alongside.
Backend (Rust)
- new crate/module desktop/src-tauri/src/history.rs: thread-safe in-
process store (OnceLock<RwLock<Vec<CallHistoryEntry>>>) backed by
<APP_DATA_DIR>/call_history.json. Atomic writes via temp+rename. Max
200 entries, FIFO pruning. CallDirection { Placed, Received, Missed }.
- Log hooks in the signal loop + commands:
* place_call → Placed entry (with target fingerprint)
* DirectCallOffer → Missed entry up front; upgraded to Received
inside answer_call when accept_mode != Reject
via history::mark_received_if_pending(call_id).
If user rejects or never answers, it stays Missed.
- New Tauri commands:
* get_call_history() → all entries, newest first
* get_recent_contacts() → unique peers by fp, newest interaction first
* clear_call_history() → wipes JSON + in-memory
* deregister() → tears down signal transport + endpoint
Backend emits `history-changed` events so the UI can live-refresh
without polling.
Frontend (main.ts + index.html + style.css)
- Direct-call panel now has:
* Recent contacts chip row (top 6 unique peers). Click a chip → dial.
* Call history list (up to 50 rows). Direction icon (↗ placed, ↙
received, ✗ missed), peer alias/fp, relative timestamp, callback
button. Both click handlers populate target-fp and fire place_call.
* Deregister button in the "registered" header — calls the new
deregister command, tears down the signal transport, returns the
UI to the pre-register state.
* Clear-history link in the history header.
- Subscribes to `history-changed` events so the list updates the moment
the backend logs a new entry. Also refreshed on register + after a
clear.
- Nothing is rendered until there is data — empty sections stay hidden.
Tasks #20 + #21 (small UX items bundled in)
- Default room "general" for new installations: the html input value
attribute is now "general" and loadSettings() defaults match. Existing
users' localStorage still wins.
- Random alias on desktop: already latent but confirmed working — the
startup IIFE at main.ts:374 calls get_app_info() and prefills the
alias input from derive_alias(seed) when the input is empty. No code
change needed, just verified it flows through the same path as the
Android client.
Known follow-ups (deferred to step 6 polish)
- Call duration tracking (currently all entries have no duration field)
- Hangup signal from an unanswered incoming should emit history-changed
so the missed state is visible even when the user never tapped accept
- Android UI layout fit-check on the smaller Nothing screen
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
</button>
|
||||
</label>
|
||||
<label>Room
|
||||
<input id="room" type="text" value="android" />
|
||||
<input id="room" type="text" value="general" />
|
||||
</label>
|
||||
<label>Alias
|
||||
<input id="alias" type="text" placeholder="your name" />
|
||||
@@ -48,7 +48,10 @@
|
||||
<div id="direct-mode" class="hidden">
|
||||
<button id="register-btn" class="primary" style="background:#2196F3">Register on Relay</button>
|
||||
<div id="direct-registered" class="hidden" style="margin-top:12px">
|
||||
<p style="color:var(--green);font-size:13px">✅ Registered — waiting for calls</p>
|
||||
<div class="direct-registered-header">
|
||||
<p style="color:var(--green);font-size:13px;margin:0">✅ Registered — waiting for calls</p>
|
||||
<button id="deregister-btn" class="secondary-btn small">Deregister</button>
|
||||
</div>
|
||||
<div id="incoming-call-panel" class="hidden" style="background:#1B5E20;padding:12px;border-radius:8px;margin:8px 0">
|
||||
<p style="font-weight:bold;margin:0 0 4px 0">Incoming Call</p>
|
||||
<p id="incoming-caller" style="font-size:12px;opacity:0.8;margin:0 0 8px 0">From: unknown</p>
|
||||
@@ -57,6 +60,22 @@
|
||||
<button id="reject-call-btn" style="flex:1;background:var(--red);color:white;border:none;padding:8px;border-radius:6px;cursor:pointer">Reject</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent contacts -->
|
||||
<div id="recent-contacts-section" class="hidden">
|
||||
<div class="history-header">Recent contacts</div>
|
||||
<div id="recent-contacts-list" class="history-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Call history -->
|
||||
<div id="call-history-section" class="hidden">
|
||||
<div class="history-header">
|
||||
History
|
||||
<button id="clear-history-btn" class="link-btn">clear</button>
|
||||
</div>
|
||||
<div id="call-history-list" class="history-list"></div>
|
||||
</div>
|
||||
|
||||
<label style="margin-top:8px">Call by fingerprint
|
||||
<input id="target-fp" type="text" placeholder="xxxx:xxxx:xxxx:..." />
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user