diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 9aa9ce867..a1fc2257c 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -38,10 +38,11 @@ import tachiyomi.data.history.anime.AnimeHistoryRepositoryImpl import tachiyomi.data.history.manga.MangaHistoryRepositoryImpl import tachiyomi.data.items.chapter.ChapterRepositoryImpl import tachiyomi.data.items.episode.EpisodeRepositoryImpl -import tachiyomi.data.source.anime.AnimeSourceDataRepositoryImpl +import tachiyomi.data.release.ReleaseServiceImpl import tachiyomi.data.source.anime.AnimeSourceRepositoryImpl -import tachiyomi.data.source.manga.MangaSourceDataRepositoryImpl +import tachiyomi.data.source.anime.AnimeStubSourceRepositoryImpl import tachiyomi.data.source.manga.MangaSourceRepositoryImpl +import tachiyomi.data.source.manga.MangaStubSourceRepositoryImpl import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl import tachiyomi.data.track.manga.MangaTrackRepositoryImpl import tachiyomi.data.updates.anime.AnimeUpdatesRepositoryImpl @@ -113,14 +114,16 @@ import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode import tachiyomi.domain.items.episode.interactor.UpdateEpisode import tachiyomi.domain.items.episode.repository.EpisodeRepository +import tachiyomi.domain.release.interactor.GetApplicationRelease +import tachiyomi.domain.release.service.ReleaseService import tachiyomi.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAnime import tachiyomi.domain.source.anime.interactor.GetRemoteAnime -import tachiyomi.domain.source.anime.repository.AnimeSourceDataRepository import tachiyomi.domain.source.anime.repository.AnimeSourceRepository +import tachiyomi.domain.source.anime.repository.AnimeStubSourceRepository import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga import tachiyomi.domain.source.manga.interactor.GetRemoteManga -import tachiyomi.domain.source.manga.repository.MangaSourceDataRepository import tachiyomi.domain.source.manga.repository.MangaSourceRepository +import tachiyomi.domain.source.manga.repository.MangaStubSourceRepository import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime @@ -206,6 +209,9 @@ class DomainModule : InjektModule { addFactory { UpdateManga(get()) } addFactory { SetMangaCategories(get()) } + addSingletonFactory { ReleaseServiceImpl(get(), get()) } + addFactory { GetApplicationRelease(get(), get()) } + addSingletonFactory { AnimeTrackRepositoryImpl(get()) } addFactory { DeleteAnimeTrack(get()) } addFactory { GetTracksPerAnime(get()) } @@ -266,7 +272,7 @@ class DomainModule : InjektModule { addFactory { GetMangaUpdates(get()) } addSingletonFactory { AnimeSourceRepositoryImpl(get(), get()) } - addSingletonFactory { AnimeSourceDataRepositoryImpl(get()) } + addSingletonFactory { AnimeStubSourceRepositoryImpl(get()) } addFactory { GetEnabledAnimeSources(get(), get()) } addFactory { GetLanguagesWithAnimeSources(get(), get()) } addFactory { GetRemoteAnime(get()) } @@ -276,7 +282,7 @@ class DomainModule : InjektModule { addFactory { ToggleAnimeSourcePin(get()) } addSingletonFactory { MangaSourceRepositoryImpl(get(), get()) } - addSingletonFactory { MangaSourceDataRepositoryImpl(get()) } + addSingletonFactory { MangaStubSourceRepositoryImpl(get()) } addFactory { GetEnabledMangaSources(get(), get()) } addFactory { GetLanguagesWithMangaSources(get(), get()) } addFactory { GetRemoteManga(get()) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt index f6f62eca9..90e8c9c8a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt @@ -29,7 +29,6 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.updater.AppUpdateChecker -import eu.kanade.tachiyomi.data.updater.AppUpdateResult import eu.kanade.tachiyomi.data.updater.RELEASE_URL import eu.kanade.tachiyomi.ui.more.NewUpdateScreen import eu.kanade.tachiyomi.util.CrashLogUtil @@ -41,6 +40,7 @@ import logcat.LogPriority import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat +import tachiyomi.domain.release.interactor.GetApplicationRelease import tachiyomi.presentation.core.components.LinkIcon import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Scaffold @@ -174,16 +174,16 @@ object AboutScreen : Screen() { /** * Checks version and shows a user prompt if an update is available. */ - private suspend fun checkVersion(context: Context, onAvailableUpdate: (AppUpdateResult.NewUpdate) -> Unit) { + private suspend fun checkVersion(context: Context, onAvailableUpdate: (GetApplicationRelease.Result.NewUpdate) -> Unit) { val updateChecker = AppUpdateChecker() withUIContext { context.toast(R.string.update_check_look_for_updates) try { - when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) { - is AppUpdateResult.NewUpdate -> { + when (val result = withIOContext { updateChecker.checkForUpdate(context, forceCheck = true) }) { + is GetApplicationRelease.Result.NewUpdate -> { onAvailableUpdate(result) } - is AppUpdateResult.NoNewUpdate -> { + is GetApplicationRelease.Result.NoNewUpdate -> { context.toast(R.string.update_check_no_new_updates) } else -> {} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt index d84855ed2..69e458f96 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt @@ -11,83 +11,38 @@ import kotlinx.serialization.json.Json import tachiyomi.core.preference.Preference import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.util.lang.withIOContext +import tachiyomi.domain.release.interactor.GetApplicationRelease import uy.kohesive.injekt.injectLazy import java.util.Date import kotlin.time.Duration.Companion.days class AppUpdateChecker { - private val networkService: NetworkHelper by injectLazy() - private val preferenceStore: PreferenceStore by injectLazy() - private val json: Json by injectLazy() + private val getApplicationRelease: GetApplicationRelease by injectLazy() - private val lastAppCheck: Preference by lazy { - preferenceStore.getLong("last_app_check", 0) - } - - suspend fun checkForUpdate(context: Context, isUserPrompt: Boolean = false): AppUpdateResult { - // Limit checks to once a every 3 days at most - if (isUserPrompt.not() && Date().time < lastAppCheck.get() + 3.days.inWholeMilliseconds) { - return AppUpdateResult.NoNewUpdate - } + suspend fun checkForUpdate(context: Context, forceCheck: Boolean = false): GetApplicationRelease.Result { return withIOContext { - val result = with(json) { - networkService.client - .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest")) - .awaitSuccess() - .parseAs() - .let { - lastAppCheck.set(Date().time) - - // Check if latest version is different from current version - if (isNewVersion(it.version)) { - if (context.isInstalledFromFDroid()) { - AppUpdateResult.NewUpdateFdroidInstallation - } else { - AppUpdateResult.NewUpdate(it) - } - } else { - AppUpdateResult.NoNewUpdate - } - } - } + val result = getApplicationRelease.await( + GetApplicationRelease.Arguments( + BuildConfig.PREVIEW, + context.isInstalledFromFDroid(), + BuildConfig.COMMIT_COUNT.toInt(), + BuildConfig.VERSION_NAME, + GITHUB_REPO, + forceCheck, + ), + ) when (result) { - is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release) - is AppUpdateResult.NewUpdateFdroidInstallation -> AppUpdateNotifier(context).promptFdroidUpdate() + is GetApplicationRelease.Result.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release) + is GetApplicationRelease.Result.ThirdPartyInstallation -> AppUpdateNotifier(context).promptFdroidUpdate() else -> {} } result } } - - private fun isNewVersion(versionTag: String): Boolean { - // Removes prefixes like "r" or "v" - val newVersion = versionTag.replace("[^\\d.]".toRegex(), "") - - return if (BuildConfig.PREVIEW) { - // Preview builds: based on releases in "aniyomiorg/aniyomi-preview" repo - // tagged as something like "r1234" - newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt() - } else { - // Release builds: based on releases in "aniyomiorg/aniyomi" repo - // tagged as something like "v0.1.2" - val oldVersion = BuildConfig.VERSION_NAME.replace("[^\\d.]".toRegex(), "") - - val newSemVer = newVersion.split(".").map { it.toInt() } - val oldSemVer = oldVersion.split(".").map { it.toInt() } - - oldSemVer.mapIndexed { index, i -> - if (newSemVer[index] > i) { - return true - } - } - - false - } - } } val GITHUB_REPO: String by lazy { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt index 9a31b1e96..1a3cff947 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notify +import tachiyomi.domain.release.model.Release internal class AppUpdateNotifier(private val context: Context) { @@ -27,18 +28,22 @@ internal class AppUpdateNotifier(private val context: Context) { context.notify(id, build()) } + fun cancel() { + NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER) + } + @SuppressLint("LaunchActivityFromNotification") - fun promptUpdate(release: GithubRelease) { - val intent = Intent(context, AppUpdateService::class.java).apply { + fun promptUpdate(release: Release) { + val updateIntent = Intent(context, AppUpdateService::class.java).run { putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink()) putExtra(AppUpdateService.EXTRA_DOWNLOAD_TITLE, release.version) + PendingIntent.getService(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } - val updateIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - val releaseIntent = Intent(Intent.ACTION_VIEW, release.releaseLink.toUri()).apply { + val releaseIntent = Intent(Intent.ACTION_VIEW, release.releaseLink.toUri()).run { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + PendingIntent.getActivity(context, release.hashCode(), this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } - val releaseInfoIntent = PendingIntent.getActivity(context, release.hashCode(), releaseIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) with(notificationBuilder) { setContentTitle(context.getString(R.string.update_check_notification_update_available)) @@ -55,7 +60,7 @@ internal class AppUpdateNotifier(private val context: Context) { addAction( R.drawable.ic_info_24dp, context.getString(R.string.whats_new), - releaseInfoIntent, + releaseIntent, ) } notificationBuilder.show() @@ -164,13 +169,12 @@ internal class AppUpdateNotifier(private val context: Context) { addAction( R.drawable.ic_close_24dp, context.getString(R.string.action_cancel), - NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER), + NotificationReceiver.dismissNotificationPendingBroadcast( + context, + Notifications.ID_APP_UPDATER + ), ) } notificationBuilder.show(Notifications.ID_APP_UPDATER) } - - fun cancel() { - NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER) - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateResult.kt deleted file mode 100644 index 695d13492..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateResult.kt +++ /dev/null @@ -1,7 +0,0 @@ -package eu.kanade.tachiyomi.data.updater - -sealed class AppUpdateResult { - class NewUpdate(val release: GithubRelease) : AppUpdateResult() - object NewUpdateFdroidInstallation : AppUpdateResult() - object NoNewUpdate : AppUpdateResult() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateService.kt index 0fb92cd43..1d53e338e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateService.kt @@ -20,13 +20,13 @@ import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.isServiceRunning import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import logcat.LogPriority -import okhttp3.Call +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import okhttp3.internal.http2.ErrorCode import okhttp3.internal.http2.StreamResetException -import tachiyomi.core.util.lang.launchIO -import tachiyomi.core.util.system.logcat import uy.kohesive.injekt.injectLazy import java.io.File @@ -41,8 +41,8 @@ class AppUpdateService : Service() { private lateinit var notifier: AppUpdateNotifier - private var runningJob: Job? = null - private var runningCall: Call? = null + private val job = SupervisorJob() + private val serviceScope = CoroutineScope(Dispatchers.IO + job) override fun onCreate() { notifier = AppUpdateNotifier(this) @@ -62,11 +62,11 @@ class AppUpdateService : Service() { val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name) - runningJob = launchIO { + serviceScope.launch { downloadApk(title, url) } - runningJob?.invokeOnCompletion { stopSelf(startId) } + job.invokeOnCompletion { stopSelf(startId) } return START_NOT_STICKY } @@ -80,8 +80,8 @@ class AppUpdateService : Service() { } private fun destroyJob() { - runningJob?.cancel() - runningCall?.cancel() + serviceScope.cancel() + job.cancel() if (wakeLock.isHeld) { wakeLock.release() } @@ -116,9 +116,8 @@ class AppUpdateService : Service() { try { // Download the new update. - val call = network.client.newCachelessCallWithProgress(GET(url), progressListener) - runningCall = call - val response = call.await() + val response = network.client.newCachelessCallWithProgress(GET(url), progressListener) + .await() // File where the apk will be saved. val apkFile = File(externalCacheDir, "update.apk") @@ -131,10 +130,9 @@ class AppUpdateService : Service() { } notifier.promptInstall(apkFile.getUriCompat(this)) } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - if (e is CancellationException || + val shouldCancel = e is CancellationException || (e is StreamResetException && e.errorCode == ErrorCode.CANCEL) - ) { + if (shouldCancel) { notifier.cancel() } else { notifier.onDownloadError(url) @@ -165,11 +163,11 @@ class AppUpdateService : Service() { fun start(context: Context, url: String, title: String? = context.getString(R.string.app_name)) { if (isRunning(context)) return - val intent = Intent(context, AppUpdateService::class.java).apply { + Intent(context, AppUpdateService::class.java).apply { putExtra(EXTRA_DOWNLOAD_TITLE, title) putExtra(EXTRA_DOWNLOAD_URL, url) + ContextCompat.startForegroundService(context, this) } - ContextCompat.startForegroundService(context, intent) } /** @@ -188,10 +186,10 @@ class AppUpdateService : Service() { * @return [PendingIntent] */ internal fun downloadApkPendingService(context: Context, url: String): PendingIntent { - val intent = Intent(context, AppUpdateService::class.java).apply { + return Intent(context, AppUpdateService::class.java).run { putExtra(EXTRA_DOWNLOAD_URL, url) + PendingIntent.getService(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } - return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubRelease.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubRelease.kt deleted file mode 100644 index dedc9be0c..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubRelease.kt +++ /dev/null @@ -1,40 +0,0 @@ -package eu.kanade.tachiyomi.data.updater - -import android.os.Build -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -/** - * Contains information about the latest release from GitHub. - */ -@Serializable -data class GithubRelease( - @SerialName("tag_name") val version: String, - @SerialName("body") val info: String, - @SerialName("html_url") val releaseLink: String, - @SerialName("assets") private val assets: List, -) { - - /** - * Get download link of latest release from the assets. - * @return download link of latest release. - */ - fun getDownloadLink(): String { - val apkVariant = when (Build.SUPPORTED_ABIS[0]) { - "arm64-v8a" -> "-arm64-v8a" - "armeabi-v7a" -> "-armeabi-v7a" - "x86" -> "-x86" - "x86_64" -> "-x86_64" - else -> "" - } - - return assets.find { it.downloadLink.contains("aniyomi$apkVariant-") }?.downloadLink - ?: assets[0].downloadLink - } - - /** - * Assets class containing download url. - */ - @Serializable - data class Assets(@SerialName("browser_download_url") val downloadLink: String) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt index db1aab20f..90fcb0caf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt @@ -8,10 +8,10 @@ import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult -import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader +import eu.kanade.tachiyomi.source.anime.toStubSource import eu.kanade.tachiyomi.util.preference.plusAssign import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.async @@ -22,7 +22,7 @@ import rx.Observable import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat -import tachiyomi.domain.source.anime.model.AnimeSourceData +import tachiyomi.domain.source.anime.model.StubAnimeSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Locale @@ -73,12 +73,12 @@ class AnimeExtensionManager( private val _availableAnimeExtensionsFlow = MutableStateFlow(emptyList()) val availableExtensionsFlow = _availableAnimeExtensionsFlow.asStateFlow() - private var availableAnimeExtensionsSourcesData: Map = emptyMap() + private var availableAnimeExtensionsSourcesData: Map = emptyMap() private fun setupAvailableAnimeExtensionsSourcesDataMap(animeextensions: List) { if (animeextensions.isEmpty()) return availableAnimeExtensionsSourcesData = animeextensions - .flatMap { ext -> ext.sources.map { it.toAnimeSourceData() } } + .flatMap { ext -> ext.sources.map { it.toStubSource() } } .associateBy { it.id } } @@ -145,8 +145,8 @@ class AnimeExtensionManager( // Use the source lang as some aren't present on the animeextension level. val availableLanguages = animeextensions .flatMap(AnimeExtension.Available::sources) - .distinctBy(AvailableAnimeSources::lang) - .map(AvailableAnimeSources::lang) + .distinctBy(AnimeExtension.Available.AnimeSource::lang) + .map(AnimeExtension.Available.AnimeSource::lang) val deviceLanguage = Locale.getDefault().language val defaultLanguages = preferences.enabledLanguages().defaultValue() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt index 8a6ffbfa5..e92061eb5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult -import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper @@ -126,24 +125,13 @@ internal class AnimeExtensionGithubApi { isNsfw = it.nsfw == 1, hasReadme = it.hasReadme == 1, hasChangelog = it.hasChangelog == 1, - sources = it.sources?.toAnimeExtensionSources().orEmpty(), + sources = it.sources?.map(extensionAnimeSourceMapper).orEmpty(), apkName = it.apk, iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}", ) } } - private fun List.toAnimeExtensionSources(): List { - return this.map { - AvailableAnimeSources( - id = it.id, - lang = it.lang, - name = it.name, - baseUrl = it.baseUrl, - ) - } - } - fun getApkUrl(extension: AnimeExtension.Available): String { return "${getUrlPrefix()}apk/${extension.apkName}" } @@ -185,3 +173,12 @@ private data class AnimeExtensionSourceJsonObject( val name: String, val baseUrl: String, ) + +private val extensionAnimeSourceMapper: (AnimeExtensionSourceJsonObject) -> AnimeExtension.Available.AnimeSource = { + AnimeExtension.Available.AnimeSource( + id = it.id, + lang = it.lang, + name = it.name, + baseUrl = it.baseUrl, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt index 4f5528794..48cb4b3f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.anime.model import android.graphics.drawable.Drawable import eu.kanade.tachiyomi.animesource.AnimeSource -import tachiyomi.domain.source.anime.model.AnimeSourceData +import tachiyomi.domain.source.anime.model.StubAnimeSource sealed class AnimeExtension { @@ -44,10 +44,26 @@ sealed class AnimeExtension { override val isNsfw: Boolean, override val hasReadme: Boolean, override val hasChangelog: Boolean, - val sources: List, + val sources: List, val apkName: String, val iconUrl: String, - ) : AnimeExtension() + ) : AnimeExtension() { + + data class AnimeSource( + val id: Long, + val lang: String, + val name: String, + val baseUrl: String, + ) { + fun toStubSource(): StubAnimeSource { + return StubAnimeSource( + id = this.id, + lang = this.lang, + name = this.name, + ) + } + } + } data class Untrusted( override val name: String, @@ -62,18 +78,3 @@ sealed class AnimeExtension { override val hasChangelog: Boolean = false, ) : AnimeExtension() } - -data class AvailableAnimeSources( - val id: Long, - val lang: String, - val name: String, - val baseUrl: String, -) { - fun toAnimeSourceData(): AnimeSourceData { - return AnimeSourceData( - id = this.id, - lang = this.lang, - name = this.name, - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt index 79059b767..62ed92758 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt @@ -6,12 +6,12 @@ import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi -import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader +import eu.kanade.tachiyomi.source.manga.toStubSource import eu.kanade.tachiyomi.util.preference.plusAssign import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.async @@ -22,7 +22,7 @@ import rx.Observable import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat -import tachiyomi.domain.source.manga.model.MangaSourceData +import tachiyomi.domain.source.manga.model.StubMangaSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Locale @@ -73,12 +73,12 @@ class MangaExtensionManager( private val _availableExtensionsFlow = MutableStateFlow(emptyList()) val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() - private var availableExtensionsSourcesData: Map = emptyMap() + private var availableExtensionsSourcesData: Map = emptyMap() private fun setupAvailableExtensionsSourcesDataMap(extensions: List) { if (extensions.isEmpty()) return availableExtensionsSourcesData = extensions - .flatMap { ext -> ext.sources.map { it.toSourceData() } } + .flatMap { ext -> ext.sources.map { it.toStubSource() } } .associateBy { it.id } } @@ -145,8 +145,8 @@ class MangaExtensionManager( // Use the source lang as some aren't present on the extension level. val availableLanguages = extensions .flatMap(MangaExtension.Available::sources) - .distinctBy(AvailableMangaSources::lang) - .map(AvailableMangaSources::lang) + .distinctBy(MangaExtension.Available.MangaSource::lang) + .map(MangaExtension.Available.MangaSource::lang) val deviceLanguage = Locale.getDefault().language val defaultLanguages = preferences.enabledLanguages().defaultValue() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt index dd4540c21..147b417dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.manga.api import android.content.Context import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager -import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader @@ -125,24 +124,13 @@ internal class MangaExtensionGithubApi { isNsfw = it.nsfw == 1, hasReadme = it.hasReadme == 1, hasChangelog = it.hasChangelog == 1, - sources = it.sources?.toExtensionSources().orEmpty(), + sources = it.sources?.map(extensionSourceMapper).orEmpty(), apkName = it.apk, iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}", ) } } - private fun List.toExtensionSources(): List { - return this.map { - AvailableMangaSources( - id = it.id, - lang = it.lang, - name = it.name, - baseUrl = it.baseUrl, - ) - } - } - fun getApkUrl(extension: MangaExtension.Available): String { return "${getUrlPrefix()}apk/${extension.apkName}" } @@ -184,3 +172,12 @@ private data class ExtensionSourceJsonObject( val name: String, val baseUrl: String, ) + +private val extensionSourceMapper: (ExtensionSourceJsonObject) -> MangaExtension.Available.MangaSource = { + MangaExtension.Available.MangaSource( + id = it.id, + lang = it.lang, + name = it.name, + baseUrl = it.baseUrl, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt index d0b6d4482..5030c24ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.manga.model import android.graphics.drawable.Drawable import eu.kanade.tachiyomi.source.MangaSource -import tachiyomi.domain.source.manga.model.MangaSourceData +import tachiyomi.domain.source.manga.model.StubMangaSource sealed class MangaExtension { @@ -44,10 +44,26 @@ sealed class MangaExtension { override val isNsfw: Boolean, override val hasReadme: Boolean, override val hasChangelog: Boolean, - val sources: List, + val sources: List, val apkName: String, val iconUrl: String, - ) : MangaExtension() + ) : MangaExtension() { + + data class MangaSource( + val id: Long, + val lang: String, + val name: String, + val baseUrl: String, + ) { + fun toStubSource(): StubMangaSource { + return StubMangaSource( + id = this.id, + lang = this.lang, + name = this.name, + ) + } + } + } data class Untrusted( override val name: String, @@ -62,18 +78,3 @@ sealed class MangaExtension { override val hasChangelog: Boolean = false, ) : MangaExtension() } - -data class AvailableMangaSources( - val id: Long, - val lang: String, - val name: String, - val baseUrl: String, -) { - fun toSourceData(): MangaSourceData { - return MangaSourceData( - id = this.id, - lang = this.lang, - name = this.name, - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt index ebcdb44ed..a00da6e50 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt @@ -15,9 +15,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import tachiyomi.domain.source.anime.model.AnimeSourceData import tachiyomi.domain.source.anime.model.StubAnimeSource -import tachiyomi.domain.source.anime.repository.AnimeSourceDataRepository +import tachiyomi.domain.source.anime.repository.AnimeStubSourceRepository import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.source.local.entries.anime.LocalAnimeSource import uy.kohesive.injekt.Injekt @@ -28,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap class AndroidAnimeSourceManager( private val context: Context, private val extensionManager: AnimeExtensionManager, - private val sourceRepository: AnimeSourceDataRepository, + private val sourceRepository: AnimeStubSourceRepository, ) : AnimeSourceManager { private val downloadManager: AnimeDownloadManager by injectLazy() @@ -56,7 +55,7 @@ class AndroidAnimeSourceManager( extensions.forEach { extension -> extension.sources.forEach { mutableMap[it.id] = it - registerStubSource(it.toSourceData()) + registerStubSource(it.toStubSource()) } } sourcesMapFlow.value = mutableMap @@ -68,7 +67,7 @@ class AndroidAnimeSourceManager( .collectLatest { sources -> val mutableMap = stubSourcesMap.toMutableMap() sources.forEach { - mutableMap[it.id] = StubAnimeSource(it) + mutableMap[it.id] = it } } } @@ -93,25 +92,25 @@ class AndroidAnimeSourceManager( return stubSourcesMap.values.filterNot { it.id in onlineSourceIds } } - private fun registerStubSource(sourceData: AnimeSourceData) { + private fun registerStubSource(source: StubAnimeSource) { scope.launch { - val (id, lang, name) = sourceData - val dbSourceData = sourceRepository.getAnimeSourceData(id) - if (dbSourceData == sourceData) return@launch - sourceRepository.upsertAnimeSourceData(id, lang, name) - if (dbSourceData != null) { - downloadManager.renameSource( - StubAnimeSource(dbSourceData), - StubAnimeSource(sourceData), - ) + val dbSource = sourceRepository.getStubAnimeSource(source.id) + if (dbSource == source) return@launch + sourceRepository.upsertStubAnimeSource(source.id, source.lang, source.name) + if (dbSource != null) { + downloadManager.renameSource(dbSource, source) } } } private suspend fun createStubSource(id: Long): StubAnimeSource { - sourceRepository.getAnimeSourceData(id)?.let { - return StubAnimeSource(it) + sourceRepository.getStubAnimeSource(id)?.let { + return it } - return StubAnimeSource(AnimeSourceData(id, "", "")) + extensionManager.getSourceData(id)?.let { + registerStubSource(it) + return it + } + return StubAnimeSource(id, "", "") } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt index a359d7656..8d1b087c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt @@ -4,7 +4,6 @@ import android.graphics.drawable.Drawable import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager -import tachiyomi.domain.source.anime.model.AnimeSourceData import tachiyomi.domain.source.anime.model.StubAnimeSource import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt @@ -14,7 +13,7 @@ fun AnimeSource.icon(): Drawable? = Injekt.get().getAppIc fun AnimeSource.getPreferenceKey(): String = "source_$id" -fun AnimeSource.toSourceData(): AnimeSourceData = AnimeSourceData(id = id, lang = lang, name = name) +fun AnimeSource.toStubSource(): StubAnimeSource = StubAnimeSource(id = id, lang = lang, name = name) fun AnimeSource.getNameForAnimeInfo(): String { val preferences = Injekt.get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt index 31cc0850b..ab424854a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt @@ -15,9 +15,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import tachiyomi.domain.source.manga.model.MangaSourceData import tachiyomi.domain.source.manga.model.StubMangaSource -import tachiyomi.domain.source.manga.repository.MangaSourceDataRepository +import tachiyomi.domain.source.manga.repository.MangaStubSourceRepository import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.source.local.entries.manga.LocalMangaSource import uy.kohesive.injekt.Injekt @@ -28,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap class AndroidMangaSourceManager( private val context: Context, private val extensionManager: MangaExtensionManager, - private val sourceRepository: MangaSourceDataRepository, + private val sourceRepository: MangaStubSourceRepository, ) : MangaSourceManager { private val downloadManager: MangaDownloadManager by injectLazy() @@ -56,7 +55,7 @@ class AndroidMangaSourceManager( extensions.forEach { extension -> extension.sources.forEach { mutableMap[it.id] = it - registerStubSource(it.toSourceData()) + registerStubSource(it.toStubSource()) } } sourcesMapFlow.value = mutableMap @@ -68,7 +67,7 @@ class AndroidMangaSourceManager( .collectLatest { sources -> val mutableMap = stubSourcesMap.toMutableMap() sources.forEach { - mutableMap[it.id] = StubMangaSource(it) + mutableMap[it.id] = it } } } @@ -93,26 +92,25 @@ class AndroidMangaSourceManager( return stubSourcesMap.values.filterNot { it.id in onlineSourceIds } } - private fun registerStubSource(sourceData: MangaSourceData) { + private fun registerStubSource(source: StubMangaSource) { scope.launch { - val (id, lang, name) = sourceData - val dbSourceData = sourceRepository.getMangaSourceData(id) - if (dbSourceData == sourceData) return@launch - sourceRepository.upsertMangaSourceData(id, lang, name) - if (dbSourceData != null) { - downloadManager.renameSource(StubMangaSource(dbSourceData), StubMangaSource(sourceData)) + val dbSource = sourceRepository.getStubMangaSource(source.id) + if (dbSource == source) return@launch + sourceRepository.upsertStubMangaSource(source.id, source.lang, source.name) + if (dbSource != null) { + downloadManager.renameSource(dbSource, source) } } } private suspend fun createStubSource(id: Long): StubMangaSource { - sourceRepository.getMangaSourceData(id)?.let { - return StubMangaSource(it) + sourceRepository.getStubMangaSource(id)?.let { + return it } extensionManager.getSourceData(id)?.let { registerStubSource(it) - return StubMangaSource(it) + return it } - return StubMangaSource(MangaSourceData(id, "", "")) + return StubMangaSource(id, "", "") } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt index afd17ceaa..61f4cdba2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt @@ -4,7 +4,6 @@ import android.graphics.drawable.Drawable import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.source.MangaSource -import tachiyomi.domain.source.manga.model.MangaSourceData import tachiyomi.domain.source.manga.model.StubMangaSource import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt @@ -14,7 +13,7 @@ fun MangaSource.icon(): Drawable? = Injekt.get().getAppIc fun MangaSource.getPreferenceKey(): String = "source_$id" -fun MangaSource.toSourceData(): MangaSourceData = MangaSourceData(id = id, lang = lang, name = name) +fun MangaSource.toStubSource(): StubMangaSource = StubMangaSource(id = id, lang = lang, name = name) fun MangaSource.getNameForMangaInfo(): String { val preferences = Injekt.get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 3631d2489..490585398 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -78,7 +78,6 @@ import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadCache import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.updater.AppUpdateChecker -import eu.kanade.tachiyomi.data.updater.AppUpdateResult import eu.kanade.tachiyomi.data.updater.RELEASE_URL import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi @@ -110,6 +109,7 @@ import kotlinx.coroutines.launch import logcat.LogPriority import tachiyomi.core.util.system.logcat import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.release.interactor.GetApplicationRelease import tachiyomi.presentation.core.components.material.Scaffold import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -352,7 +352,7 @@ class MainActivity : BaseActivity() { if (BuildConfig.INCLUDE_UPDATER) { try { val result = AppUpdateChecker().checkForUpdate(context) - if (result is AppUpdateResult.NewUpdate) { + if (result is GetApplicationRelease.Result.NewUpdate) { val updateScreen = NewUpdateScreen( versionName = result.release.version, changelogInfo = result.release.info, diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 827c05645..80fb752b9 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.library") kotlin("android") + kotlin("plugin.serialization") id("com.squareup.sqldelight") } @@ -34,3 +35,12 @@ dependencies { api(libs.sqldelight.coroutines) api(libs.sqldelight.android.paging) } + +tasks { + withType { + kotlinOptions.freeCompilerArgs += listOf( + "-Xcontext-receivers", + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", + ) + } +} diff --git a/data/src/main/java/tachiyomi/data/release/GithubRelease.kt b/data/src/main/java/tachiyomi/data/release/GithubRelease.kt new file mode 100644 index 000000000..3677dc122 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/release/GithubRelease.kt @@ -0,0 +1,31 @@ +package tachiyomi.data.release + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import tachiyomi.domain.release.model.Release + +/** + * Contains information about the latest release from GitHub. + */ +@Serializable +data class GithubRelease( + @SerialName("tag_name") val version: String, + @SerialName("body") val info: String, + @SerialName("html_url") val releaseLink: String, + @SerialName("assets") val assets: List, +) + +/** + * Assets class containing download url. + */ +@Serializable +data class GitHubAssets(@SerialName("browser_download_url") val downloadLink: String) + +val releaseMapper: (GithubRelease) -> Release = { + Release( + it.version, + it.info, + it.releaseLink, + it.assets.map(GitHubAssets::downloadLink), + ) +} diff --git a/data/src/main/java/tachiyomi/data/release/ReleaseServiceImpl.kt b/data/src/main/java/tachiyomi/data/release/ReleaseServiceImpl.kt new file mode 100644 index 000000000..ea2363d53 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/release/ReleaseServiceImpl.kt @@ -0,0 +1,25 @@ +package tachiyomi.data.release + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.awaitSuccess +import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json +import tachiyomi.domain.release.model.Release +import tachiyomi.domain.release.service.ReleaseService + +class ReleaseServiceImpl( + private val networkService: NetworkHelper, + private val json: Json, +) : ReleaseService { + + override suspend fun latest(repository: String): Release { + return with(json) { + networkService.client + .newCall(GET("https://api.github.com/repos/$repository/releases/latest")) + .awaitSuccess() + .parseAs() + .let(releaseMapper) + } + } +} diff --git a/data/src/main/java/tachiyomi/data/source/anime/AnimeSourceMapper.kt b/data/src/main/java/tachiyomi/data/source/anime/AnimeSourceMapper.kt index 779bf3543..50dfe7ea6 100644 --- a/data/src/main/java/tachiyomi/data/source/anime/AnimeSourceMapper.kt +++ b/data/src/main/java/tachiyomi/data/source/anime/AnimeSourceMapper.kt @@ -1,7 +1,7 @@ package tachiyomi.data.source.anime import tachiyomi.domain.source.anime.model.AnimeSource -import tachiyomi.domain.source.anime.model.AnimeSourceData +import tachiyomi.domain.source.anime.model.StubAnimeSource val animeSourceMapper: (eu.kanade.tachiyomi.animesource.AnimeSource) -> AnimeSource = { source -> AnimeSource( @@ -13,6 +13,6 @@ val animeSourceMapper: (eu.kanade.tachiyomi.animesource.AnimeSource) -> AnimeSou ) } -val animeSourceDataMapper: (Long, String, String) -> AnimeSourceData = { id, lang, name -> - AnimeSourceData(id, lang, name) +val animeSourceDataMapper: (Long, String, String) -> StubAnimeSource = { id, lang, name -> + StubAnimeSource(id, lang, name) } diff --git a/data/src/main/java/tachiyomi/data/source/anime/AnimeSourceDataRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/anime/AnimeStubSourceRepositoryImpl.kt similarity index 58% rename from data/src/main/java/tachiyomi/data/source/anime/AnimeSourceDataRepositoryImpl.kt rename to data/src/main/java/tachiyomi/data/source/anime/AnimeStubSourceRepositoryImpl.kt index 54a98d0fb..2ca7bd8cc 100644 --- a/data/src/main/java/tachiyomi/data/source/anime/AnimeSourceDataRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/anime/AnimeStubSourceRepositoryImpl.kt @@ -2,18 +2,18 @@ package tachiyomi.data.source.anime import kotlinx.coroutines.flow.Flow import tachiyomi.data.handlers.anime.AnimeDatabaseHandler -import tachiyomi.domain.source.anime.model.AnimeSourceData -import tachiyomi.domain.source.anime.repository.AnimeSourceDataRepository +import tachiyomi.domain.source.anime.model.StubAnimeSource +import tachiyomi.domain.source.anime.repository.AnimeStubSourceRepository -class AnimeSourceDataRepositoryImpl( +class AnimeStubSourceRepositoryImpl( private val handler: AnimeDatabaseHandler, -) : AnimeSourceDataRepository { +) : AnimeStubSourceRepository { - override fun subscribeAllAnime(): Flow> { + override fun subscribeAllAnime(): Flow> { return handler.subscribeToList { animesourcesQueries.findAll(animeSourceDataMapper) } } - override suspend fun getAnimeSourceData(id: Long): AnimeSourceData? { + override suspend fun getStubAnimeSource(id: Long): StubAnimeSource? { return handler.awaitOneOrNull { animesourcesQueries.findOne( id, @@ -22,7 +22,7 @@ class AnimeSourceDataRepositoryImpl( } } - override suspend fun upsertAnimeSourceData(id: Long, lang: String, name: String) { + override suspend fun upsertStubAnimeSource(id: Long, lang: String, name: String) { handler.await { animesourcesQueries.upsert(id, lang, name) } } } diff --git a/data/src/main/java/tachiyomi/data/source/manga/MangaSourceMapper.kt b/data/src/main/java/tachiyomi/data/source/manga/MangaSourceMapper.kt index be3044b1b..dbb75a2a1 100644 --- a/data/src/main/java/tachiyomi/data/source/manga/MangaSourceMapper.kt +++ b/data/src/main/java/tachiyomi/data/source/manga/MangaSourceMapper.kt @@ -1,7 +1,7 @@ package tachiyomi.data.source.manga -import tachiyomi.domain.source.manga.model.MangaSourceData import tachiyomi.domain.source.manga.model.Source +import tachiyomi.domain.source.manga.model.StubMangaSource val mangaSourceMapper: (eu.kanade.tachiyomi.source.MangaSource) -> Source = { source -> Source( @@ -13,6 +13,6 @@ val mangaSourceMapper: (eu.kanade.tachiyomi.source.MangaSource) -> Source = { so ) } -val mangaSourceDataMapper: (Long, String, String) -> MangaSourceData = { id, lang, name -> - MangaSourceData(id, lang, name) +val mangaSourceDataMapper: (Long, String, String) -> StubMangaSource = { id, lang, name -> + StubMangaSource(id, lang, name) } diff --git a/data/src/main/java/tachiyomi/data/source/manga/MangaSourceDataRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/manga/MangaStubSourceRepositoryImpl.kt similarity index 58% rename from data/src/main/java/tachiyomi/data/source/manga/MangaSourceDataRepositoryImpl.kt rename to data/src/main/java/tachiyomi/data/source/manga/MangaStubSourceRepositoryImpl.kt index 4e437c781..abd7a3668 100644 --- a/data/src/main/java/tachiyomi/data/source/manga/MangaSourceDataRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/manga/MangaStubSourceRepositoryImpl.kt @@ -2,18 +2,18 @@ package tachiyomi.data.source.manga import kotlinx.coroutines.flow.Flow import tachiyomi.data.handlers.manga.MangaDatabaseHandler -import tachiyomi.domain.source.manga.model.MangaSourceData -import tachiyomi.domain.source.manga.repository.MangaSourceDataRepository +import tachiyomi.domain.source.manga.model.StubMangaSource +import tachiyomi.domain.source.manga.repository.MangaStubSourceRepository -class MangaSourceDataRepositoryImpl( +class MangaStubSourceRepositoryImpl( private val handler: MangaDatabaseHandler, -) : MangaSourceDataRepository { +) : MangaStubSourceRepository { - override fun subscribeAllManga(): Flow> { + override fun subscribeAllManga(): Flow> { return handler.subscribeToList { sourcesQueries.findAll(mangaSourceDataMapper) } } - override suspend fun getMangaSourceData(id: Long): MangaSourceData? { + override suspend fun getStubMangaSource(id: Long): StubMangaSource? { return handler.awaitOneOrNull { sourcesQueries.findOne( id, @@ -22,7 +22,7 @@ class MangaSourceDataRepositoryImpl( } } - override suspend fun upsertMangaSourceData(id: Long, lang: String, name: String) { + override suspend fun upsertStubMangaSource(id: Long, lang: String, name: String) { handler.await { sourcesQueries.upsert(id, lang, name) } } } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index da6715940..d2d2c71e9 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -23,4 +23,13 @@ dependencies { api(libs.sqldelight.android.paging) testImplementation(libs.bundles.test) + testImplementation(kotlinx.coroutines.test) +} + +tasks { + withType { + kotlinOptions.freeCompilerArgs += listOf( + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + ) + } } diff --git a/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt b/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt new file mode 100644 index 000000000..435d87a29 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt @@ -0,0 +1,79 @@ +package tachiyomi.domain.release.interactor + +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.domain.release.model.Release +import tachiyomi.domain.release.service.ReleaseService +import java.time.Instant +import java.time.temporal.ChronoUnit + +class GetApplicationRelease( + private val service: ReleaseService, + private val preferenceStore: PreferenceStore, +) { + + private val lastChecked: Preference by lazy { + preferenceStore.getLong("last_app_check", 0) + } + + suspend fun await(arguments: Arguments): Result { + val now = Instant.now() + + // Limit checks to once every 3 days at most + if (arguments.forceCheck.not() && now.isBefore(Instant.ofEpochMilli(lastChecked.get()).plus(3, ChronoUnit.DAYS))) { + return Result.NoNewUpdate + } + + val release = service.latest(arguments.repository) + + lastChecked.set(now.toEpochMilli()) + + // Check if latest version is different from current version + val isNewVersion = isNewVersion(arguments.isPreview, arguments.commitCount, arguments.versionName, release.version) + return when { + isNewVersion && arguments.isThirdParty -> Result.ThirdPartyInstallation + isNewVersion -> Result.NewUpdate(release) + else -> Result.NoNewUpdate + } + } + + private fun isNewVersion(isPreview: Boolean, commitCount: Int, versionName: String, versionTag: String): Boolean { + // Removes prefixes like "r" or "v" + val newVersion = versionTag.replace("[^\\d.]".toRegex(), "") + return if (isPreview) { + // Preview builds: based on releases in "tachiyomiorg/tachiyomi-preview" repo + // tagged as something like "r1234" + newVersion.toInt() > commitCount + } else { + // Release builds: based on releases in "tachiyomiorg/tachiyomi" repo + // tagged as something like "v0.1.2" + val oldVersion = versionName.replace("[^\\d.]".toRegex(), "") + + val newSemVer = newVersion.split(".").map { it.toInt() } + val oldSemVer = oldVersion.split(".").map { it.toInt() } + + oldSemVer.mapIndexed { index, i -> + if (newSemVer[index] > i) { + return true + } + } + + false + } + } + + data class Arguments( + val isPreview: Boolean, + val isThirdParty: Boolean, + val commitCount: Int, + val versionName: String, + val repository: String, + val forceCheck: Boolean = false, + ) + + sealed class Result { + class NewUpdate(val release: Release) : Result() + object NoNewUpdate : Result() + object ThirdPartyInstallation : Result() + } +} diff --git a/domain/src/main/java/tachiyomi/domain/release/model/Release.kt b/domain/src/main/java/tachiyomi/domain/release/model/Release.kt new file mode 100644 index 000000000..f8b0cc2b5 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/release/model/Release.kt @@ -0,0 +1,35 @@ +package tachiyomi.domain.release.model + +import android.os.Build + +/** + * Contains information about the latest release. + */ +data class Release( + val version: String, + val info: String, + val releaseLink: String, + private val assets: List, +) { + + /** + * Get download link of latest release from the assets. + * @return download link of latest release. + */ + fun getDownloadLink(): String { + val apkVariant = when (Build.SUPPORTED_ABIS[0]) { + "arm64-v8a" -> "-arm64-v8a" + "armeabi-v7a" -> "-armeabi-v7a" + "x86" -> "-x86" + "x86_64" -> "-x86_64" + else -> "" + } + + return assets.find { it.contains("aniyomi$apkVariant-") } ?: assets[0] + } + + /** + * Assets class containing download url. + */ + data class Assets(val downloadLink: String) +} diff --git a/domain/src/main/java/tachiyomi/domain/release/service/ReleaseService.kt b/domain/src/main/java/tachiyomi/domain/release/service/ReleaseService.kt new file mode 100644 index 000000000..61bbdb351 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/release/service/ReleaseService.kt @@ -0,0 +1,8 @@ +package tachiyomi.domain.release.service + +import tachiyomi.domain.release.model.Release + +interface ReleaseService { + + suspend fun latest(repository: String): Release +} diff --git a/domain/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceData.kt b/domain/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceData.kt deleted file mode 100644 index 22bf2fcee..000000000 --- a/domain/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceData.kt +++ /dev/null @@ -1,10 +0,0 @@ -package tachiyomi.domain.source.anime.model - -data class AnimeSourceData( - val id: Long, - val lang: String, - val name: String, -) { - - val isMissingInfo: Boolean = name.isBlank() || lang.isBlank() -} diff --git a/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt b/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt index 7631c49cc..c2f35f8f0 100644 --- a/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt +++ b/domain/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt @@ -6,13 +6,13 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video @Suppress("OverridingDeprecatedMember") -class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource { +class StubAnimeSource( + override val id: Long, + override val name: String, + override val lang: String, +) : AnimeSource { - override val id: Long = sourceData.id - - override val name: String = sourceData.name.ifBlank { id.toString() } - - override val lang: String = sourceData.lang + val isInvalid: Boolean = name.isBlank() || lang.isBlank() override suspend fun getAnimeDetails(anime: SAnime): SAnime { throw AnimeSourceNotInstalledException() @@ -27,7 +27,7 @@ class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource { } override fun toString(): String { - return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString() + return if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString() } } class AnimeSourceNotInstalledException : Exception() diff --git a/domain/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceDataRepository.kt b/domain/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceDataRepository.kt deleted file mode 100644 index 8482a0dea..000000000 --- a/domain/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceDataRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package tachiyomi.domain.source.anime.repository - -import kotlinx.coroutines.flow.Flow -import tachiyomi.domain.source.anime.model.AnimeSourceData - -interface AnimeSourceDataRepository { - fun subscribeAllAnime(): Flow> - - suspend fun getAnimeSourceData(id: Long): AnimeSourceData? - - suspend fun upsertAnimeSourceData(id: Long, lang: String, name: String) -} diff --git a/domain/src/main/java/tachiyomi/domain/source/anime/repository/AnimeStubSourceRepository.kt b/domain/src/main/java/tachiyomi/domain/source/anime/repository/AnimeStubSourceRepository.kt new file mode 100644 index 000000000..d1fe81f97 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/source/anime/repository/AnimeStubSourceRepository.kt @@ -0,0 +1,13 @@ + +package tachiyomi.domain.source.anime.repository + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.source.anime.model.StubAnimeSource + +interface AnimeStubSourceRepository { + fun subscribeAllAnime(): Flow> + + suspend fun getStubAnimeSource(id: Long): StubAnimeSource? + + suspend fun upsertStubAnimeSource(id: Long, lang: String, name: String) +} diff --git a/domain/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt b/domain/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt deleted file mode 100644 index 9b8571dcd..000000000 --- a/domain/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt +++ /dev/null @@ -1,10 +0,0 @@ -package tachiyomi.domain.source.manga.model - -data class MangaSourceData( - val id: Long, - val lang: String, - val name: String, -) { - - val isMissingInfo: Boolean = name.isBlank() || lang.isBlank() -} diff --git a/domain/src/main/java/tachiyomi/domain/source/manga/model/StubMangaSource.kt b/domain/src/main/java/tachiyomi/domain/source/manga/model/StubMangaSource.kt index 1b4efe6f2..ab015424a 100644 --- a/domain/src/main/java/tachiyomi/domain/source/manga/model/StubMangaSource.kt +++ b/domain/src/main/java/tachiyomi/domain/source/manga/model/StubMangaSource.kt @@ -7,13 +7,13 @@ import eu.kanade.tachiyomi.source.model.SManga import rx.Observable @Suppress("OverridingDeprecatedMember") -class StubMangaSource(private val sourceData: MangaSourceData) : MangaSource { +class StubMangaSource( + override val id: Long, + override val name: String, + override val lang: String, +) : MangaSource { - override val id: Long = sourceData.id - - override val name: String = sourceData.name.ifBlank { id.toString() } - - override val lang: String = sourceData.lang + val isInvalid: Boolean = name.isBlank() || lang.isBlank() override suspend fun getMangaDetails(manga: SManga): SManga { throw SourceNotInstalledException() @@ -43,7 +43,7 @@ class StubMangaSource(private val sourceData: MangaSourceData) : MangaSource { } override fun toString(): String { - return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString() + return if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString() } } diff --git a/domain/src/main/java/tachiyomi/domain/source/manga/repository/MangaSourceDataRepository.kt b/domain/src/main/java/tachiyomi/domain/source/manga/repository/MangaSourceDataRepository.kt deleted file mode 100644 index d1d5f3488..000000000 --- a/domain/src/main/java/tachiyomi/domain/source/manga/repository/MangaSourceDataRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package tachiyomi.domain.source.manga.repository - -import kotlinx.coroutines.flow.Flow -import tachiyomi.domain.source.manga.model.MangaSourceData - -interface MangaSourceDataRepository { - fun subscribeAllManga(): Flow> - - suspend fun getMangaSourceData(id: Long): MangaSourceData? - - suspend fun upsertMangaSourceData(id: Long, lang: String, name: String) -} diff --git a/domain/src/main/java/tachiyomi/domain/source/manga/repository/MangaStubSourceRepository.kt b/domain/src/main/java/tachiyomi/domain/source/manga/repository/MangaStubSourceRepository.kt new file mode 100644 index 000000000..94a462fe4 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/source/manga/repository/MangaStubSourceRepository.kt @@ -0,0 +1,12 @@ +package tachiyomi.domain.source.manga.repository + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.source.manga.model.StubMangaSource + +interface MangaStubSourceRepository { + fun subscribeAllManga(): Flow> + + suspend fun getStubMangaSource(id: Long): StubMangaSource? + + suspend fun upsertStubMangaSource(id: Long, lang: String, name: String) +} diff --git a/domain/src/test/java/tachiyomi/domain/release/interactor/GetApplicationReleaseTest.kt b/domain/src/test/java/tachiyomi/domain/release/interactor/GetApplicationReleaseTest.kt new file mode 100644 index 000000000..41df15221 --- /dev/null +++ b/domain/src/test/java/tachiyomi/domain/release/interactor/GetApplicationReleaseTest.kt @@ -0,0 +1,166 @@ +package tachiyomi.domain.release.interactor + +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.domain.release.model.Release +import tachiyomi.domain.release.service.ReleaseService +import java.time.Instant + +class GetApplicationReleaseTest { + + lateinit var getApplicationRelease: GetApplicationRelease + lateinit var releaseService: ReleaseService + lateinit var preference: Preference + + @BeforeEach + fun beforeEach() { + val preferenceStore = mockk() + preference = mockk() + every { preferenceStore.getLong(any(), any()) } returns preference + releaseService = mockk() + + getApplicationRelease = GetApplicationRelease(releaseService, preferenceStore) + } + + @Test + fun `When has update but is third party expect third party installation`() = runTest { + every { preference.get() } returns 0 + every { preference.set(any()) }.answers { } + + coEvery { releaseService.latest(any()) } returns Release( + "v2.0.0", + "info", + "http://example.com/release_link", + listOf("http://example.com/assets"), + ) + + val result = getApplicationRelease.await( + GetApplicationRelease.Arguments( + isPreview = false, + isThirdParty = true, + commitCount = 0, + versionName = "v1.0.0", + repository = "test", + ), + ) + + result shouldBe GetApplicationRelease.Result.ThirdPartyInstallation + } + + @Test + fun `When has update but is preview expect new update`() = runTest { + every { preference.get() } returns 0 + every { preference.set(any()) }.answers { } + + val release = Release( + "r2000", + "info", + "http://example.com/release_link", + listOf("http://example.com/assets"), + ) + + coEvery { releaseService.latest(any()) } returns release + + val result = getApplicationRelease.await( + GetApplicationRelease.Arguments( + isPreview = true, + isThirdParty = false, + commitCount = 1000, + versionName = "", + repository = "test", + ), + ) + + (result as GetApplicationRelease.Result.NewUpdate).release shouldBe GetApplicationRelease.Result.NewUpdate(release).release + } + + @Test + fun `When has update expect new update`() = runTest { + every { preference.get() } returns 0 + every { preference.set(any()) }.answers { } + + val release = Release( + "v2.0.0", + "info", + "http://example.com/release_link", + listOf("http://example.com/assets"), + ) + + coEvery { releaseService.latest(any()) } returns release + + val result = getApplicationRelease.await( + GetApplicationRelease.Arguments( + isPreview = false, + isThirdParty = false, + commitCount = 0, + versionName = "v1.0.0", + repository = "test", + ), + ) + + (result as GetApplicationRelease.Result.NewUpdate).release shouldBe GetApplicationRelease.Result.NewUpdate(release).release + } + + @Test + fun `When has no update expect no new update`() = runTest { + every { preference.get() } returns 0 + every { preference.set(any()) }.answers { } + + val release = Release( + "v1.0.0", + "info", + "http://example.com/release_link", + listOf("http://example.com/assets"), + ) + + coEvery { releaseService.latest(any()) } returns release + + val result = getApplicationRelease.await( + GetApplicationRelease.Arguments( + isPreview = false, + isThirdParty = false, + commitCount = 0, + versionName = "v2.0.0", + repository = "test", + ), + ) + + result shouldBe GetApplicationRelease.Result.NoNewUpdate + } + + @Test + fun `When now is before three days expect no new update`() = runTest { + every { preference.get() } returns Instant.now().toEpochMilli() + every { preference.set(any()) }.answers { } + + val release = Release( + "v1.0.0", + "info", + "http://example.com/release_link", + listOf("http://example.com/assets"), + ) + + coEvery { releaseService.latest(any()) } returns release + + val result = getApplicationRelease.await( + GetApplicationRelease.Arguments( + isPreview = false, + isThirdParty = false, + commitCount = 0, + versionName = "v2.0.0", + repository = "test", + ), + ) + + coVerify(exactly = 0) { releaseService.latest(any()) } + result shouldBe GetApplicationRelease.Result.NoNewUpdate + } +} diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 7d5eba9d7..4e0ac36f3 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -11,6 +11,7 @@ coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", vers coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava" } +coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" } serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization_version" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 16730c66c..106ad9c0e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,7 +60,7 @@ photoview = "com.github.chrisbanes:PhotoView:2.3.0" directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" insetter = "dev.chrisbanes.insetter:insetter:0.6.1" compose-cascade = "me.saket.cascade:cascade-compose:2.0.0-rc02" -compose-materialmotion = "io.github.fornewid:material-motion-compose-core:0.11.3" +compose-materialmotion = "io.github.fornewid:material-motion-compose-core:0.12.1" compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0" logcat = "com.squareup.logcat:logcat:0.1" @@ -91,6 +91,8 @@ voyager-transitions = { module = "ca.gosyer:voyager-transitions", version.ref = kotlinter = "org.jmailen.gradle:kotlinter-gradle:3.13.0" +mockk = "io.mockk:mockk:1.13.5" + aniyomi-mpv = "com.github.aniyomiorg:aniyomi-mpv-lib:1.10.n" ffmpeg-kit = "com.github.jmir1:ffmpeg-kit:1.10" arthenica-smartexceptions = "com.arthenica:smart-exception-java:0.1.1" @@ -106,4 +108,4 @@ coil = ["coil-core", "coil-gif", "coil-compose"] shizuku = ["shizuku-api", "shizuku-provider"] voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"] richtext = ["richtext-commonmark", "richtext-m3"] -test = ["junit", "kotest-assertions"] +test = ["junit", "kotest-assertions", "mockk"]