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

269 lines
8.7 KiB
Markdown

# 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
```kotlin
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
```kotlin
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
```kotlin
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