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>
269 lines
8.7 KiB
Markdown
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
|