feat: Safely set last logged account public key using null safety
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user