feat: identicons, tap-to-copy fingerprint, recent rooms (Phase 1 backport)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 36s
Build Release Binaries / build-amd64 (push) Failing after 3m47s

Backport from desktop client to Android:

Identicons:
- New Identicon.kt composable: deterministic 5x5 symmetric Canvas pattern
  from fingerprint hash (same algorithm as desktop identicon.ts)
- Participant list shows identicon + name + tappable fingerprint
- Settings page shows identicon next to fingerprint

CopyableFingerprint:
- Tap any fingerprint text to copy to clipboard with Toast feedback
- Used in participant list and settings page

Recent rooms:
- SettingsRepository: persists last 5 (relay, room) pairs
- CallViewModel: saves on startCall, exposes as StateFlow
- InCallScreen: clickable chips that fill room + select matching server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-06 22:37:46 +04:00
parent a39b074d6e
commit a9adb5cfd7
5 changed files with 253 additions and 13 deletions

View File

@@ -70,6 +70,9 @@ class CallViewModel : ViewModel(), WzpCallback {
private val _preferIPv6 = MutableStateFlow(false)
val preferIPv6: StateFlow<Boolean> = _preferIPv6.asStateFlow()
private val _recentRooms = MutableStateFlow<List<com.wzp.data.SettingsRepository.RecentRoom>>(emptyList())
val recentRooms: StateFlow<List<com.wzp.data.SettingsRepository.RecentRoom>> = _recentRooms.asStateFlow()
private val _playoutGainDb = MutableStateFlow(0f)
val playoutGainDb: StateFlow<Float> = _playoutGainDb.asStateFlow()
@@ -139,6 +142,7 @@ class CallViewModel : ViewModel(), WzpCallback {
_captureGainDb.value = s.loadCaptureGain()
_seedHex.value = s.getOrCreateSeedHex()
_aecEnabled.value = s.loadAecEnabled()
_recentRooms.value = s.loadRecentRooms()
}
fun selectServer(index: Int) {
@@ -287,6 +291,8 @@ class CallViewModel : ViewModel(), WzpCallback {
_debugReportAvailable.value = false
_debugReportStatus.value = null
lastCallServer = serverEntry.address
settings?.addRecentRoom(serverEntry.address, room)
_recentRooms.value = settings?.loadRecentRooms() ?: emptyList()
debugReporter?.prepareForCall()
try {
// Teardown previous call but don't stop the service (we're about to restart it)

View File

@@ -42,6 +42,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -200,6 +201,36 @@ fun InCallScreen(
modifier = Modifier.fillMaxWidth(0.6f)
)
// Recent rooms
val recentRooms by viewModel.recentRooms.collectAsState()
if (recentRooms.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
recentRooms.forEach { recent ->
Surface(
onClick = {
viewModel.setRoomName(recent.room)
// Select matching server
val idx = servers.indexOfFirst { it.address == recent.relay }
if (idx >= 0) viewModel.selectServer(idx)
},
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.padding(2.dp)
) {
Text(
text = recent.room,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)
)
}
}
}
}
Spacer(modifier = Modifier.height(24.dp))
Button(
@@ -262,11 +293,33 @@ fun InCallScreen(
color = MaterialTheme.colorScheme.onSurfaceVariant
)
unique.forEach { member ->
Text(
text = member.displayName,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 2.dp)
) {
com.wzp.ui.components.Identicon(
fingerprint = member.fingerprint.ifEmpty { member.displayName },
size = 28.dp,
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = member.displayName,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (member.fingerprint.isNotEmpty()) {
com.wzp.ui.components.CopyableFingerprint(
fingerprint = member.fingerprint.take(16),
style = MaterialTheme.typography.labelSmall.copy(
fontSize = 9.sp,
fontFamily = FontFamily.Monospace,
),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
)
}
}
}
}
}