207 lines
7.5 KiB
Kotlin
207 lines
7.5 KiB
Kotlin
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"
|
||
)
|