Files
Kecalek/specs/agent-d-auth-screens.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

5.6 KiB

Agent D: Auth Screens

Phase: 2 (UI Shells)

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

Context

You are building authentication screens for "Kecalek" encrypted chat app. The login uses RSA challenge-response (no password sent to server). Registration requires email verification code. Device pairing allows linking a new device to an existing account.

Task

Create Login, Register, Pairing screens and AuthViewModel skeleton.

Files to Create

1. ui/auth/LoginScreen.kt

Jetpack Compose screen with:

  • App title "Kecalek" at the top (headlineLarge)
  • Subtitle "Encrypted Messaging" (bodyMedium, Subtext1 color)
  • Email/Username text field (OutlinedTextField, Surface1 background)
  • Password text field (password visibility toggle icon)
  • Login button (filled, Lavender primary, full width)
  • "Create Account" text button below (navigates to Register)
  • "Link Device" text button below (navigates to Pairing)
  • Server config section (expandable/collapsible):
    • Host text field (default: "chat.ai-tech.news")
    • Port text field (default: "9999")
    • TLS toggle switch (default: off)
  • Loading state: CircularProgressIndicator replacing login button
  • Error state: Red error text below password field
  • Biometric login button (fingerprint icon, shown only if biometric available)

Layout: Centered vertically, max width 400dp, padding 24dp.

2. ui/auth/RegisterScreen.kt

Jetpack Compose screen with:

  • Back arrow in top bar (navigates back)
  • Title "Create Account"
  • Username text field
  • Email text field
  • Password text field (with visibility toggle)
  • Confirm Password text field
  • Register button (filled, Lavender, full width)
  • Confirmation code section (shown after successful registration):
    • Info text "Check your email for a verification code"
    • 6-digit code input field
    • Confirm button
  • Loading state + Error state (same pattern as Login)

3. ui/auth/PairingScreen.kt

Jetpack Compose screen with:

  • Back arrow in top bar
  • Title "Link New Device"
  • Info text explaining pairing process
  • 8-digit pairing code displayed prominently (headlineLarge, monospace, letter-spacing)
  • Countdown timer or progress indicator showing poll status
  • "Waiting for authorization..." text with animated dots
  • Cancel button (outlined)
  • Status messages: "Device authorized", "Pairing failed", etc.

4. ui/auth/AuthViewModel.kt

package com.kecalek.chat.ui.auth

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 AuthUiState(
    val isLoading: Boolean = false,
    val error: String? = null,
    val isLoggedIn: Boolean = false,
    val isRegistered: Boolean = false,
    val needsConfirmation: Boolean = false,
    val pairingCode: String? = null,
    val isPairingWaiting: Boolean = false,
    val serverHost: String = "chat.ai-tech.news",
    val serverPort: Int = 9999,
    val useTls: Boolean = false,
    val biometricAvailable: Boolean = false,
)

@HiltViewModel
class AuthViewModel @Inject constructor(
    // TODO: Inject ChatClient, SessionManager
) : ViewModel() {

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

    fun login(emailOrUsername: String, password: String) {
        // TODO: Implement RSA challenge-response login
        // 1. ChatClient.login(email, password)
        // 2. On success: navigate to ConversationList
        // 3. On failure: show error
    }

    fun register(username: String, email: String, password: String) {
        // TODO: Implement registration
        // 1. ChatClient.register(username, email, password)
        // 2. On success: show confirmation code input
        // 3. On failure: show error
    }

    fun confirmRegistration(email: String, code: String) {
        // TODO: Confirm with 6-digit code
        // 1. ChatClient.confirm_registration(email, code)
        // 2. On success: auto-login
    }

    fun startPairing() {
        // TODO: Start device pairing
        // 1. ChatClient.pairing_start() -> get 8-digit code
        // 2. Show code to user
        // 3. Start polling for authorization
    }

    fun loginWithBiometric() {
        // TODO: Biometric authentication
    }

    fun updateServerConfig(host: String, port: Int, useTls: Boolean) {
        _uiState.value = _uiState.value.copy(
            serverHost = host,
            serverPort = port,
            useTls = useTls,
        )
    }

    fun clearError() {
        _uiState.value = _uiState.value.copy(error = null)
    }
}

Reference: iOS LoginView behavior

  • Login/Register are modes of the same view (iOS uses toggle tabs)
  • Server configuration is expandable (collapsed by default)
  • Biometric login uses Face ID/Touch ID
  • Error messages appear below the form
  • Loading spinner replaces the action button

Constraints

  • Use Material 3 components (OutlinedTextField, FilledTonalButton, etc.)
  • Use CatppuccinMocha colors from theme
  • Use hiltViewModel() for ViewModel injection
  • All screens receive navController: NavHostController parameter
  • Password fields must have visibility toggle
  • Support keyboard "Done" action to submit form
  • Use rememberSaveable for form state to survive config changes

DO NOT

  • Implement actual login/register/pairing logic (just skeleton functions)
  • Handle cryptographic operations
  • Store credentials or keys
  • Modify navigation graph (screen placeholders already exist)