From fb339642fa87399dcf6c7c20fe0e89f0b9bd3a0e Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 12 Jan 2026 17:37:42 +0500 Subject: [PATCH] feat: Safely set last logged account public key using null safety --- .../com/rosetta/messenger/MainActivity.kt | 2 +- .../rosetta/messenger/data/AccountManager.kt | 19 +++++- .../messenger/database/BlacklistEntities.kt | 61 +++++++++++++++++++ .../messenger/database/RosettaDatabase.kt | 6 +- .../rosetta/messenger/ui/auth/UnlockScreen.kt | 28 ++++----- .../messenger/ui/chats/ChatDetailScreen.kt | 44 +++++++++++-- 6 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/com/rosetta/messenger/database/BlacklistEntities.kt diff --git a/app/src/main/java/com/rosetta/messenger/MainActivity.kt b/app/src/main/java/com/rosetta/messenger/MainActivity.kt index 7db543f..c60841b 100644 --- a/app/src/main/java/com/rosetta/messenger/MainActivity.kt +++ b/app/src/main/java/com/rosetta/messenger/MainActivity.kt @@ -147,7 +147,7 @@ class MainActivity : ComponentActivity() { currentAccount = account hasExistingAccount = true // Save as last logged account - accountManager.setLastLoggedPublicKey(account.publicKey) + account?.let { accountManager.setLastLoggedPublicKey(it.publicKey) } // Reload accounts list scope.launch { val accounts = accountManager.getAllAccounts() diff --git a/app/src/main/java/com/rosetta/messenger/data/AccountManager.kt b/app/src/main/java/com/rosetta/messenger/data/AccountManager.kt index 57401c2..0154d9c 100644 --- a/app/src/main/java/com/rosetta/messenger/data/AccountManager.kt +++ b/app/src/main/java/com/rosetta/messenger/data/AccountManager.kt @@ -40,12 +40,20 @@ class AccountManager(private val context: Context) { // Synchronous read from SharedPreferences - always up to date 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 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) { @@ -78,13 +86,18 @@ class AccountManager(private val context: Context) { } suspend fun setCurrentAccount(publicKey: String) { - // Save to SharedPreferences synchronously first + android.util.Log.d("AccountManager", "🔐 setCurrentAccount called for: ${publicKey.take(16)}...") + + // ⚡ ВАЖНО: Сначала сохраняем в SharedPreferences синхронно setLastLoggedPublicKey(publicKey) + // Потом в DataStore асинхронно (для совместимости с потоками) context.accountDataStore.edit { preferences -> preferences[CURRENT_PUBLIC_KEY] = publicKey preferences[IS_LOGGED_IN] = true } + + android.util.Log.d("AccountManager", "✅ setCurrentAccount completed for: ${publicKey.take(16)}...") } suspend fun logout() { diff --git a/app/src/main/java/com/rosetta/messenger/database/BlacklistEntities.kt b/app/src/main/java/com/rosetta/messenger/database/BlacklistEntities.kt new file mode 100644 index 0000000..88f0a65 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/database/BlacklistEntities.kt @@ -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> + + /** + * Получить список всех заблокированных публичных ключей + */ + @Query("SELECT public_key FROM blacklist WHERE account = :account") + suspend fun getBlockedPublicKeys(account: String): List +} diff --git a/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt b/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt index 95529a4..e0fd07e 100644 --- a/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt +++ b/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt @@ -9,15 +9,17 @@ import androidx.room.RoomDatabase entities = [ EncryptedAccountEntity::class, MessageEntity::class, - DialogEntity::class + DialogEntity::class, + BlacklistEntity::class ], - version = 2, + version = 3, exportSchema = false ) abstract class RosettaDatabase : RoomDatabase() { abstract fun accountDao(): AccountDao abstract fun messageDao(): MessageDao abstract fun dialogDao(): DialogDao + abstract fun blacklistDao(): BlacklistDao companion object { @Volatile diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/UnlockScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/UnlockScreen.kt index 50c87fd..3ccee7c 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/UnlockScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/UnlockScreen.kt @@ -82,8 +82,12 @@ fun UnlockScreen( var searchQuery by remember { mutableStateOf("") } val searchFocusRequester = remember { FocusRequester() } - // Load accounts + // Load accounts and pre-select last used account LaunchedEffect(Unit) { + // ⚡ СИНХРОННОЕ чтение последнего аккаунта ДО загрузки списка + val lastLoggedKey = accountManager.getLastLoggedPublicKey() + Log.d("UnlockScreen", "🔍 Last logged account from SharedPrefs: ${lastLoggedKey?.take(16) ?: "none"}") + val allAccounts = accountManager.getAllAccounts() accounts = allAccounts.map { acc -> AccountItem( @@ -93,30 +97,26 @@ fun UnlockScreen( ) } - // Get last logged account from SharedPreferences (synchronous, reliable) - val lastLoggedKey = accountManager.getLastLoggedPublicKey() + Log.d("UnlockScreen", "📋 Available accounts: ${accounts.map { "${it.name}:${it.publicKey.take(8)}" }}") - Log.d("UnlockScreen", "Loading accounts. lastLoggedKey=$lastLoggedKey") - Log.d("UnlockScreen", "Available accounts: ${accounts.map { "${it.name}:${it.publicKey.take(8)}" }}") - - // Find the target account - prioritize selectedAccountId, then lastLoggedKey + // Find the target account - приоритет: selectedAccountId > lastLoggedKey > первый val targetAccount = when { - selectedAccountId != null -> { - Log.d("UnlockScreen", "Using selectedAccountId: $selectedAccountId") + !selectedAccountId.isNullOrEmpty() -> { + Log.d("UnlockScreen", "✅ Using selectedAccountId: ${selectedAccountId.take(16)}") accounts.find { it.publicKey == selectedAccountId } } - lastLoggedKey != null -> { - Log.d("UnlockScreen", "Using lastLoggedKey: ${lastLoggedKey.take(8)}") + !lastLoggedKey.isNullOrEmpty() -> { + Log.d("UnlockScreen", "✅ Using lastLoggedKey: ${lastLoggedKey.take(16)}") accounts.find { it.publicKey == lastLoggedKey } } else -> { - Log.d("UnlockScreen", "No lastLoggedKey, using first account") - null + Log.d("UnlockScreen", "⚠️ No preference, using first account") + 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 diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 6a25da2..c5499b7 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -176,6 +176,8 @@ fun ChatDetailScreen( val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.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 val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black @@ -1103,7 +1105,22 @@ fun ChatDetailScreen( TextButton( onClick = { 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() } ) { @@ -1140,7 +1157,21 @@ fun ChatDetailScreen( TextButton( onClick = { 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)) @@ -1193,8 +1224,8 @@ private fun MessageBubble( onLongClick: () -> Unit = {}, onClick: () -> Unit = {} ) { - // Telegram-style enter animation - val (alpha, translationY) = rememberMessageEnterAnimation(message.id) + // ❌ УБРАЛИ: Telegram-style enter animation - она мешает при скролле + // val (alpha, translationY) = rememberMessageEnterAnimation(message.id) // Selection animation val selectionScale by animateFloatAsState( @@ -1242,8 +1273,9 @@ private fun MessageBubble( Modifier.fillMaxWidth() .padding(horizontal = 8.dp, vertical = 1.dp) .graphicsLayer { - this.alpha = alpha * selectionAlpha - this.translationY = translationY + // ❌ УБРАЛИ: alpha = alpha * selectionAlpha и translationY + // Оставляем только selection анимацию + this.alpha = selectionAlpha this.scaleX = selectionScale this.scaleY = selectionScale },