-
Notifications
You must be signed in to change notification settings - Fork 11
Upgrade library to AGP 9+ #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # AndroidAppUpdater - AppUpdater (XML/View) module consumer ProGuard rules | ||
| # These rules are bundled with the library and applied to consumer projects. | ||
|
|
||
| # Keep the public DialogFragment entry point | ||
| -keep class com.pouyaheydari.appupdater.main.ui.AppUpdaterDialog { *; } | ||
|
|
||
| # Keep public API model classes | ||
| -keep class com.pouyaheydari.appupdater.main.ui.model.UpdaterDialogData { *; } | ||
| -keep class com.pouyaheydari.appupdater.main.ui.model.UpdaterFragmentModel { *; } | ||
|
|
||
| # Keep DSL builder functions and classes | ||
| -keep class com.pouyaheydari.appupdater.main.dsl.** { *; } | ||
|
|
||
| # Keep Parcelable CREATOR fields | ||
| -keepclassmembers class * implements android.os.Parcelable { | ||
| public static final ** CREATOR; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,22 +1,99 @@ | ||||||||||||||||||||||||||||||||||||||
| package com.pouyaheydari.appupdater.main.dsl | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import android.graphics.Typeface | ||||||||||||||||||||||||||||||||||||||
| import com.pouyaheydari.appupdater.core.model.Theme | ||||||||||||||||||||||||||||||||||||||
| import com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem | ||||||||||||||||||||||||||||||||||||||
| import com.pouyaheydari.appupdater.main.ui.AppUpdaterDialog | ||||||||||||||||||||||||||||||||||||||
| import com.pouyaheydari.appupdater.main.ui.model.UpdaterDialogData | ||||||||||||||||||||||||||||||||||||||
| import com.pouyaheydari.appupdater.store.domain.StoreListItem | ||||||||||||||||||||||||||||||||||||||
| import com.pouyaheydari.appupdater.store.domain.stores.AppStore | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * This inline function helps building stores in DSL way | ||||||||||||||||||||||||||||||||||||||
| * Mutable builder for [StoreListItem] used in DSL context. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| inline fun store(block: StoreListItem.() -> Unit): StoreListItem = StoreListItem().apply(block) | ||||||||||||||||||||||||||||||||||||||
| class StoreListItemBuilder { | ||||||||||||||||||||||||||||||||||||||
| lateinit var store: AppStore | ||||||||||||||||||||||||||||||||||||||
| var title: String = "" | ||||||||||||||||||||||||||||||||||||||
| var icon: Int = 0 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| fun build(): StoreListItem = StoreListItem(store = store, title = title, icon = icon) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+14
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid Same issue as the Compose version: if the user forgets to set Proposed fix class StoreListItemBuilder {
- lateinit var store: AppStore
+ var store: AppStore? = null
var title: String = ""
var icon: Int = 0
- fun build(): StoreListItem = StoreListItem(store = store, title = title, icon = icon)
+ fun build(): StoreListItem = StoreListItem(
+ store = requireNotNull(store) { "StoreListItemBuilder requires 'store' to be set" },
+ title = title,
+ icon = icon
+ )
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Mutable builder for [DirectDownloadListItem] used in DSL context. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| class DirectDownloadListItemBuilder { | ||||||||||||||||||||||||||||||||||||||
| var title: String = "" | ||||||||||||||||||||||||||||||||||||||
| var url: String = "" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| fun build(): DirectDownloadListItem = DirectDownloadListItem(title = title, url = url) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Mutable builder for [UpdaterDialogData] used in DSL context. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| class UpdaterDialogDataBuilder { | ||||||||||||||||||||||||||||||||||||||
| var title: String = "" | ||||||||||||||||||||||||||||||||||||||
| var description: String = "" | ||||||||||||||||||||||||||||||||||||||
| var storeList: List<StoreListItem> = listOf() | ||||||||||||||||||||||||||||||||||||||
| var directDownloadList: List<DirectDownloadListItem> = listOf() | ||||||||||||||||||||||||||||||||||||||
| var isForceUpdate: Boolean = false | ||||||||||||||||||||||||||||||||||||||
| var typeface: Typeface? = null | ||||||||||||||||||||||||||||||||||||||
| var errorWhileOpeningStoreCallback: ((String) -> Unit)? = null | ||||||||||||||||||||||||||||||||||||||
| var theme: Theme = Theme.SYSTEM_DEFAULT | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| fun build(): UpdaterDialogData = UpdaterDialogData( | ||||||||||||||||||||||||||||||||||||||
| title = title, | ||||||||||||||||||||||||||||||||||||||
| description = description, | ||||||||||||||||||||||||||||||||||||||
| storeList = storeList, | ||||||||||||||||||||||||||||||||||||||
| directDownloadList = directDownloadList, | ||||||||||||||||||||||||||||||||||||||
| isForceUpdate = isForceUpdate, | ||||||||||||||||||||||||||||||||||||||
| typeface = typeface, | ||||||||||||||||||||||||||||||||||||||
| errorWhileOpeningStoreCallback = errorWhileOpeningStoreCallback, | ||||||||||||||||||||||||||||||||||||||
| theme = theme, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
13
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider extracting shared builders to a common module.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * DSL builder for constructing a [StoreListItem]. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * Example usage: | ||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||
| * val item = store { | ||||||||||||||||||||||||||||||||||||||
| * store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "com.example.app") | ||||||||||||||||||||||||||||||||||||||
| * title = "Google Play" | ||||||||||||||||||||||||||||||||||||||
| * icon = R.drawable.ic_google_play | ||||||||||||||||||||||||||||||||||||||
| * } | ||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| inline fun store(block: StoreListItemBuilder.() -> Unit): StoreListItem = | ||||||||||||||||||||||||||||||||||||||
| StoreListItemBuilder().apply(block).build() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * This inline function helps building direct download links in DSL way | ||||||||||||||||||||||||||||||||||||||
| * DSL builder for constructing a [DirectDownloadListItem]. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * Example usage: | ||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||
| * val item = directDownload { | ||||||||||||||||||||||||||||||||||||||
| * title = "Direct APK" | ||||||||||||||||||||||||||||||||||||||
| * url = "https://example.com/app.apk" | ||||||||||||||||||||||||||||||||||||||
| * } | ||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| inline fun directDownload(block: DirectDownloadListItem.() -> Unit): DirectDownloadListItem = DirectDownloadListItem().apply(block) | ||||||||||||||||||||||||||||||||||||||
| inline fun directDownload(block: DirectDownloadListItemBuilder.() -> Unit): DirectDownloadListItem = | ||||||||||||||||||||||||||||||||||||||
| DirectDownloadListItemBuilder().apply(block).build() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * This inline function helps building UpdateDialog in DSL way | ||||||||||||||||||||||||||||||||||||||
| * DSL builder for constructing and obtaining an [AppUpdaterDialog] instance. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * Example usage: | ||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||
| * val dialog = updateDialogBuilder { | ||||||||||||||||||||||||||||||||||||||
| * title = "New Update Available" | ||||||||||||||||||||||||||||||||||||||
| * description = "Version 2.0 is ready" | ||||||||||||||||||||||||||||||||||||||
| * storeList = listOf(...) | ||||||||||||||||||||||||||||||||||||||
| * } | ||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| inline fun updateDialogBuilder(block: UpdaterDialogData.() -> Unit): AppUpdaterDialog = | ||||||||||||||||||||||||||||||||||||||
| AppUpdaterDialog.getInstance(UpdaterDialogData().apply(block)) | ||||||||||||||||||||||||||||||||||||||
| inline fun updateDialogBuilder(block: UpdaterDialogDataBuilder.() -> Unit): AppUpdaterDialog = | ||||||||||||||||||||||||||||||||||||||
| AppUpdaterDialog.getInstance(UpdaterDialogDataBuilder().apply(block).build()) | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ import androidx.lifecycle.flowWithLifecycle | |
| import androidx.lifecycle.lifecycleScope | ||
| import androidx.recyclerview.widget.GridLayoutManager | ||
| import com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem | ||
| import com.pouyaheydari.appupdater.directdownload.utils.donwloadapk.checkPermissionsAndDownloadApk | ||
| import com.pouyaheydari.appupdater.directdownload.utils.downloadapk.checkPermissionsAndDownloadApk | ||
| import com.pouyaheydari.appupdater.directdownload.utils.installapk.installAPK | ||
| import com.pouyaheydari.appupdater.main.R | ||
| import com.pouyaheydari.appupdater.main.data.mapper.mapToSelectedTheme | ||
|
|
@@ -62,7 +62,9 @@ class AppUpdaterDialog : DialogFragment() { | |
| // Getting data passed to the library | ||
| val data = arguments?.parcelable(UPDATE_DIALOG_KEY) ?: UpdaterFragmentModel.EMPTY | ||
| if (data == UpdaterFragmentModel.EMPTY || (data.storeList.isEmpty() && data.directDownloadList.isEmpty())) { | ||
| throw IllegalArgumentException("Invalid data provided to the updater dialog. Either 'storeList' or 'directDownloadList' must be non-empty. For more details, refer to the documentation at $UPDATE_DIALOG_README_URL") | ||
| throw IllegalArgumentException( | ||
| "Invalid data provided to the updater dialog. Either 'storeList' or 'directDownloadList' must be non-empty. For more details, refer to the documentation at $UPDATE_DIALOG_README_URL", | ||
| ) | ||
| } | ||
| setDialogBackground(mapToSelectedTheme(data.theme, requireContext())) | ||
| isCancelable = data.isForceUpdate | ||
|
|
@@ -95,7 +97,19 @@ class AppUpdaterDialog : DialogFragment() { | |
| } | ||
|
|
||
| private fun subscribeToViewModel(theme: UserSelectedTheme) { | ||
| viewModel.screenState.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED) | ||
| // Observe persistent UI state (survives config changes) | ||
| viewModel.screenState | ||
| .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED) | ||
| .onEach { uiState -> | ||
| when (uiState) { | ||
| DialogScreenUiState.Idle -> hideUpdateInProgressDialog() | ||
| DialogScreenUiState.UpdateInProgress -> showUpdateInProgressDialog(theme) | ||
| } | ||
| }.launchIn(lifecycleScope) | ||
|
|
||
| // Observe one-shot side-effects (consumed exactly once, no replay on config change) | ||
| viewModel.sideEffect | ||
| .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED) | ||
| .onEach { | ||
| when (it) { | ||
| is DialogScreenStates.DownloadApk -> { | ||
|
|
@@ -105,7 +119,7 @@ class AppUpdaterDialog : DialogFragment() { | |
| androidSdkVersion = Build.VERSION.SDK_INT, | ||
| notificationTitle = requireContext().getString(com.pouyaheydari.appupdater.directdownload.R.string.appupdater_download_notification_title), | ||
| notificationDescription = requireContext().getString(com.pouyaheydari.appupdater.directdownload.R.string.appupdater_download_notification_desc), | ||
| downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager | ||
| downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager, | ||
| ) { | ||
| viewModel.handleIntent(DialogScreenIntents.OnApkDownloadStarted) | ||
| } | ||
|
|
@@ -121,10 +135,13 @@ class AppUpdaterDialog : DialogFragment() { | |
| viewModel.handleIntent(DialogScreenIntents.OnErrorCallbackExecuted) | ||
| } | ||
|
|
||
| DialogScreenStates.HideUpdateInProgress -> hideUpdateInProgressDialog() | ||
| DialogScreenStates.ShowUpdateInProgress -> showUpdateInProgressDialog(theme) | ||
| DialogScreenStates.Empty -> hideUpdateInProgressDialog() | ||
| is DialogScreenStates.InstallApk -> installDownloadedApk(it) | ||
|
|
||
| // Persistent states are handled by screenState collector above | ||
| DialogScreenStates.HideUpdateInProgress, | ||
| DialogScreenStates.ShowUpdateInProgress, | ||
| DialogScreenStates.Empty, | ||
| -> { /* handled by screenState */ } | ||
|
Comment on lines
+139
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if these DialogScreenStates variants are used anywhere else
echo "Searching for usages of HideUpdateInProgress, ShowUpdateInProgress, Empty..."
rg -n "DialogScreenStates\.(HideUpdateInProgress|ShowUpdateInProgress|Empty)" --type kotlinRepository: HeyPouya/AndroidAppUpdater Length of output: 569 🏁 Script executed: fd -e kt -type f | xargs grep -l "sealed class DialogScreenStates" | head -5Repository: HeyPouya/AndroidAppUpdater Length of output: 240 🏁 Script executed: fd DialogScreenStates .Repository: HeyPouya/AndroidAppUpdater Length of output: 160 🏁 Script executed: cat -n ./appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/DialogScreenStates.ktRepository: HeyPouya/AndroidAppUpdater Length of output: 788 🏁 Script executed: rg -n "(HideUpdateInProgress|ShowUpdateInProgress|Empty)" --type kotlin appupdater/src/main/java/com/pouyaheydari/appupdater/Repository: HeyPouya/AndroidAppUpdater Length of output: 1531 Remove unused These three variants ( 🤖 Prompt for AI Agents |
||
| } | ||
| }.launchIn(lifecycleScope) | ||
| } | ||
|
|
@@ -265,7 +282,7 @@ class AppUpdaterDialog : DialogFragment() { | |
| storeList, | ||
| directDownloadList, | ||
| !isForceUpdate, | ||
| theme | ||
| theme, | ||
| ) | ||
|
|
||
| TypefaceHolder.typeface = typeface | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,32 +9,55 @@ import com.pouyaheydari.appupdater.main.ui.model.DialogScreenIntents | |
| import com.pouyaheydari.appupdater.main.ui.model.DialogScreenStates | ||
| import com.pouyaheydari.appupdater.main.utils.ErrorCallbackHolder | ||
| import com.pouyaheydari.appupdater.main.utils.TypefaceHolder | ||
| import kotlinx.coroutines.channels.Channel | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.flow.asStateFlow | ||
| import kotlinx.coroutines.flow.collectLatest | ||
| import kotlinx.coroutines.flow.receiveAsFlow | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| /** | ||
| * ViewModel for the app updater dialog (XML/View module). | ||
| * | ||
| * Uses [StateFlow] for persistent UI state (e.g. update-in-progress indicator) and | ||
| * [Channel] for one-shot side-effects (e.g. open store, download APK) that should | ||
| * not replay on configuration change. | ||
| */ | ||
| internal class AppUpdaterViewModel( | ||
| private val isUpdateInProgressUseCase: GetDownloadStateUseCase, | ||
| private val setDownloadStateUseCase: SetDownloadStateUseCase | ||
| private val setDownloadStateUseCase: SetDownloadStateUseCase, | ||
| ) : ViewModel() { | ||
| val screenState = MutableStateFlow<DialogScreenStates>(DialogScreenStates.HideUpdateInProgress) | ||
| /** Persistent UI state that survives configuration changes. */ | ||
| private val _screenState = MutableStateFlow<DialogScreenUiState>(DialogScreenUiState.Idle) | ||
| val screenState: StateFlow<DialogScreenUiState> = _screenState.asStateFlow() | ||
|
|
||
| /** One-shot side-effect events consumed exactly once by the UI. */ | ||
| private val _sideEffect = Channel<DialogScreenStates>(Channel.BUFFERED) | ||
| val sideEffect = _sideEffect.receiveAsFlow() | ||
|
|
||
| fun handleIntent(intent: DialogScreenIntents) { | ||
| when (intent) { | ||
| is DialogScreenIntents.OnDirectLinkClicked -> screenState.value = DialogScreenStates.DownloadApk(intent.item.url) | ||
| is DialogScreenIntents.OnStoreClicked -> screenState.value = DialogScreenStates.OpenStore(intent.item.store) | ||
| DialogScreenIntents.OnStoreOpened -> screenState.value = DialogScreenStates.Empty | ||
| DialogScreenIntents.OnErrorCallbackExecuted -> screenState.value = DialogScreenStates.Empty | ||
| DialogScreenIntents.OnApkDownloadRequested -> screenState.value = DialogScreenStates.Empty | ||
| is DialogScreenIntents.OnDirectLinkClicked -> | ||
| _sideEffect.trySend(DialogScreenStates.DownloadApk(intent.item.url)) | ||
|
|
||
| is DialogScreenIntents.OnStoreClicked -> | ||
| _sideEffect.trySend(DialogScreenStates.OpenStore(intent.item.store)) | ||
|
|
||
| is DialogScreenIntents.OnOpeningStoreFailed -> | ||
| _sideEffect.trySend(DialogScreenStates.ExecuteErrorCallback(intent.store.getUserReadableName())) | ||
|
Comment on lines
+41
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider handling
Example: Log on failure_sideEffect.trySend(DialogScreenStates.DownloadApk(intent.item.url))
.onFailure { Log.w(TAG, "Failed to send DownloadApk side effect") }🤖 Prompt for AI Agents |
||
|
|
||
| DialogScreenIntents.OnApkDownloadStarted -> { | ||
| setUpdateInProgress() | ||
| observeUpdateInProgressStatus() | ||
| } | ||
|
|
||
| is DialogScreenIntents.OnOpeningStoreFailed -> | ||
| screenState.value = DialogScreenStates.ExecuteErrorCallback(intent.store.getUserReadableName()) | ||
|
|
||
| DialogScreenIntents.OnApkInstallationStarted -> screenState.value = DialogScreenStates.Empty | ||
| // These intents signal the UI consumed a side-effect; no further action needed. | ||
| DialogScreenIntents.OnStoreOpened, | ||
| DialogScreenIntents.OnErrorCallbackExecuted, | ||
| DialogScreenIntents.OnApkDownloadRequested, | ||
| DialogScreenIntents.OnApkInstallationStarted, | ||
| -> { /* consumed */ } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -47,12 +70,17 @@ internal class AppUpdaterViewModel( | |
| private fun observeUpdateInProgressStatus() { | ||
| viewModelScope.launch { | ||
| isUpdateInProgressUseCase().collectLatest { downloadState -> | ||
| screenState.value = when (downloadState) { | ||
| is DownloadState.Downloaded -> | ||
| DialogScreenStates.InstallApk(downloadState.apk) | ||
| when (downloadState) { | ||
| is DownloadState.Downloaded -> { | ||
| _screenState.value = DialogScreenUiState.Idle | ||
| _sideEffect.trySend(DialogScreenStates.InstallApk(downloadState.apk)) | ||
| } | ||
|
|
||
| is DownloadState.Downloading -> | ||
| DialogScreenStates.ShowUpdateInProgress | ||
| _screenState.value = DialogScreenUiState.UpdateInProgress | ||
|
|
||
| is DownloadState.Failed -> | ||
| _screenState.value = DialogScreenUiState.Idle | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -64,3 +92,15 @@ internal class AppUpdaterViewModel( | |
| super.onCleared() | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Persistent UI state for the updater dialog. | ||
| * This state survives configuration changes and is safe to replay. | ||
| */ | ||
| internal sealed interface DialogScreenUiState { | ||
| /** No download in progress; idle state. */ | ||
| data object Idle : DialogScreenUiState | ||
|
|
||
| /** An APK download is in progress; show the progress indicator. */ | ||
| data object UpdateInProgress : DialogScreenUiState | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.