feat: Update ProfileNavigationItem to use a rounded corner shape with increased radius
This commit is contained in:
@@ -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 = {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user