feat: Safely set last logged account public key using null safety

This commit is contained in:
k1ngsterr1
2026-01-12 17:37:42 +05:00
parent 8aa17383cf
commit fb339642fa
6 changed files with 134 additions and 26 deletions

View File

@@ -147,7 +147,7 @@ class MainActivity : ComponentActivity() {
currentAccount = account currentAccount = account
hasExistingAccount = true hasExistingAccount = true
// Save as last logged account // Save as last logged account
accountManager.setLastLoggedPublicKey(account.publicKey) account?.let { accountManager.setLastLoggedPublicKey(it.publicKey) }
// Reload accounts list // Reload accounts list
scope.launch { scope.launch {
val accounts = accountManager.getAllAccounts() val accounts = accountManager.getAllAccounts()

View File

@@ -40,12 +40,20 @@ class AccountManager(private val context: Context) {
// Synchronous read from SharedPreferences - always up to date // Synchronous read from SharedPreferences - always up to date
fun getLastLoggedPublicKey(): String? { fun getLastLoggedPublicKey(): String? {
return sharedPrefs.getString(KEY_LAST_LOGGED, null) val publicKey = sharedPrefs.getString(KEY_LAST_LOGGED, null)
android.util.Log.d("AccountManager", "📖 getLastLoggedPublicKey: ${publicKey?.take(16) ?: "null"}")
return publicKey
} }
// Synchronous write to SharedPreferences // Synchronous write to SharedPreferences
fun setLastLoggedPublicKey(publicKey: String) { fun setLastLoggedPublicKey(publicKey: String) {
sharedPrefs.edit().putString(KEY_LAST_LOGGED, publicKey).commit() // commit() is synchronous android.util.Log.d("AccountManager", "💾 Saving last logged account: ${publicKey.take(16)}...")
val success = sharedPrefs.edit().putString(KEY_LAST_LOGGED, publicKey).commit() // commit() is synchronous
android.util.Log.d("AccountManager", "💾 Save result: $success")
// Verify immediately
val saved = sharedPrefs.getString(KEY_LAST_LOGGED, null)
android.util.Log.d("AccountManager", "💾 Verification read: ${saved?.take(16) ?: "null"}")
} }
suspend fun saveAccount(account: EncryptedAccount) { suspend fun saveAccount(account: EncryptedAccount) {
@@ -78,13 +86,18 @@ class AccountManager(private val context: Context) {
} }
suspend fun setCurrentAccount(publicKey: String) { suspend fun setCurrentAccount(publicKey: String) {
// Save to SharedPreferences synchronously first android.util.Log.d("AccountManager", "🔐 setCurrentAccount called for: ${publicKey.take(16)}...")
// ⚡ ВАЖНО: Сначала сохраняем в SharedPreferences синхронно
setLastLoggedPublicKey(publicKey) setLastLoggedPublicKey(publicKey)
// Потом в DataStore асинхронно (для совместимости с потоками)
context.accountDataStore.edit { preferences -> context.accountDataStore.edit { preferences ->
preferences[CURRENT_PUBLIC_KEY] = publicKey preferences[CURRENT_PUBLIC_KEY] = publicKey
preferences[IS_LOGGED_IN] = true preferences[IS_LOGGED_IN] = true
} }
android.util.Log.d("AccountManager", "✅ setCurrentAccount completed for: ${publicKey.take(16)}...")
} }
suspend fun logout() { suspend fun logout() {

View File

@@ -0,0 +1,61 @@
package com.rosetta.messenger.database
import androidx.room.*
import kotlinx.coroutines.flow.Flow
/**
* Entity для blacklist (заблокированные пользователи)
*/
@Entity(
tableName = "blacklist",
indices = [
Index(value = ["account", "public_key"], unique = true)
]
)
data class BlacklistEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "public_key")
val publicKey: String, // Публичный ключ заблокированного пользователя
@ColumnInfo(name = "account")
val account: String // Мой публичный ключ
)
/**
* DAO для работы с blacklist
*/
@Dao
interface BlacklistDao {
/**
* Добавить пользователя в blacklist
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun blockUser(blacklist: BlacklistEntity)
/**
* Удалить пользователя из blacklist
*/
@Query("DELETE FROM blacklist WHERE public_key = :publicKey AND account = :account")
suspend fun unblockUser(publicKey: String, account: String)
/**
* Проверить, заблокирован ли пользователь
*/
@Query("SELECT COUNT(*) > 0 FROM blacklist WHERE public_key = :publicKey AND account = :account")
suspend fun isUserBlocked(publicKey: String, account: String): Boolean
/**
* Получить все заблокированные аккаунты для данного пользователя
*/
@Query("SELECT * FROM blacklist WHERE account = :account")
fun getBlockedUsers(account: String): Flow<List<BlacklistEntity>>
/**
* Получить список всех заблокированных публичных ключей
*/
@Query("SELECT public_key FROM blacklist WHERE account = :account")
suspend fun getBlockedPublicKeys(account: String): List<String>
}

View File

@@ -9,15 +9,17 @@ import androidx.room.RoomDatabase
entities = [ entities = [
EncryptedAccountEntity::class, EncryptedAccountEntity::class,
MessageEntity::class, MessageEntity::class,
DialogEntity::class DialogEntity::class,
BlacklistEntity::class
], ],
version = 2, version = 3,
exportSchema = false exportSchema = false
) )
abstract class RosettaDatabase : RoomDatabase() { abstract class RosettaDatabase : RoomDatabase() {
abstract fun accountDao(): AccountDao abstract fun accountDao(): AccountDao
abstract fun messageDao(): MessageDao abstract fun messageDao(): MessageDao
abstract fun dialogDao(): DialogDao abstract fun dialogDao(): DialogDao
abstract fun blacklistDao(): BlacklistDao
companion object { companion object {
@Volatile @Volatile

View File

@@ -82,8 +82,12 @@ fun UnlockScreen(
var searchQuery by remember { mutableStateOf("") } var searchQuery by remember { mutableStateOf("") }
val searchFocusRequester = remember { FocusRequester() } val searchFocusRequester = remember { FocusRequester() }
// Load accounts // Load accounts and pre-select last used account
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// ⚡ СИНХРОННОЕ чтение последнего аккаунта ДО загрузки списка
val lastLoggedKey = accountManager.getLastLoggedPublicKey()
Log.d("UnlockScreen", "🔍 Last logged account from SharedPrefs: ${lastLoggedKey?.take(16) ?: "none"}")
val allAccounts = accountManager.getAllAccounts() val allAccounts = accountManager.getAllAccounts()
accounts = allAccounts.map { acc -> accounts = allAccounts.map { acc ->
AccountItem( AccountItem(
@@ -93,30 +97,26 @@ fun UnlockScreen(
) )
} }
// Get last logged account from SharedPreferences (synchronous, reliable) Log.d("UnlockScreen", "📋 Available accounts: ${accounts.map { "${it.name}:${it.publicKey.take(8)}" }}")
val lastLoggedKey = accountManager.getLastLoggedPublicKey()
Log.d("UnlockScreen", "Loading accounts. lastLoggedKey=$lastLoggedKey") // Find the target account - приоритет: selectedAccountId > lastLoggedKey > первый
Log.d("UnlockScreen", "Available accounts: ${accounts.map { "${it.name}:${it.publicKey.take(8)}" }}")
// Find the target account - prioritize selectedAccountId, then lastLoggedKey
val targetAccount = when { val targetAccount = when {
selectedAccountId != null -> { !selectedAccountId.isNullOrEmpty() -> {
Log.d("UnlockScreen", "Using selectedAccountId: $selectedAccountId") Log.d("UnlockScreen", "Using selectedAccountId: ${selectedAccountId.take(16)}")
accounts.find { it.publicKey == selectedAccountId } accounts.find { it.publicKey == selectedAccountId }
} }
lastLoggedKey != null -> { !lastLoggedKey.isNullOrEmpty() -> {
Log.d("UnlockScreen", "Using lastLoggedKey: ${lastLoggedKey.take(8)}") Log.d("UnlockScreen", "Using lastLoggedKey: ${lastLoggedKey.take(16)}")
accounts.find { it.publicKey == lastLoggedKey } accounts.find { it.publicKey == lastLoggedKey }
} }
else -> { else -> {
Log.d("UnlockScreen", "No lastLoggedKey, using first account") Log.d("UnlockScreen", "⚠️ No preference, using first account")
null accounts.firstOrNull()
} }
} }
selectedAccount = targetAccount ?: accounts.firstOrNull() selectedAccount = targetAccount ?: accounts.firstOrNull()
Log.d("UnlockScreen", "Final selected: ${selectedAccount?.name}:${selectedAccount?.publicKey?.take(8)}") Log.d("UnlockScreen", "🎯 Final selected: ${selectedAccount?.name} (${selectedAccount?.publicKey?.take(16)})")
} }
// Filter accounts by search // Filter accounts by search

View File

@@ -176,6 +176,8 @@ fun ChatDetailScreen(
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val clipboardManager = androidx.compose.ui.platform.LocalClipboardManager.current val clipboardManager = androidx.compose.ui.platform.LocalClipboardManager.current
val context = LocalContext.current
val database = remember { com.rosetta.messenger.database.RosettaDatabase.getDatabase(context) }
// Цвета как в React Native themes.ts // Цвета как в React Native themes.ts
val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -1103,7 +1105,22 @@ fun ChatDetailScreen(
TextButton( TextButton(
onClick = { onClick = {
showDeleteConfirm = false showDeleteConfirm = false
// TODO: Implement delete chat scope.launch {
try {
// Delete all messages in this dialog
database.messageDao().deleteDialog(
account = currentUserPublicKey,
dialogKey = user.publicKey
)
// Delete dialog cache
database.dialogDao().deleteDialog(
account = currentUserPublicKey,
opponentKey = user.publicKey
)
} catch (e: Exception) {
android.util.Log.e("ChatDetail", "Error deleting chat", e)
}
}
onBack() onBack()
} }
) { ) {
@@ -1140,7 +1157,21 @@ fun ChatDetailScreen(
TextButton( TextButton(
onClick = { onClick = {
showBlockConfirm = false showBlockConfirm = false
// TODO: Implement block user scope.launch {
try {
// Add user to blacklist
database.blacklistDao().blockUser(
com.rosetta.messenger.database.BlacklistEntity(
publicKey = user.publicKey,
account = currentUserPublicKey
)
)
android.util.Log.d("ChatDetail", "User blocked: ${user.publicKey.take(10)}")
} catch (e: Exception) {
android.util.Log.e("ChatDetail", "Error blocking user", e)
}
}
onBack()
} }
) { ) {
Text("Block", color = Color(0xFFFF3B30)) Text("Block", color = Color(0xFFFF3B30))
@@ -1193,8 +1224,8 @@ private fun MessageBubble(
onLongClick: () -> Unit = {}, onLongClick: () -> Unit = {},
onClick: () -> Unit = {} onClick: () -> Unit = {}
) { ) {
// Telegram-style enter animation // ❌ УБРАЛИ: Telegram-style enter animation - она мешает при скролле
val (alpha, translationY) = rememberMessageEnterAnimation(message.id) // val (alpha, translationY) = rememberMessageEnterAnimation(message.id)
// Selection animation // Selection animation
val selectionScale by animateFloatAsState( val selectionScale by animateFloatAsState(
@@ -1242,8 +1273,9 @@ private fun MessageBubble(
Modifier.fillMaxWidth() Modifier.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 1.dp) .padding(horizontal = 8.dp, vertical = 1.dp)
.graphicsLayer { .graphicsLayer {
this.alpha = alpha * selectionAlpha // ❌ УБРАЛИ: alpha = alpha * selectionAlpha и translationY
this.translationY = translationY // Оставляем только selection анимацию
this.alpha = selectionAlpha
this.scaleX = selectionScale this.scaleX = selectionScale
this.scaleY = selectionScale this.scaleY = selectionScale
}, },