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>
This commit is contained in:
268
specs/agent-g-profile-group-verification.md
Normal file
268
specs/agent-g-profile-group-verification.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user