diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4cb61aa..23f80b4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,66 +5,36 @@ on: [ push, pull_request ] jobs: build: name: Gradle Build + timeout-minutes: 60 runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v1 - - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }} + uses: actions/checkout@v3 - - name: set up JDK 11 - uses: actions/setup-java@v2 + - name: set up JDK + uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'adopt' + - name: Setup Gradle with cache + uses: gradle/actions/setup-gradle@v3 + - name: Setup Gradle run: chmod +x gradlew - - name: Gradle build - run: bash ./gradlew assembleDebug + - name: Build library + run: bash ./gradlew library:assembleDebug - test: - name: Run Tests and lint checks - runs-on: macos-latest - timeout-minutes: 60 - needs: build + - name: Build sample + run: bash ./gradlew sample:assembleDebug - steps: - - name: Checkout repo - uses: actions/checkout@v2 + - name: Run kltlint in library + run: ./gradlew library:ktlintCheck - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }} - - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' - - - name: Setup Gradle - run: chmod +x gradlew - - - name: Run kltlint - run: ./gradlew ktlintCheck + - name: Run kltlint in sample + run: ./gradlew sample:ktlintCheck - name: Unit tests - run: ./gradlew test --stacktrace - - - name: Upload Reports - uses: actions/upload-artifact@v2 - with: - name: Test-Reports - path: AppUpdateChecker/build/reports - if: always() \ No newline at end of file + run: ./gradlew test --stacktrace \ No newline at end of file diff --git a/README.md b/README.md index 130a7b0..84d8962 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,308 @@ -

-

AppUpdateChecker

-

- An Android library that checks for app updates. -
-

-

