Fix license list loading and crash on duplicate subs.

Read admin token from DataStore before API calls, reload licenses after save, and use unique product key as LazyColumn item key.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-06-28 14:08:58 +08:00
parent f2e0c61010
commit 74a6795b6f
3 changed files with 13 additions and 19 deletions
@@ -4,12 +4,11 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.update
import ru.mailib.ttrpg.licensemanager.data.AppSettings import ru.mailib.ttrpg.licensemanager.data.AppSettings
import ru.mailib.ttrpg.licensemanager.data.CreateProductKeyRequest import ru.mailib.ttrpg.licensemanager.data.CreateProductKeyRequest
import ru.mailib.ttrpg.licensemanager.data.LicenseEntry import ru.mailib.ttrpg.licensemanager.data.LicenseEntry
@@ -45,8 +44,7 @@ class LicenseViewModel(application: Application) : AndroidViewModel(application)
private val settingsRepository = SettingsRepository(application) private val settingsRepository = SettingsRepository(application)
private val licenseRepository = LicenseRepository(settingsRepository) private val licenseRepository = LicenseRepository(settingsRepository)
val settings: StateFlow<AppSettings> = settingsRepository.settings private suspend fun currentSettings(): AppSettings = settingsRepository.settings.first()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), AppSettings())
private val _listState = MutableStateFlow(LicenseListUiState()) private val _listState = MutableStateFlow(LicenseListUiState())
val listState: StateFlow<LicenseListUiState> = _listState.asStateFlow() val listState: StateFlow<LicenseListUiState> = _listState.asStateFlow()
@@ -59,17 +57,14 @@ class LicenseViewModel(application: Application) : AndroidViewModel(application)
init { init {
viewModelScope.launch { viewModelScope.launch {
settings.collect { appSettings -> val initial = settingsRepository.settings.first()
_settingsState.update { _settingsState.update { it.copy(adminToken = initial.adminToken) }
it.copy(adminToken = appSettings.adminToken)
}
}
} }
} }
fun loadLicenses() { fun loadLicenses() {
viewModelScope.launch { viewModelScope.launch {
val currentSettings = settings.value val currentSettings = currentSettings()
_listState.update { it.copy(isLoading = true, error = null) } _listState.update { it.copy(isLoading = true, error = null) }
licenseRepository.listLicenses(currentSettings) licenseRepository.listLicenses(currentSettings)
.onSuccess { licenses -> .onSuccess { licenses ->
@@ -84,7 +79,7 @@ class LicenseViewModel(application: Application) : AndroidViewModel(application)
fun revokeLicense(sub: String) { fun revokeLicense(sub: String) {
viewModelScope.launch { viewModelScope.launch {
_listState.update { it.copy(revokingSub = sub, error = null) } _listState.update { it.copy(revokingSub = sub, error = null) }
licenseRepository.revokeLicense(settings.value, sub) licenseRepository.revokeLicense(currentSettings(), sub)
.onSuccess { .onSuccess {
loadLicenses() loadLicenses()
_listState.update { it.copy(revokingSub = null) } _listState.update { it.copy(revokingSub = null) }
@@ -136,7 +131,7 @@ class LicenseViewModel(application: Application) : AndroidViewModel(application)
viewModelScope.launch { viewModelScope.launch {
_generateState.update { it.copy(isSubmitting = true, error = null, createdKey = null) } _generateState.update { it.copy(isSubmitting = true, error = null, createdKey = null) }
licenseRepository.createProductKey( licenseRepository.createProductKey(
settings.value, currentSettings(),
CreateProductKeyRequest( CreateProductKeyRequest(
pid = state.pid.trim(), pid = state.pid.trim(),
maxDevices = maxDevices, maxDevices = maxDevices,
@@ -161,11 +156,13 @@ class LicenseViewModel(application: Application) : AndroidViewModel(application)
} }
} }
fun saveSettings() { fun saveSettings(onSaved: () -> Unit = {}) {
val draft = _settingsState.value val draft = _settingsState.value
viewModelScope.launch { viewModelScope.launch {
settingsRepository.save(draft.adminToken) settingsRepository.save(draft.adminToken)
_settingsState.update { it.copy(savedMessage = "Сохранено") } _settingsState.update { it.copy(savedMessage = "Сохранено") }
loadLicenses()
onSaved()
} }
} }
} }
@@ -113,7 +113,7 @@ fun LicenseListScreen(viewModel: LicenseViewModel) {
ErrorMessage(message = state.error ?: "") ErrorMessage(message = state.error ?: "")
} }
} }
items(state.licenses, key = { it.sub }) { license -> items(state.licenses, key = { it.key }) { license ->
LicenseCard( LicenseCard(
license = license, license = license,
isRevoking = state.revokingSub == license.sub, isRevoking = state.revokingSub == license.sub,
@@ -64,10 +64,7 @@ fun SettingsScreen(viewModel: LicenseViewModel, onBack: () -> Unit) {
} }
Button( Button(
onClick = { onClick = { viewModel.saveSettings(onSaved = onBack) },
viewModel.saveSettings()
onBack()
},
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Text("Сохранить") Text("Сохранить")