mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 12:17:12 +03:00
parent
861a5ad913
commit
c3062d2ed7
40 changed files with 598 additions and 344 deletions
|
@ -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<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
|
||||
addSingletonFactory<AnimeTrackRepository> { AnimeTrackRepositoryImpl(get()) }
|
||||
addFactory { DeleteAnimeTrack(get()) }
|
||||
addFactory { GetTracksPerAnime(get()) }
|
||||
|
@ -266,7 +272,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetMangaUpdates(get()) }
|
||||
|
||||
addSingletonFactory<AnimeSourceRepository> { AnimeSourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<AnimeSourceDataRepository> { AnimeSourceDataRepositoryImpl(get()) }
|
||||
addSingletonFactory<AnimeStubSourceRepository> { AnimeStubSourceRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledAnimeSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithAnimeSources(get(), get()) }
|
||||
addFactory { GetRemoteAnime(get()) }
|
||||
|
@ -276,7 +282,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { ToggleAnimeSourcePin(get()) }
|
||||
|
||||
addSingletonFactory<MangaSourceRepository> { MangaSourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<MangaSourceDataRepository> { MangaSourceDataRepositoryImpl(get()) }
|
||||
addSingletonFactory<MangaStubSourceRepository> { MangaStubSourceRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledMangaSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithMangaSources(get(), get()) }
|
||||
addFactory { GetRemoteManga(get()) }
|
||||
|
|
|
@ -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 -> {}
|
||||
|
|
|
@ -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<Long> 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<GithubRelease>()
|
||||
.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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Assets>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
|
@ -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<AnimeExtension.Available>())
|
||||
val availableExtensionsFlow = _availableAnimeExtensionsFlow.asStateFlow()
|
||||
|
||||
private var availableAnimeExtensionsSourcesData: Map<Long, AnimeSourceData> = emptyMap()
|
||||
private var availableAnimeExtensionsSourcesData: Map<Long, StubAnimeSource> = emptyMap()
|
||||
|
||||
private fun setupAvailableAnimeExtensionsSourcesDataMap(animeextensions: List<AnimeExtension.Available>) {
|
||||
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()
|
||||
|
|
|
@ -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<AnimeExtensionSourceJsonObject>.toAnimeExtensionSources(): List<AvailableAnimeSources> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<AvailableAnimeSources>,
|
||||
val sources: List<AnimeSource>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MangaExtension.Available>())
|
||||
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow()
|
||||
|
||||
private var availableExtensionsSourcesData: Map<Long, MangaSourceData> = emptyMap()
|
||||
private var availableExtensionsSourcesData: Map<Long, StubMangaSource> = emptyMap()
|
||||
|
||||
private fun setupAvailableExtensionsSourcesDataMap(extensions: List<MangaExtension.Available>) {
|
||||
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()
|
||||
|
|
|
@ -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<ExtensionSourceJsonObject>.toExtensionSources(): List<AvailableMangaSources> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<AvailableMangaSources>,
|
||||
val sources: List<MangaSource>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, "", "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AnimeExtensionManager>().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<SourcePreferences>()
|
||||
|
|
|
@ -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, "", "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MangaExtensionManager>().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<SourcePreferences>()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-Xcontext-receivers",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
31
data/src/main/java/tachiyomi/data/release/GithubRelease.kt
Normal file
31
data/src/main/java/tachiyomi/data/release/GithubRelease.kt
Normal file
|
@ -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<GitHubAssets>,
|
||||
)
|
||||
|
||||
/**
|
||||
* 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),
|
||||
)
|
||||
}
|
|
@ -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<GithubRelease>()
|
||||
.let(releaseMapper)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<List<AnimeSourceData>> {
|
||||
override fun subscribeAllAnime(): Flow<List<StubAnimeSource>> {
|
||||
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) }
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<List<MangaSourceData>> {
|
||||
override fun subscribeAllManga(): Flow<List<StubMangaSource>> {
|
||||
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) }
|
||||
}
|
||||
}
|
|
@ -23,4 +23,13 @@ dependencies {
|
|||
api(libs.sqldelight.android.paging)
|
||||
|
||||
testImplementation(libs.bundles.test)
|
||||
testImplementation(kotlinx.coroutines.test)
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Long> 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()
|
||||
}
|
||||
}
|
|
@ -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<String>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package tachiyomi.domain.release.service
|
||||
|
||||
import tachiyomi.domain.release.model.Release
|
||||
|
||||
interface ReleaseService {
|
||||
|
||||
suspend fun latest(repository: String): Release
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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<List<AnimeSourceData>>
|
||||
|
||||
suspend fun getAnimeSourceData(id: Long): AnimeSourceData?
|
||||
|
||||
suspend fun upsertAnimeSourceData(id: Long, lang: String, name: String)
|
||||
}
|
|
@ -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<List<StubAnimeSource>>
|
||||
|
||||
suspend fun getStubAnimeSource(id: Long): StubAnimeSource?
|
||||
|
||||
suspend fun upsertStubAnimeSource(id: Long, lang: String, name: String)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<List<MangaSourceData>>
|
||||
|
||||
suspend fun getMangaSourceData(id: Long): MangaSourceData?
|
||||
|
||||
suspend fun upsertMangaSourceData(id: Long, lang: String, name: String)
|
||||
}
|
|
@ -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<List<StubMangaSource>>
|
||||
|
||||
suspend fun getStubMangaSource(id: Long): StubMangaSource?
|
||||
|
||||
suspend fun upsertStubMangaSource(id: Long, lang: String, name: String)
|
||||
}
|
|
@ -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<Long>
|
||||
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
val preferenceStore = mockk<PreferenceStore>()
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Reference in a new issue