diff --git a/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/LicenseApi.kt b/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/LicenseApi.kt index e34a4dc..7042d38 100644 --- a/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/LicenseApi.kt +++ b/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/LicenseApi.kt @@ -15,6 +15,9 @@ interface LicenseApi { @POST("v1/admin/product-keys/delete") suspend fun deleteProductKey(@Body body: DeleteProductKeyRequest): DeleteProductKeyResponse + @POST("v1/admin/revoke") + suspend fun revokeLicense(@Body body: RevokeLicenseRequest): RevokeLicenseResponse + @GET("v1/admin/stats/downloads") suspend fun getDownloadStats(): DownloadStatsOverview diff --git a/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Models.kt b/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Models.kt index 2812924..295f963 100644 --- a/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Models.kt +++ b/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Models.kt @@ -77,6 +77,14 @@ data class DeleteProductKeyResponse( val deleted: String? = null, ) +data class RevokeLicenseRequest( + val key: String, +) + +data class RevokeLicenseResponse( + val ok: Boolean, +) + data class DownloadStatsMonth( val month: String, val windows: Int, diff --git a/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Repositories.kt b/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Repositories.kt index 9f5e2d0..f64dbba 100644 --- a/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Repositories.kt +++ b/app/src/main/java/ru/mailib/ttrpg/licensemanager/data/Repositories.kt @@ -71,6 +71,10 @@ class LicenseRepository( createApi(settings).deleteProductKey(DeleteProductKeyRequest(key)) } + suspend fun revokeLicense(settings: AppSettings, key: String): Result = runCatching { + createApi(settings).revokeLicense(RevokeLicenseRequest(key)) + } + suspend fun getDownloadStatsOverview(settings: AppSettings): Result = runCatching { createApi(settings).getDownloadStats() } diff --git a/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/LicenseViewModel.kt b/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/LicenseViewModel.kt index c3d953a..c64db65 100644 --- a/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/LicenseViewModel.kt +++ b/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/LicenseViewModel.kt @@ -42,6 +42,7 @@ data class LicenseListUiState( val licenses: List = emptyList(), val isLoading: Boolean = false, val deletingKey: String? = null, + val revokingKey: String? = null, val error: String? = null, ) @@ -129,6 +130,25 @@ class LicenseViewModel(application: Application) : AndroidViewModel(application) } } + fun revokeLicense(key: String) { + viewModelScope.launch { + _listState.update { it.copy(revokingKey = key, error = null) } + licenseRepository.revokeLicense(currentSettings(), key) + .onSuccess { + loadLicenses() + _listState.update { it.copy(revokingKey = null) } + } + .onFailure { e -> + _listState.update { + it.copy( + revokingKey = null, + error = e.message ?: "Не удалось отозвать лицензию", + ) + } + } + } + } + fun updatePid(value: String) { _generateState.update { it.copy(pid = value, error = null) } } diff --git a/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/screens/LicenseListScreen.kt b/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/screens/LicenseListScreen.kt index 7784e66..caab307 100644 --- a/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/screens/LicenseListScreen.kt +++ b/app/src/main/java/ru/mailib/ttrpg/licensemanager/ui/screens/LicenseListScreen.kt @@ -47,6 +47,7 @@ import ru.mailib.ttrpg.licensemanager.ui.formatLicenseExpiry fun LicenseListScreen(viewModel: LicenseViewModel) { val state by viewModel.listState.collectAsStateWithLifecycle() var confirmDelete by remember { mutableStateOf(null) } + var confirmRevoke by remember { mutableStateOf(null) } val refreshState = rememberPullRefreshState( refreshing = state.isLoading, onRefresh = viewModel::loadLicenses, @@ -81,6 +82,31 @@ fun LicenseListScreen(viewModel: LicenseViewModel) { ) } + confirmRevoke?.let { license -> + AlertDialog( + onDismissRequest = { confirmRevoke = null }, + title = { Text(stringResource(R.string.revoke_key_title)) }, + text = { + Text(stringResource(R.string.revoke_key_message, license.key)) + }, + confirmButton = { + TextButton( + onClick = { + viewModel.revokeLicense(license.key) + confirmRevoke = null + }, + ) { + Text(stringResource(R.string.revoke)) + } + }, + dismissButton = { + TextButton(onClick = { confirmRevoke = null }) { + Text(stringResource(R.string.cancel)) + } + }, + ) + } + Box( modifier = Modifier .fillMaxSize() @@ -120,7 +146,9 @@ fun LicenseListScreen(viewModel: LicenseViewModel) { LicenseCard( license = license, isDeleting = state.deletingKey == license.key, + isRevoking = state.revokingKey == license.key, onDelete = { confirmDelete = license }, + onRevoke = { confirmRevoke = license }, ) } } @@ -139,7 +167,9 @@ fun LicenseListScreen(viewModel: LicenseViewModel) { private fun LicenseCard( license: LicenseEntry, isDeleting: Boolean, + isRevoking: Boolean, onDelete: () -> Unit, + onRevoke: () -> Unit, ) { Card( modifier = Modifier.fillMaxWidth(), @@ -172,10 +202,27 @@ private fun LicenseCard( if (license.activatedDevices.isNotEmpty()) { DetailRow("deviceId", license.activatedDevices.joinToString(", ")) } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + if (!license.revoked) { + OutlinedButton( + onClick = onRevoke, + enabled = !isDeleting && !isRevoking, + modifier = Modifier.padding(end = 8.dp), + ) { + if (isRevoking) { + CircularProgressIndicator(strokeWidth = 2.dp) + } else { + Text(stringResource(R.string.revoke)) + } + } + } OutlinedButton( onClick = onDelete, - enabled = !isDeleting, + enabled = !isDeleting && !isRevoking, ) { if (isDeleting) { CircularProgressIndicator(strokeWidth = 2.dp) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c0bd41..fff37ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,9 +7,12 @@ Настройки Удалить + Отозвать Отмена Удалить ключ? - Это действие необратимо. + Ключ будет удалён с сервера безвозвратно. + Отозвать лицензию? + Ключ %1$s перестанет работать у всех пользователей. Лицензий пока нет Создан Устройства