feat: Update ProfileNavigationItem to use a rounded corner shape with increased radius

This commit is contained in:
2026-01-21 03:09:39 +05:00
parent 5145388e02
commit dcfbb020be
7 changed files with 105 additions and 42 deletions

View File

@@ -19,6 +19,7 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
@@ -437,6 +438,21 @@ fun MainScreen(
val accountPrivateKey = account?.privateKey ?: "" val accountPrivateKey = account?.privateKey ?: ""
val privateKeyHash = account?.privateKeyHash ?: "" val privateKeyHash = account?.privateKeyHash ?: ""
// Username state - загружается из EncryptedAccount
var accountUsername by remember { mutableStateOf("") }
var reloadTrigger by remember { mutableIntStateOf(0) }
// Load username from AccountManager
val context = LocalContext.current
LaunchedEffect(accountPublicKey, reloadTrigger) {
if (accountPublicKey.isNotBlank() && accountPublicKey != "04c266b98ae5") {
val accountManager = AccountManager(context)
val encryptedAccount = accountManager.getAccount(accountPublicKey)
accountUsername = encryptedAccount?.username ?: ""
Log.d("MainActivity", "Loaded username from DB: $accountUsername")
}
}
// Состояние протокола для передачи в SearchScreen // Состояние протокола для передачи в SearchScreen
val protocolState by ProtocolManager.state.collectAsState() val protocolState by ProtocolManager.state.collectAsState()
@@ -660,13 +676,15 @@ fun MainScreen(
ProfileScreen( ProfileScreen(
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
accountName = accountName, accountName = accountName,
accountUsername = "", // TODO: Get from account accountUsername = accountUsername,
accountPublicKey = accountPublicKey, accountPublicKey = accountPublicKey,
accountPrivateKeyHash = privateKeyHash, accountPrivateKeyHash = privateKeyHash,
onBack = { showProfileScreen = false }, onBack = { showProfileScreen = false },
onSaveProfile = { name, username -> onSaveProfile = { name, username ->
// Profile saved via ViewModel // Update username state and trigger reload from DB
Log.d("MainActivity", "Profile saved: name=$name, username=$username") accountUsername = username
reloadTrigger++
Log.d("MainActivity", "Profile saved: name=$name, username=$username, reloading...")
}, },
onLogout = onLogout, onLogout = onLogout,
onNavigateToTheme = { onNavigateToTheme = {

View File

@@ -100,13 +100,31 @@ class AccountManager(private val context: Context) {
} }
} }
/**
* Update account username
*/
suspend fun updateAccountUsername(publicKey: String, username: String) {
val account = getAccount(publicKey) ?: return
val updatedAccount = account.copy(username = username)
saveAccount(updatedAccount)
}
/**
* Update account name
*/
suspend fun updateAccountName(publicKey: String, name: String) {
val account = getAccount(publicKey) ?: return
val updatedAccount = account.copy(name = name)
saveAccount(updatedAccount)
}
suspend fun clearAll() { suspend fun clearAll() {
context.accountDataStore.edit { it.clear() } context.accountDataStore.edit { it.clear() }
} }
private fun serializeAccounts(accounts: List<EncryptedAccount>): String { private fun serializeAccounts(accounts: List<EncryptedAccount>): String {
return accounts.joinToString("|||") { account -> return accounts.joinToString("|||") { account ->
"${account.publicKey}::${account.encryptedPrivateKey}::${account.encryptedSeedPhrase}::${account.name}" "${account.publicKey}::${account.encryptedPrivateKey}::${account.encryptedSeedPhrase}::${account.name}::${account.username ?: ""}"
} }
} }
@@ -120,7 +138,8 @@ class AccountManager(private val context: Context) {
publicKey = parts[0], publicKey = parts[0],
encryptedPrivateKey = parts[1], encryptedPrivateKey = parts[1],
encryptedSeedPhrase = parts[2], encryptedSeedPhrase = parts[2],
name = parts[3] name = parts[3],
username = parts.getOrNull(4)?.takeIf { it.isNotBlank() }
) )
} else null } else null
} }
@@ -131,7 +150,8 @@ data class EncryptedAccount(
val publicKey: String, val publicKey: String,
val encryptedPrivateKey: String, val encryptedPrivateKey: String,
val encryptedSeedPhrase: String, val encryptedSeedPhrase: String,
val name: String = "Account" val name: String = "Account",
val username: String? = null
) )
data class DecryptedAccount( data class DecryptedAccount(

View File

@@ -34,5 +34,8 @@ data class EncryptedAccountEntity(
val lastUsed: String? = null, val lastUsed: String? = null,
@ColumnInfo(name = "is_active") @ColumnInfo(name = "is_active")
val isActive: Boolean = true val isActive: Boolean = true,
@ColumnInfo(name = "username")
val username: String? = null
) )

View File

@@ -14,7 +14,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
DialogEntity::class, DialogEntity::class,
BlacklistEntity::class BlacklistEntity::class
], ],
version = 5, version = 6,
exportSchema = false exportSchema = false
) )
abstract class RosettaDatabase : RoomDatabase() { abstract class RosettaDatabase : RoomDatabase() {
@@ -36,6 +36,13 @@ abstract class RosettaDatabase : RoomDatabase() {
} }
} }
private val MIGRATION_5_6 = object : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
// Добавляем поле username в encrypted_accounts
database.execSQL("ALTER TABLE encrypted_accounts ADD COLUMN username TEXT")
}
}
fun getDatabase(context: Context): RosettaDatabase { fun getDatabase(context: Context): RosettaDatabase {
return INSTANCE ?: synchronized(this) { return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder( val instance = Room.databaseBuilder(
@@ -44,7 +51,7 @@ abstract class RosettaDatabase : RoomDatabase() {
"rosetta_secure.db" "rosetta_secure.db"
) )
.setJournalMode(JournalMode.WRITE_AHEAD_LOGGING) // WAL mode for performance .setJournalMode(JournalMode.WRITE_AHEAD_LOGGING) // WAL mode for performance
.addMigrations(MIGRATION_4_5) .addMigrations(MIGRATION_4_5, MIGRATION_5_6)
.fallbackToDestructiveMigration() // Для разработки - только если миграция не найдена .fallbackToDestructiveMigration() // Для разработки - только если миграция не найдена
.build() .build()
INSTANCE = instance INSTANCE = instance

View File

@@ -113,30 +113,27 @@ data class SearchUser(
/** /**
* User Info packet (ID: 0x01) * User Info packet (ID: 0x01)
* Get/Set user information * Get/Set user information
* Protocol order: username, title, privateKey (matching TypeScript version)
*/ */
class PacketUserInfo : Packet() { class PacketUserInfo : Packet() {
var publicKey: String = "" var privateKey: String = ""
var title: String = ""
var username: String = "" var username: String = ""
var verified: Int = 0 var title: String = ""
var online: Int = 0
override fun getPacketId(): Int = 0x01 override fun getPacketId(): Int = 0x01
override fun receive(stream: Stream) { override fun receive(stream: Stream) {
publicKey = stream.readString()
title = stream.readString()
username = stream.readString() username = stream.readString()
verified = stream.readInt8() title = stream.readString()
online = stream.readInt8() privateKey = stream.readString()
} }
override fun send(): Stream { override fun send(): Stream {
val stream = Stream() val stream = Stream()
stream.writeInt16(getPacketId()) stream.writeInt16(getPacketId())
stream.writeString(publicKey)
stream.writeString(title)
stream.writeString(username) stream.writeString(username)
stream.writeString(title)
stream.writeString(privateKey)
return stream return stream
} }
} }

View File

@@ -412,7 +412,7 @@ fun ProfileScreen(
name = editedName, name = editedName,
username = editedUsername username = editedUsername
) )
viewModel.updateLocalAccountName(accountPublicKey, editedName) viewModel.updateLocalProfile(accountPublicKey, editedName, editedUsername)
}, },
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
@@ -903,7 +903,7 @@ fun ProfileNavigationItem(
Box( Box(
modifier = Modifier modifier = Modifier
.size(36.dp) .size(36.dp)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(16.dp))
.background(iconBackground), .background(iconBackground),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {

View File

@@ -13,6 +13,7 @@ import com.rosetta.messenger.network.ProtocolManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
data class ProfileState( data class ProfileState(
val isLoading: Boolean = false, val isLoading: Boolean = false,
@@ -72,18 +73,47 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
_state.value = _state.value.copy(isSaving = true, error = null, saveSuccess = false) _state.value = _state.value.copy(isSaving = true, error = null, saveSuccess = false)
// Create and send PacketUserInfo // Create and send PacketUserInfo (protocol order: username, title, privateKey)
val packet = PacketUserInfo() val packet = PacketUserInfo()
packet.publicKey = publicKey
packet.title = name
packet.username = username packet.username = username
packet.title = name
packet.privateKey = privateKeyHash
addLog("Packet created: PacketUserInfo") addLog("Packet created: PacketUserInfo")
addLog("Packet ID: 0x${packet.getPacketId().toString(16).uppercase()}") addLog("Packet ID: 0x${packet.getPacketId().toString(16).uppercase()}")
addLog("Packet data:")
addLog(" - username: '$username'")
addLog(" - title: '$name'")
addLog(" - privateKey: ${privateKeyHash.take(16)}...")
addLog("Sending packet to server...") addLog("Sending packet to server...")
ProtocolManager.send(packet) ProtocolManager.send(packet)
addLog("Packet sent successfully") addLog("Packet sent successfully")
addLog("Waiting for PacketResult (0x02) from server...")
// 🔥 ВРЕМЕННОЕ РЕШЕНИЕ: Сохраняем локально сразу (как в React Native)
// Это нужно потому что сервер может не ответить, если пользователь не создан в БД
addLog("💾 Saving to local database (temporary workaround)...")
updateLocalProfile(publicKey, name, username)
addLog("✅ Local database updated")
// Set timeout for server response
viewModelScope.launch {
delay(5000) // 5 seconds timeout
if (_state.value.isSaving) {
addLog("⚠️ WARNING: No response from server after 5 seconds")
addLog("This might indicate:")
addLog(" 1. Server did not accept the packet")
addLog(" 2. Server did not send PacketResult back")
addLog(" 3. Network issue")
addLog("📝 NOTE: Profile saved locally, but NOT confirmed by server")
_state.value = _state.value.copy(
isSaving = false,
saveSuccess = true, // Считаем успехом т.к. локально сохранено
error = null
)
}
}
// Wait for response (handled in handlePacketResult) // Wait for response (handled in handlePacketResult)
@@ -115,10 +145,6 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
error = null error = null
) )
addLog("State updated: saveSuccess=true") addLog("State updated: saveSuccess=true")
// Update local account name if needed
// (username is stored on server only)
} }
else -> { // ERROR else -> { // ERROR
addLog("❌ ERROR: Profile save failed") addLog("❌ ERROR: Profile save failed")
@@ -135,24 +161,16 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
} }
/** /**
* Update local account name * Update local account name and username
*/ */
fun updateLocalAccountName(publicKey: String, name: String) { fun updateLocalProfile(publicKey: String, name: String, username: String) {
viewModelScope.launch { viewModelScope.launch {
try { try {
val account = accountManager.getAccount(publicKey) accountManager.updateAccountName(publicKey, name)
if (account != null) { accountManager.updateAccountUsername(publicKey, username)
val updatedAccount = EncryptedAccount( Log.d("ProfileViewModel", "Local profile updated: name=$name, username=$username")
publicKey = account.publicKey,
encryptedPrivateKey = account.encryptedPrivateKey,
encryptedSeedPhrase = account.encryptedSeedPhrase,
name = name
)
accountManager.saveAccount(updatedAccount)
Log.d("ProfileViewModel", "Local account name updated")
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ProfileViewModel", "Error updating local account name", e) Log.e("ProfileViewModel", "Error updating local profile", e)
} }
} }
} }