diff --git a/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergApp.kt b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergApp.kt new file mode 100644 index 00000000..4c3e8e26 --- /dev/null +++ b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergApp.kt @@ -0,0 +1,12 @@ +package com.apkupdater.data.codeberg + +data class CodeBergApp( + val packageName: String, + val user: String, + val repo: String +) + +val CodeBergApps = listOf( + CodeBergApp("eu.kanade.fabsemanga.psyduck", "fabseman", "fabsemanga"), + CodeBergApp("com.draco.buoy", "s1m", "savertuner") +) diff --git a/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergAuthor.kt b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergAuthor.kt new file mode 100644 index 00000000..b7310b41 --- /dev/null +++ b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergAuthor.kt @@ -0,0 +1,3 @@ +package com.apkupdater.data.codeberg + +data class CodeBergAuthor(val avatar_url: String) diff --git a/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergRelease.kt b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergRelease.kt new file mode 100644 index 00000000..bddcc1ce --- /dev/null +++ b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergRelease.kt @@ -0,0 +1,11 @@ +package com.apkupdater.data.codeberg + + +data class CodeBergRelease( + val name: String, + val prerelease: Boolean, + val assets: List, + val tag_name: String, + val author: CodeBergAuthor, + val body: String = "" +) diff --git a/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergReleaseAsset.kt b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergReleaseAsset.kt new file mode 100644 index 00000000..ebab8311 --- /dev/null +++ b/app/src/main/kotlin/com/apkupdater/data/codeberg/CodeBergReleaseAsset.kt @@ -0,0 +1,6 @@ +package com.apkupdater.data.codeberg + +data class CodeBergReleaseAsset( + val size: Long, + val browser_download_url: String +) diff --git a/app/src/main/kotlin/com/apkupdater/data/ui/Source.kt b/app/src/main/kotlin/com/apkupdater/data/ui/Source.kt index 6df68eac..3f70d4c8 100644 --- a/app/src/main/kotlin/com/apkupdater/data/ui/Source.kt +++ b/app/src/main/kotlin/com/apkupdater/data/ui/Source.kt @@ -15,3 +15,4 @@ val IzzySource = Source("F-Droid (Izzy)", R.drawable.ic_izzy) val AptoideSource = Source("Aptoide", R.drawable.ic_aptoide) val ApkPureSource = Source("ApkPure", R.drawable.ic_apkpure) val GitLabSource = Source("GitLab", R.drawable.ic_gitlab) +val CodeBergSource = Source("GitLab", R.drawable.ic_codeberg) diff --git a/app/src/main/kotlin/com/apkupdater/di/MainModule.kt b/app/src/main/kotlin/com/apkupdater/di/MainModule.kt index 88ea3556..d726ff53 100644 --- a/app/src/main/kotlin/com/apkupdater/di/MainModule.kt +++ b/app/src/main/kotlin/com/apkupdater/di/MainModule.kt @@ -13,6 +13,7 @@ import com.apkupdater.repository.AptoideRepository import com.apkupdater.repository.FdroidRepository import com.apkupdater.repository.GitHubRepository import com.apkupdater.repository.GitLabRepository +import com.apkupdater.repository.CodeBergRepository import com.apkupdater.repository.SearchRepository import com.apkupdater.repository.UpdatesRepository import com.apkupdater.service.ApkMirrorService @@ -21,6 +22,7 @@ import com.apkupdater.service.AptoideService import com.apkupdater.service.FdroidService import com.apkupdater.service.GitHubService import com.apkupdater.service.GitLabService +import com.apkupdater.service.CodeBergService import com.apkupdater.util.Clipboard import com.apkupdater.util.Downloader import com.apkupdater.util.SessionInstaller @@ -93,6 +95,15 @@ val mainModule = module { .create(GitLabService::class.java) } + single { + Retrofit.Builder() + .client(get()) + .baseUrl("https://codeberg.org/api/v1/") + .addConverterFactory(GsonConverterFactory.create(get())) + .build() + .create(CodeBergService::class.java) + } + single { Retrofit.Builder() .client(get()) @@ -139,6 +150,8 @@ val mainModule = module { single { GitHubRepository(get(), get()) } + single { CodeBergRepository(get(), get()) } + single { GitLabRepository(get(), get()) } single { ApkPureRepository(get(), get(), get()) } diff --git a/app/src/main/kotlin/com/apkupdater/prefs/Prefs.kt b/app/src/main/kotlin/com/apkupdater/prefs/Prefs.kt index 3737be09..65cee307 100644 --- a/app/src/main/kotlin/com/apkupdater/prefs/Prefs.kt +++ b/app/src/main/kotlin/com/apkupdater/prefs/Prefs.kt @@ -25,6 +25,7 @@ class Prefs( val useApkMirror = boolean("useApkMirror", defValue = !isAndroidTv, backed = true) val useGitHub = boolean("useGitHub", defValue = true, backed = true) val useGitLab = boolean("useGitLab", defValue = true, backed = true) + val useCodeBerg = boolean("useCodeBerg", defValue = true, backed = true) val useFdroid = boolean("useFdroid", defValue = true, backed = true) val useIzzy = boolean("useIzzy", defValue = true, backed = true) val useAptoide = boolean("useAptoide", defValue = true, backed = true) diff --git a/app/src/main/kotlin/com/apkupdater/repository/CodeBergRepository.kt b/app/src/main/kotlin/com/apkupdater/repository/CodeBergRepository.kt new file mode 100644 index 00000000..2f5b9311 --- /dev/null +++ b/app/src/main/kotlin/com/apkupdater/repository/CodeBergRepository.kt @@ -0,0 +1,178 @@ +package com.apkupdater.repository + +import android.net.Uri +import android.os.Build +import android.util.Log +import io.github.g00fy2.versioncompare.Version +import com.apkupdater.BuildConfig +import com.apkupdater.data.codeberg.CodeBergApps +import com.apkupdater.data.codeberg.CodeBergRelease +import com.apkupdater.data.codeberg.CodeBergReleaseAsset +import com.apkupdater.data.ui.AppInstalled +import com.apkupdater.data.ui.AppUpdate +import com.apkupdater.data.ui.CodeBergSource +import com.apkupdater.data.ui.getApp +import com.apkupdater.prefs.Prefs +import com.apkupdater.service.CodeBergService +import com.apkupdater.util.combine +import com.apkupdater.util.filterVersionTag +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import java.util.Scanner + +class CodeBergRepository( + private val service: CodeBergService, + private val prefs: Prefs +) { + + suspend fun updates(apps: List) = flow { + val checks = mutableListOf>>() + + CodeBergApps.forEachIndexed { i, app -> + if (i != 0) { + apps.find { it.packageName == app.packageName }?.let { + checks.add(checkApp(apps, app.user, app.repo, app.packageName, it.version, app.extra)) + } + } + } + + checks.combine { all -> + emit(all.flatMap { it }) + }.collect() + }.catch { + emit(emptyList()) + Log.e("CodeBergRepository", "Error fetching releases.", it) + } + + suspend fun search(text: String) = flow { + val checks = mutableListOf>>() + + CodeBergApps.forEach { app -> + if (app.repo.contains(text, true) || app.user.contains(text, true) || app.packageName.contains(text, true)) { + checks.add(checkApp(null, app.user, app.repo, app.packageName, "?", null)) + } + } + + if (checks.isEmpty()) { + emit(Result.success(emptyList())) + } else { + checks.combine { all -> + val r = all.flatMap { it } + emit(Result.success(r)) + }.collect() + } + }.catch { + emit(Result.failure(it)) + Log.e("CodeBergRepository", "Error searching.", it) + } + + private fun checkApp( + apps: List?, + user: String, + repo: String, + packageName: String, + currentVersion: String, + extra: Regex? + ) = flow { + val releases = service.getReleases(user, repo) + .filter { filterPreRelease(it) } + .filter { findApkAsset(it.assets).isNotEmpty() } + + if (releases.isNotEmpty() && Version(filterVersionTag(releases[0].tag_name)) > Version(currentVersion)) { + val app = apps?.getApp(packageName) + emit(listOf(AppUpdate( + name = repo, + packageName = packageName, + version = releases[0].tag_name, + oldVersion = app?.version ?: "?", + versionCode = 0L, + oldVersionCode = app?.versionCode ?: 0L, + source = CodeBergSource, + link = findApkAssetArch(releases[0].assets, extra), + whatsNew = releases[0].body, + iconUri = if (apps == null) Uri.parse(releases[0].author.avatar_url) else Uri.EMPTY + ))) + } else { + emit(emptyList()) + } + }.catch { + emit(emptyList()) + Log.e("CodeBergRepository", "Error fetching releases for $packageName.", it) + } + + private fun getVersions(name: String) = runCatching { + val scanner = Scanner(name) + val version = scanner.next() + val versionCode = scanner.next().trim('(', ')').toLong() + Pair(version, versionCode) + }.getOrDefault(Pair(name, 0L)) + + private fun filterPreRelease(release: CodeBergRelease) = when { + prefs.ignorePreRelease.get() && release.prerelease -> false + else -> true + } + + private fun findApkAsset(assets: List) = assets + .filter { it.browser_download_url.endsWith(".apk", true) } + .maxByOrNull { it.size } + ?.browser_download_url + .orEmpty() + + private fun findApkAssetArch( + assets: List, + extra: Regex? + ): String { + val apks = assets + .filter { it.browser_download_url.endsWith(".apk", true) } + .filter { filterExtra(it, extra) } + + when { + apks.isEmpty() -> return "" + apks.size == 1 -> return apks.first().browser_download_url + else -> { + // Try to match exact arch + Build.SUPPORTED_ABIS.forEach { arch -> + apks.forEach { apk -> + if (apk.browser_download_url.contains(arch, true)) { + return apk.browser_download_url + } + } + } + // Try to match arm64 + if (Build.SUPPORTED_ABIS.contains("arm64-v8a")) { + apks.forEach { apk -> + if (apk.browser_download_url.contains("arm64", true)) { + return apk.browser_download_url + } + } + } + // Try to match x64 + if (Build.SUPPORTED_ABIS.contains("x86_64")) { + apks.forEach { apk -> + if (apk.browser_download_url.contains("x64", true)) { + return apk.browser_download_url + } + } + } + // Try to match arm + if (Build.SUPPORTED_ABIS.contains("armeabi-v7a")) { + apks.forEach { apk -> + if (apk.browser_download_url.contains("arm", true)) { + return apk.browser_download_url + } + } + } + // If no match, return biggest apk in the hope it's universal + return apks.maxByOrNull { it.size }?.browser_download_url.orEmpty() + } + } + } + + private fun filterExtra(asset: CodeBergReleaseAsset, extra: Regex?) = when(extra) { + null -> true + else -> asset.browser_download_url.matches(extra) + } + +} diff --git a/app/src/main/kotlin/com/apkupdater/service/CodeBergService.kt b/app/src/main/kotlin/com/apkupdater/service/CodeBergService.kt new file mode 100644 index 00000000..c63003be --- /dev/null +++ b/app/src/main/kotlin/com/apkupdater/service/CodeBergService.kt @@ -0,0 +1,15 @@ +package com.apkupdater.service + +import com.apkupdater.data.codeberg.CodeBergRelease +import retrofit2.http.GET +import retrofit2.http.Path + +interface CodeBergService { + + @GET("/repos/{user}/{repo}/releases") + suspend fun getReleases( + @Path("user") user: String, + @Path("repo") user: String + ): List + +} diff --git a/app/src/main/kotlin/com/apkupdater/ui/screen/SettingsScreen.kt b/app/src/main/kotlin/com/apkupdater/ui/screen/SettingsScreen.kt index e24080c5..f82cd837 100644 --- a/app/src/main/kotlin/com/apkupdater/ui/screen/SettingsScreen.kt +++ b/app/src/main/kotlin/com/apkupdater/ui/screen/SettingsScreen.kt @@ -235,6 +235,12 @@ fun Settings(viewModel: SettingsViewModel) = LazyColumn { stringResource(R.string.source_aptoide), R.drawable.ic_aptoide ) + SwitchSetting( + { viewModel.getUseCodeBerg() }, + { viewModel.setUseCodeBerg(it) }, + stringResource(R.string.source_codeberg), + R.drawable.ic_codeberg + ) SwitchSetting( { viewModel.getUseApkPure() }, { viewModel.setUseApkPure(it) }, diff --git a/app/src/main/kotlin/com/apkupdater/viewmodel/SettingsViewModel.kt b/app/src/main/kotlin/com/apkupdater/viewmodel/SettingsViewModel.kt index 73bbaa5a..8332972c 100644 --- a/app/src/main/kotlin/com/apkupdater/viewmodel/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/apkupdater/viewmodel/SettingsViewModel.kt @@ -54,6 +54,8 @@ class SettingsViewModel( fun setUseIzzy(b: Boolean) = prefs.useIzzy.put(b) fun getUseGitHub() = prefs.useGitHub.get() fun setUseGitHub(b: Boolean) = prefs.useGitHub.put(b) + fun getUseCodeBerg() = prefs.useCodeBerg.get() + fun setUseCodeBerg(b: Boolean) = prefs.useCodeBerg.put(b) fun getUseGitLab() = prefs.useGitLab.get() fun setUseGitLab(b: Boolean) = prefs.useGitLab.put(b) fun getUseAptoide() = prefs.useAptoide.get() diff --git a/app/src/main/res/drawable/ic_codeberg.png b/app/src/main/res/drawable/ic_codeberg.png new file mode 100644 index 00000000..5396c40c Binary files /dev/null and b/app/src/main/res/drawable/ic_codeberg.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00d1a73c..84c882ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,7 @@ GitHub GitLab APKPure + CodeBerg About Frequency Theme @@ -69,4 +70,4 @@ Found %1$d updates. - \ No newline at end of file +