# Profile Username & Name Logic ## Overview This document describes how username and name are set and synchronized between the Android app and the server, following the desktop version implementation. ## Architecture ### Desktop Version (TypeScript - Reference Implementation) Located in: `rosette-messenger-app/Архив/app/views/Profile/MyProfile.tsx` **Flow:** 1. User edits `title` (name) and `username` in UI 2. On save, creates `PacketUserInfo` with: - `username` - user's username (e.g., "john123") - `title` - user's display name (e.g., "John Doe") - `privateKey` - user's private key hash 3. Sends packet to server 4. Waits for `PacketResult` (0x02) 5. **Only on SUCCESS** (resultCode = 0): - Updates local state via `updateUserInformation()` - Shows success message **Key Protocol Details:** - Packet ID: 0x01 (PacketUserInfo) - Send order: username → title → privateKey - Receive order: username → title → privateKey --- ### Android Version (Kotlin - Current Implementation) #### 1. **Data Storage Layer** (`AccountManager.kt`) ```kotlin data class EncryptedAccount( val publicKey: String, val encryptedPrivateKey: String, val encryptedSeedPhrase: String, val name: String, // User's display name (title) val username: String? // User's username ) ``` **Methods:** - `updateAccountName(publicKey, name)` - Updates display name locally - `updateAccountUsername(publicKey, username)` - Updates username locally - `getAccount(publicKey)` - Retrieves account data --- #### 2. **Network Layer** (`Packets.kt`) ```kotlin class PacketUserInfo : Packet() { var privateKey: String = "" var username: String = "" var title: String = "" override fun getPacketId(): Int = 0x01 override fun send(): Stream { stream.writeString(username) // Order matches desktop stream.writeString(title) stream.writeString(privateKey) } } ``` --- #### 3. **ViewModel Layer** (`ProfileViewModel.kt`) **State Management:** ```kotlin data class ProfileState( val isLoading: Boolean = false, val isSaving: Boolean = false, val error: String? = null, val saveSuccess: Boolean = false, val logs: List = emptyList() ) ``` **Save Profile Flow:** ```kotlin fun saveProfile(publicKey, privateKeyHash, name, username) { // 1. Create PacketUserInfo val packet = PacketUserInfo() packet.username = username packet.title = name packet.privateKey = privateKeyHash // 2. Send to server ProtocolManager.send(packet) // 3. Set 10s timeout (fallback: save locally if no response) // 4. Wait for PacketResult (handled in handlePacketResult) } ``` **Handle Server Response:** ```kotlin private fun handlePacketResult(packet: PacketResult) { when (packet.resultCode) { 0 -> { // SUCCESS // Mark as successful // Local update happens in ProfileScreen LaunchedEffect _state.value = _state.value.copy(saveSuccess = true) } else -> { // ERROR _state.value = _state.value.copy(error = packet.message) } } } ``` **Fallback Logic:** - If server doesn't respond within 10 seconds: - Profile is saved locally anyway - User sees success (but warned in logs) - This prevents data loss if server is down --- #### 4. **UI Layer** (`ProfileScreen.kt`) **State Tracking:** ```kotlin var editedName by remember { mutableStateOf(accountName) } var editedUsername by remember { mutableStateOf(accountUsername) } var hasChanges by remember { mutableStateOf(false) } ``` **Save Flow:** ```kotlin onSave = { // 1. Send to server (via ViewModel) viewModel.saveProfile( publicKey = accountPublicKey, privateKeyHash = accountPrivateKeyHash, name = editedName, username = editedUsername ) // Note: Local update happens AFTER success (see below) } ``` **Success Handler:** ```kotlin LaunchedEffect(profileState.saveSuccess) { if (profileState.saveSuccess) { // 1. Show success toast Toast.makeText(context, "Profile updated successfully", LENGTH_SHORT).show() // 2. Update local database (AFTER server confirms) viewModel.updateLocalProfile(accountPublicKey, editedName, editedUsername) // 3. Reset UI state hasChanges = false viewModel.resetSaveState() // 4. Notify parent (MainActivity) to reload onSaveProfile(editedName, editedUsername) } } ``` --- #### 5. **Parent Layer** (`MainActivity.kt`) **Username Loading:** ```kotlin var accountUsername by remember { mutableStateOf("") } var reloadTrigger by remember { mutableIntStateOf(0) } // Load username from DataStore on app start or profile change LaunchedEffect(accountPublicKey, reloadTrigger) { val accountManager = AccountManager(context) val encryptedAccount = accountManager.getAccount(accountPublicKey) accountUsername = encryptedAccount?.username ?: "" } ``` **Profile Update Callback:** ```kotlin onSaveProfile = { name, username -> // 1. Update local state accountUsername = username // 2. Trigger reload from DB (to ensure sync) reloadTrigger++ } ``` --- ## Complete Flow Diagram ``` ┌─────────────┐ │ USER │ │ Edit fields │ └──────┬──────┘ │ ↓ ┌──────────────────────────────────────────┐ │ ProfileScreen.kt │ │ - editedName: "John Doe" │ │ - editedUsername: "john123" │ │ - hasChanges: true │ └──────┬───────────────────────────────────┘ │ [User clicks Save] ↓ ┌──────────────────────────────────────────┐ │ ProfileViewModel.saveProfile() │ │ 1. Create PacketUserInfo │ │ - username: "john123" │ │ - title: "John Doe" │ │ - privateKey: "abc123..." │ │ 2. ProtocolManager.send(packet) │ │ 3. Start 10s timeout │ └──────┬───────────────────────────────────┘ │ ↓ ┌──────────────────────────────────────────┐ │ SERVER (via WebSocket) │ │ - Receives 0x01 PacketUserInfo │ │ - Validates & saves to database │ │ - Sends 0x02 PacketResult │ │ - resultCode: 0 (SUCCESS) │ │ - message: "" │ └──────┬───────────────────────────────────┘ │ ↓ ┌──────────────────────────────────────────┐ │ ProfileViewModel.handlePacketResult() │ │ - resultCode == 0 → SUCCESS │ │ - _state.saveSuccess = true │ └──────┬───────────────────────────────────┘ │ ↓ ┌──────────────────────────────────────────┐ │ ProfileScreen LaunchedEffect │ │ - Observes profileState.saveSuccess │ │ - Calls updateLocalProfile() │ │ → AccountManager.updateAccountName() │ │ → AccountManager.updateAccountUsername()│ │ - Shows success Toast │ │ - Calls onSaveProfile() callback │ └──────┬───────────────────────────────────┘ │ ↓ ┌──────────────────────────────────────────┐ │ MainActivity │ │ - accountUsername = "john123" │ │ - reloadTrigger++ │ │ - LaunchedEffect re-runs │ │ - Loads fresh data from DataStore │ └──────────────────────────────────────────┘ ``` --- ## Error Handling ### Server Timeout (10 seconds) ```kotlin if (no response after 10s) { // Fallback: save locally anyway updateLocalProfile(publicKey, name, username) _state.value = saveSuccess = true // Logs show warning: // "⚠️ No response from server after 10 seconds" // "📝 NOTE: Saving locally as fallback" } ``` ### Server Error Response ```kotlin when (packet.resultCode) { else -> { _state.value = _state.value.copy( isSaving = false, error = packet.message ) // Shows error Toast in ProfileScreen } } ``` --- ## Key Differences from Desktop | Aspect | Desktop (TypeScript) | Android (Kotlin) | | -------------- | --------------------------- | ----------------------------------- | | Storage | In-memory state | DataStore (persistent) | | Update trigger | On PacketResult SUCCESS | On PacketResult SUCCESS | | Fallback | None (fails if server down) | Saves locally after 10s | | UI feedback | State update → re-render | StateFlow → Toast + callback | | Logging | Console.log | Android Log + ProfileViewModel logs | --- ## Testing Checklist - [ ] Name updates and persists across app restarts - [ ] Username updates and persists across app restarts - [ ] Changes show in ChatsListScreen header - [ ] Success toast appears on save - [ ] Error toast appears on server error - [ ] Fallback works if server is down (10s timeout) - [ ] Logs show correct packet flow - [ ] hasChanges flag updates correctly - [ ] Save button appears/disappears correctly --- ## Related Files ### Android - `ProfileScreen.kt` - UI layer - `ProfileViewModel.kt` - Business logic - `MainActivity.kt` - Parent container - `AccountManager.kt` - Data persistence - `Packets.kt` - Protocol definitions ### Desktop (Reference) - `MyProfile.tsx` - Desktop implementation - `packet.userinfo.ts` - Protocol definition - `useUserInformation.ts` - State management --- ## Protocol Specification ### PacketUserInfo (0x01) **Client → Server (Send):** ``` ┌──────────┬──────────┬───────┬────────────┐ │ Packet ID│ Username │ Title │ PrivateKey │ │ (0x01) │ (string) │(string)│ (string) │ └──────────┴──────────┴───────┴────────────┘ ``` **Server → Client (Receive):** Same format (not typically used - server sends PacketResult instead) ### PacketResult (0x02) **Server → Client:** ``` ┌──────────┬────────────┬─────────┐ │ Packet ID│ Result Code│ Message │ │ (0x02) │ (int8) │(string) │ └──────────┴────────────┴─────────┘ ``` **Result Codes:** - `0` - SUCCESS - Other values indicate errors (message field contains details) --- ## Conclusion The Android implementation now follows the desktop version pattern: 1. ✅ Send packet to server first 2. ✅ Wait for server confirmation 3. ✅ Update local data ONLY after success 4. ✅ Proper error handling 5. ✅ Fallback for offline/timeout scenarios This ensures data consistency between client and server while providing a good user experience even when the server is temporarily unavailable.