Files
mobile-android/app/src/main/java/com/rosetta/messenger/data/AccountManager.kt

207 lines
7.5 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.rosetta.messenger.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
private val Context.accountDataStore: DataStore<Preferences> by preferencesDataStore(name = "account_store")
/**
* Manages encrypted account storage using DataStore
*/
class AccountManager(private val context: Context) {
companion object {
private val CURRENT_PUBLIC_KEY = stringPreferencesKey("current_public_key")
private val ACCOUNTS_JSON = stringPreferencesKey("accounts_json")
private val IS_LOGGED_IN = booleanPreferencesKey("is_logged_in")
private const val PREFS_NAME = "rosetta_account_prefs"
private const val KEY_LAST_LOGGED = "last_logged_public_key"
private const val KEY_LAST_LOGGED_PRIVATE_HASH = "last_logged_private_hash"
}
// Use SharedPreferences for last logged account - more reliable for immediate reads
private val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val currentPublicKey: Flow<String?> = context.accountDataStore.data.map { preferences ->
preferences[CURRENT_PUBLIC_KEY]
}
val isLoggedIn: Flow<Boolean> = context.accountDataStore.data.map { preferences ->
preferences[IS_LOGGED_IN] ?: false
}
val accountsJson: Flow<String?> = context.accountDataStore.data.map { preferences ->
preferences[ACCOUNTS_JSON]
}
// Synchronous read from SharedPreferences - always up to date
fun getLastLoggedPublicKey(): String? {
val publicKey = sharedPrefs.getString(KEY_LAST_LOGGED, null)
return publicKey
}
fun getLastLoggedPrivateKeyHash(): String? {
return sharedPrefs.getString(KEY_LAST_LOGGED_PRIVATE_HASH, null)
}
// Synchronous write to SharedPreferences
fun setLastLoggedPublicKey(publicKey: String) {
sharedPrefs.edit().putString(KEY_LAST_LOGGED, publicKey).commit() // commit() is synchronous
}
fun setLastLoggedPrivateKeyHash(privateKeyHash: String) {
if (privateKeyHash.isBlank()) return
sharedPrefs.edit().putString(KEY_LAST_LOGGED_PRIVATE_HASH, privateKeyHash).apply()
}
suspend fun saveAccount(account: EncryptedAccount) {
context.accountDataStore.edit { preferences ->
val existingJson = preferences[ACCOUNTS_JSON]
val accounts = if (existingJson != null) {
parseAccounts(existingJson).toMutableList()
} else {
mutableListOf()
}
// Remove existing account with same public key
accounts.removeAll { it.publicKey == account.publicKey }
accounts.add(account)
preferences[ACCOUNTS_JSON] = serializeAccounts(accounts)
}
}
suspend fun getAccount(publicKey: String): EncryptedAccount? {
val preferences = context.accountDataStore.data.first()
val json = preferences[ACCOUNTS_JSON]
return if (json != null) parseAccounts(json).find { it.publicKey == publicKey } else null
}
suspend fun getAllAccounts(): List<EncryptedAccount> {
val preferences = context.accountDataStore.data.first()
val json = preferences[ACCOUNTS_JSON]
return if (json != null) parseAccounts(json) else emptyList()
}
suspend fun setCurrentAccount(publicKey: String) {
// ⚡ ВАЖНО: Сначала сохраняем в SharedPreferences синхронно
setLastLoggedPublicKey(publicKey)
// Потом в DataStore асинхронно (для совместимости с потоками)
context.accountDataStore.edit { preferences ->
preferences[CURRENT_PUBLIC_KEY] = publicKey
preferences[IS_LOGGED_IN] = true
}
}
suspend fun logout() {
context.accountDataStore.edit { preferences ->
preferences[IS_LOGGED_IN] = false
}
sharedPrefs.edit().remove(KEY_LAST_LOGGED_PRIVATE_HASH).apply()
}
/**
* 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)
}
/**
* Delete account completely - remove from accounts list and clear login state
*/
suspend fun deleteAccount(publicKey: String) {
context.accountDataStore.edit { preferences ->
// Remove from accounts list
val existingJson = preferences[ACCOUNTS_JSON]
if (existingJson != null) {
val accounts = parseAccounts(existingJson).toMutableList()
accounts.removeAll { it.publicKey == publicKey }
preferences[ACCOUNTS_JSON] = serializeAccounts(accounts)
}
// Clear current login if this was the active account
val currentKey = preferences[CURRENT_PUBLIC_KEY]
if (currentKey == publicKey) {
preferences[IS_LOGGED_IN] = false
preferences.remove(CURRENT_PUBLIC_KEY)
}
}
// Clear SharedPreferences if this was the last logged account
val lastLogged = sharedPrefs.getString(KEY_LAST_LOGGED, null)
if (lastLogged == publicKey) {
sharedPrefs
.edit()
.remove(KEY_LAST_LOGGED)
.remove(KEY_LAST_LOGGED_PRIVATE_HASH)
.commit()
}
}
suspend fun clearAll() {
context.accountDataStore.edit { it.clear() }
sharedPrefs
.edit()
.remove(KEY_LAST_LOGGED)
.remove(KEY_LAST_LOGGED_PRIVATE_HASH)
.apply()
}
private fun serializeAccounts(accounts: List<EncryptedAccount>): String {
return accounts.joinToString("|||") { account ->
"${account.publicKey}::${account.encryptedPrivateKey}::${account.encryptedSeedPhrase}::${account.name}::${account.username ?: ""}"
}
}
private fun parseAccounts(json: String): List<EncryptedAccount> {
if (json.isBlank()) return emptyList()
return json.split("|||").mapNotNull { accountStr ->
val parts = accountStr.split("::")
if (parts.size >= 4) {
EncryptedAccount(
publicKey = parts[0],
encryptedPrivateKey = parts[1],
encryptedSeedPhrase = parts[2],
name = parts[3],
username = parts.getOrNull(4)?.takeIf { it.isNotBlank() }
)
} else null
}
}
}
data class EncryptedAccount(
val publicKey: String,
val encryptedPrivateKey: String,
val encryptedSeedPhrase: String,
val name: String = "Account",
val username: String? = null
)
data class DecryptedAccount(
val publicKey: String,
val privateKey: String,
val seedPhrase: List<String>,
val privateKeyHash: String,
val name: String = "Account"
)