Files
Kecalek/specs/agent-g-profile-group-verification.md
filip fe861cfafa Initial commit: Kecalek Android client
Complete Android client for encrypted chat platform.
78+ Kotlin files: crypto (X3DH, Double Ratchet, AES-GCM, Ed25519, X25519,
RSA-PSS), network (TCP/TLS, 50 endpoints), Hilt DI, Room+SQLCipher DB,
Jetpack Compose UI with Catppuccin Mocha theme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 01:19:17 +01:00

8.7 KiB

Agent G: Profile + Group + Verification Screens

Phase: 2 (UI Shells)

Depends on: Agent A, Agent B (theme + navigation), Agent C (models)

Context

These screens handle user profiles, group management, and contact verification. Verification uses Signal-style safety numbers, fingerprints, and QR codes.

Task

Create Profile, EditProfile, GroupInfo, SafetyNumber, QRScanner, DeviceList screens and ViewModels.

Files to Create

1. ui/profile/ProfileScreen.kt

Jetpack Compose screen showing user profile:

  • Top bar: Back arrow + "Profile" title
  • Own profile (editable mode):
    • Large circular avatar (80dp) with camera overlay icon
    • Username (editable text field)
    • Email (read-only, Subtext1 color)
    • Phone text field (with visibility toggle switch)
    • Location text field (with visibility toggle switch)
    • "Save" button (Lavender, full width)
    • Divider
    • "Key Rotation" button (Peach/warning color)
    • "Authorize Device" button
    • "Logout" button (Red color)
  • Other user profile (read-only mode):
    • Large circular avatar (80dp)
    • Username (headlineMedium)
    • Email (bodyMedium, Subtext1)
    • Phone (if visible)
    • Location (if visible)
    • Divider
    • Security section:
      • Verification status badge ("Verified" green or "Not Verified" muted)
      • "View Safety Number" button (navigates to Verification screen)
      • Fingerprint display (monospace, small)

2. ui/profile/EditProfileScreen.kt

(Can be merged into ProfileScreen as a mode, or separate — up to implementation)

3. ui/profile/ProfileViewModel.kt

package com.kecalek.chat.ui.profile

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.kecalek.chat.data.model.UserProfile
import javax.inject.Inject

data class ProfileUiState(
    val profile: UserProfile? = null,
    val isOwnProfile: Boolean = false,
    val isEditing: Boolean = false,
    val isLoading: Boolean = false,
    val error: String? = null,
    val verificationStatus: String = "unverified", // "verified", "trusted", "unverified"
    val fingerprint: String = "",
)

@HiltViewModel
class ProfileViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    // TODO: Inject repositories
) : ViewModel() {

    val userId: String = savedStateHandle["userId"] ?: ""

    private val _uiState = MutableStateFlow(ProfileUiState())
    val uiState: StateFlow<ProfileUiState> = _uiState.asStateFlow()

    fun loadProfile() { /* TODO */ }
    fun updateProfile(phone: String?, location: String?, phoneVisible: Boolean, locationVisible: Boolean) { /* TODO */ }
    fun updateAvatar(imageBytes: ByteArray) { /* TODO */ }
    fun rotateKeys() { /* TODO */ }
    fun logout() { /* TODO */ }
}

4. ui/groups/GroupInfoScreen.kt

Jetpack Compose screen for group management:

  • Top bar: Back arrow + "Group Info" title
  • Group avatar (80dp, circular) with camera overlay (creator only)
  • Group name (editable by creator, with edit icon)
  • Member count ("X members")
  • Divider
  • Members list:
    • Each member: avatar (32dp) + username + email
    • Creator badge (small crown or "Admin" label)
    • Verified checkmark for verified members (not self)
    • Creator can tap member → "Remove" option
  • "Add Member" button (creator or all members, depending on server):
    • Opens dialog with email input
  • Divider
  • "Leave Group" button (Red, outlined)
  • "Delete Group" button (Red, filled — creator only)
  • Confirmation dialogs for destructive actions

5. ui/groups/CreateGroupSheet.kt

Bottom sheet for group creation (reuse from Agent E or create here):

  • Group name field
  • Email field + "Add" button
  • Member chip list
  • "Create" button

6. ui/groups/InvitationBanner.kt

Composable for invitation display in conversation list:

  • Peach/Amber border card
  • Group name + "invited by Username"
  • Accept (Green icon button) + Decline (Red icon button)