+# AppUpdateChecker -![Downloads](https://img.shields.io/github/downloads/Sharkaboi/AppUpdateChecker/total) ![Contributors](https://img.shields.io/github/contributors/Sharkaboi/AppUpdateChecker?color=dark-green) ![Issues](https://img.shields.io/github/issues/Sharkaboi/AppUpdateChecker) ![License](https://img.shields.io/github/license/Sharkaboi/AppUpdateChecker) ![Forks](https://img.shields.io/github/forks/Sharkaboi/AppUpdateChecker?style=social) ![Stargazers](https://img.shields.io/github/stars/Sharkaboi/AppUpdateChecker?style=social) +An Android library that checks for app updates. -## TechStack -* Kotlin -* Coroutines -* Retrofit -* Moshi -* SimpleXML +[![](https://jitpack.io/v/Sharkaboi/AppUpdateChecker.svg)](https://jitpack.io/#Sharkaboi/AppUpdateChecker) ![License](https://img.shields.io/github/license/Sharkaboi/AppUpdateChecker) ![Contributors](https://img.shields.io/github/contributors/Sharkaboi/AppUpdateChecker?color=dark-green) ![Issues](https://img.shields.io/github/issues/Sharkaboi/AppUpdateChecker) ![Forks](https://img.shields.io/github/forks/Sharkaboi/AppUpdateChecker?style=social) ![Stargazers](https://img.shields.io/github/stars/Sharkaboi/AppUpdateChecker?style=social) ## Instructions -* Add Jitpack to your project +- Add Jitpack to your project ```groovy -allprojects { - repositories { - ... - maven { url 'https://jitpack.io' } - } +repositories { + ... + maven { url 'https://jitpack.io' } } ``` -* Add Dependency -[![](https://jitpack.io/v/Sharkaboi/AppUpdateChecker.svg)](https://jitpack.io/#Sharkaboi/AppUpdateChecker) +- Add + Dependency [![](https://jitpack.io/v/Sharkaboi/AppUpdateChecker.svg)](https://jitpack.io/#Sharkaboi/AppUpdateChecker) + ```groovy dependencies { - implementation 'com.github.Sharkaboi:AppUpdateChecker:' + implementation 'com.github.Sharkaboi:AppUpdateChecker:' } ``` ## Usage -* Initialize update checker instance +### Initialize update checker instance + ```kotlin val updateChecker = AppUpdateChecker( - context = context, - // Any of the sources mentioned below - source = AppUpdateCheckerSource.*(), - // Optional : Takes versionName mentioned in gradle by default - currentVersionTag = "v1.0.0" + // Any of the sources mentioned below + source = >( + ... // params, + currentVersion = "v1.0.0" + ) ) ``` + This is so that you can instantiate with your existing DI setup. -* Available sources : +### Available update check sources + +#### Github release tags + +##### Using github public API + +Github public API has rate limit as +mentioned [here](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api). + ```kotlin -// Github source -AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "AppUpdateChecker" +GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "AppUpdateChecker", + currentVersion = "v1.0.0" ) +``` + +##### Using github authenticated API + +Github authenticated API has higher rate limit. Check +github's [docs](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#getting-a-higher-rate-limit) +to obtain a token. -// FDroid source -AppUpdateCheckerSource.FDroidSource( - // Optional : Takes packageName from context by default - packageName = "com.sharkaboi.appupdatechecker" +```kotlin +GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "AppUpdateChecker", + currentVersion = "v1.0.0", + bearerToken = "" ) +``` + +#### FDroid -// JSON source -AppUpdateCheckerSource.JsonSource( - // JSON structure mentioned below - jsonEndpoint = "https://mywebsite.com/version.json" +##### With version name + +```kotlin +FDroidVersionNameSource( + packageName = "com.sharkaboi.appupdatechecker", + currentVersion = "v1.0.0" ) +``` + +##### With version code -// XML source -AppUpdateCheckerSource.XMLSource( - // XML structure mentioned below - xmlEndpoint = "https://mywebsite.com/version.xml" +```kotlin +FDroidVersionCodeSource( + packageName = "com.sharkaboi.appupdatechecker", + currentVersion = 10100 ) ``` -JSON structure : + +#### JSON + +##### With version name + +```kotlin +JsonVersionNameSource( + jsonEndpoint = "https://mywebsite.com/version.json", + currentVersion = "v1.0.0" +) +``` + +JSON structure supported by default : + ```javascript { - "latestVersion": "v1.2", + "latestVersionName": "v1.2", "latestVersionUrl": "https://mywebsite.com/v1.2", "releaseNotes": "My epic changelog" } ``` -XML structure : + +##### With version code + +```kotlin +JsonVersionCodeSource( + jsonEndpoint = "https://mywebsite.com/version.json", + currentVersion = 10100 +) +``` + +JSON structure supported by default : + +```javascript +{ + "latestVersionCode": 10102, + "latestVersionUrl": "https://mywebsite.com/v1.2", + "releaseNotes": "My epic changelog" +} +``` + +#### XML + +##### With version name + +```kotlin +XMLVersionNameSource( + xmlEndpoint = "https://mywebsite.com/version.xml", + currentVersion = "v1.0.0" +) +``` + +XML structure supported by default : + +```xml + + + v1.2 + https://mywebsite.com/v1.2 + My epic changelog + +``` + +##### With version code + +```kotlin +XMLVersionCodeSource( + xmlEndpoint = "https://mywebsite.com/version.xml", + currentVersion = 10100 +) +``` + +XML structure supported by default : + ```xml + - v1.2 - https://mywebsite.com/v1.2 - My epic changelog + 10102 + https://mywebsite.com/v1.2 + My epic changelog ``` -* Check update +#### Custom source + +```kotlin +class CustomVersionSource( + override val currentVersion: String, + override var versionComparator: VersionComparator = DefaultStringVersionComparator +) : AppUpdateCheckerSource() { + private val customSource = "https://mywebsite.com/latestVersion" + + override suspend fun queryVersionDetails(): VersionDetails { + // Do your processing to fetch from source here + ... + // Handle exceptions if needed here, wrap with AppUpdateCheckerException if needed + return VersionDetails( + latestVersion = version, + latestVersionUrl = "https://mywebsite.com/download.apk", + releaseNotes = null + ) + } +} +``` + +### Check for update + ```kotlin viewModelScope.launch { - val result = updateChecker.checkUpdate() - - //or - - val deferred = updateChecker.checkUpdateAsync() - val result = deferred.await() - - when(result){ - // Update found - is UpdateState.UpdateAvailable -> - - // Already has the latest version installed - UpdateState.LatestVersionInstalled -> - - // Mentioned package name not found in FDroid - UpdateState.FDroidInvalid -> - - // Mentioned package name was invalid - UpdateState.FDroidMalformed -> - - // Mentioned repo with username not found in Github - UpdateState.GithubInvalid -> - - // Mentioned repo or username was invalid - UpdateState.GithubMalformed -> - - // Mentioned JSON endpoint had invalid structure or wasn't reachable - UpdateState.JSONInvalid -> - - // Mentioned JSON endpoint was not a valid URL - UpdateState.JSONMalformed -> - - // Mentioned XML endpoint had invalid structure or wasn't reachable - UpdateState.XMLInvalid -> - - // Mentioned XML endpoint was not a valid URL - UpdateState.XMLMalformed -> - - // No network found - UpdateState.NoNetworkFound -> - - // Generic error to wrap other errors - is UpdateState.GenericError -> + val result = updateChecker.checkUpdate() + + // or + + val deferred = updateChecker.checkUpdateAsync() + val result = deferred.await() + + when (result) { + // Update found + is UpdateResult.NoUpdate -> println(result.versionDetails.toString()) + // Already has the latest version installed + UpdateState.NoUpdate ->.. + } +} +``` + +### Error handling + +```kotlin + +if (throwable is AppUpdateCheckerException) { + when (throwable) { + is GenericError -> { + // Generic errors (network, ssl, parse) + } + + is InvalidEndPointException -> { + // Invalid endpoint passed for sources with endpoints (json/xml) } + + is InvalidPackageNameException -> { + // Invalid package name passed + } + + is InvalidRepositoryNameException -> { + // Invalid repository name passed + } + + is InvalidUserNameException -> { + // Invalid username passed + } + + is InvalidVersionException -> { + // Invalid version format for default version name comparator + } + + is PackageNotFoundException -> { + // Remote Service returned 404 + } + + is RemoteError -> { + // Other remote server error code + } + } +} +``` + +### Custom version comparator + +```kotlin +val source = JsonVersionNameSource( + jsonEndpoint = "https://mywebsite.com/version.json", + currentVersion = "v1.0-alpha" +) + +val customVersionComparator = object : VersionComparator { + override fun isNewerVersion( + currentVersion: String, + newVersion: String + ): Boolean { + // Perform custom logic here or can use default comparators provided with library. + return DefaultStringVersionComparator.isNewerVersion( + currentVersion.substringBefore('-'), + newVersion.substringBefore('-') + ) } +} +source.setCustomVersionComparator(customVersionComparator) +val testChecker = AppUpdateChecker(source = source) +val result = testChecker.checkUpdate() +... ``` + +## TechStack + +- Kotlin +- Coroutines +- Retrofit +- Moshi +- SimpleXML + ## Background -This project is heavily inspired by [javiersantos/AppUpdater](https://github.com/javiersantos/AppUpdater). The project had been stale for a while and had been on AsyncTasks so decided to write my own library. +This project is heavily inspired +by [javiersantos/AppUpdater](https://github.com/javiersantos/AppUpdater). The project had been stale +for a while and had been on AsyncTasks so decided to write my own library. Of course, the library is very early stage and doesn't have all the features but is WIP. ## Roadmap -See the [open issues](https://github.com/Sharkaboi/AppUpdateChecker/issues) for a list of proposed features (and known issues). +See the [open issues](https://github.com/Sharkaboi/AppUpdateChecker/issues) for a list of proposed +features (and known issues). ## Contributing @@ -164,15 +316,14 @@ PR's are welcome. Please try to follow the template. 4. Push to the Branch (`git push origin feature/AmazingFeature`) 5. Open a Pull Request - ## Authors -* [Sharkaboi](https://github.com/Sharkaboi) +- [Sharkaboi](https://github.com/Sharkaboi) ## Credits -* [javiersantos](https://github.com/javiersantos) -* [Shields](https://shields.io/) +- [javiersantos](https://github.com/javiersantos) +- [Shields](https://shields.io/) ## License diff --git a/build.gradle b/build.gradle index 6e00895..63dff84 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,4 @@ buildscript { - ext { - agpVersion = "4.2.2" - kotlinVersion = "1.5.30" - ktxCoreVersion = "1.6.0" - appCompatVersion = "1.3.1" - coroutinesVersion = "1.5.2" - retrofitVersion = "2.9.0" - moshiVersion = "1.12.0" - moshiRetrofitVersion = "2.9.0" - simpleXmlVersion = "2.9.0" - ktLintVersion = "10.1.0" - coroutineCallAdapterVersion = "0.9.2" - jUnitVersion = "4.13.2" - mockkVersion = "1.12.0" - } repositories { google() mavenCentral() @@ -21,10 +6,34 @@ buildscript { url "https://plugins.gradle.org/m2/" } } + dependencies { - classpath "com.android.tools.build:gradle:$agpVersion" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "org.jlleitschuh.gradle:ktlint-gradle:$ktLintVersion" + classpath "com.android.tools.build:gradle:8.2.2" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23" + } +} + +plugins { + id 'com.google.devtools.ksp' version '1.9.23-1.0.19' apply false + id "org.jlleitschuh.gradle.ktlint" version "12.1.0" apply false +} + +subprojects { + apply plugin: "org.jlleitschuh.gradle.ktlint" + + ktlint { + version.set("1.2.1") + android = true + outputToConsole = true + disabledRules = ["no-wildcard-imports"] + filter { + exclude { element -> + element.file.path.contains("/test/") + || element.file.path.contains("\\test\\") + || element.file.path.contains("/androidTest/") + || element.file.path.contains("\\androidTest\\") + } + } } } @@ -37,8 +46,4 @@ allprojects { } maven { url "https://jitpack.io" } } -} - -task clean(type: Delete) { - delete rootProject.buildDir } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 30a925a..44c1065 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Sep 12 11:25:08 IST 2021 +#Sat Mar 23 21:51:53 IST 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/library/build.gradle b/library/build.gradle index 113246f..5621924 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,79 +1,110 @@ plugins { id 'com.android.library' id 'kotlin-android' - id 'kotlin-kapt' - id 'org.jlleitschuh.gradle.ktlint' + id 'com.google.devtools.ksp' id 'maven-publish' } -group = 'com.github.Sharkaboi' -version = 'v1.0.2' - -repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - maven { url "https://jitpack.io" } +project.ext { + group_id = "com.github.sharkaboi" + package_name = "com.github.sharkaboi.appupdatechecker" + artifact_id = "appupdatechecker" + version_name = "1.0.3" + version_code = 10103 + github_url = "com.github.sharkaboi.appupdatechecker" + license_url = "https://opensource.org/license/mit" + license_name = "MIT License" + dev_name = "sharkaboi" + dev_email = "cybersharkbusiness@gmail.com" + lib_description = "An android library that checks for app updates." } -android { - compileSdkVersion 31 +group = project.ext.group_id +version = project.ext.version_name +android { defaultConfig { - minSdkVersion 21 - targetSdkVersion 31 + namespace project.ext.package_name + minSdk 19 + compileSdk 34 + targetSdkVersion 34 versionCode 1 - versionName "1.0.2" + versionName project.ext.version_name testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" + freeCompilerArgs = ["-Xstring-concat=inline"] } } dependencies { - implementation "androidx.core:core-ktx:$ktxCoreVersion" - implementation "androidx.appcompat:appcompat:$appCompatVersion" - api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" - api "com.squareup.retrofit2:retrofit:$retrofitVersion" - api "com.squareup.moshi:moshi-kotlin:$moshiVersion" - kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" - api "com.squareup.retrofit2:converter-moshi:$moshiRetrofitVersion" - api "com.squareup.retrofit2:converter-simplexml:$simpleXmlVersion" - api "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$coroutineCallAdapterVersion" - testImplementation "junit:junit:$jUnitVersion" - testImplementation "io.mockk:mockk:$mockkVersion" -} + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0" + def moshiVersion = '1.15.1' + implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion" + ksp "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" + def retrofitVersion = '2.10.0' + implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" + implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion" + implementation "com.squareup.retrofit2:converter-simplexml:$retrofitVersion" -ktlint { - disabledRules.set(["no-wildcard-imports"]) + // For unit test + testImplementation "com.squareup.retrofit2:converter-scalars:$retrofitVersion" + testImplementation "junit:junit:4.13.2" + testImplementation "io.mockk:mockk:1.13.10" } afterEvaluate { publishing { publications { - // Creates a Maven publication called "release". - release(MavenPublication) { + + def pomConfig = { + licenses { + license { + name project.ext.license_name + url project.ext.license_url + distribution "repo" + } + } + developers { + developer { + id project.ext.dev_name + name project.ext.dev_name + email project.ext.dev_email + } + } + + scm { + url project.ext.github_url + } + } + + mavenPublication(MavenPublication) { from components.release - groupId = 'com.github.Sharkaboi' - artifactId = 'AppUpdateChecker' - version = 'v1.0.2' + groupId = project.ext.group_id + artifactId = project.ext.artifact_id + version = project.ext.version_name + pom.withXml { + def root = asNode(null) + root.appendNode('description', project.ext.lib_description) + root.appendNode('name', project.ext.artifact_id) + root.appendNode('url', project.ext.github_url) + root.children().last() + pomConfig + } } } } diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro index e69de29..ebb610c 100644 --- a/library/consumer-rules.pro +++ b/library/consumer-rules.pro @@ -0,0 +1,6 @@ +-dontwarn org.xmlpull.v1.** +-dontwarn org.kxml2.io.** +-dontwarn android.content.res.** + +-keep class org.xmlpull.** { *; } +-keepclassmembers class org.xmlpull.** { *; } \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 227dea5..a880029 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1,6 +1,4 @@ - - - + + \ No newline at end of file diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/AppUpdateChecker.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/AppUpdateChecker.kt index 51b9eb3..bddb2bd 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/AppUpdateChecker.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/AppUpdateChecker.kt @@ -1,133 +1,25 @@ package com.sharkaboi.appupdatechecker -import android.content.Context -import com.sharkaboi.appupdatechecker.extensions.* -import com.sharkaboi.appupdatechecker.interfaces.IAppUpdateChecker -import com.sharkaboi.appupdatechecker.mappers.toUpdateAvailableState -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import com.sharkaboi.appupdatechecker.provider.AppUpdateServices -import com.sharkaboi.appupdatechecker.sources.fdroid.isAfterVersion -import kotlinx.coroutines.* - -class AppUpdateChecker( - private val context: Context, - private val source: AppUpdateCheckerSource, - private val currentVersionTag: String = context.installedVersionTag -) : IAppUpdateChecker { - +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.AppUpdateCheckerSource +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext + +class AppUpdateChecker(private val source: AppUpdateCheckerSource) { private val dispatcher: CoroutineDispatcher = Dispatchers.IO - override suspend fun checkUpdate(): UpdateState = withContext(dispatcher) { - return@withContext checkUpdateAsync().await() - } - - override suspend fun checkUpdateAsync(): Deferred = withContext(dispatcher) { - return@withContext async { - try { - if (!context.isInternetConnected) { - return@async UpdateState.NoNetworkFound - } - require(currentVersionTag.matches(versionRegex)) { "Invalid current version tag" } - return@async when (source) { - is AppUpdateCheckerSource.FDroidSource -> handleFDroidCheck() - is AppUpdateCheckerSource.GithubSource -> handleGithubCheck() - is AppUpdateCheckerSource.JsonSource -> handleJsonCheck() - is AppUpdateCheckerSource.XMLSource -> handleXmlCheck() - } - } catch (e: Exception) { - e.printStackTrace() - return@async UpdateState.GenericError(e) - } - } - } - - private suspend fun handleFDroidCheck(): UpdateState { - require(source is AppUpdateCheckerSource.FDroidSource) { "Invalid source" } - val packageName = source.packageName ?: context.packageName - val modifiedSource = source.copy( - packageName = packageName - ) - if (modifiedSource.isValid()) { - return try { - val release = AppUpdateServices.fDroidService.getReleasesAsync( - packageName = packageName - ).await() - if (release.isAfterVersion(currentVersionTag)) { - release.toUpdateAvailableState(modifiedSource) - } else { - UpdateState.LatestVersionInstalled - } - } catch (e: Exception) { - e.printStackTrace() - UpdateState.FDroidInvalid - } - } else { - return UpdateState.FDroidMalformed - } - } - - private suspend fun handleGithubCheck(): UpdateState { - require(source is AppUpdateCheckerSource.GithubSource) { "Invalid source" } - if (source.isValid()) { - return try { - val release = AppUpdateServices.githubService.getLatestReleaseAsync( - owner = source.ownerUsername, - repo = source.repoName - ).await() - if (release.tagName.isAfterVersion(currentVersionTag)) { - release.toUpdateAvailableState(source) - } else { - UpdateState.LatestVersionInstalled - } - } catch (e: Exception) { - e.printStackTrace() - UpdateState.GithubInvalid - } - } else { - return UpdateState.GithubMalformed - } - } - - private suspend fun handleJsonCheck(): UpdateState { - require(source is AppUpdateCheckerSource.JsonSource) { "Invalid source" } - if (source.isValid()) { - return try { - val release = AppUpdateServices.jsonService.getJsonReleaseMetaDataAsync( - url = source.jsonEndpoint - ).await() - if (release.latestVersion.isAfterVersion(currentVersionTag)) { - release.toUpdateAvailableState(source) - } else { - UpdateState.LatestVersionInstalled - } - } catch (e: Exception) { - e.printStackTrace() - UpdateState.JSONInvalid - } - } else { - return UpdateState.JSONMalformed + suspend fun checkUpdate(): UpdateResult = + withContext(dispatcher) { + return@withContext checkUpdateAsync().await() } - } - private suspend fun handleXmlCheck(): UpdateState { - require(source is AppUpdateCheckerSource.XMLSource) { "Invalid source" } - if (source.isValid()) { - return try { - val release = AppUpdateServices.xmlService.getXMLReleaseMetaDataAsync( - url = source.xmlEndpoint - ).await() - if (release.latestVersion.isAfterVersion(currentVersionTag)) { - release.toUpdateAvailableState(source) - } else { - UpdateState.LatestVersionInstalled - } - } catch (e: Exception) { - e.printStackTrace() - UpdateState.XMLInvalid + suspend fun checkUpdateAsync(): Deferred = + withContext(dispatcher) { + return@withContext async { + return@async source.getUpdateState() } - } else { - return UpdateState.XMLMalformed } - } } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/ContextExtensions.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/ContextExtensions.kt deleted file mode 100644 index a53afa9..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/ContextExtensions.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.sharkaboi.appupdatechecker.extensions - -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import android.os.Build - -internal val Context.isInternetConnected: Boolean - get() { - val connectivityManager = - this.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager - return connectivityManager?.let { cm -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - cm.activeNetwork?.let { an -> - val networkCapabilities = cm.getNetworkCapabilities(an) - networkCapabilities?.let { nc -> - nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || - nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) - } - } - } else { - val netInfo = cm.activeNetworkInfo - netInfo != null && netInfo.isConnectedOrConnecting - } - } ?: false - } - -internal val Context.installedVersionTag: String - get() { - return this.packageManager.getPackageInfo(this.packageName, 0).versionName - } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/StringExtensions.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/StringExtensions.kt deleted file mode 100644 index 4315a57..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/StringExtensions.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.sharkaboi.appupdatechecker.extensions - -internal fun String.isAfterVersion(other: String): Boolean { - val thisVersionTrimmed = this.trim() - val otherVersionTrimmed = other.trim() - require(thisVersionTrimmed.matches(versionRegex)) { "Current version tag is invalid" } - require(otherVersionTrimmed.matches(versionRegex)) { "Incoming version tag is invalid" } - val thisVersion = thisVersionTrimmed.removePrefix("v").removePrefix("V") - val otherVersion = otherVersionTrimmed.removePrefix("v").removePrefix("V") - if (thisVersion == otherVersion) { - return false - } - val thisParts = thisVersion.split(".").map { it.toLong() } - val otherParts = otherVersion.split(".").map { it.toLong() } - val length = thisParts.size.coerceAtLeast(otherParts.size) - for (i in 0 until length) { - val thisPart = if (i < thisParts.size) thisParts[i] else 0 - val otherPart = if (i < otherParts.size) otherParts[i] else 0 - if (thisPart == otherPart) continue - if (thisPart < otherPart) return false - if (thisPart > otherPart) return true - } - return false -} - -// Starts with v or V and followed by matched numbers and dots. -internal val versionRegex = Regex("[v|V]?\\d+(.\\d+)*") diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/ValidationExtensions.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/ValidationExtensions.kt deleted file mode 100644 index a6bcc58..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/extensions/ValidationExtensions.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.sharkaboi.appupdatechecker.extensions - -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import okhttp3.HttpUrl - -internal fun AppUpdateCheckerSource.GithubSource.isValid(): Boolean { - return ownerUsername.isNotBlank() && repoName.isNotBlank() -} - -internal fun AppUpdateCheckerSource.JsonSource.isValid(): Boolean { - return HttpUrl.parse(jsonEndpoint) != null && jsonEndpoint.isNotBlank() -} - -internal fun AppUpdateCheckerSource.XMLSource.isValid(): Boolean { - return HttpUrl.parse(xmlEndpoint) != null && xmlEndpoint.isNotBlank() -} - -internal fun AppUpdateCheckerSource.FDroidSource.isValid(): Boolean { - return packageName != null && packageName.isNotBlank() && packageName.contains('.') -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/interfaces/IAppUpdateChecker.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/interfaces/IAppUpdateChecker.kt deleted file mode 100644 index e87a1df..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/interfaces/IAppUpdateChecker.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.sharkaboi.appupdatechecker.interfaces - -import com.sharkaboi.appupdatechecker.models.UpdateState -import kotlinx.coroutines.Deferred - -internal interface IAppUpdateChecker { - suspend fun checkUpdate(): UpdateState - suspend fun checkUpdateAsync(): Deferred -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/mappers/UpdateStateMappers.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/mappers/UpdateStateMappers.kt deleted file mode 100644 index 90bd46b..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/mappers/UpdateStateMappers.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.sharkaboi.appupdatechecker.mappers - -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import com.sharkaboi.appupdatechecker.sources.fdroid.FdroidConstants -import com.sharkaboi.appupdatechecker.sources.fdroid.FdroidResponse -import com.sharkaboi.appupdatechecker.sources.github.GithubResponse -import com.sharkaboi.appupdatechecker.sources.json.JsonResponse -import com.sharkaboi.appupdatechecker.sources.xml.XMLResponse - -internal fun GithubResponse.toUpdateAvailableState(source: AppUpdateCheckerSource): UpdateState.UpdateAvailable { - return UpdateState.UpdateAvailable( - releaseNotes = body, - latestVersionUrl = htmlUrl, - latestVersion = tagName, - source = source - ) -} - -internal fun JsonResponse.toUpdateAvailableState(source: AppUpdateCheckerSource): UpdateState.UpdateAvailable { - return UpdateState.UpdateAvailable( - releaseNotes = releaseNotes, - latestVersionUrl = latestVersionUrl, - latestVersion = latestVersion, - source = source - ) -} - -internal fun FdroidResponse.toUpdateAvailableState(source: AppUpdateCheckerSource): UpdateState.UpdateAvailable { - return UpdateState.UpdateAvailable( - releaseNotes = null, - latestVersionUrl = FdroidConstants.HTML_BASE_URL + this.packageName, - latestVersion = this.packages.first().versionName, - source = source - ) -} - -internal fun XMLResponse.toUpdateAvailableState(source: AppUpdateCheckerSource): UpdateState.UpdateAvailable { - return UpdateState.UpdateAvailable( - releaseNotes = this.releaseNotes, - latestVersionUrl = this.latestVersionUrl, - latestVersion = this.latestVersion, - source = source - ) -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/models/AppUpdateCheckerSource.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/models/AppUpdateCheckerSource.kt deleted file mode 100644 index c0f2901..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/models/AppUpdateCheckerSource.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.sharkaboi.appupdatechecker.models - -sealed class AppUpdateCheckerSource { - - data class GithubSource( - val ownerUsername: String, - val repoName: String - ) : AppUpdateCheckerSource() - - data class FDroidSource( - val packageName: String? = null - ) : AppUpdateCheckerSource() - - data class JsonSource( - val jsonEndpoint: String - ) : AppUpdateCheckerSource() - - data class XMLSource( - val xmlEndpoint: String - ) : AppUpdateCheckerSource() -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/models/Exceptions.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/models/Exceptions.kt new file mode 100644 index 0000000..689ebff --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/models/Exceptions.kt @@ -0,0 +1,21 @@ +package com.sharkaboi.appupdatechecker.models + +sealed interface AppUpdateCheckerException + +class InvalidVersionException(message: String) : Exception(message), AppUpdateCheckerException + +class InvalidPackageNameException(message: String) : Exception(message), AppUpdateCheckerException + +class PackageNotFoundException(message: String) : Exception(message), AppUpdateCheckerException + +class InvalidUserNameException(message: String) : Exception(message), AppUpdateCheckerException + +class InvalidRepositoryNameException(message: String) : + Exception(message), + AppUpdateCheckerException + +class InvalidEndPointException(message: String) : Exception(message), AppUpdateCheckerException + +class RemoteError(throwable: Throwable) : Exception(throwable), AppUpdateCheckerException + +class GenericError(throwable: Throwable) : Exception(throwable), AppUpdateCheckerException diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/models/UpdateResult.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/models/UpdateResult.kt new file mode 100644 index 0000000..d504889 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/models/UpdateResult.kt @@ -0,0 +1,9 @@ +package com.sharkaboi.appupdatechecker.models + +sealed interface UpdateResult { + data class UpdateAvailable( + val versionDetails: VersionDetails, + ) : UpdateResult + + data object NoUpdate : UpdateResult +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/models/UpdateState.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/models/UpdateState.kt deleted file mode 100644 index a321d1e..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/models/UpdateState.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.sharkaboi.appupdatechecker.models - -sealed class UpdateState { - - data class UpdateAvailable( - val latestVersion: String, - val latestVersionUrl: String, - val releaseNotes: String?, - val source: AppUpdateCheckerSource - ) : UpdateState() - - // Latest version already installed - object LatestVersionInstalled : UpdateState() - - // GitHub user or repo is empty string - object GithubMalformed : UpdateState() - - // GitHub repo is private or no releases found of matching type - object GithubInvalid : UpdateState() - - // FDroid package name not provided or empty or does not contain '.' - object FDroidMalformed : UpdateState() - - // Package not found in FDroid - object FDroidInvalid : UpdateState() - - // No Internet connection available - object NoNetworkFound : UpdateState() - - // URL for the XML file is not valid - object XMLMalformed : UpdateState() - - // XML file is invalid or is unreachable - object XMLInvalid : UpdateState() - - // URL for the JSON file is not valid - object JSONMalformed : UpdateState() - - // URL for the JSON file is not valid - object JSONInvalid : UpdateState() - - // Generic UpdateState type to handle other UpdateStates - data class GenericError( - val exception: Exception - ) : UpdateState() -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/models/VersionDetails.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/models/VersionDetails.kt new file mode 100644 index 0000000..53e92fb --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/models/VersionDetails.kt @@ -0,0 +1,7 @@ +package com.sharkaboi.appupdatechecker.models + +data class VersionDetails( + val latestVersion: T, + val latestVersionUrl: String, + val releaseNotes: String?, +) diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/provider/AppUpdateServices.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/provider/AppUpdateServices.kt deleted file mode 100644 index 8e6fbf2..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/provider/AppUpdateServices.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.sharkaboi.appupdatechecker.provider - -import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory -import com.sharkaboi.appupdatechecker.sources.fdroid.FdroidConstants -import com.sharkaboi.appupdatechecker.sources.fdroid.FdroidService -import com.sharkaboi.appupdatechecker.sources.github.GithubConstants -import com.sharkaboi.appupdatechecker.sources.github.GithubService -import com.sharkaboi.appupdatechecker.sources.json.JsonConstants -import com.sharkaboi.appupdatechecker.sources.json.JsonService -import com.sharkaboi.appupdatechecker.sources.xml.XMLConstants -import com.sharkaboi.appupdatechecker.sources.xml.XMLService -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import retrofit2.converter.simplexml.SimpleXmlConverterFactory - -internal object AppUpdateServices { - - val fDroidService: FdroidService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { - Retrofit.Builder() - .baseUrl(FdroidConstants.BASE_URL) - .addCallAdapterFactory(CoroutineCallAdapterFactory()) - .addConverterFactory(MoshiConverterFactory.create()) - .build() - .create(FdroidService::class.java) - } - - val jsonService: JsonService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { - Retrofit.Builder() - .baseUrl(JsonConstants.BASE_URL) - .addCallAdapterFactory(CoroutineCallAdapterFactory()) - .addConverterFactory(MoshiConverterFactory.create()) - .build() - .create(JsonService::class.java) - } - - val xmlService: XMLService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { - Retrofit.Builder() - .baseUrl(XMLConstants.BASE_URL) - .addCallAdapterFactory(CoroutineCallAdapterFactory()) - .addConverterFactory(SimpleXmlConverterFactory.create()) - .build() - .create(XMLService::class.java) - } - - val githubService: GithubService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { - Retrofit.Builder() - .baseUrl(GithubConstants.BASE_URL) - .addCallAdapterFactory(CoroutineCallAdapterFactory()) - .addConverterFactory(MoshiConverterFactory.create()) - .build() - .create(GithubService::class.java) - } -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/AppUpdateCheckerSource.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/AppUpdateCheckerSource.kt new file mode 100644 index 0000000..377e39f --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/AppUpdateCheckerSource.kt @@ -0,0 +1,43 @@ +package com.sharkaboi.appupdatechecker.sources + +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.InvalidPackageNameException +import com.sharkaboi.appupdatechecker.models.InvalidRepositoryNameException +import com.sharkaboi.appupdatechecker.models.InvalidUserNameException +import com.sharkaboi.appupdatechecker.models.InvalidVersionException +import com.sharkaboi.appupdatechecker.models.PackageNotFoundException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.models.VersionDetails +import com.sharkaboi.appupdatechecker.versions.VersionComparator + +abstract class AppUpdateCheckerSource { + protected abstract val currentVersion: T + protected abstract var versionComparator: VersionComparator + + protected abstract suspend fun queryVersionDetails(): VersionDetails + + @Throws( + InvalidVersionException::class, + InvalidPackageNameException::class, + PackageNotFoundException::class, + InvalidUserNameException::class, + InvalidRepositoryNameException::class, + InvalidEndPointException::class, + RemoteError::class, + GenericError::class, + ) + suspend fun getUpdateState(): UpdateResult { + val versionDetails = queryVersionDetails() + if (versionComparator.isNewerVersion(currentVersion, versionDetails.latestVersion)) { + return UpdateResult.UpdateAvailable(versionDetails) + } + + return UpdateResult.NoUpdate + } + + fun setCustomVersionComparator(comparator: VersionComparator) { + this.versionComparator = comparator + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidResponse.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidResponse.kt similarity index 70% rename from library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidResponse.kt rename to library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidResponse.kt index ce17c02..5bcbd01 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidResponse.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidResponse.kt @@ -1,23 +1,20 @@ package com.sharkaboi.appupdatechecker.sources.fdroid -import androidx.annotation.Keep import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@Keep @JsonClass(generateAdapter = true) -data class FdroidResponse( +data class FDroidResponse( @Json(name = "packageName") val packageName: String, @Json(name = "packages") - val packages: List + val packages: List, ) { - @Keep @JsonClass(generateAdapter = true) data class Package( @Json(name = "versionCode") - val versionCode: Int, + val versionCode: Long, @Json(name = "versionName") - val versionName: String + val versionName: String, ) } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidService.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidService.kt similarity index 60% rename from library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidService.kt rename to library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidService.kt index 3e2e8b8..98593d1 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidService.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidService.kt @@ -1,13 +1,12 @@ package com.sharkaboi.appupdatechecker.sources.fdroid -import kotlinx.coroutines.Deferred +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path -internal interface FdroidService { - +internal interface FDroidService { @GET(FdroidConstants.PATH) - fun getReleasesAsync( - @Path(FdroidConstants.PACKAGE_PATH_ID) packageName: String - ): Deferred + suspend fun getReleases( + @Path(FdroidConstants.PACKAGE_PATH_ID) packageName: String, + ): Response } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidSource.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidSource.kt new file mode 100644 index 0000000..4341315 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FDroidSource.kt @@ -0,0 +1,82 @@ +package com.sharkaboi.appupdatechecker.sources.fdroid + +import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerException +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidPackageNameException +import com.sharkaboi.appupdatechecker.models.PackageNotFoundException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.VersionDetails +import com.sharkaboi.appupdatechecker.sources.AppUpdateCheckerSource +import com.sharkaboi.appupdatechecker.versions.DefaultStringVersionComparator +import com.sharkaboi.appupdatechecker.versions.DefaultVersionCodeComparator +import com.sharkaboi.appupdatechecker.versions.VersionComparator +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + +sealed class FDroidSource : AppUpdateCheckerSource() { + abstract val packageName: String + + private val service = + Retrofit.Builder() + .baseUrl(FdroidConstants.BASE_URL) + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(FDroidService::class.java) + + protected suspend fun queryResponse(): FDroidResponse.Package { + if (packageName.isBlank()) { + throw InvalidPackageNameException("Invalid package name $packageName") + } + + if (!packageName.contains('.')) { + throw InvalidPackageNameException("Invalid package name $packageName") + } + try { + val response = service.getReleases(packageName = packageName) + if (response.code() == 404) { + throw PackageNotFoundException("No details found for package name $packageName") + } + + val release = + response.body() + ?: throw RemoteError(Throwable(response.errorBody()?.string())) + return release.packages.firstOrNull() + ?: throw PackageNotFoundException("No details found for package name $packageName") + } catch (e: Exception) { + if (e is AppUpdateCheckerException) { + throw e + } + throw GenericError(e) + } + } +} + +data class FDroidVersionNameSource( + override val packageName: String, + override val currentVersion: String, + override var versionComparator: VersionComparator = DefaultStringVersionComparator, +) : FDroidSource() { + override suspend fun queryVersionDetails(): VersionDetails { + val response = queryResponse() + return VersionDetails( + releaseNotes = null, + latestVersionUrl = FdroidConstants.HTML_BASE_URL + this.packageName, + latestVersion = response.versionName, + ) + } +} + +data class FDroidVersionCodeSource( + override val packageName: String, + override val currentVersion: Long, + override var versionComparator: VersionComparator = DefaultVersionCodeComparator, +) : FDroidSource() { + override suspend fun queryVersionDetails(): VersionDetails { + val response = queryResponse() + return VersionDetails( + releaseNotes = null, + latestVersionUrl = FdroidConstants.HTML_BASE_URL + this.packageName, + latestVersion = response.versionCode, + ) + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidExtensions.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidExtensions.kt deleted file mode 100644 index 694de3f..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/fdroid/FdroidExtensions.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.sharkaboi.appupdatechecker.sources.fdroid - -import com.sharkaboi.appupdatechecker.extensions.isAfterVersion - -internal fun FdroidResponse.isAfterVersion(currentVersion: String): Boolean { - require(this.packages.isNotEmpty()) { "No package found in FDroid for $packageName" } - val latestVersion = this.packages.first() - return latestVersion.versionName.isAfterVersion(currentVersion) -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubResponse.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubResponse.kt index cfc0c24..b1e0326 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubResponse.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubResponse.kt @@ -1,10 +1,8 @@ package com.sharkaboi.appupdatechecker.sources.github -import androidx.annotation.Keep import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@Keep @JsonClass(generateAdapter = true) internal data class GithubResponse( @Json(name = "body") @@ -12,5 +10,5 @@ internal data class GithubResponse( @Json(name = "html_url") val htmlUrl: String, @Json(name = "tag_name") - val tagName: String + val tagName: String, ) diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubService.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubService.kt index cc80222..8c22bd3 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubService.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubService.kt @@ -1,14 +1,21 @@ package com.sharkaboi.appupdatechecker.sources.github -import kotlinx.coroutines.Deferred +import retrofit2.Response import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.Path internal interface GithubService { + @GET(GithubConstants.PATH) + suspend fun getLatestRelease( + @Path(GithubConstants.OWNER_PATH_ID) owner: String, + @Path(GithubConstants.REPO_PATH_ID) repo: String, + ): Response @GET(GithubConstants.PATH) - fun getLatestReleaseAsync( + suspend fun getLatestReleaseWithToken( @Path(GithubConstants.OWNER_PATH_ID) owner: String, @Path(GithubConstants.REPO_PATH_ID) repo: String, - ): Deferred + @Header("Authorization") authHeader: String, + ): Response } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubTagSource.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubTagSource.kt new file mode 100644 index 0000000..a0d0456 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/github/GithubTagSource.kt @@ -0,0 +1,69 @@ +package com.sharkaboi.appupdatechecker.sources.github + +import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerException +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidRepositoryNameException +import com.sharkaboi.appupdatechecker.models.InvalidUserNameException +import com.sharkaboi.appupdatechecker.models.PackageNotFoundException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.VersionDetails +import com.sharkaboi.appupdatechecker.sources.AppUpdateCheckerSource +import com.sharkaboi.appupdatechecker.versions.DefaultStringVersionComparator +import com.sharkaboi.appupdatechecker.versions.VersionComparator +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + +data class GithubTagSource( + val ownerUsername: String, + val repoName: String, + val bearerToken: String? = null, + override val currentVersion: String, + override var versionComparator: VersionComparator = DefaultStringVersionComparator, +) : AppUpdateCheckerSource() { + private val service = + Retrofit.Builder() + .baseUrl(GithubConstants.BASE_URL) + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(GithubService::class.java) + + override suspend fun queryVersionDetails(): VersionDetails { + if (ownerUsername.isBlank()) { + throw InvalidUserNameException("Invalid username $ownerUsername") + } + + if (repoName.isBlank()) { + throw InvalidRepositoryNameException("Invalid repository name $repoName") + } + + try { + val response = + if (!bearerToken.isNullOrBlank()) { + service.getLatestReleaseWithToken( + owner = ownerUsername, + repo = repoName, + authHeader = "Bearer $bearerToken", + ) + } else { + service.getLatestRelease(owner = ownerUsername, repo = repoName) + } + + if (response.code() == 404) { + throw PackageNotFoundException("Project not found in github with username $ownerUsername and repo $repoName") + } + val githubResponse = + response.body() ?: throw RemoteError(Throwable(response.errorBody()?.string())) + + return VersionDetails( + releaseNotes = githubResponse.body, + latestVersionUrl = githubResponse.htmlUrl, + latestVersion = githubResponse.tagName, + ) + } catch (e: Exception) { + if (e is AppUpdateCheckerException) { + throw e + } + throw GenericError(e) + } + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonConstants.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonConstants.kt deleted file mode 100644 index 868ab59..0000000 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonConstants.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.sharkaboi.appupdatechecker.sources.json - -internal object JsonConstants { - const val BASE_URL = "https://google.com" -} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonResponse.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonResponse.kt index 3211fc5..8d648e6 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonResponse.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonResponse.kt @@ -1,16 +1,16 @@ package com.sharkaboi.appupdatechecker.sources.json -import androidx.annotation.Keep import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@Keep @JsonClass(generateAdapter = true) data class JsonResponse( - @Json(name = "latestVersion") - val latestVersion: String, + @Json(name = "latestVersionName") + val latestVersionName: String?, + @Json(name = "latestVersionCode") + val latestVersionCode: Long?, @Json(name = "latestVersionUrl") val latestVersionUrl: String, @Json(name = "releaseNotes") - val releaseNotes: String? + val releaseNotes: String?, ) diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonService.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonService.kt index 3ef7db2..112afa1 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonService.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonService.kt @@ -1,12 +1,12 @@ package com.sharkaboi.appupdatechecker.sources.json -import kotlinx.coroutines.Deferred +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Url internal interface JsonService { @GET - fun getJsonReleaseMetaDataAsync( - @Url url: String - ): Deferred + suspend fun getJsonReleaseMetaData( + @Url url: String, + ): Response } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonSource.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonSource.kt new file mode 100644 index 0000000..dad6267 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/json/JsonSource.kt @@ -0,0 +1,79 @@ +package com.sharkaboi.appupdatechecker.sources.json + +import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerException +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.InvalidVersionException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.VersionDetails +import com.sharkaboi.appupdatechecker.sources.AppUpdateCheckerSource +import com.sharkaboi.appupdatechecker.versions.DefaultStringVersionComparator +import com.sharkaboi.appupdatechecker.versions.DefaultVersionCodeComparator +import com.sharkaboi.appupdatechecker.versions.VersionComparator +import okhttp3.HttpUrl +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + +sealed class JsonSource : AppUpdateCheckerSource() { + abstract val jsonEndpoint: String + + private val service = + Retrofit.Builder() + .baseUrl("https://google.com") + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(JsonService::class.java) + + protected suspend fun queryResponse(): JsonResponse { + if (HttpUrl.parse(jsonEndpoint) == null) { + throw InvalidEndPointException("Invalid endpoint $jsonEndpoint") + } + + try { + val response = service.getJsonReleaseMetaData(url = jsonEndpoint) + return response.body() + ?: throw RemoteError(Throwable(response.errorBody()?.string())) + } catch (e: Exception) { + if (e is AppUpdateCheckerException) { + throw e + } + throw GenericError(e) + } + } +} + +data class JsonVersionNameSource( + override val jsonEndpoint: String, + override val currentVersion: String, + override var versionComparator: VersionComparator = DefaultStringVersionComparator, +) : JsonSource() { + override suspend fun queryVersionDetails(): VersionDetails { + val response = queryResponse() + val latestVersion = + response.latestVersionName + ?: throw InvalidVersionException("Version name was not found or null in response") + return VersionDetails( + releaseNotes = response.releaseNotes, + latestVersionUrl = response.latestVersionUrl, + latestVersion = latestVersion, + ) + } +} + +data class JsonVersionCodeSource( + override val jsonEndpoint: String, + override val currentVersion: Long, + override var versionComparator: VersionComparator = DefaultVersionCodeComparator, +) : JsonSource() { + override suspend fun queryVersionDetails(): VersionDetails { + val response = queryResponse() + val latestVersion = + response.latestVersionCode + ?: throw InvalidVersionException("Version code was not found or null in response") + return VersionDetails( + releaseNotes = response.releaseNotes, + latestVersionUrl = response.latestVersionUrl, + latestVersion = latestVersion, + ) + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLResponse.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLResponse.kt index a19d8b1..9ed5213 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLResponse.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLResponse.kt @@ -1,19 +1,20 @@ package com.sharkaboi.appupdatechecker.sources.xml -import androidx.annotation.Keep import org.simpleframework.xml.Element import org.simpleframework.xml.Root -@Keep @Root(name = "version") data class XMLResponse( - @field:Element(name = "latestVersion") - @param:Element(name = "latestVersion") - val latestVersion: String, + @field:Element(name = "latestVersionName", required = false) + @param:Element(name = "latestVersionName", required = false) + val latestVersionName: String?, + @field:Element(name = "latestVersionCode", required = false) + @param:Element(name = "latestVersionCode", required = false) + val latestVersionCode: Long?, @field:Element(name = "latestVersionUrl") @param:Element(name = "latestVersionUrl") val latestVersionUrl: String, @field:Element(name = "releaseNotes") @param:Element(name = "releaseNotes") - val releaseNotes: String? + val releaseNotes: String?, ) diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLService.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLService.kt index f7afc21..14fc2c8 100644 --- a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLService.kt +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLService.kt @@ -1,12 +1,12 @@ package com.sharkaboi.appupdatechecker.sources.xml -import kotlinx.coroutines.Deferred +import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Url interface XMLService { @GET - fun getXMLReleaseMetaDataAsync( - @Url url: String - ): Deferred + suspend fun getXMLReleaseMetaData( + @Url url: String, + ): Response } diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLSource.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLSource.kt new file mode 100644 index 0000000..f5a9cd1 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/sources/xml/XMLSource.kt @@ -0,0 +1,79 @@ +package com.sharkaboi.appupdatechecker.sources.xml + +import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerException +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.InvalidVersionException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.VersionDetails +import com.sharkaboi.appupdatechecker.sources.AppUpdateCheckerSource +import com.sharkaboi.appupdatechecker.versions.DefaultStringVersionComparator +import com.sharkaboi.appupdatechecker.versions.DefaultVersionCodeComparator +import com.sharkaboi.appupdatechecker.versions.VersionComparator +import okhttp3.HttpUrl +import retrofit2.Retrofit +import retrofit2.converter.simplexml.SimpleXmlConverterFactory + +sealed class XMLSource : AppUpdateCheckerSource() { + abstract val xmlEndpoint: String + + private val service = + Retrofit.Builder() + .baseUrl("https://google.com") + .addConverterFactory(SimpleXmlConverterFactory.create()) + .build() + .create(XMLService::class.java) + + protected suspend fun queryResponse(): XMLResponse { + if (HttpUrl.parse(xmlEndpoint) == null) { + throw InvalidEndPointException("Invalid endpoint $xmlEndpoint") + } + + try { + val response = service.getXMLReleaseMetaData(url = xmlEndpoint) + return response.body() + ?: throw RemoteError(Throwable(response.errorBody()?.string())) + } catch (e: Exception) { + if (e is AppUpdateCheckerException) { + throw e + } + throw GenericError(e) + } + } +} + +data class XMLVersionNameSource( + override val xmlEndpoint: String, + override val currentVersion: String, + override var versionComparator: VersionComparator = DefaultStringVersionComparator, +) : XMLSource() { + override suspend fun queryVersionDetails(): VersionDetails { + val response = queryResponse() + val latestVersion = + response.latestVersionName + ?: throw InvalidVersionException("Version name was not found or null in response") + return VersionDetails( + releaseNotes = response.releaseNotes, + latestVersionUrl = response.latestVersionUrl, + latestVersion = latestVersion, + ) + } +} + +data class XMLVersionCodeSource( + override val xmlEndpoint: String, + override val currentVersion: Long, + override var versionComparator: VersionComparator = DefaultVersionCodeComparator, +) : XMLSource() { + override suspend fun queryVersionDetails(): VersionDetails { + val response = queryResponse() + val latestVersion = + response.latestVersionCode + ?: throw InvalidVersionException("Version code was not found or null in response") + return VersionDetails( + releaseNotes = response.releaseNotes, + latestVersionUrl = response.latestVersionUrl, + latestVersion = latestVersion, + ) + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/versions/DefaultStringVersionComparator.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/versions/DefaultStringVersionComparator.kt new file mode 100644 index 0000000..a916a3f --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/versions/DefaultStringVersionComparator.kt @@ -0,0 +1,45 @@ +package com.sharkaboi.appupdatechecker.versions + +import com.sharkaboi.appupdatechecker.models.InvalidVersionException +import kotlin.math.max + +object DefaultStringVersionComparator : VersionComparator { + override fun isNewerVersion( + currentVersion: String, + newVersion: String, + ): Boolean { + val currentVersionSubParts = parseVersion(currentVersion) + val newVersionSubParts = parseVersion(newVersion) + val length = max(currentVersionSubParts.size, newVersionSubParts.size) + + val paddedCurrentVersionParts = padUntil(currentVersionSubParts, length) + val paddedNewVersionParts = padUntil(newVersionSubParts, length) + + if (paddedCurrentVersionParts == paddedNewVersionParts) return false + + for (i in 0 until length) { + val newVersionPart = paddedNewVersionParts[i] + val currentVersionPart = paddedCurrentVersionParts[i] + if (newVersionPart == currentVersionPart) continue + + return newVersionPart > currentVersionPart + } + + return false + } + + private fun padUntil( + list: List, + length: Int, + ): List { + return list + List(length - list.size) { 0L } + } + + private fun parseVersion(version: String): List { + return version + .trimStart('v', 'V', ' ') + .trimEnd(' ') + .split('.') + .map { it.toLongOrNull() ?: throw InvalidVersionException("Invalid version $version") } + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/versions/DefaultVersionCodeComparator.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/versions/DefaultVersionCodeComparator.kt new file mode 100644 index 0000000..f7a21a6 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/versions/DefaultVersionCodeComparator.kt @@ -0,0 +1,16 @@ +package com.sharkaboi.appupdatechecker.versions + +import com.sharkaboi.appupdatechecker.models.InvalidVersionException + +object DefaultVersionCodeComparator : VersionComparator { + override fun isNewerVersion( + currentVersion: Long, + newVersion: Long, + ): Boolean { + if (currentVersion < 0) throw InvalidVersionException("Invalid current version $currentVersion") + + if (newVersion < 0) throw InvalidVersionException("Invalid new version $newVersion") + + return newVersion > currentVersion + } +} diff --git a/library/src/main/java/com/sharkaboi/appupdatechecker/versions/VersionComparator.kt b/library/src/main/java/com/sharkaboi/appupdatechecker/versions/VersionComparator.kt new file mode 100644 index 0000000..616a274 --- /dev/null +++ b/library/src/main/java/com/sharkaboi/appupdatechecker/versions/VersionComparator.kt @@ -0,0 +1,11 @@ +package com.sharkaboi.appupdatechecker.versions + +import com.sharkaboi.appupdatechecker.models.InvalidVersionException + +interface VersionComparator { + @Throws(InvalidVersionException::class) + fun isNewerVersion( + currentVersion: T, + newVersion: T, + ): Boolean +} diff --git a/library/src/test/java/com/github/sharkaboi/appupdatechecker/CustomSourceTest.kt b/library/src/test/java/com/github/sharkaboi/appupdatechecker/CustomSourceTest.kt new file mode 100644 index 0000000..d386682 --- /dev/null +++ b/library/src/test/java/com/github/sharkaboi/appupdatechecker/CustomSourceTest.kt @@ -0,0 +1,83 @@ +package com.github.sharkaboi.appupdatechecker + +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerException +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.models.VersionDetails +import com.sharkaboi.appupdatechecker.sources.AppUpdateCheckerSource +import com.sharkaboi.appupdatechecker.versions.DefaultStringVersionComparator +import com.sharkaboi.appupdatechecker.versions.VersionComparator +import kotlinx.coroutines.runBlocking +import okhttp3.HttpUrl +import org.junit.Assert +import org.junit.Test +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.scalars.ScalarsConverterFactory +import retrofit2.http.GET +import retrofit2.http.Url + +class CustomSourceTest { + class CustomVersionSource( + override val currentVersion: String, + override var versionComparator: VersionComparator = DefaultStringVersionComparator, + ) : AppUpdateCheckerSource() { + private val customSource = + """https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/087bdb3151dc54eda2e7a98e88f1264184972313/custom-source/""" + + interface CustomService { + @GET + suspend fun getReleaseVersion( + @Url url: String, + ): Response + } + + private val service = + Retrofit.Builder() + .baseUrl("https://gist.githubusercontent.com") + .addConverterFactory(ScalarsConverterFactory.create()) + .build() + .create(CustomService::class.java) + + override suspend fun queryVersionDetails(): VersionDetails { + if (HttpUrl.parse(customSource) == null) { + throw InvalidEndPointException("Invalid endpoint $customSource") + } + + try { + val response = service.getReleaseVersion(customSource) + val version = + response.body() + ?: throw RemoteError(Throwable(response.errorBody()?.string())) + return VersionDetails( + latestVersion = version, + latestVersionUrl = "https://mywebsite.com/download.apk", + releaseNotes = null, + ) + } catch (e: Exception) { + if (e is AppUpdateCheckerException) { + throw e + } + throw GenericError(e) + } + } + } + + @Test + fun `Setting custom version comparator returns proper update status`() = + runBlocking { + val testChecker = + AppUpdateChecker( + source = + CustomVersionSource( + currentVersion = "v1.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + Assert.assertTrue(result is UpdateResult.UpdateAvailable<*>) + } +} diff --git a/library/src/test/java/com/github/sharkaboi/appupdatechecker/FDroidTest.kt b/library/src/test/java/com/github/sharkaboi/appupdatechecker/FDroidTest.kt new file mode 100644 index 0000000..55414c2 --- /dev/null +++ b/library/src/test/java/com/github/sharkaboi/appupdatechecker/FDroidTest.kt @@ -0,0 +1,131 @@ +package com.github.sharkaboi.appupdatechecker + +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.InvalidPackageNameException +import com.sharkaboi.appupdatechecker.models.PackageNotFoundException +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.fdroid.FDroidVersionCodeSource +import com.sharkaboi.appupdatechecker.sources.fdroid.FDroidVersionNameSource +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue +import org.junit.Test + +class FDroidTest { + private val packageName = "org.fdroid.fdroid" + + @Test + fun `Checker on older installed version returns new version`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + FDroidVersionNameSource( + packageName = packageName, + currentVersion = "v0.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.UpdateAvailable<*>) + + val versionCodeChecker = + AppUpdateChecker( + source = + FDroidVersionCodeSource( + packageName = packageName, + currentVersion = 0, + ), + ) + val versionCodeResult = versionCodeChecker.checkUpdate() + println(versionCodeResult) + assertTrue(versionCodeResult is UpdateResult.UpdateAvailable<*>) + } + + @Test + fun `Checker on newer installed version returns no update`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + FDroidVersionNameSource( + packageName = packageName, + currentVersion = "v${Long.MAX_VALUE}.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.NoUpdate) + + val versionCodeChecker = + AppUpdateChecker( + source = + FDroidVersionCodeSource( + packageName = packageName, + currentVersion = Long.MAX_VALUE, + ), + ) + val versionCodeResult = versionCodeChecker.checkUpdate() + println(versionCodeResult) + assertTrue(versionCodeResult is UpdateResult.NoUpdate) + } + + @Test + fun `Checker on invalid fdroid package name returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + FDroidVersionCodeSource( + packageName = "invalid.app.package.name.fdroid", + currentVersion = 0, + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is PackageNotFoundException) + } + + @Test + fun `Checker on blank package name returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + FDroidVersionCodeSource( + packageName = " ", + currentVersion = 0, + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidPackageNameException) + } + + @Test + fun `Checker on repo name without dot returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + FDroidVersionCodeSource( + packageName = "comsimplemobiletoolsgallerypro", + currentVersion = 0, + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidPackageNameException) + } +} diff --git a/library/src/test/java/com/github/sharkaboi/appupdatechecker/GithubTest.kt b/library/src/test/java/com/github/sharkaboi/appupdatechecker/GithubTest.kt new file mode 100644 index 0000000..1307c0a --- /dev/null +++ b/library/src/test/java/com/github/sharkaboi/appupdatechecker/GithubTest.kt @@ -0,0 +1,178 @@ +package com.github.sharkaboi.appupdatechecker + +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.InvalidRepositoryNameException +import com.sharkaboi.appupdatechecker.models.InvalidUserNameException +import com.sharkaboi.appupdatechecker.models.PackageNotFoundException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.github.GithubTagSource +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue +import org.junit.Test + +class GithubTest { + private val ownerUsername = "Sharkaboi" + private val repoName = "MediaHub" + + @Test + fun `Checker on older installed version returns new version`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = ownerUsername, + repoName = repoName, + currentVersion = "v0.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.UpdateAvailable<*>) + } + + @Test + fun `Checker on newer installed version returns no update`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = ownerUsername, + repoName = repoName, + currentVersion = "v${Long.MAX_VALUE}.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.NoUpdate) + } + + @Test + fun `Checker on invalid github repo returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "adadadadadadadadaadada", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is PackageNotFoundException) + } + + @Test + fun `Checker on invalid github user returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "adadadadadadadadaadada", + repoName = "MediaHub", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is PackageNotFoundException) + } + + @Test + fun `Checker on github with no release returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "sharkaboi.github.io", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is PackageNotFoundException) + } + + @Test + fun `Checker on blank username returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = " ", + repoName = "sharkaboi.github.io", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidUserNameException) + } + + @Test + fun `Checker on empty repo name returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidRepositoryNameException) + } + + @Test + fun `Checker with invalid auth token returns error`() = + runBlocking { + val exception = + runCatching { + val versionNameChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = ownerUsername, + repoName = repoName, + currentVersion = "v0.0.0", + bearerToken = "asdhaskdhakjhd", + ), + ) + val result = versionNameChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is RemoteError) + } +} diff --git a/library/src/test/java/com/github/sharkaboi/appupdatechecker/JsonTest.kt b/library/src/test/java/com/github/sharkaboi/appupdatechecker/JsonTest.kt new file mode 100644 index 0000000..e03e234 --- /dev/null +++ b/library/src/test/java/com/github/sharkaboi/appupdatechecker/JsonTest.kt @@ -0,0 +1,152 @@ +package com.github.sharkaboi.appupdatechecker + +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.json.JsonVersionCodeSource +import com.sharkaboi.appupdatechecker.sources.json.JsonVersionNameSource +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue +import org.junit.Test + +class JsonTest { + private val jsonEndpoint = + """https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/3b168fc906490cc7f3cf0fc2b843461abf52422e/test.json""" + + @Test + fun `Checker on older installed version returns new version`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + JsonVersionNameSource( + jsonEndpoint = jsonEndpoint, + currentVersion = "v0.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.UpdateAvailable<*>) + + val versionCodeChecker = + AppUpdateChecker( + source = + JsonVersionCodeSource( + jsonEndpoint = jsonEndpoint, + currentVersion = 0, + ), + ) + val versionCodeResult = versionCodeChecker.checkUpdate() + println(versionCodeResult) + assertTrue(versionCodeResult is UpdateResult.UpdateAvailable<*>) + } + + @Test + fun `Checker on newer installed version returns no update`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + JsonVersionNameSource( + jsonEndpoint = jsonEndpoint, + currentVersion = "v${Long.MAX_VALUE}.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.NoUpdate) + + val versionCodeChecker = + AppUpdateChecker( + source = + JsonVersionCodeSource( + jsonEndpoint = jsonEndpoint, + currentVersion = Long.MAX_VALUE, + ), + ) + val versionCodeResult = versionCodeChecker.checkUpdate() + println(versionCodeResult) + assertTrue(versionCodeResult is UpdateResult.NoUpdate) + } + + @Test + fun `Checker on invalid json repo returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + JsonVersionNameSource( + jsonEndpoint = "https://google.com", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is GenericError) + } + + @Test + fun `Checker on invalid json schema returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + JsonVersionNameSource( + jsonEndpoint = """https://gist.github.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/3b168fc906490cc7f3cf0fc2b843461abf52422e/invalid.json""", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is GenericError) + } + + @Test + fun `Checker on invalid url returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + JsonVersionNameSource( + jsonEndpoint = "invalid url", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidEndPointException) + } + + @Test + fun `Checker on blank endpoint returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + JsonVersionNameSource( + jsonEndpoint = " ", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidEndPointException) + } +} diff --git a/library/src/test/java/com/github/sharkaboi/appupdatechecker/VersionComparatorTest.kt b/library/src/test/java/com/github/sharkaboi/appupdatechecker/VersionComparatorTest.kt new file mode 100644 index 0000000..b8f9fcc --- /dev/null +++ b/library/src/test/java/com/github/sharkaboi/appupdatechecker/VersionComparatorTest.kt @@ -0,0 +1,147 @@ +package com.github.sharkaboi.appupdatechecker + +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.InvalidVersionException +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.fdroid.FDroidVersionCodeSource +import com.sharkaboi.appupdatechecker.sources.github.GithubTagSource +import com.sharkaboi.appupdatechecker.sources.json.JsonVersionNameSource +import com.sharkaboi.appupdatechecker.versions.DefaultStringVersionComparator +import com.sharkaboi.appupdatechecker.versions.VersionComparator +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue +import org.junit.Test + +class VersionComparatorTest { + @Test + fun `Checker on invalid current version name returns invalid version name error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "MediaHub", + currentVersion = "vdasd.ada.adas.jj", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidVersionException) + } + + @Test + fun `Checker on invalid current version name ending returns invalid version name error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "MediaHub", + currentVersion = "1.", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidVersionException) + } + + @Test + fun `Checker on invalid current version name start returns invalid version name error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "MediaHub", + currentVersion = ".1.2", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidVersionException) + } + + @Test + fun `Checker on invalid current version name char returns invalid version name error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "MediaHub", + currentVersion = "g.1.2", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidVersionException) + } + + @Test + fun `Checker on invalid current version code returns invalid version code error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + FDroidVersionCodeSource( + packageName = "org.fdroid.fdroid", + currentVersion = -5000, + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidVersionException) + } + + @Test + fun `Setting custom version comparator returns proper update status`() = + runBlocking { + val source = + JsonVersionNameSource( + jsonEndpoint = """https://gist.github.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/1b99f59babe56a63aa95a7fb31b5d3682c4b18db/test-custom.json""", + currentVersion = "v1.0-alpha", + ) + + val customVersionComparator = + object : VersionComparator { + override fun isNewerVersion( + currentVersion: String, + newVersion: String, + ): Boolean { + return DefaultStringVersionComparator.isNewerVersion( + currentVersion.substringBefore('-'), + newVersion.substringBefore('-'), + ) + } + } + source.setCustomVersionComparator(customVersionComparator) + val testChecker = AppUpdateChecker(source = source) + val result = testChecker.checkUpdate() + println(result) + assertTrue(result is UpdateResult.UpdateAvailable<*>) + } +} diff --git a/library/src/test/java/com/github/sharkaboi/appupdatechecker/XMLTest.kt b/library/src/test/java/com/github/sharkaboi/appupdatechecker/XMLTest.kt new file mode 100644 index 0000000..71ec4e7 --- /dev/null +++ b/library/src/test/java/com/github/sharkaboi/appupdatechecker/XMLTest.kt @@ -0,0 +1,152 @@ +package com.github.sharkaboi.appupdatechecker + +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.xml.XMLVersionCodeSource +import com.sharkaboi.appupdatechecker.sources.xml.XMLVersionNameSource +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue +import org.junit.Test + +class XMLTest { + private val xmlEndpoint = + "https://gist.github.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/3b168fc906490cc7f3cf0fc2b843461abf52422e/test.xml" + + @Test + fun `Checker on older installed version returns new version`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + XMLVersionNameSource( + xmlEndpoint = xmlEndpoint, + currentVersion = "v0.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.UpdateAvailable<*>) + + val versionCodeChecker = + AppUpdateChecker( + source = + XMLVersionCodeSource( + xmlEndpoint = xmlEndpoint, + currentVersion = 0, + ), + ) + val versionCodeResult = versionCodeChecker.checkUpdate() + println(versionCodeResult) + assertTrue(versionCodeResult is UpdateResult.UpdateAvailable<*>) + } + + @Test + fun `Checker on newer installed version returns no update`() = + runBlocking { + val versionNameChecker = + AppUpdateChecker( + source = + XMLVersionNameSource( + xmlEndpoint = xmlEndpoint, + currentVersion = "v${Long.MAX_VALUE}.0.0", + ), + ) + val versionNameResult = versionNameChecker.checkUpdate() + println(versionNameResult) + assertTrue(versionNameResult is UpdateResult.NoUpdate) + + val versionCodeChecker = + AppUpdateChecker( + source = + XMLVersionCodeSource( + xmlEndpoint = xmlEndpoint, + currentVersion = Long.MAX_VALUE, + ), + ) + val versionCodeResult = versionCodeChecker.checkUpdate() + println(versionCodeResult) + assertTrue(versionCodeResult is UpdateResult.NoUpdate) + } + + @Test + fun `Checker on invalid xml repo returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + XMLVersionNameSource( + xmlEndpoint = "https://google.com", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is GenericError) + } + + @Test + fun `Checker on invalid xml schema returns invalid error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + XMLVersionNameSource( + xmlEndpoint = """https://gist.github.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/3b168fc906490cc7f3cf0fc2b843461abf52422e/invalid.xml""", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is GenericError) + } + + @Test + fun `Checker on invalid url returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + XMLVersionNameSource( + xmlEndpoint = "invalid url", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidEndPointException) + } + + @Test + fun `Checker on blank endpoint returns malformed error`() = + runBlocking { + val exception = + runCatching { + val testChecker = + AppUpdateChecker( + source = + XMLVersionNameSource( + xmlEndpoint = " ", + currentVersion = "v0.0.0", + ), + ) + val result = testChecker.checkUpdate() + println(result) + }.exceptionOrNull() + println(exception) + assertTrue(exception is InvalidEndPointException) + } +} diff --git a/library/src/test/java/com/sharkaboi/appupdatechecker/AppUpdateCheckerTest.kt b/library/src/test/java/com/sharkaboi/appupdatechecker/AppUpdateCheckerTest.kt deleted file mode 100644 index b12870b..0000000 --- a/library/src/test/java/com/sharkaboi/appupdatechecker/AppUpdateCheckerTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.sharkaboi.appupdatechecker - -import android.content.Context -import com.sharkaboi.appupdatechecker.extensions.isInternetConnected -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test - -class AppUpdateCheckerTest { - private lateinit var context: Context - - @Before - fun init() { - context = mockk(relaxed = true) - mockkStatic("com.sharkaboi.appupdatechecker.extensions.ContextExtensionsKt") - } - - @Test - fun `Checker on invalid current version tag returns generic error`() = runBlocking { - every { context.isInternetConnected } returns true - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "MediaHub" - ), - currentVersionTag = "vdasd.ada.adas.jj" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.GenericError) - } - - @Test - fun `Checker on no internet returns network error`() = runBlocking { - every { context.isInternetConnected } returns false - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "MediaHub" - ), - currentVersionTag = "v1.1" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.NoNetworkFound) - } -} diff --git a/library/src/test/java/com/sharkaboi/appupdatechecker/FdroidTest.kt b/library/src/test/java/com/sharkaboi/appupdatechecker/FdroidTest.kt deleted file mode 100644 index 42336d5..0000000 --- a/library/src/test/java/com/sharkaboi/appupdatechecker/FdroidTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.sharkaboi.appupdatechecker - -import android.content.Context -import com.sharkaboi.appupdatechecker.extensions.isInternetConnected -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test - -class FdroidTest { - private lateinit var context: Context - - @Before - fun init() { - context = mockk(relaxed = true) - mockkStatic("com.sharkaboi.appupdatechecker.extensions.ContextExtensionsKt") - every { context.isInternetConnected } returns true - every { context.packageName } returns "com.simplemobiletools.gallery.pro" - } - - @Test - fun `Checker on older installed version returns new version`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.FDroidSource(), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.UpdateAvailable) - } - - @Test - fun `Checker on newer installed version returns no update`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.FDroidSource(), - currentVersionTag = "v${Int.MAX_VALUE}.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.LatestVersionInstalled) - } - - @Test - fun `Checker on invalid fdroid package name returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.FDroidSource( - packageName = "invalid.app.package.name.fdroid", - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.FDroidInvalid) - } - - @Test - fun `Checker on blank package name returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.FDroidSource( - packageName = " " - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.FDroidMalformed) - } - - @Test - fun `Checker on repo name without dot returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.FDroidSource( - packageName = "comsimplemobiletoolsgallerypro" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.FDroidMalformed) - } -} diff --git a/library/src/test/java/com/sharkaboi/appupdatechecker/GithubTest.kt b/library/src/test/java/com/sharkaboi/appupdatechecker/GithubTest.kt deleted file mode 100644 index e2db4f6..0000000 --- a/library/src/test/java/com/sharkaboi/appupdatechecker/GithubTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.sharkaboi.appupdatechecker - -import android.content.Context -import com.sharkaboi.appupdatechecker.extensions.isInternetConnected -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test - -class GithubTest { - private lateinit var context: Context - - @Before - fun init() { - context = mockk(relaxed = true) - mockkStatic("com.sharkaboi.appupdatechecker.extensions.ContextExtensionsKt") - every { context.isInternetConnected } returns true - } - - @Test - fun `Checker on older installed version returns new version`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "MediaHub" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.UpdateAvailable) - } - - @Test - fun `Checker on newer installed version returns no update`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "MediaHub" - ), - currentVersionTag = "v${Int.MAX_VALUE}.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.LatestVersionInstalled) - } - - @Test - fun `Checker on invalid github repo returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "adadadadadadadadaadada" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.GithubInvalid) - } - - @Test - fun `Checker on invalid github user returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "adadadadadadadadaadada", - repoName = "MediaHub" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.GithubInvalid) - } - - @Test - fun `Checker on github with no release returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "sharkaboi.github.io" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.GithubInvalid) - } - - @Test - fun `Checker on blank username returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = " ", - repoName = "sharkaboi.github.io" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.GithubMalformed) - } - - @Test - fun `Checker on empty repo name returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.GithubSource( - ownerUsername = "Sharkaboi", - repoName = "" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.GithubMalformed) - } -} diff --git a/library/src/test/java/com/sharkaboi/appupdatechecker/JsonTest.kt b/library/src/test/java/com/sharkaboi/appupdatechecker/JsonTest.kt deleted file mode 100644 index 8f14e5f..0000000 --- a/library/src/test/java/com/sharkaboi/appupdatechecker/JsonTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.sharkaboi.appupdatechecker - -import android.content.Context -import com.sharkaboi.appupdatechecker.extensions.isInternetConnected -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test - -class JsonTest { - private lateinit var context: Context - - @Before - fun init() { - context = mockk(relaxed = true) - mockkStatic("com.sharkaboi.appupdatechecker.extensions.ContextExtensionsKt") - every { context.isInternetConnected } returns true - } - - @Test - fun `Checker on older installed version returns new version`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.JsonSource( - jsonEndpoint = "https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/e0efd6ebdaf88615945fc8c7727a7fc620ad61bf/test.json" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.UpdateAvailable) - } - - @Test - fun `Checker on newer installed version returns no update`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.JsonSource( - jsonEndpoint = "https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/e0efd6ebdaf88615945fc8c7727a7fc620ad61bf/test.json" - ), - currentVersionTag = "v${Int.MAX_VALUE}.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.LatestVersionInstalled) - } - - @Test - fun `Checker on invalid json repo returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.JsonSource( - jsonEndpoint = "https://google.com" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.JSONInvalid) - } - - @Test - fun `Checker on invalid json schema returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.JsonSource( - jsonEndpoint = "https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/a3998cc1728cd42ba02b5b9789f505de42090c84/invalid.json" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.JSONInvalid) - } - - @Test - fun `Checker on invalid url returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.JsonSource( - jsonEndpoint = "invalid url" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.JSONMalformed) - } - - @Test - fun `Checker on blank endpoint returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.JsonSource( - jsonEndpoint = " " - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.JSONMalformed) - } -} diff --git a/library/src/test/java/com/sharkaboi/appupdatechecker/XMLTest.kt b/library/src/test/java/com/sharkaboi/appupdatechecker/XMLTest.kt deleted file mode 100644 index 76e8b9a..0000000 --- a/library/src/test/java/com/sharkaboi/appupdatechecker/XMLTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.sharkaboi.appupdatechecker - -import android.content.Context -import com.sharkaboi.appupdatechecker.extensions.isInternetConnected -import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerSource -import com.sharkaboi.appupdatechecker.models.UpdateState -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test - -class XMLTest { - private lateinit var context: Context - - @Before - fun init() { - context = mockk(relaxed = true) - mockkStatic("com.sharkaboi.appupdatechecker.extensions.ContextExtensionsKt") - every { context.isInternetConnected } returns true - } - - @Test - fun `Checker on older installed version returns new version`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.XMLSource( - xmlEndpoint = "https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/132296a0e85d254cd982959bc8f198ca61c213b7/test.xml" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.UpdateAvailable) - } - - @Test - fun `Checker on newer installed version returns no update`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.XMLSource( - xmlEndpoint = "https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/132296a0e85d254cd982959bc8f198ca61c213b7/test.xml" - ), - currentVersionTag = "v${Int.MAX_VALUE}.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.LatestVersionInstalled) - } - - @Test - fun `Checker on invalid xml repo returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.XMLSource( - xmlEndpoint = "https://google.com" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.XMLInvalid) - } - - @Test - fun `Checker on invalid xml schema returns invalid error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.XMLSource( - xmlEndpoint = "https://gist.githubusercontent.com/Sharkaboi/66b45a22afde23a9b2781eeec6f10c56/raw/132296a0e85d254cd982959bc8f198ca61c213b7/invalid.xml" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.XMLInvalid) - } - - @Test - fun `Checker on invalid url returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.XMLSource( - xmlEndpoint = "invalid url" - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.XMLMalformed) - } - - @Test - fun `Checker on blank endpoint returns malformed error`() = runBlocking { - val testChecker = AppUpdateChecker( - context, - source = AppUpdateCheckerSource.XMLSource( - xmlEndpoint = " " - ), - currentVersionTag = "v0.0.0" - ) - val result = testChecker.checkUpdate() - println(result) - assert(result is UpdateState.XMLMalformed) - } -} diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..566057f --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.github.sharkaboi.appupdatechecker.sample' + compileSdk 34 + + defaultConfig { + applicationId "com.github.sharkaboi.appupdatechecker.sample" + minSdk 21 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = '17' + } + buildFeatures { + viewBinding true + } +} + +dependencies { + implementation(project(":library")) + + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} \ No newline at end of file diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/sample/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3503454 --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/com/github/sharkaboi/appupdatechecker/sample/MainActivity.kt b/sample/src/main/java/com/github/sharkaboi/appupdatechecker/sample/MainActivity.kt new file mode 100644 index 0000000..4fc99fa --- /dev/null +++ b/sample/src/main/java/com/github/sharkaboi/appupdatechecker/sample/MainActivity.kt @@ -0,0 +1,92 @@ +package com.github.sharkaboi.appupdatechecker.sample + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.github.sharkaboi.appupdatechecker.sample.databinding.ActivityMainBinding +import com.sharkaboi.appupdatechecker.AppUpdateChecker +import com.sharkaboi.appupdatechecker.models.AppUpdateCheckerException +import com.sharkaboi.appupdatechecker.models.GenericError +import com.sharkaboi.appupdatechecker.models.InvalidEndPointException +import com.sharkaboi.appupdatechecker.models.InvalidPackageNameException +import com.sharkaboi.appupdatechecker.models.InvalidRepositoryNameException +import com.sharkaboi.appupdatechecker.models.InvalidUserNameException +import com.sharkaboi.appupdatechecker.models.InvalidVersionException +import com.sharkaboi.appupdatechecker.models.PackageNotFoundException +import com.sharkaboi.appupdatechecker.models.RemoteError +import com.sharkaboi.appupdatechecker.models.UpdateResult +import com.sharkaboi.appupdatechecker.sources.github.GithubTagSource +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + private val errorHandler = + CoroutineExceptionHandler { _, throwable -> + if (throwable is AppUpdateCheckerException) { + when (throwable) { + is GenericError -> { + // Handle explicitly if needed + } + + is InvalidEndPointException -> { + // Handle explicitly if needed + } + + is InvalidPackageNameException -> { + // Handle explicitly if needed + } + + is InvalidRepositoryNameException -> { + // Handle explicitly if needed + } + + is InvalidUserNameException -> { + // Handle explicitly if needed + } + + is InvalidVersionException -> { + // Handle explicitly if needed + } + + is PackageNotFoundException -> { + // Handle explicitly if needed + } + + is RemoteError -> { + // Handle explicitly if needed + } + } + } + + binding.tvUpdateDetails.text = "Error occurred when checking for updates:\n\n$throwable" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + // Ideally manage this using your DI implementation, creates a retrofit service underneath. + val updateChecker = + AppUpdateChecker( + source = + GithubTagSource( + ownerUsername = "Sharkaboi", + repoName = "AppUpdateChecker", + currentVersion = "v0.0.0", + ), + ) + + binding.tvUpdateDetails.text = "Checking for updates" + + lifecycleScope.launch(errorHandler) { + // Automatically switches to IO thread behind the scenes. + binding.tvUpdateDetails.text = + when (val result = updateChecker.checkUpdate()) { + UpdateResult.NoUpdate -> "Checking for updates" + is UpdateResult.UpdateAvailable<*> -> "Update found : " + result.versionDetails.toString() + } + } + } +} diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/sample/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0688f95 --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.webp b/sample/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.webp b/sample/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp b/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/sample/src/main/res/values-night/themes.xml b/sample/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..a22d511 --- /dev/null +++ b/sample/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml new file mode 100644 index 0000000..c8524cd --- /dev/null +++ b/sample/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml new file mode 100644 index 0000000..82aa71e --- /dev/null +++ b/sample/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + sample + \ No newline at end of file diff --git a/sample/src/main/res/values/themes.xml b/sample/src/main/res/values/themes.xml new file mode 100644 index 0000000..a4d87e5 --- /dev/null +++ b/sample/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +