diff --git a/README.md b/README.md
index 7fca10cf..24951dc1 100644
--- a/README.md
+++ b/README.md
@@ -1,116 +1,96 @@
-
+
-# Android App Updater
+Android App Updater
-[](https://android-arsenal.com/details/1/7388)[](https://app.codacy.com/gh/SirLordPouya/AndroidAppUpdater/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)[](https://jitpack.io/#SirLordPouya/AndroidAppUpdater)[](https://android-arsenal.com/api?level=16)[](https://ktlint.github.io/)
+
+ A flexible, lightweight Android library to prompt users to update your app β via 16 app stores or direct APK download.
+
-**Android App Updater** is a powerful and flexible library to display an update dialog in your Android app. It supports multiple app stores and direct APK download links. Use it easily with **DialogFragment** or **Jetpack Compose**.
+
+
+
+
+
+
+
-
+
-## π Installation
+---
+
+## Features
-### Step 1: Add JitPack Repository
+- Works with **Jetpack Compose** and **XML Views (DialogFragment)**
+- Supports **16 app stores** out of the box (Google Play, Huawei, Samsung, Amazon, and more)
+- **Direct APK download** with built-in download management and installation
+- **Light, Dark, and System Default** themes
+- **Force update** mode (non-dismissable dialog)
+- **Custom typeface** support
+- **DSL builders** for clean, expressive configuration
+- Built-in store icons for all supported stores
+- Handles APK installation across all API levels (M through latest)
+- Customizable string resources for localization
-Add the following to your projectβs **build.gradle.kts**:
+---
+
+## Installation
+
+### Step 1 β Add the JitPack repository
+
+In your **settings.gradle.kts**:
```kotlin
-allprojects {
+dependencyResolutionManagement {
repositories {
maven("https://jitpack.io")
}
}
```
-Or to your projectβs **settings.gradle.kts**:
+### Step 2 β Add the dependency
+
+Pick the module that matches your UI toolkit:
+
```kotlin
-dependencyResolutionManagement {
- repositories {
- maven ( url = "https://jitpack.io" )
- }
+dependencies {
+ // Jetpack Compose
+ implementation("com.github.HeyPouya.AndroidAppUpdater:compose:latest_version")
+
+ // XML Views / DialogFragment
+ implementation("com.github.HeyPouya.AndroidAppUpdater:main:latest_version")
}
```
-### Step 2: Add Dependencies
+> Replace `latest_version` with the latest release tag from [JitPack](https://jitpack.io/#HeyPouya/AndroidAppUpdater).
-```kotlin
-// For DialogFragment integration
-implementation("com.github.SirLordPouya.AndroidAppUpdater:main:latest_version")
-
-// For Jetpack Compose integration
-implementation("com.github.SirLordPouya.AndroidAppUpdater:compose:latest_version")
-```
-
-## π― Supported App Stores
-
-| Store | Enum |
-|----------------------|-----------------------------------|
-| Google Play | AppStoreType.GOOGLE_PLAY |
-| Huawei App Gallery | AppStoreType.HUAWEI_APP_GALLERY |
-| Samsung Galaxy Store | AppStoreType.SAMSUNG_GALAXY_STORE |
-| Amazon App Store | AppStoreType.AMAZON_APP_STORE |
-| Xiaomi GetApp Market | AppStoreType.MI_GET_APP_STORE |
-| Oppo App Market | AppStoreType.OPPO_APP_MARKET |
-| F-Droid | AppStoreType.FDROID |
-| Aptoide | AppStoreType.APTOIDE |
-| OneStore | AppStoreType.ONE_STORE_APP_MARKET |
-| Vivo V-AppStore | AppStoreType.V_APP_STORE |
-| 9-Apps Market | AppStoreType.NINE_APPS_STORE |
-| ZTE App Center | AppStoreType.ZTE_APP_CENTER |
-| Lenovo App Center | AppStoreType.LENOVO_APP_CENTER |
-| Tencent App Store | AppStoreType.TENCENT_APPS_STORE |
-| Cafe Bazaar | AppStoreType.CAFE_BAZAAR |
-| Myket | AppStoreType.MYKET |
-
-## π Usage
-
-### Define App Stores
+---
+
+## Quick Start
+
+### 1. Define your store list
```kotlin
-val storesList = listOf(
+val stores = listOf(
StoreListItem(
- store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "YOUR_APP_PACKAGE"),
+ store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "com.your.package"),
title = "Google Play",
icon = R.drawable.appupdater_ic_google_play
+ ),
+ StoreListItem(
+ store = StoreFactory.getStore(AppStoreType.HUAWEI_APP_GALLERY, "com.your.package"),
+ title = "Huawei AppGallery",
+ icon = R.drawable.appupdater_ic_app_gallery
)
)
```
-### Default Store Icons
-
-All supported store icons are available in the library. You can use them directly:
-
-| Market Name | Icon Resource |
-|----------------------|--------------------------------------------|
-| Google Play | R.drawable.appupdater_ic_google_play |
-| Huawei App Gallery | R.drawable.appupdater_ic_app_gallery |
-| Samsung Galaxy Store | R.drawable.appupdater_ic_galaxy_store |
-| Amazon App Store | R.drawable.appupdater_ic_amazon_app_store |
-| Xiaomi GetApp Store | R.drawable.appupdater_ic_get_app_store |
-| Oppo App Market | R.drawable.appupdater_ic_oppo_app_market |
-| F-Droid App Store | R.drawable.appupdater_ic_fdroid |
-| Aptoide App Store | R.drawable.appupdater_ic_aptoide |
-| OneStore App Market | R.drawable.appupdater_ic_one_store |
-| Vivo V-AppStore | R.drawable.appupdater_ic_v_app_store |
-| 9-Apps Market | R.drawable.appupdater_ic_nine_apps |
-| ZTE App Center | R.drawable.appupdater_ic_zte_app_center |
-| Lenovo App Center | R.drawable.appupdater_ic_lenovo_app_center |
-| Tencent App Store | R.drawable.appupdater_ic_tencent_app_store |
-| Cafe Bazaar Store | R.drawable.appupdater_ic_bazar |
-| Myket App Store | R.drawable.appupdater_ic_myket |
-
-π **Note**: Make sure to import:
-```kotlin
-import com.pouyaheydari.appupdater.R.*
-```
-
-### Add a Direct Download Link
+### 2. (Optional) Define direct download links
-Add the required permissions to your `AndroidManifest.xml`:
+Add these permissions to your `AndroidManifest.xml`:
```xml
@@ -118,77 +98,282 @@ Add the required permissions to your `AndroidManifest.xml`:
```
-Then create the download link:
+Then create the list:
```kotlin
-val directDownloadLinksList = listOf(
+val directLinks = listOf(
DirectDownloadListItem(
title = "Direct Download",
- url = "https://example.com/app.apk"
+ url = "https://example.com/your-app.apk"
)
)
```
-### Show the Update Dialog
+### 3. Show the dialog
-#### β
With Jetpack Compose
+#### Jetpack Compose
```kotlin
-var shouldShowDialog by remember { mutableStateOf(true) }
+var showDialog by remember { mutableStateOf(true) }
-if (shouldShowDialog) {
+if (showDialog) {
AndroidAppUpdater(
dialogData = UpdaterDialogData(
- dialogTitle = "New Update Available",
- dialogDescription = "We've fixed bugs and improved performance!",
- dividerText = "Or",
- storeList = storesList,
- directDownloadList = directDownloadLinksList,
- onDismissRequested = { shouldShowDialog = false },
- errorWhileOpeningStoreCallback = { storeName -> /* Handle error */ },
- theme = Theme.LIGHT
+ dialogTitle = "New Update Available!",
+ dialogDescription = "We've added new features and fixed bugs.",
+ dividerText = "or",
+ storeList = stores,
+ directDownloadList = directLinks,
+ onDismissRequested = { showDialog = false },
+ theme = Theme.SYSTEM_DEFAULT
)
)
}
```
-#### β
With Fragments
+#### DialogFragment (XML Views)
```kotlin
-val data = UpdaterDialogData(
- title = "New Update Available",
- description = "We've fixed bugs and improved performance!",
- storeList = storesList,
- directDownloadList = directDownloadLinksList,
- isForceUpdate = false,
- errorWhileOpeningStoreCallback = { storeName -> /* Handle error */ },
- theme = Theme.SYSTEM_DEFAULT,
-)
+AppUpdaterDialog.getInstance(
+ UpdaterDialogData(
+ title = "New Update Available!",
+ description = "We've added new features and fixed bugs.",
+ storeList = stores,
+ directDownloadList = directLinks,
+ isForceUpdate = false,
+ theme = Theme.LIGHT
+ )
+).show(supportFragmentManager, "updater")
+```
+
+---
+
+## DSL Builders
+
+Both Compose and Fragment APIs offer Kotlin DSL builders for a more expressive syntax.
-AppUpdaterDialog.getInstance(data).show(supportFragmentManager, "UPDATE_DIALOG")
+#### Fragment DSL
+
+```kotlin
+updateDialogBuilder {
+ title = "New Update Available!"
+ description = "We've added new features and fixed bugs."
+ isForceUpdate = false
+ theme = Theme.DARK
+ storeList = listOf(
+ store {
+ store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "com.your.package")
+ title = "Google Play"
+ icon = R.drawable.appupdater_ic_google_play
+ }
+ )
+ directDownloadList = listOf(
+ directDownload {
+ title = "Direct Download"
+ url = "https://example.com/your-app.apk"
+ }
+ )
+ typeface = Typeface.createFromAsset(assets, "fonts/custom.ttf")
+ errorWhileOpeningStoreCallback = { storeName ->
+ Toast.makeText(this@MainActivity, "$storeName is not installed", Toast.LENGTH_SHORT).show()
+ }
+}.show(supportFragmentManager, "updater")
```
-## π¨ Customization
+#### Compose DSL
-You can override default texts in `strings.xml`:
+```kotlin
+val dialogData = updaterDialogData {
+ dialogTitle = "New Update Available!"
+ dialogDescription = "We've added new features and fixed bugs."
+ dividerText = "or"
+ theme = Theme.SYSTEM_DEFAULT
+ storeList = listOf(
+ store {
+ store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "com.your.package")
+ title = "Google Play"
+ icon = R.drawable.appupdater_ic_google_play
+ }
+ )
+ directDownloadList = listOf(
+ directDownload {
+ title = "Direct Download"
+ url = "https://example.com/your-app.apk"
+ }
+ )
+ onDismissRequested = { /* handle dismiss */ }
+ errorWhileOpeningStoreCallback = { storeName -> /* handle error */ }
+}
+
+AndroidAppUpdater(dialogData)
+```
+
+---
+
+## Configuration Reference
+
+### Fragment `UpdaterDialogData`
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `title` | `String` | `""` | Title shown at the top of the dialog |
+| `description` | `String` | `""` | Description text below the title |
+| `storeList` | `List` | `[]` | App stores to show as update options |
+| `directDownloadList` | `List` | `[]` | Direct APK download links |
+| `isForceUpdate` | `Boolean` | `false` | If `true`, the dialog cannot be dismissed |
+| `typeface` | `Typeface?` | `null` | Custom typeface for dialog text |
+| `theme` | `Theme` | `SYSTEM_DEFAULT` | `LIGHT`, `DARK`, or `SYSTEM_DEFAULT` |
+| `errorWhileOpeningStoreCallback` | `((String) -> Unit)?` | `null` | Called with store name if opening fails |
+
+### Compose `UpdaterDialogData`
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `dialogTitle` | `String` | `""` | Title shown at the top of the dialog |
+| `dialogDescription` | `String` | `""` | Description text below the title |
+| `dividerText` | `String` | `""` | Text on the divider between stores and downloads |
+| `storeList` | `List` | `[]` | App stores to show as update options |
+| `directDownloadList` | `List` | `[]` | Direct APK download links |
+| `onDismissRequested` | `() -> Unit` | `{}` | Called when the user dismisses the dialog |
+| `typeface` | `Typeface?` | `null` | Custom typeface for dialog text |
+| `theme` | `Theme` | `SYSTEM_DEFAULT` | `LIGHT`, `DARK`, or `SYSTEM_DEFAULT` |
+| `errorWhileOpeningStoreCallback` | `(String) -> Unit` | `{}` | Called with store name if opening fails |
+
+---
+
+## Supported Stores
+
+| Store | Enum Value | Built-in Icon |
+|-------|-----------|---------------|
+| Google Play | `GOOGLE_PLAY` | `appupdater_ic_google_play` |
+| Cafe Bazaar | `CAFE_BAZAAR` | `appupdater_ic_bazar` |
+| Myket | `MYKET` | `appupdater_ic_myket` |
+| Huawei AppGallery | `HUAWEI_APP_GALLERY` | `appupdater_ic_app_gallery` |
+| Samsung Galaxy Store | `SAMSUNG_GALAXY_STORE` | `appupdater_ic_galaxy_store` |
+| Amazon App Store | `AMAZON_APP_STORE` | `appupdater_ic_amazon_app_store` |
+| Aptoide | `APTOIDE` | `appupdater_ic_aptoide` |
+| F-Droid | `FDROID` | `appupdater_ic_fdroid` |
+| Xiaomi GetApps | `MI_GET_APP_STORE` | `appupdater_ic_get_app_store` |
+| OneStore | `ONE_STORE_APP_MARKET` | `appupdater_ic_one_store` |
+| Oppo App Market | `OPPO_APP_MARKET` | `appupdater_ic_oppo_app_market` |
+| Vivo V-AppStore | `V_APP_STORE` | `appupdater_ic_v_app_store` |
+| 9Apps | `NINE_APPS_STORE` | `appupdater_ic_nine_apps` |
+| Tencent App Store | `TENCENT_APPS_STORE` | `appupdater_ic_tencent_app_store` |
+| ZTE App Center | `ZTE_APP_CENTER` | `appupdater_ic_zte_app_center` |
+| Lenovo App Center | `LENOVO_APP_CENTER` | `appupdater_ic_lenovo_app_center` |
+
+All icons are bundled with the library. Use them via `R.drawable.appupdater_ic_*`.
+
+---
+
+## Customization
+
+### Themes
+
+Pass one of the `Theme` enum values to control the dialog appearance:
+
+```kotlin
+Theme.LIGHT // Light background, dark text
+Theme.DARK // Dark background, light text
+Theme.SYSTEM_DEFAULT // Follows the device's current theme
+```
+
+### Custom Typeface
+
+```kotlin
+val typeface = Typeface.createFromAsset(assets, "fonts/your_font.ttf")
+
+// Pass it to either API:
+UpdaterDialogData(
+ // ...
+ typeface = typeface
+)
+```
+
+### String Resources
+
+Override these in your `strings.xml` to localize or customize dialog text:
```xml
-
- Please wait
- Downloading new version...
- Downloading...
- Downloading new version
- Please install
- or
- Download from store
-
+Please wait
+Downloading new version...
+Downloading...
+Downloading new version
+Please install
+or
+Download from store
+Couldn't find downloaded file
+```
+
+---
+
+## Architecture
+
+The library is split into focused modules:
+
+```
+AndroidAppUpdater/
+βββ core/ # Theme enum and shared constants (pure Kotlin)
+βββ store/ # Store implementations, StoreFactory, icons
+βββ directdownload/ # APK download via DownloadManager + installation
+βββ appupdater/ # Fragment/XML UI (published as "main")
+βββ compose/ # Jetpack Compose UI
+βββ app/ # Sample/demo application
```
-## π License
+| Module | Artifact | Description |
+|--------|----------|-------------|
+| `:core` | `core` | Theme enum and constants β no Android dependency |
+| `:store` | `store` | All 16 store implementations with built-in icons |
+| `:directdownload` | `directdownload` | Download manager, permissions, APK installation |
+| `:appupdater` | `main` | DialogFragment-based update dialog |
+| `:compose` | `compose` | Jetpack Compose update dialog |
+
+---
+
+## Requirements
+
+| Requirement | Value |
+|------------|-------|
+| Min SDK | 23 (Android 6.0) |
+| Compile SDK | 36 |
+| Kotlin | 2.3+ |
+| Java | 17 |
+
+---
+
+## Sample App
+
+The `:app` module contains a fully working demo with examples for:
+
+- **Kotlin API** β Direct constructor usage
+- **DSL API** β Builder-style configuration
+- **Compose** β Composable dialog integration
+
+Clone the repo and run the `app` module to see the library in action.
+
+---
+
+## License
```
-Android App Updater is released under the Apache License 2.0. See LICENSE for details.
-Copyright (c) 2018 Pouya Heydari
+Copyright 2018 Pouya Heydari
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
```
-Library icon and design by Amir Gerdakane
+---
+
+
+ Library icon and design by Amir Gerdakane
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a6b4bd23..90afbcac 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.androidApplication)
- alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.compose.compiler)
}
@@ -24,10 +23,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
- kotlin {
- jvmToolchain(17)
- }
-
buildFeatures {
compose = true
}
@@ -38,6 +33,10 @@ android {
}
}
+kotlin {
+ jvmToolchain(17)
+}
+
dependencies {
// library dependency
diff --git a/app/src/main/java/com/pouyaheydari/appupdater/demo/utils/Constants.kt b/app/src/main/java/com/pouyaheydari/appupdater/demo/utils/Constants.kt
index 4f91520d..b77a1308 100644
--- a/app/src/main/java/com/pouyaheydari/appupdater/demo/utils/Constants.kt
+++ b/app/src/main/java/com/pouyaheydari/appupdater/demo/utils/Constants.kt
@@ -1,6 +1,6 @@
package com.pouyaheydari.appupdater.demo.utils
-internal const val APK_URL = "https://cafebazaar.ir/download/bazaar.apk"
+internal const val APK_URL = "https://d.apkpure.com/custom/com.apkpure.aegon-latest.apk"
internal const val SAMPLE_PACKAGE_NAME = "com.tencent.mm"
internal const val FDROID_SAMPLE_PACKAGE_NAME = "de.storchp.fdroidbuildstatus"
internal const val GET_APP_SAMPLE_PACKAGE_NAME = "com.opera.browser"
diff --git a/appupdater/consumer-rules.pro b/appupdater/consumer-rules.pro
new file mode 100644
index 00000000..22319f17
--- /dev/null
+++ b/appupdater/consumer-rules.pro
@@ -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;
+}
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt
index 4fa52c2d..96dd7e91 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt
@@ -1,22 +1,106 @@
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
+import com.pouyaheydari.appupdater.store.R as storeR
/**
- * 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 {
+ var store: AppStore? = null
+ var title: String = ""
+ var icon: Int = storeR.drawable.appupdater_ic_cloud
+
+ fun build(): StoreListItem {
+ val store = requireNotNull(store) { "StoreListItemBuilder requires 'store' to be set." }
+ return StoreListItem(store = store, title = title, icon = icon)
+ }
+}
+
+/**
+ * Mutable builder for [DirectDownloadListItem] used in DSL context.
+ */
+class DirectDownloadListItemBuilder {
+ var title: String = ""
+ var url: String = ""
+
+ fun build(): DirectDownloadListItem {
+ require(url.isNotBlank()) { "DirectDownloadListItemBuilder requires 'url' to be set." }
+ return DirectDownloadListItem(title = title, url = url)
+ }
+}
+
+/**
+ * Mutable builder for [UpdaterDialogData] used in DSL context.
+ */
+class UpdaterDialogDataBuilder {
+ var title: String = ""
+ var description: String = ""
+ var storeList: List = listOf()
+ var directDownloadList: List = 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,
+ )
+}
+
+/**
+ * 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())
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt
index 24e11d80..1eefee5f 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt
@@ -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,9 +135,6 @@ class AppUpdaterDialog : DialogFragment() {
viewModel.handleIntent(DialogScreenIntents.OnErrorCallbackExecuted)
}
- DialogScreenStates.HideUpdateInProgress -> hideUpdateInProgressDialog()
- DialogScreenStates.ShowUpdateInProgress -> showUpdateInProgressDialog(theme)
- DialogScreenStates.Empty -> hideUpdateInProgressDialog()
is DialogScreenStates.InstallApk -> installDownloadedApk(it)
}
}.launchIn(lifecycleScope)
@@ -265,7 +276,7 @@ class AppUpdaterDialog : DialogFragment() {
storeList,
directDownloadList,
!isForceUpdate,
- theme
+ theme,
)
TypefaceHolder.typeface = typeface
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt
index 307192cb..ec2483df 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt
@@ -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.HideUpdateInProgress)
+ /** Persistent UI state that survives configuration changes. */
+ private val _screenState = MutableStateFlow(DialogScreenUiState.Idle)
+ val screenState: StateFlow = _screenState.asStateFlow()
+
+ /** One-shot side-effect events consumed exactly once by the UI. */
+ private val _sideEffect = Channel(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()))
+
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
+}
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/DirectRecyclerAdapter.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/DirectRecyclerAdapter.kt
index 09a1f9be..d10d336e 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/DirectRecyclerAdapter.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/DirectRecyclerAdapter.kt
@@ -11,16 +11,19 @@ internal class DirectRecyclerAdapter(
private val list: List,
private val typeface: Typeface?,
private val listener: (DirectDownloadListItem) -> Unit,
-) : RecyclerView.Adapter() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SoresViewHolder =
- DownloadDirectItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- .run { SoresViewHolder(this) }
+) : RecyclerView.Adapter() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DirectDownloadViewHolder =
+ DownloadDirectItemBinding
+ .inflate(LayoutInflater.from(parent.context), parent, false)
+ .run { DirectDownloadViewHolder(this) }
override fun getItemCount(): Int = list.size
- override fun onBindViewHolder(holder: SoresViewHolder, position: Int) = holder.onBind(list[position])
+ override fun onBindViewHolder(holder: DirectDownloadViewHolder, position: Int) = holder.onBind(list[position])
- inner class SoresViewHolder(private val binding: DownloadDirectItemBinding) : RecyclerView.ViewHolder(binding.root) {
+ inner class DirectDownloadViewHolder(
+ private val binding: DownloadDirectItemBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
fun onBind(item: DirectDownloadListItem) {
val txtDirect = binding.txtDirect
txtDirect.text = item.title
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/StoresRecyclerAdapter.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/StoresRecyclerAdapter.kt
index 04fc3f98..2122587e 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/StoresRecyclerAdapter.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/StoresRecyclerAdapter.kt
@@ -17,16 +17,19 @@ internal class StoresRecyclerAdapter(
private val theme: UserSelectedTheme,
private val typeface: Typeface?,
private val listener: (StoreListItem) -> Unit,
-) : RecyclerView.Adapter() {
+) : RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
- DownloadStoresItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- .run { SoresViewHolder(this) }
+ DownloadStoresItemBinding
+ .inflate(LayoutInflater.from(parent.context), parent, false)
+ .run { StoresViewHolder(this) }
override fun getItemCount(): Int = list.size
- override fun onBindViewHolder(holder: SoresViewHolder, position: Int) = holder.onBind(list[position])
+ override fun onBindViewHolder(holder: StoresViewHolder, position: Int) = holder.onBind(list[position])
- inner class SoresViewHolder(private val binding: DownloadStoresItemBinding) : RecyclerView.ViewHolder(binding.root) {
+ inner class StoresViewHolder(
+ private val binding: DownloadStoresItemBinding,
+ ) : RecyclerView.ViewHolder(binding.root) {
fun onBind(item: StoreListItem) {
val txtStoreTitle = binding.txtStoreTitle
val imgStore = binding.imgStore
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/DialogScreenStates.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/DialogScreenStates.kt
index 9d203d01..4ce92ab9 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/DialogScreenStates.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/DialogScreenStates.kt
@@ -4,9 +4,6 @@ import com.pouyaheydari.appupdater.store.domain.stores.AppStore
import java.io.File
internal sealed interface DialogScreenStates {
- data object Empty : DialogScreenStates
- data object ShowUpdateInProgress : DialogScreenStates
- data object HideUpdateInProgress : DialogScreenStates
data class OpenStore(val store: AppStore) : DialogScreenStates
data class DownloadApk(val apkUrl: String) : DialogScreenStates
data class ExecuteErrorCallback(val storeName: String) : DialogScreenStates
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterDialogData.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterDialogData.kt
index 07dacea0..2ceae311 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterDialogData.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterDialogData.kt
@@ -6,15 +6,24 @@ import com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem
import com.pouyaheydari.appupdater.store.domain.StoreListItem
/**
- * This model is used to pass the data to dialog fragment via bundles
+ * Configuration data for the updater dialog.
+ *
+ * @property title the title displayed at the top of the dialog
+ * @property description the description text displayed below the title
+ * @property storeList list of app stores to display as update options
+ * @property directDownloadList list of direct APK download links
+ * @property isForceUpdate when true, the dialog cannot be dismissed by the user
+ * @property typeface optional custom typeface for all text in the dialog
+ * @property errorWhileOpeningStoreCallback optional callback invoked with the store name when opening a store fails
+ * @property theme the visual theme for the dialog
*/
data class UpdaterDialogData(
- var title: String = "",
- var description: String = "",
- var storeList: List = listOf(),
- var directDownloadList: List = listOf(),
- var isForceUpdate: Boolean = false,
- var typeface: Typeface? = null,
- var errorWhileOpeningStoreCallback: ((String) -> Unit)? = null,
- var theme: Theme = Theme.SYSTEM_DEFAULT,
+ val title: String = "",
+ val description: String = "",
+ val storeList: List = listOf(),
+ val directDownloadList: List = listOf(),
+ val isForceUpdate: Boolean = false,
+ val typeface: Typeface? = null,
+ val errorWhileOpeningStoreCallback: ((String) -> Unit)? = null,
+ val theme: Theme = Theme.SYSTEM_DEFAULT,
)
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterFragmentModel.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterFragmentModel.kt
index 3ccbeab7..00b72c05 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterFragmentModel.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterFragmentModel.kt
@@ -1,51 +1,24 @@
package com.pouyaheydari.appupdater.main.ui.model
-import android.os.Parcel
import android.os.Parcelable
import com.pouyaheydari.appupdater.core.model.Theme
import com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem
import com.pouyaheydari.appupdater.store.domain.StoreListItem
+import kotlinx.parcelize.Parcelize
/**
- * This model is used to pass the data to dialog fragment via bundles
+ * This model is used to pass the data to dialog fragment via bundles.
*/
+@Parcelize
internal data class UpdaterFragmentModel(
- var title: String = "",
- var description: String = "",
- var storeList: List = listOf(),
- var directDownloadList: List = listOf(),
- var isForceUpdate: Boolean = false,
- var theme: Theme = Theme.SYSTEM_DEFAULT,
+ val title: String = "",
+ val description: String = "",
+ val storeList: List = listOf(),
+ val directDownloadList: List = listOf(),
+ val isForceUpdate: Boolean = false,
+ val theme: Theme = Theme.SYSTEM_DEFAULT,
) : Parcelable {
- constructor(parcel: Parcel) : this(
- parcel.readString().orEmpty(),
- parcel.readString().orEmpty(),
- parcel.createTypedArrayList(StoreListItem).orEmpty(),
- parcel.createTypedArrayList(DirectDownloadListItem).orEmpty(),
- parcel.readByte() != 0.toByte(),
- Theme.entries[parcel.readInt()],
- )
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(title)
- parcel.writeString(description)
- parcel.writeTypedList(storeList)
- parcel.writeByte(if (isForceUpdate) 1 else 0)
- parcel.writeInt(theme.ordinal)
- }
-
- override fun describeContents(): Int {
- return 0
- }
-
- companion object CREATOR : Parcelable.Creator {
+ companion object {
val EMPTY = UpdaterFragmentModel()
- override fun createFromParcel(parcel: Parcel): UpdaterFragmentModel {
- return UpdaterFragmentModel(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
}
}
diff --git a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/utils/ErrorCallbackHolder.kt b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/utils/ErrorCallbackHolder.kt
index 1e3197d9..daac3ec9 100644
--- a/appupdater/src/main/java/com/pouyaheydari/appupdater/main/utils/ErrorCallbackHolder.kt
+++ b/appupdater/src/main/java/com/pouyaheydari/appupdater/main/utils/ErrorCallbackHolder.kt
@@ -1,9 +1,7 @@
package com.pouyaheydari.appupdater.main.utils
-import android.graphics.Typeface
-
/**
- * Holds an instance of [Typeface] to be used in dialogs while being shown.
+ * Holds an error callback lambda to be invoked when opening a store fails.
*/
internal object ErrorCallbackHolder {
var callback: ((String) -> Unit)? = null
diff --git a/appupdater/src/test/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModelTest.kt b/appupdater/src/test/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModelTest.kt
index 4433042a..9c4ce8dc 100644
--- a/appupdater/src/test/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModelTest.kt
+++ b/appupdater/src/test/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModelTest.kt
@@ -24,7 +24,6 @@ import org.mockito.kotlin.whenever
import java.io.File
class AppUpdaterViewModelTest {
-
private lateinit var viewModel: AppUpdaterViewModel
private val isUpdateInProgressUseCase: GetDownloadStateUseCase = mock()
private val setDownloadStateUseCase: SetDownloadStateUseCase = mock()
@@ -38,24 +37,22 @@ class AppUpdaterViewModelTest {
}
@Test
- fun `handleIntent should update screen state for OnDirectLinkClicked`() = runTest {
+ fun `handleIntent should emit side effect for OnDirectLinkClicked`() = runTest {
val testUrl = "https://example.com/app.apk"
val testItem = DirectDownloadListItem(title = "Test", url = testUrl)
- viewModel.screenState.test {
+ viewModel.sideEffect.test {
viewModel.handleIntent(DialogScreenIntents.OnDirectLinkClicked(testItem))
- assertEquals(DialogScreenStates.HideUpdateInProgress, awaitItem())
assertEquals(DialogScreenStates.DownloadApk(testUrl), awaitItem())
}
}
@Test
- fun `handleIntent should update screen state for OnStoreClicked`() = runTest {
+ fun `handleIntent should emit side effect for OnStoreClicked`() = runTest {
val testItem = StoreListItem(store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "package"), title = "Google Play", icon = 0)
- viewModel.screenState.test {
+ viewModel.sideEffect.test {
viewModel.handleIntent(DialogScreenIntents.OnStoreClicked(testItem))
- assertEquals(DialogScreenStates.HideUpdateInProgress, awaitItem())
assertEquals(DialogScreenStates.OpenStore(testItem.store), awaitItem())
}
}
@@ -65,10 +62,9 @@ class AppUpdaterViewModelTest {
val file: File = mock()
whenever(isUpdateInProgressUseCase()).thenReturn(flowOf(DownloadState.Downloading, DownloadState.Downloaded(file)))
- viewModel.screenState.test {
+ viewModel.sideEffect.test {
viewModel.handleIntent(DialogScreenIntents.OnApkDownloadStarted)
verify(setDownloadStateUseCase).invoke(DownloadState.Downloading)
- assertEquals(DialogScreenStates.HideUpdateInProgress, awaitItem())
assertEquals(DialogScreenStates.InstallApk(file), awaitItem())
}
}
diff --git a/build-logic/convention/src/main/java/com/pouyaheydari/appupdater/convention/plugins/AndroidLibraryPlugin.kt b/build-logic/convention/src/main/java/com/pouyaheydari/appupdater/convention/plugins/AndroidLibraryPlugin.kt
index 78019ace..fb44c749 100644
--- a/build-logic/convention/src/main/java/com/pouyaheydari/appupdater/convention/plugins/AndroidLibraryPlugin.kt
+++ b/build-logic/convention/src/main/java/com/pouyaheydari/appupdater/convention/plugins/AndroidLibraryPlugin.kt
@@ -1,22 +1,20 @@
package com.pouyaheydari.appupdater.convention.plugins
-import com.android.build.gradle.LibraryExtension
+import com.android.build.api.dsl.LibraryExtension
import com.pouyaheydari.appupdater.convention.helpers.baseLibs
import com.pouyaheydari.appupdater.convention.helpers.compileSdk
import com.pouyaheydari.appupdater.convention.helpers.javaVersion
import com.pouyaheydari.appupdater.convention.helpers.minSdk
import org.gradle.api.Plugin
import org.gradle.api.Project
-import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.configure
-import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
class AndroidLibraryPlugin : Plugin {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply(baseLibs.findPlugin("androidLibrary").get().get().pluginId)
- apply(baseLibs.findPlugin("jetbrainsKotlinAndroid").get().get().pluginId)
+ apply(baseLibs.findPlugin("kotlin.parcelize").get().get().pluginId)
apply(baseLibs.findPlugin("maven.publish").get().get().pluginId)
}
@@ -25,17 +23,13 @@ class AndroidLibraryPlugin : Plugin {
defaultConfig {
minSdk = minSdk()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
}
compileOptions {
sourceCompatibility = javaVersion()
targetCompatibility = javaVersion()
}
}
- extensions.configure {
- jvmToolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
- }
- }
}
}
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 2a601f8b..2307af0d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.androidApplication) apply false
- alias(libs.plugins.jetbrainsKotlinAndroid) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.maven.publish) apply false
diff --git a/compose/consumer-rules.pro b/compose/consumer-rules.pro
new file mode 100644
index 00000000..03b78404
--- /dev/null
+++ b/compose/consumer-rules.pro
@@ -0,0 +1,13 @@
+# AndroidAppUpdater - Compose module consumer ProGuard rules
+# These rules are bundled with the library and applied to consumer projects.
+
+# Keep the public composable entry point (referenced by name in consumer code)
+-keep class com.pouyaheydari.appupdater.compose.ui.AndroidAppUpdaterScreenKt { *; }
+
+# Keep public API model classes
+-keep class com.pouyaheydari.appupdater.compose.ui.models.UpdaterDialogData { *; }
+
+# Keep Parcelable CREATOR fields
+-keepclassmembers class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
diff --git a/compose/src/main/java/com/pouyaheydari/appupdater/compose/dsl/DSLUtils.kt b/compose/src/main/java/com/pouyaheydari/appupdater/compose/dsl/DSLUtils.kt
new file mode 100644
index 00000000..e8078467
--- /dev/null
+++ b/compose/src/main/java/com/pouyaheydari/appupdater/compose/dsl/DSLUtils.kt
@@ -0,0 +1,113 @@
+package com.pouyaheydari.appupdater.compose.dsl
+
+import android.graphics.Typeface
+import com.pouyaheydari.appupdater.compose.ui.models.UpdaterDialogData
+import com.pouyaheydari.appupdater.core.model.Theme
+import com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem
+import com.pouyaheydari.appupdater.store.domain.StoreListItem
+import com.pouyaheydari.appupdater.store.domain.stores.AppStore
+import com.pouyaheydari.appupdater.store.R as storeR
+
+/**
+ * Mutable builder for [StoreListItem] used in DSL context.
+ */
+class StoreListItemBuilder {
+ var store: AppStore? = null
+ var title: String = ""
+ var icon: Int = storeR.drawable.appupdater_ic_cloud
+
+ fun build(): StoreListItem {
+ val store = requireNotNull(store) { "StoreListItemBuilder requires 'store' to be set." }
+ return StoreListItem(store = store, title = title, icon = icon)
+ }
+}
+
+/**
+ * Mutable builder for [DirectDownloadListItem] used in DSL context.
+ */
+class DirectDownloadListItemBuilder {
+ var title: String = ""
+ var url: String = ""
+
+ fun build(): DirectDownloadListItem {
+ require(url.isNotBlank()) { "DirectDownloadListItemBuilder requires 'url' to be set." }
+ return DirectDownloadListItem(title = title, url = url)
+ }
+}
+
+/**
+ * Mutable builder for [UpdaterDialogData] used in DSL context.
+ */
+class UpdaterDialogDataBuilder {
+ var dialogTitle: String = ""
+ var dialogDescription: String = ""
+ var dividerText: String = ""
+ var storeList: List = listOf()
+ var directDownloadList: List = listOf()
+ var onDismissRequested: () -> Unit = {}
+ var errorWhileOpeningStoreCallback: (String) -> Unit = {}
+ var typeface: Typeface? = null
+ var theme: Theme = Theme.SYSTEM_DEFAULT
+
+ fun build(): UpdaterDialogData = UpdaterDialogData(
+ dialogTitle = dialogTitle,
+ dialogDescription = dialogDescription,
+ dividerText = dividerText,
+ storeList = storeList,
+ directDownloadList = directDownloadList,
+ onDismissRequested = onDismissRequested,
+ errorWhileOpeningStoreCallback = errorWhileOpeningStoreCallback,
+ typeface = typeface,
+ theme = theme,
+ )
+}
+
+/**
+ * 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()
+
+/**
+ * DSL builder for constructing a [DirectDownloadListItem].
+ *
+ * Example usage:
+ * ```
+ * val item = directDownload {
+ * title = "Direct APK"
+ * url = "https://example.com/app.apk"
+ * }
+ * ```
+ */
+inline fun directDownload(block: DirectDownloadListItemBuilder.() -> Unit): DirectDownloadListItem =
+ DirectDownloadListItemBuilder().apply(block).build()
+
+/**
+ * DSL builder for constructing an [UpdaterDialogData] for the Compose updater.
+ *
+ * Example usage:
+ * ```
+ * val dialogData = updaterDialogData {
+ * dialogTitle = "New Update Available"
+ * dialogDescription = "Version 2.0 is ready"
+ * storeList = listOf(
+ * store {
+ * store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "com.example.app")
+ * title = "Google Play"
+ * }
+ * )
+ * theme = Theme.SYSTEM_DEFAULT
+ * }
+ * ```
+ */
+inline fun updaterDialogData(block: UpdaterDialogDataBuilder.() -> Unit): UpdaterDialogData =
+ UpdaterDialogDataBuilder().apply(block).build()
diff --git a/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterScreen.kt b/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterScreen.kt
index 6fd6c0b0..5fb32915 100644
--- a/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterScreen.kt
+++ b/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterScreen.kt
@@ -26,7 +26,7 @@ import com.pouyaheydari.appupdater.compose.ui.utils.previewDirectDownloadListDat
import com.pouyaheydari.appupdater.compose.ui.utils.previewStoreListData
import com.pouyaheydari.appupdater.core.model.Theme
import com.pouyaheydari.appupdater.core.utils.ANDROID_APP_UPDATER_DEBUG_TAG
-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.store.domain.AppStoreCallback
import com.pouyaheydari.appupdater.store.domain.showAppInSelectedStore
@@ -35,9 +35,10 @@ import java.io.File
import com.pouyaheydari.appupdater.directdownload.R as directDownloadR
/**
- * Use this composable to show the updater dialog.
+ * Entry-point composable for showing the Android App Updater dialog.
*
- * @param dialogData is th
+ * @param dialogData configuration for the updater dialog including title, description,
+ * store list, direct download links, theme, and callbacks
*/
@Composable
fun AndroidAppUpdater(dialogData: UpdaterDialogData) {
@@ -143,7 +144,7 @@ private fun setupDirectApkDownload(
url = url,
notificationTitle = notificationTitle,
notificationDescription = notificationDescription,
- onDownloadingApkStarted = onDownloadingApkStarted
+ onDownloadingApkStarted = onDownloadingApkStarted,
)
onDownloadApkRequested()
}
@@ -154,7 +155,7 @@ private fun getApkIfActivityIsNotNull(
url: String,
notificationTitle: String,
notificationDescription: String,
- onDownloadingApkStarted: () -> Unit
+ onDownloadingApkStarted: () -> Unit,
) {
if (activity == null) {
Log.e(ANDROID_APP_UPDATER_DEBUG_TAG, "Provided activity is null. Skipping downloading the apk")
@@ -166,7 +167,7 @@ private fun getApkIfActivityIsNotNull(
notificationTitle = notificationTitle,
notificationDescription = notificationDescription,
downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager,
- onDownloadingApkStarted = onDownloadingApkStarted
+ onDownloadingApkStarted = onDownloadingApkStarted,
)
}
}
diff --git a/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterViewModel.kt b/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterViewModel.kt
index e87c62b7..ae531f5b 100644
--- a/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterViewModel.kt
+++ b/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterViewModel.kt
@@ -25,7 +25,7 @@ import kotlinx.coroutines.launch
internal class AndroidAppUpdaterViewModel(
viewModelData: UpdaterViewModelData,
private val getDownloadStateUseCase: GetDownloadStateUseCase,
- private val setDownloadStateUseCase: SetDownloadStateUseCase
+ private val setDownloadStateUseCase: SetDownloadStateUseCase,
) : ViewModel() {
private val _uiState = MutableStateFlow(DialogScreenState())
val uiState: StateFlow = _uiState.asStateFlow()
@@ -90,6 +90,9 @@ internal class AndroidAppUpdaterViewModel(
is DownloadState.Downloading ->
_uiState.update { it.copy(downloadState = it.downloadState.copy(shouldShowUpdateInProgress = true)) }
+
+ is DownloadState.Failed ->
+ _uiState.update { it.copy(downloadState = it.downloadState.copy(shouldShowUpdateInProgress = false)) }
}
}
}
diff --git a/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/models/UpdaterDialogData.kt b/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/models/UpdaterDialogData.kt
index df9bbdca..f7b97f0d 100644
--- a/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/models/UpdaterDialogData.kt
+++ b/compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/models/UpdaterDialogData.kt
@@ -6,7 +6,17 @@ import com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem
import com.pouyaheydari.appupdater.store.domain.StoreListItem
/**
- * This model is used to pass data to config the [com.pouyaheydari.appupdater.compose.ui.AndroidAppUpdater]
+ * Configuration data for the [com.pouyaheydari.appupdater.compose.ui.AndroidAppUpdater] composable.
+ *
+ * @property dialogTitle title text shown at the top of the update dialog
+ * @property dialogDescription description text shown below the title
+ * @property dividerText text displayed on the divider between store list and direct download list
+ * @property storeList list of app stores the user can choose to update from
+ * @property directDownloadList list of direct APK download links
+ * @property onDismissRequested callback invoked when the user requests to dismiss the dialog
+ * @property errorWhileOpeningStoreCallback callback invoked with the store name when a store fails to open
+ * @property typeface optional custom [Typeface] applied to all text in the dialog
+ * @property theme the visual [Theme] for the dialog (light, dark, or system default)
*/
data class UpdaterDialogData(
val dialogTitle: String = "",
diff --git a/directdownload/consumer-rules.pro b/directdownload/consumer-rules.pro
new file mode 100644
index 00000000..882ddd26
--- /dev/null
+++ b/directdownload/consumer-rules.pro
@@ -0,0 +1,18 @@
+# AndroidAppUpdater - DirectDownload module consumer ProGuard rules
+# These rules are bundled with the library and applied to consumer projects.
+
+# Keep public API data classes
+-keep class com.pouyaheydari.appupdater.directdownload.data.DirectDownloadListItem { *; }
+-keep class com.pouyaheydari.appupdater.directdownload.domain.DownloadState { *; }
+-keep class com.pouyaheydari.appupdater.directdownload.domain.DownloadState$* { *; }
+
+# Keep Parcelable CREATOR fields
+-keepclassmembers class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
+
+# Keep the BroadcastReceiver declared in the manifest
+-keep class com.pouyaheydari.appupdater.directdownload.receiver.DownloadFinishedReceiver { *; }
+
+# Keep the FileProvider subclass referenced in the manifest
+-keep class com.pouyaheydari.appupdater.directdownload.utils.downloadapk.APKFileProviderImpl { *; }
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/data/DirectDownloadListItem.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/data/DirectDownloadListItem.kt
index c2b12e95..3db25c90 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/data/DirectDownloadListItem.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/data/DirectDownloadListItem.kt
@@ -1,28 +1,16 @@
package com.pouyaheydari.appupdater.directdownload.data
-import android.os.Parcel
import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
-data class DirectDownloadListItem(var title: String = "", var url: String = "") : Parcelable {
- private constructor(parcel: Parcel) : this(
- parcel.readString().orEmpty(),
- parcel.readString().orEmpty(),
- )
-
- override fun describeContents(): Int = 0
-
- override fun writeToParcel(dest: Parcel, flags: Int) {
- dest.writeString(title)
- dest.writeString(url)
- }
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): DirectDownloadListItem {
- return DirectDownloadListItem(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
-}
+/**
+ * Represents a direct download item to be displayed in the updater dialog.
+ *
+ * @property title the display title shown to the user
+ * @property url the direct URL to the APK file
+ */
+@Parcelize
+data class DirectDownloadListItem(
+ val title: String = "",
+ val url: String = "",
+) : Parcelable
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/domain/DownloadState.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/domain/DownloadState.kt
index 33b4b77b..603ff7ab 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/domain/DownloadState.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/domain/DownloadState.kt
@@ -2,7 +2,24 @@ package com.pouyaheydari.appupdater.directdownload.domain
import java.io.File
+/**
+ * Represents the current state of an APK download.
+ */
sealed interface DownloadState {
+ /** The APK is currently being downloaded. */
data object Downloading : DownloadState
- data class Downloaded(val apk: File) : DownloadState
+
+ /** The APK has been downloaded successfully. */
+ data class Downloaded(
+ val apk: File,
+ ) : DownloadState
+
+ /**
+ * The APK download failed.
+ *
+ * @property reason a human-readable description of the failure, or null if unknown
+ */
+ data class Failed(
+ val reason: String? = null,
+ ) : DownloadState
}
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/receiver/DownloadFinishedReceiver.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/receiver/DownloadFinishedReceiver.kt
index abf54c9e..f9f20342 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/receiver/DownloadFinishedReceiver.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/receiver/DownloadFinishedReceiver.kt
@@ -8,7 +8,7 @@ import com.pouyaheydari.appupdater.directdownload.data.UpdateInProgressRepositor
import com.pouyaheydari.appupdater.directdownload.domain.DownloadState
import com.pouyaheydari.appupdater.directdownload.domain.GetRequestIdUseCase
import com.pouyaheydari.appupdater.directdownload.domain.SetDownloadStateUseCase
-import com.pouyaheydari.appupdater.directdownload.utils.donwloadapk.APKFileProviderImpl
+import com.pouyaheydari.appupdater.directdownload.utils.downloadapk.APKFileProviderImpl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKDownloadManager.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManager.kt
similarity index 93%
rename from directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKDownloadManager.kt
rename to directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManager.kt
index c80576ec..336f6ff3 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKDownloadManager.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManager.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.app.DownloadManager
import android.content.Context
@@ -35,7 +35,7 @@ class APKDownloadManager(
uri = url.toUri(),
context = context,
notificationTitle = notificationTitle,
- notificationDescription = notificationDescription
+ notificationDescription = notificationDescription,
)
setRequestIdUseCase(downloadManager.enqueue(downloadManagerRequest))
}
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProvider.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProvider.kt
similarity index 66%
rename from directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProvider.kt
rename to directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProvider.kt
index 9105b95e..8299f670 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProvider.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProvider.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.content.Context
import java.io.File
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProviderImpl.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImpl.kt
similarity index 83%
rename from directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProviderImpl.kt
rename to directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImpl.kt
index 7e9a8b76..e77dcfba 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProviderImpl.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImpl.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.content.Context
import android.os.Environment
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadAPKHelper.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelper.kt
similarity index 86%
rename from directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadAPKHelper.kt
rename to directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelper.kt
index 0b781e11..bf31938c 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadAPKHelper.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelper.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.app.Activity
import android.app.DownloadManager
@@ -14,7 +14,7 @@ fun checkPermissionsAndDownloadApk(
downloadManager: DownloadManager,
downloadAPKPermission: DownloadAPKPermission = DownloadAPKPermissionFactory().getDownloadAPKPermissionHandler(androidSdkVersion),
apkDownloadManager: APKDownloadManager = APKDownloadManager(),
- onDownloadingApkStarted: () -> Unit
+ onDownloadingApkStarted: () -> Unit,
) {
if (downloadAPKPermission.resolvePermissions(activity)) {
apkDownloadManager.deleteExistingAPKAndDownloadNewAPK(
@@ -22,7 +22,7 @@ fun checkPermissionsAndDownloadApk(
context = activity,
notificationTitle = notificationTitle,
notificationDescription = notificationDescription,
- downloadManager = downloadManager
+ downloadManager = downloadManager,
)
onDownloadingApkStarted()
}
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadManagerRequestCreator.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreator.kt
similarity index 93%
rename from directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadManagerRequestCreator.kt
rename to directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreator.kt
index ae4893e2..a6fb6f34 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadManagerRequestCreator.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreator.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.app.DownloadManager
import android.app.DownloadManager.Request.NETWORK_MOBILE
@@ -12,13 +12,12 @@ import com.pouyaheydari.appupdater.core.utils.ANDROID_APP_UPDATER_DEBUG_TAG
import com.pouyaheydari.appupdater.core.utils.APK_NAME
class DownloadManagerRequestCreator {
-
fun create(
uri: Uri,
context: Context,
notificationTitle: String,
notificationDescription: String,
- downloadManagerRequest: DownloadManager.Request = DownloadManager.Request(uri)
+ downloadManagerRequest: DownloadManager.Request = DownloadManager.Request(uri),
): DownloadManager.Request = downloadManagerRequest.apply {
setTitle(notificationTitle)
setDescription(notificationDescription)
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactory.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactory.kt
index 86061885..e448cea2 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactory.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactory.kt
@@ -1,9 +1,8 @@
package com.pouyaheydari.appupdater.directdownload.utils.permission
class DownloadAPKPermissionFactory {
-
fun getDownloadAPKPermissionHandler(androidSdkVersion: Int): DownloadAPKPermission = when {
androidSdkVersion >= android.os.Build.VERSION_CODES.P -> DownloadAPKPermissionForPAndAbove()
- else -> DownloadAPKPermissionForOAndBellow()
+ else -> DownloadAPKPermissionForOAndBelow()
}
}
diff --git a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBellow.kt b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelow.kt
similarity index 78%
rename from directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBellow.kt
rename to directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelow.kt
index dc4d8218..01bbe35e 100644
--- a/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBellow.kt
+++ b/directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelow.kt
@@ -10,16 +10,13 @@ import androidx.core.content.ContextCompat
private const val PERMISSION_REQUEST_CODE = 2000
-internal class DownloadAPKPermissionForOAndBellow : DownloadAPKPermission {
-
+internal class DownloadAPKPermissionForOAndBelow : DownloadAPKPermission {
@RequiresApi(Build.VERSION_CODES.O)
- override fun resolvePermissions(activity: Activity): Boolean {
- return if (isExternalStoragePermissionGranted(activity)) {
- true
- } else {
- getWriteToStoragePermission(activity)
- false
- }
+ override fun resolvePermissions(activity: Activity): Boolean = if (isExternalStoragePermissionGranted(activity)) {
+ true
+ } else {
+ getWriteToStoragePermission(activity)
+ false
}
private fun isExternalStoragePermissionGranted(activity: Activity) =
diff --git a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKDownloadManagerTest.kt b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManagerTest.kt
similarity index 91%
rename from directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKDownloadManagerTest.kt
rename to directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManagerTest.kt
index 7673cc6f..4d0179a2 100644
--- a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKDownloadManagerTest.kt
+++ b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManagerTest.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.app.DownloadManager
import android.content.Context
@@ -16,7 +16,6 @@ import java.io.File
@RunWith(RobolectricTestRunner::class)
class APKDownloadManagerTest {
-
private val downloadManager: DownloadManager = mock()
private val context: Context = mock()
private val downloadManagerRequestCreator: DownloadManagerRequestCreator = mock()
@@ -31,7 +30,7 @@ class APKDownloadManagerTest {
apkDownloadManager = APKDownloadManager(
downloadManagerRequestCreator,
setRequestIdUseCase,
- apkFileProvider
+ apkFileProvider,
)
}
@@ -51,7 +50,7 @@ class APKDownloadManagerTest {
url,
context,
notificationTitle,
- notificationDescription
+ notificationDescription,
)
verify(apkFileProvider).getFile(context)
@@ -72,7 +71,11 @@ class APKDownloadManagerTest {
whenever(downloadManager.enqueue(any())).thenReturn(5678L)
apkDownloadManager.deleteExistingAPKAndDownloadNewAPK(
- downloadManager, url, context, notificationTitle, notificationDescription
+ downloadManager,
+ url,
+ context,
+ notificationTitle,
+ notificationDescription,
)
verify(apkFileProvider).getFile(context)
diff --git a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProviderImplTest.kt b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImplTest.kt
similarity index 94%
rename from directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProviderImplTest.kt
rename to directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImplTest.kt
index a9d88fb2..6dbe7e14 100644
--- a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/APKFileProviderImplTest.kt
+++ b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImplTest.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.content.Context
import android.os.Environment
@@ -14,7 +14,6 @@ import java.io.File
@RunWith(MockitoJUnitRunner::class)
class APKFileProviderImplTest {
-
private val context: Context = mock()
private lateinit var apkFileProvider: APKFileProviderImpl
diff --git a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadAPKHelperKtTest.kt b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelperKtTest.kt
similarity index 93%
rename from directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadAPKHelperKtTest.kt
rename to directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelperKtTest.kt
index 99b367bc..ad04551e 100644
--- a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadAPKHelperKtTest.kt
+++ b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelperKtTest.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.app.Activity
import android.app.DownloadManager
@@ -12,7 +12,6 @@ import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.whenever
class DownloadAPKHelperTest {
-
private val url = "https://example.com/app.apk"
private val notificationTitle = "New Update"
private val notificationDescription = "Downloading latest version"
@@ -36,7 +35,7 @@ class DownloadAPKHelperTest {
downloadManager,
downloadAPKPermission,
apkDownloadManager,
- onDownloadingApkStarted
+ onDownloadingApkStarted,
)
verify(apkDownloadManager).deleteExistingAPKAndDownloadNewAPK(
@@ -62,7 +61,7 @@ class DownloadAPKHelperTest {
downloadManager,
downloadAPKPermission,
apkDownloadManager,
- onDownloadingApkStarted
+ onDownloadingApkStarted,
)
verify(apkDownloadManager, never()).deleteExistingAPKAndDownloadNewAPK(
@@ -70,7 +69,7 @@ class DownloadAPKHelperTest {
anyString(),
anyOrNull(),
anyString(),
- anyOrNull()
+ anyOrNull(),
)
verify(onDownloadingApkStarted, never()).invoke()
}
diff --git a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadManagerRequestCreatorTest.kt b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreatorTest.kt
similarity index 96%
rename from directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadManagerRequestCreatorTest.kt
rename to directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreatorTest.kt
index 87f69c45..2ebbaae3 100644
--- a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/donwloadapk/DownloadManagerRequestCreatorTest.kt
+++ b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreatorTest.kt
@@ -1,4 +1,4 @@
-package com.pouyaheydari.appupdater.directdownload.utils.donwloadapk
+package com.pouyaheydari.appupdater.directdownload.utils.downloadapk
import android.app.DownloadManager
import android.content.Context
@@ -15,7 +15,6 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class DownloadManagerRequestCreatorTest {
-
private val context: Context = mock()
private val downloadManagerRequestCreator: DownloadManagerRequestCreator = DownloadManagerRequestCreator()
diff --git a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactoryTest.kt b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactoryTest.kt
index bf4eacb3..5f8f07fa 100644
--- a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactoryTest.kt
+++ b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactoryTest.kt
@@ -5,7 +5,6 @@ import org.junit.Assert.assertTrue
import org.junit.Test
class DownloadAPKPermissionFactoryTest {
-
private val factory = DownloadAPKPermissionFactory()
@Test
@@ -21,7 +20,7 @@ class DownloadAPKPermissionFactoryTest {
}
@Test
- fun `getDownloadAPKPermissionHandler should return DownloadAPKPermissionForOAndBellow for Android O and below`() {
+ fun `getDownloadAPKPermissionHandler should return DownloadAPKPermissionForOAndBelow for Android O and below`() {
// Arrange
val androidSdkVersion = Build.VERSION_CODES.O
@@ -29,6 +28,6 @@ class DownloadAPKPermissionFactoryTest {
val result = factory.getDownloadAPKPermissionHandler(androidSdkVersion)
// Assert
- assertTrue(result is DownloadAPKPermissionForOAndBellow)
+ assertTrue(result is DownloadAPKPermissionForOAndBelow)
}
}
diff --git a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBellowTest.kt b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelowTest.kt
similarity index 95%
rename from directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBellowTest.kt
rename to directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelowTest.kt
index 7caf0983..cd1af2a0 100644
--- a/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBellowTest.kt
+++ b/directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelowTest.kt
@@ -15,10 +15,9 @@ import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
-class DownloadAPKPermissionForOAndBellowTest {
-
+class DownloadAPKPermissionForOAndBelowTest {
private val mockActivity: Activity = mock()
- private val permissionHandler = DownloadAPKPermissionForOAndBellow()
+ private val permissionHandler = DownloadAPKPermissionForOAndBelow()
@Test
fun `resolvePermissions should return true when permission is granted`() {
@@ -49,7 +48,7 @@ class DownloadAPKPermissionForOAndBellowTest {
ActivityCompat.requestPermissions(
mockActivity,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
- 2000
+ 2000,
)
}
}
diff --git a/gradle.properties b/gradle.properties
index 07bbf1ef..eab04f6f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,7 +6,7 @@ android.nonFinalResIds=true
android.useAndroidX=true
org.gradle.parallel=true
POM_GROUP_ID=com.pouyaheydari.updater
-POM_VERSION=11.0.2
+POM_VERSION=11.1.0
POM_DESCRIPTION=App Updater is an easy-to-use and fully customizable library to show update dialog to users.
POM_URL=https://github.com/HeyPouya/AndroidAppUpdater
POM_SCM_URL=https://github.com/HeyPouya/AndroidAppUpdater
diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties
new file mode 100644
index 00000000..9fed0a0d
--- /dev/null
+++ b/gradle/gradle-daemon-jvm.properties
@@ -0,0 +1,13 @@
+#This file is generated by updateDaemonJvm
+toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
+toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
+toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
+toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/2ddfb13e430f2b3a94c9c937d8d2f67e/redirect
+toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/e7337738591f6120002875ec9a5cf45c/redirect
+toolchainVendor=JETBRAINS
+toolchainVersion=21
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3454cd3e..a156eb29 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,10 +3,10 @@ appVersion = "1000"
compileSdkVersion = "36"
minSdkVersion = "23"
targetSdkVersion = "36"
-agp = "8.13.2"
-kotlin = "2.2.21"
+agp = "9.1.0"
+kotlin = "2.3.20"
appcompat = "1.7.1"
-androidXCore = "1.17.0"
+androidXCore = "1.18.0"
constraintLayout = "2.2.1"
junit4 = "4.13.2"
androidTestJUnit = "1.3.0"
@@ -16,14 +16,14 @@ recyclerView = "1.4.0"
lifecycle = "2.10.0"
coroutines = "1.10.2"
fragment = "1.8.9"
-androidxComposeBom = "2025.12.00"
-composeActivity = "1.12.1"
-mockito = "5.21.0"
-mockitoKotlin = "6.1.0"
-roboelectric = "4.16"
+androidxComposeBom = "2026.03.00"
+composeActivity = "1.13.0"
+mockito = "5.23.0"
+mockitoKotlin = "6.3.0"
+roboelectric = "4.16.1"
turbine = "1.2.1"
uiautomator = "2.3.0"
-mavenPublish = "0.35.0"
+mavenPublish = "0.36.0"
[libraries]
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
@@ -60,8 +60,8 @@ kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-pl
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
-jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
project-androidLibrary = { id = "com.pouyaheydari.androidLibraryPlugin"}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 18f4d987..73eb6c49 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f6da1a18..de9aba68 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -8,6 +8,9 @@ pluginManagement {
gradlePluginPortal()
}
}
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
+}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
diff --git a/store/consumer-rules.pro b/store/consumer-rules.pro
new file mode 100644
index 00000000..7bbe867a
--- /dev/null
+++ b/store/consumer-rules.pro
@@ -0,0 +1,10 @@
+# AndroidAppUpdater - Store module consumer ProGuard rules
+# These rules are bundled with the library and applied to consumer projects.
+
+# Keep all public API classes (domain models, interfaces, enums)
+-keep class com.pouyaheydari.appupdater.store.domain.** { *; }
+
+# Keep Parcelable CREATOR fields for all AppStore implementations
+-keepclassmembers class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/AppStoreCallback.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/AppStoreCallback.kt
index 937c1f0d..ba6ecdec 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/AppStoreCallback.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/AppStoreCallback.kt
@@ -1,9 +1,31 @@
package com.pouyaheydari.appupdater.store.domain
-import android.content.ActivityNotFoundException
import com.pouyaheydari.appupdater.store.domain.stores.AppStore
+/**
+ * Sealed interface representing the result of attempting to open an app store.
+ *
+ * @see [showAppInSelectedStore]
+ */
sealed interface AppStoreCallback {
- data class Success(val store: AppStore) : AppStoreCallback
- data class Failure(val store: AppStore, val exception: ActivityNotFoundException) : AppStoreCallback
+ /**
+ * Indicates the store was opened successfully.
+ *
+ * @property store the [AppStore] that was opened
+ */
+ data class Success(
+ val store: AppStore,
+ ) : AppStoreCallback
+
+ /**
+ * Indicates the store could not be opened.
+ *
+ * @property store the [AppStore] that failed to open
+ * @property exception the exception that caused the failure (e.g. [android.content.ActivityNotFoundException]
+ * when no activity can handle the store intent, or [IllegalStateException] when context is null)
+ */
+ data class Failure(
+ val store: AppStore,
+ val exception: Exception,
+ ) : AppStoreCallback
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreFactory.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreFactory.kt
index d33c3a51..013a917b 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreFactory.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreFactory.kt
@@ -35,7 +35,21 @@ import com.pouyaheydari.appupdater.store.domain.stores.TencentAppStore
import com.pouyaheydari.appupdater.store.domain.stores.VAppStore
import com.pouyaheydari.appupdater.store.domain.stores.ZTEAppCenter
+/**
+ * Factory that creates [AppStore] instances from [AppStoreType] and a package name.
+ *
+ * Example usage:
+ * ```
+ * val store = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, "com.example.app")
+ * ```
+ */
object StoreFactory {
+ /**
+ * Returns an [AppStore] for the given [storeType] configured with [packageName].
+ *
+ * @param storeType the type of store to create
+ * @param packageName the application package name to open in that store
+ */
fun getStore(storeType: AppStoreType, packageName: String): AppStore = when (storeType) {
GOOGLE_PLAY -> GooglePlayStore(packageName)
CAFE_BAZAAR -> CafeBazaarStore(packageName)
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt
index deca4b61..b9b1186b 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt
@@ -3,16 +3,33 @@ package com.pouyaheydari.appupdater.store.domain
import android.content.Intent
import android.net.Uri
+/**
+ * Internal builder for creating store-opening [Intent]s.
+ */
internal object StoreIntentBuilder {
- internal class Builder(private val uriString: String) {
+ /**
+ * Builder for constructing an [Intent] to view a URI, optionally scoped to a specific store package.
+ *
+ * @param uriString the URI string to open (e.g. a market:// or https:// URL)
+ */
+ internal class Builder(
+ private val uriString: String,
+ ) {
private var storePackageName: String? = null
+ /**
+ * Restricts the intent to the given store package.
+ *
+ * @param storePackageName the package name of the store app
+ * @throws IllegalArgumentException if [storePackageName] is blank
+ */
fun withPackage(storePackageName: String): Builder {
- require(storePackageName.isNotBlank()) { "Store's package name most not be empty" }
+ require(storePackageName.isNotBlank()) { "Store's package name must not be empty" }
this.storePackageName = storePackageName
return this
}
+ /** Builds and returns the configured [Intent]. */
fun build(): Intent = Intent(Intent.ACTION_VIEW, Uri.parse(uriString)).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
storePackageName?.takeIf { it.isNotBlank() }?.let { setPackage(it) }
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt
index 7f77b3f9..4c578d6b 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt
@@ -1,38 +1,20 @@
package com.pouyaheydari.appupdater.store.domain
-import android.os.Parcel
import android.os.Parcelable
-import androidx.core.os.ParcelCompat
import com.pouyaheydari.appupdater.store.R
import com.pouyaheydari.appupdater.store.domain.stores.AppStore
-import com.pouyaheydari.appupdater.store.domain.stores.AppStoreType
+import kotlinx.parcelize.Parcelize
+/**
+ * Represents a store item to be displayed in the updater dialog.
+ *
+ * @property store the [AppStore] implementation to open when the user taps this item
+ * @property title the display title shown to the user
+ * @property icon the drawable resource ID for the store icon
+ */
+@Parcelize
data class StoreListItem(
- var store: AppStore = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, ""),
- var title: String = "",
- var icon: Int = R.drawable.appupdater_ic_cloud,
-) : Parcelable {
- private constructor(parcel: Parcel) : this(
- ParcelCompat.readParcelable(parcel, AppStore::class.java.classLoader, AppStore::class.java) ?: StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, ""),
- parcel.readString().orEmpty(),
- parcel.readInt(),
- )
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeParcelable(store, flags)
- parcel.writeString(title)
- parcel.writeInt(icon)
- }
-
- override fun describeContents(): Int = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): StoreListItem {
- return StoreListItem(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
-}
+ val store: AppStore,
+ val title: String = "",
+ val icon: Int = R.drawable.appupdater_ic_cloud,
+) : Parcelable
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreManager.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreManager.kt
index 738510bb..5e96cbad 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreManager.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreManager.kt
@@ -4,10 +4,21 @@ import android.content.ActivityNotFoundException
import android.content.Context
import com.pouyaheydari.appupdater.store.domain.stores.AppStore
+/**
+ * Attempts to open the given [store] in the corresponding app store.
+ *
+ * @param context the Android context used to start the store activity
+ * @param store the [AppStore] to open
+ * @param callback invoked with [AppStoreCallback.Success] on success or [AppStoreCallback.Failure] on failure
+ */
fun showAppInSelectedStore(context: Context?, store: AppStore, callback: (AppStoreCallback) -> Unit) {
+ if (context == null) {
+ callback(AppStoreCallback.Failure(store, IllegalStateException("Context is null, cannot open store")))
+ return
+ }
try {
val intent = store.getIntent()
- context?.startActivity(intent)
+ context.startActivity(intent)
callback(AppStoreCallback.Success(store))
} catch (exception: ActivityNotFoundException) {
callback(AppStoreCallback.Failure(store, exception))
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AmazonAppStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AmazonAppStore.kt
index 6b909701..cf45e3fe 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AmazonAppStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AmazonAppStore.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val AMAZON_APP_STORE_URL = "amzn://apps/android?p="
internal const val AMAZON_PACKAGE = "com.amazon.venezia"
@@ -10,9 +9,10 @@ internal const val AMAZON_PACKAGE = "com.amazon.venezia"
/**
* Opens application's page in [Amazon App Store](https://www.amazon.com/gp/mas/get/amazonapp)
*/
-internal data class AmazonAppStore(val packageName: String) : AppStore {
- private constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class AmazonAppStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() =
StoreIntentBuilder
.Builder("$AMAZON_APP_STORE_URL$packageName")
@@ -22,20 +22,4 @@ internal data class AmazonAppStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.AMAZON_APP_STORE
override fun getUserReadableName(): String = AppStoreType.AMAZON_APP_STORE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): AmazonAppStore {
- return AmazonAppStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStore.kt
index 542e3abe..3e2b6e88 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStore.kt
@@ -3,8 +3,21 @@ package com.pouyaheydari.appupdater.store.domain.stores
import android.content.Intent
import android.os.Parcelable
+/**
+ * Strategy interface for an app store.
+ *
+ * Each implementation represents a specific app store (e.g. Google Play, Cafe Bazaar)
+ * and knows how to build the [Intent] to open an app's page in that store.
+ *
+ * Implementations must be [Parcelable] so they can be passed through Bundle arguments.
+ */
interface AppStore : Parcelable {
+ /** Returns the [Intent] that opens this app's page in the store. */
fun getIntent(): Intent
+
+ /** Returns the [AppStoreType] enum value identifying this store. */
fun getType(): AppStoreType
+
+ /** Returns a human-readable display name for this store (e.g. "Google Play"). */
fun getUserReadableName(): String
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStoreType.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStoreType.kt
index 4d4d9bb8..ad15e49f 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStoreType.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStoreType.kt
@@ -1,6 +1,15 @@
package com.pouyaheydari.appupdater.store.domain.stores
-enum class AppStoreType(internal val userReadableName: String) {
+/**
+ * Enum of all supported app stores.
+ *
+ * Each entry carries a [userReadableName] that can be displayed in the UI.
+ * Use [StoreFactory][com.pouyaheydari.appupdater.store.domain.StoreFactory] to obtain
+ * an [AppStore] instance from a given type.
+ */
+enum class AppStoreType(
+ internal val userReadableName: String,
+) {
GOOGLE_PLAY("Google Play"),
CAFE_BAZAAR("Cafe Bazaar"),
MYKET("Myket"),
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/Aptoide.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/Aptoide.kt
index 221efbbe..555c10aa 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/Aptoide.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/Aptoide.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val APTOIDE_URL = "aptoideinstall://package="
internal const val APTOIDE_PACKAGE = "cm.aptoide.pt"
@@ -10,9 +9,10 @@ internal const val APTOIDE_PACKAGE = "cm.aptoide.pt"
/**
* Opens application's page in [Aptoide App Store](https://en.aptoide.com/)
*/
-internal data class Aptoide(val packageName: String) : AppStore {
- private constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class Aptoide(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$APTOIDE_URL$packageName")
.withPackage(APTOIDE_PACKAGE)
@@ -21,20 +21,4 @@ internal data class Aptoide(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.APTOIDE
override fun getUserReadableName(): String = AppStoreType.APTOIDE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): Aptoide {
- return Aptoide(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/CafeBazaarStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/CafeBazaarStore.kt
index 1e667590..33f382a8 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/CafeBazaarStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/CafeBazaarStore.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val BAZAAR_URL = "bazaar://details?id="
internal const val BAZAAR_PACKAGE = "com.farsitel.bazaar"
@@ -10,9 +9,10 @@ internal const val BAZAAR_PACKAGE = "com.farsitel.bazaar"
/**
* Opens application's page in [CafeBazaar App Store](https://cafebazaar.ir)
*/
-internal data class CafeBazaarStore(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class CafeBazaarStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$BAZAAR_URL$packageName")
.withPackage(BAZAAR_PACKAGE)
@@ -21,20 +21,4 @@ internal data class CafeBazaarStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.CAFE_BAZAAR
override fun getUserReadableName(): String = AppStoreType.CAFE_BAZAAR.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): CafeBazaarStore {
- return CafeBazaarStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/FDroid.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/FDroid.kt
index 0ac8c99c..342ad330 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/FDroid.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/FDroid.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val FDROID_URL = "fdroid.app://details?id="
internal const val FDROID_PACKAGE = "org.fdroid.fdroid"
@@ -10,9 +9,10 @@ internal const val FDROID_PACKAGE = "org.fdroid.fdroid"
/**
* Opens application's page in [F-Droid App Store](https://f-droid.org/)
*/
-internal data class FDroid(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class FDroid(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$FDROID_URL$packageName")
.withPackage(FDROID_PACKAGE)
@@ -21,20 +21,4 @@ internal data class FDroid(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.FDROID
override fun getUserReadableName(): String = AppStoreType.FDROID.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): FDroid {
- return FDroid(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/GooglePlayStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/GooglePlayStore.kt
index 7a83bcae..33205213 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/GooglePlayStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/GooglePlayStore.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val PLAY_URL = "market://details?id="
internal const val PLAY_PACKAGE = "com.android.vending"
@@ -10,9 +9,10 @@ internal const val PLAY_PACKAGE = "com.android.vending"
/**
* Opens application's page in [GooglePlay Store](https://play.google.com)
*/
-internal data class GooglePlayStore(val packageName: String) : AppStore {
- private constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class GooglePlayStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$PLAY_URL$packageName")
.withPackage(PLAY_PACKAGE)
@@ -21,20 +21,4 @@ internal data class GooglePlayStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.GOOGLE_PLAY
override fun getUserReadableName(): String = AppStoreType.GOOGLE_PLAY.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): GooglePlayStore {
- return GooglePlayStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/HuaweiAppGallery.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/HuaweiAppGallery.kt
index f94256fb..a9a3f3ae 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/HuaweiAppGallery.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/HuaweiAppGallery.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val HUAWEI_APP_GALLERY_URL = "appmarket://details?id="
internal const val HUAWEI_APP_GALLERY_PACKAGE = "com.huawei.appmarket"
@@ -10,9 +9,10 @@ internal const val HUAWEI_APP_GALLERY_PACKAGE = "com.huawei.appmarket"
/**
* Opens application's page in [Huawei App Gallery](https://appgallery.huawei.com/)
*/
-internal data class HuaweiAppGallery(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class HuaweiAppGallery(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$HUAWEI_APP_GALLERY_URL$packageName")
.withPackage(HUAWEI_APP_GALLERY_PACKAGE)
@@ -21,20 +21,4 @@ internal data class HuaweiAppGallery(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.HUAWEI_APP_GALLERY
override fun getUserReadableName(): String = AppStoreType.HUAWEI_APP_GALLERY.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): HuaweiAppGallery {
- return HuaweiAppGallery(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/LenovoAppCenter.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/LenovoAppCenter.kt
index 93378076..78c08f36 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/LenovoAppCenter.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/LenovoAppCenter.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val LENOVO_APP_CENTER_URL = "leapp://ptn/appinfo.do?pn="
/**
* Opens application's page in [Lenovo App Store](https://www.lenovomm.com/)
*/
-internal data class LenovoAppCenter(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class LenovoAppCenter(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$LENOVO_APP_CENTER_URL$packageName")
.build()
@@ -19,20 +19,4 @@ internal data class LenovoAppCenter(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.LENOVO_APP_CENTER
override fun getUserReadableName(): String = AppStoreType.LENOVO_APP_CENTER.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): LenovoAppCenter {
- return LenovoAppCenter(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MiGetAppStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MiGetAppStore.kt
index b4c5d318..d9397f59 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MiGetAppStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MiGetAppStore.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val MI_APP_STORE_URL = "mimarket://details?id="
/**
* Opens application's page in [Xiaomi GetApp store](https://global.app.mi.com/)
*/
-internal data class MiGetAppStore(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class MiGetAppStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() =
StoreIntentBuilder
.Builder("$MI_APP_STORE_URL$packageName")
@@ -20,20 +20,4 @@ internal data class MiGetAppStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.MI_GET_APP_STORE
override fun getUserReadableName(): String = AppStoreType.MI_GET_APP_STORE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): MiGetAppStore {
- return MiGetAppStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MyketStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MyketStore.kt
index 873d65dc..10186bb5 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MyketStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MyketStore.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val MYKET_URL = "myket://details?id="
internal const val MYKET_PACKAGE = "ir.mservices.market"
@@ -10,9 +9,10 @@ internal const val MYKET_PACKAGE = "ir.mservices.market"
/**
* Opens application's page in [Myket Store](https://myket.ir/)
*/
-internal data class MyketStore(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class MyketStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$MYKET_URL$packageName")
.withPackage(MYKET_PACKAGE)
@@ -21,20 +21,4 @@ internal data class MyketStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.MYKET
override fun getUserReadableName(): String = AppStoreType.MYKET.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): MyketStore {
- return MyketStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt
index e8f08e06..a30e3fee 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val NINE_APPS_STORE_URL = "nineapps://AppDetail?id="
internal const val NINE_APPS_PACKAGE = "com.gamefun.apk2u"
@@ -10,9 +9,10 @@ internal const val NINE_APPS_PACKAGE = "com.gamefun.apk2u"
/**
* Opens application's page in [9-Apps](https://www.9apps.com/)
*/
-internal data class NineApps(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class NineApps(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$NINE_APPS_STORE_URL$packageName")
.withPackage(NINE_APPS_PACKAGE)
@@ -21,20 +21,4 @@ internal data class NineApps(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.NINE_APPS_STORE
override fun getUserReadableName(): String = AppStoreType.NINE_APPS_STORE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): NineApps {
- return NineApps(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OneStoreAppMarket.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OneStoreAppMarket.kt
index 26b37d23..c52792f8 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OneStoreAppMarket.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OneStoreAppMarket.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val ONE_STORE_APP_MARKET_URL = "onestore://common/product/"
/**
* Opens application's page in [OneStore App Market](https://m.onestore.co.kr/mobilepoc/main/main.omp)
*/
-internal data class OneStoreAppMarket(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class OneStoreAppMarket(
+ val packageName: String,
+) : AppStore {
override fun getIntent() =
StoreIntentBuilder
.Builder("$ONE_STORE_APP_MARKET_URL$packageName")
@@ -20,20 +20,4 @@ internal data class OneStoreAppMarket(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.ONE_STORE_APP_MARKET
override fun getUserReadableName(): String = AppStoreType.ONE_STORE_APP_MARKET.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): OneStoreAppMarket {
- return OneStoreAppMarket(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OppoAppMarket.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OppoAppMarket.kt
index 898721e9..71dcd5ba 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OppoAppMarket.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OppoAppMarket.kt
@@ -1,8 +1,7 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val OPPO_APP_MARKET_URL = "market://details?id="
internal const val OPPO_PACKAGE = "com.heytap.market"
@@ -10,9 +9,10 @@ internal const val OPPO_PACKAGE = "com.heytap.market"
/**
* Opens application's page in [OppoAppMarket](https://oppomobile.com/)
*/
-internal data class OppoAppMarket(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class OppoAppMarket(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$OPPO_APP_MARKET_URL$packageName")
.withPackage(OPPO_PACKAGE)
@@ -21,20 +21,4 @@ internal data class OppoAppMarket(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.OPPO_APP_MARKET
override fun getUserReadableName(): String = AppStoreType.OPPO_APP_MARKET.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): OppoAppMarket {
- return OppoAppMarket(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/SamsungGalaxyStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/SamsungGalaxyStore.kt
index ab049558..9054c17f 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/SamsungGalaxyStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/SamsungGalaxyStore.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val SAMSUNG_GALAXY_STORE_URL = "samsungapps://ProductDetail/"
/**
* Opens application's page in [Samsung Galaxy store](https://www.samsung.com/de/apps/galaxy-store/)
*/
-internal data class SamsungGalaxyStore(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class SamsungGalaxyStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$SAMSUNG_GALAXY_STORE_URL$packageName")
.build()
@@ -19,20 +19,4 @@ internal data class SamsungGalaxyStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.SAMSUNG_GALAXY_STORE
override fun getUserReadableName(): String = AppStoreType.SAMSUNG_GALAXY_STORE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): SamsungGalaxyStore {
- return SamsungGalaxyStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/TencentAppStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/TencentAppStore.kt
index f7768df9..fa3dee6c 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/TencentAppStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/TencentAppStore.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val TENCENT_APP_STORE_URL = "tmast://appdetails?pname="
/**
* Opens application's page in [Tencent App Store](https://appstore.tencent.com/)
*/
-internal data class TencentAppStore(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class TencentAppStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$TENCENT_APP_STORE_URL$packageName")
.build()
@@ -19,20 +19,4 @@ internal data class TencentAppStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.TENCENT_APPS_STORE
override fun getUserReadableName(): String = AppStoreType.TENCENT_APPS_STORE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): TencentAppStore {
- return TencentAppStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/VAppStore.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/VAppStore.kt
index eb99d7cd..b8a65a19 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/VAppStore.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/VAppStore.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val V_APP_STORE_URL = "vivoMarket://details?id="
/**
* Opens application's page in [V-AppStore](https://developer.vivo.com/home)
*/
-internal data class VAppStore(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class VAppStore(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$V_APP_STORE_URL$packageName")
.build()
@@ -19,20 +19,4 @@ internal data class VAppStore(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.V_APP_STORE
override fun getUserReadableName(): String = AppStoreType.V_APP_STORE.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): VAppStore {
- return VAppStore(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}
diff --git a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/ZTEAppCenter.kt b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/ZTEAppCenter.kt
index 830b3245..0fa29739 100644
--- a/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/ZTEAppCenter.kt
+++ b/store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/ZTEAppCenter.kt
@@ -1,17 +1,17 @@
package com.pouyaheydari.appupdater.store.domain.stores
-import android.os.Parcel
-import android.os.Parcelable
import com.pouyaheydari.appupdater.store.domain.StoreIntentBuilder
+import kotlinx.parcelize.Parcelize
internal const val ZTE_APP_CENTER_URL = "zte_market://appdetails?pname="
/**
* Opens application's page in [ZTE App Store](https://apps.ztems.com/)
*/
-internal data class ZTEAppCenter(val packageName: String) : AppStore {
- constructor(parcel: Parcel) : this(parcel.readString().orEmpty())
-
+@Parcelize
+internal data class ZTEAppCenter(
+ val packageName: String,
+) : AppStore {
override fun getIntent() = StoreIntentBuilder
.Builder("$ZTE_APP_CENTER_URL$packageName")
.build()
@@ -19,20 +19,4 @@ internal data class ZTEAppCenter(val packageName: String) : AppStore {
override fun getType(): AppStoreType = AppStoreType.ZTE_APP_CENTER
override fun getUserReadableName(): String = AppStoreType.ZTE_APP_CENTER.userReadableName
-
- override fun writeToParcel(parcel: Parcel, flags: Int) {
- parcel.writeString(packageName)
- }
-
- override fun describeContents() = 0
-
- companion object CREATOR : Parcelable.Creator {
- override fun createFromParcel(parcel: Parcel): ZTEAppCenter {
- return ZTEAppCenter(parcel)
- }
-
- override fun newArray(size: Int): Array {
- return arrayOfNulls(size)
- }
- }
}