7. ui/verification/SafetyNumberScreen.kt

Jetpack Compose screen for contact verification:

  • Top bar: Back arrow + "Verify Contact" title
  • Verification status badge:
    • "Verified" (Green background)
    • "Trusted" (Lavender background)
    • "Not Verified" (Surface1 background)
  • Safety number display:
    • 60 digits displayed as 12 groups of 5
    • 3 lines of 4 groups each
    • Monospace font, large text
    • Info text: "Compare this number with your contact's device"
  • QR code:
    • Generated QR code image (200dp)
    • "Show my QR code" section
  • Fingerprints section:
    • "My fingerprint": 30 digits (6 groups of 5, 2 lines)
    • "Their fingerprint": 30 digits
    • Monospace font
  • Action buttons:
    • "Mark as Verified" (Green, filled) — shown when not verified
    • "Remove Verification" (Red, outlined) — shown when verified
    • "Scan QR Code" button (opens camera)

8. ui/verification/QRScannerScreen.kt

Camera-based QR code scanner:

  • Full-screen camera preview
  • QR code detection overlay (frame guide)
  • On successful scan: verify and show result
  • "Cancel" button overlay
  • Uses CameraX + ZXing for detection

9. ui/verification/VerificationVM.kt

package com.kecalek.chat.ui.verification

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject

data class VerificationUiState(
    val peerUsername: String = "",
    val verificationStatus: String = "unverified",
    val safetyNumber: String = "",       // 60 digits formatted
    val myFingerprint: String = "",      // 30 digits formatted
    val peerFingerprint: String = "",    // 30 digits formatted
    val qrCodeData: ByteArray? = null,   // QR payload for generation
    val isLoading: Boolean = false,
    val scanResult: String? = null,      // Success/failure message
)

@HiltViewModel
class VerificationVM @Inject constructor(
    savedStateHandle: SavedStateHandle,
    // TODO: Inject ChatClient for verification operations
) : ViewModel() {

    val userId: String = savedStateHandle["userId"] ?: ""

    private val _uiState = MutableStateFlow(VerificationUiState())
    val uiState: StateFlow<VerificationUiState> = _uiState.asStateFlow()

    fun loadVerificationData() { /* TODO: get safety number, fingerprints, QR data */ }
    fun markAsVerified() { /* TODO: verify_contact() */ }
    fun removeVerification() { /* TODO: unverify_contact() */ }
    fun processQrScanResult(data: String) { /* TODO: verify_qr_code() */ }
}

10. ui/devices/DeviceListScreen.kt

Jetpack Compose screen for device management:

  • Top bar: Back arrow + "My Devices" title
  • Device list (LazyColumn):
    • Each device: device name/ID + last seen timestamp
    • Current device highlighted with "(This device)" label
    • "Remove" button for other devices (Red icon)
  • Info text: "Removing a device will end its session"

11. ui/devices/DeviceViewModel.kt

package com.kecalek.chat.ui.devices

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject

data class Device(
    val id: String,
    val name: String?,
    val lastSeen: String,
    val isCurrentDevice: Boolean,
)

data class DeviceListState(
    val devices: List<Device> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null,
)

@HiltViewModel
class DeviceViewModel @Inject constructor(
    // TODO: Inject ChatClient
) : ViewModel() {

    private val _uiState = MutableStateFlow(DeviceListState())
    val uiState: StateFlow<DeviceListState> = _uiState.asStateFlow()

    fun loadDevices() { /* TODO */ }
    fun removeDevice(deviceId: String) { /* TODO */ }
}

Safety Number Display Format

12345 67890 12345 67890
12345 67890 12345 67890
12345 67890 12345 67890
  • 12 groups of 5 digits
  • 3 lines of 4 groups
  • Monospace font
  • Large text (20sp)

Fingerprint Display Format

12345 67890 12345
67890 12345 67890
  • 6 groups of 5 digits
  • 2 lines of 3 groups
  • Monospace font

Constraints

  • Use Material 3 components
  • CameraX for QR scanner (not deprecated Camera1)
  • QR generation via ZXing BarcodeEncoder
  • Confirmation dialogs for destructive actions (leave group, delete group, remove device)
  • Creator-only actions clearly gated in UI

DO NOT

  • Implement actual crypto verification logic
  • Generate real safety numbers or fingerprints
  • Handle actual server communication
  • Implement actual QR code encoding/decoding logic