mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-22 04:39:32 +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.history.manga.MangaHistoryRepositoryImpl
|
||||||
import tachiyomi.data.items.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.items.chapter.ChapterRepositoryImpl
|
||||||
import tachiyomi.data.items.episode.EpisodeRepositoryImpl
|
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.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.MangaSourceRepositoryImpl
|
||||||
|
import tachiyomi.data.source.manga.MangaStubSourceRepositoryImpl
|
||||||
import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl
|
import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl
|
||||||
import tachiyomi.data.track.manga.MangaTrackRepositoryImpl
|
import tachiyomi.data.track.manga.MangaTrackRepositoryImpl
|
||||||
import tachiyomi.data.updates.anime.AnimeUpdatesRepositoryImpl
|
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.ShouldUpdateDbEpisode
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
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.GetAnimeSourcesWithNonLibraryAnime
|
||||||
import tachiyomi.domain.source.anime.interactor.GetRemoteAnime
|
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.AnimeSourceRepository
|
||||||
|
import tachiyomi.domain.source.anime.repository.AnimeStubSourceRepository
|
||||||
import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga
|
import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga
|
||||||
import tachiyomi.domain.source.manga.interactor.GetRemoteManga
|
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.MangaSourceRepository
|
||||||
|
import tachiyomi.domain.source.manga.repository.MangaStubSourceRepository
|
||||||
import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack
|
import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack
|
||||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||||
import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime
|
import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime
|
||||||
|
@ -206,6 +209,9 @@ class DomainModule : InjektModule {
|
||||||
addFactory { UpdateManga(get()) }
|
addFactory { UpdateManga(get()) }
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<AnimeTrackRepository> { AnimeTrackRepositoryImpl(get()) }
|
addSingletonFactory<AnimeTrackRepository> { AnimeTrackRepositoryImpl(get()) }
|
||||||
addFactory { DeleteAnimeTrack(get()) }
|
addFactory { DeleteAnimeTrack(get()) }
|
||||||
addFactory { GetTracksPerAnime(get()) }
|
addFactory { GetTracksPerAnime(get()) }
|
||||||
|
@ -266,7 +272,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetMangaUpdates(get()) }
|
addFactory { GetMangaUpdates(get()) }
|
||||||
|
|
||||||
addSingletonFactory<AnimeSourceRepository> { AnimeSourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<AnimeSourceRepository> { AnimeSourceRepositoryImpl(get(), get()) }
|
||||||
addSingletonFactory<AnimeSourceDataRepository> { AnimeSourceDataRepositoryImpl(get()) }
|
addSingletonFactory<AnimeStubSourceRepository> { AnimeStubSourceRepositoryImpl(get()) }
|
||||||
addFactory { GetEnabledAnimeSources(get(), get()) }
|
addFactory { GetEnabledAnimeSources(get(), get()) }
|
||||||
addFactory { GetLanguagesWithAnimeSources(get(), get()) }
|
addFactory { GetLanguagesWithAnimeSources(get(), get()) }
|
||||||
addFactory { GetRemoteAnime(get()) }
|
addFactory { GetRemoteAnime(get()) }
|
||||||
|
@ -276,7 +282,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { ToggleAnimeSourcePin(get()) }
|
addFactory { ToggleAnimeSourcePin(get()) }
|
||||||
|
|
||||||
addSingletonFactory<MangaSourceRepository> { MangaSourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<MangaSourceRepository> { MangaSourceRepositoryImpl(get(), get()) }
|
||||||
addSingletonFactory<MangaSourceDataRepository> { MangaSourceDataRepositoryImpl(get()) }
|
addSingletonFactory<MangaStubSourceRepository> { MangaStubSourceRepositoryImpl(get()) }
|
||||||
addFactory { GetEnabledMangaSources(get(), get()) }
|
addFactory { GetEnabledMangaSources(get(), get()) }
|
||||||
addFactory { GetLanguagesWithMangaSources(get(), get()) }
|
addFactory { GetLanguagesWithMangaSources(get(), get()) }
|
||||||
addFactory { GetRemoteManga(get()) }
|
addFactory { GetRemoteManga(get()) }
|
||||||
|
|
|
@ -29,7 +29,6 @@ import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
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.data.updater.RELEASE_URL
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
|
@ -41,6 +40,7 @@ import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
import tachiyomi.presentation.core.components.LinkIcon
|
import tachiyomi.presentation.core.components.LinkIcon
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
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.
|
* 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()
|
val updateChecker = AppUpdateChecker()
|
||||||
withUIContext {
|
withUIContext {
|
||||||
context.toast(R.string.update_check_look_for_updates)
|
context.toast(R.string.update_check_look_for_updates)
|
||||||
try {
|
try {
|
||||||
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
|
when (val result = withIOContext { updateChecker.checkForUpdate(context, forceCheck = true) }) {
|
||||||
is AppUpdateResult.NewUpdate -> {
|
is GetApplicationRelease.Result.NewUpdate -> {
|
||||||
onAvailableUpdate(result)
|
onAvailableUpdate(result)
|
||||||
}
|
}
|
||||||
is AppUpdateResult.NoNewUpdate -> {
|
is GetApplicationRelease.Result.NoNewUpdate -> {
|
||||||
context.toast(R.string.update_check_no_new_updates)
|
context.toast(R.string.update_check_no_new_updates)
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
|
|
|
@ -11,83 +11,38 @@ import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
class AppUpdateChecker {
|
class AppUpdateChecker {
|
||||||
|
|
||||||
private val networkService: NetworkHelper by injectLazy()
|
private val getApplicationRelease: GetApplicationRelease by injectLazy()
|
||||||
private val preferenceStore: PreferenceStore by injectLazy()
|
|
||||||
private val json: Json 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 {
|
return withIOContext {
|
||||||
val result = with(json) {
|
val result = getApplicationRelease.await(
|
||||||
networkService.client
|
GetApplicationRelease.Arguments(
|
||||||
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
|
BuildConfig.PREVIEW,
|
||||||
.awaitSuccess()
|
context.isInstalledFromFDroid(),
|
||||||
.parseAs<GithubRelease>()
|
BuildConfig.COMMIT_COUNT.toInt(),
|
||||||
.let {
|
BuildConfig.VERSION_NAME,
|
||||||
lastAppCheck.set(Date().time)
|
GITHUB_REPO,
|
||||||
|
forceCheck,
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
|
is GetApplicationRelease.Result.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
|
||||||
is AppUpdateResult.NewUpdateFdroidInstallation -> AppUpdateNotifier(context).promptFdroidUpdate()
|
is GetApplicationRelease.Result.ThirdPartyInstallation -> AppUpdateNotifier(context).promptFdroidUpdate()
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
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 {
|
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.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notify
|
import eu.kanade.tachiyomi.util.system.notify
|
||||||
|
import tachiyomi.domain.release.model.Release
|
||||||
|
|
||||||
internal class AppUpdateNotifier(private val context: Context) {
|
internal class AppUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
@ -27,18 +28,22 @@ internal class AppUpdateNotifier(private val context: Context) {
|
||||||
context.notify(id, build())
|
context.notify(id, build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER)
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("LaunchActivityFromNotification")
|
@SuppressLint("LaunchActivityFromNotification")
|
||||||
fun promptUpdate(release: GithubRelease) {
|
fun promptUpdate(release: Release) {
|
||||||
val intent = Intent(context, AppUpdateService::class.java).apply {
|
val updateIntent = Intent(context, AppUpdateService::class.java).run {
|
||||||
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
|
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
|
||||||
putExtra(AppUpdateService.EXTRA_DOWNLOAD_TITLE, release.version)
|
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
|
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) {
|
with(notificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.update_check_notification_update_available))
|
setContentTitle(context.getString(R.string.update_check_notification_update_available))
|
||||||
|
@ -55,7 +60,7 @@ internal class AppUpdateNotifier(private val context: Context) {
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_info_24dp,
|
R.drawable.ic_info_24dp,
|
||||||
context.getString(R.string.whats_new),
|
context.getString(R.string.whats_new),
|
||||||
releaseInfoIntent,
|
releaseIntent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
notificationBuilder.show()
|
notificationBuilder.show()
|
||||||
|
@ -164,13 +169,12 @@ internal class AppUpdateNotifier(private val context: Context) {
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_close_24dp,
|
R.drawable.ic_close_24dp,
|
||||||
context.getString(R.string.action_cancel),
|
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)
|
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.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import logcat.LogPriority
|
import kotlinx.coroutines.Dispatchers
|
||||||
import okhttp3.Call
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.internal.http2.ErrorCode
|
import okhttp3.internal.http2.ErrorCode
|
||||||
import okhttp3.internal.http2.StreamResetException
|
import okhttp3.internal.http2.StreamResetException
|
||||||
import tachiyomi.core.util.lang.launchIO
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ class AppUpdateService : Service() {
|
||||||
|
|
||||||
private lateinit var notifier: AppUpdateNotifier
|
private lateinit var notifier: AppUpdateNotifier
|
||||||
|
|
||||||
private var runningJob: Job? = null
|
private val job = SupervisorJob()
|
||||||
private var runningCall: Call? = null
|
private val serviceScope = CoroutineScope(Dispatchers.IO + job)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
notifier = AppUpdateNotifier(this)
|
notifier = AppUpdateNotifier(this)
|
||||||
|
@ -62,11 +62,11 @@ class AppUpdateService : Service() {
|
||||||
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY
|
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY
|
||||||
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
|
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
|
||||||
|
|
||||||
runningJob = launchIO {
|
serviceScope.launch {
|
||||||
downloadApk(title, url)
|
downloadApk(title, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
runningJob?.invokeOnCompletion { stopSelf(startId) }
|
job.invokeOnCompletion { stopSelf(startId) }
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ class AppUpdateService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun destroyJob() {
|
private fun destroyJob() {
|
||||||
runningJob?.cancel()
|
serviceScope.cancel()
|
||||||
runningCall?.cancel()
|
job.cancel()
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
}
|
}
|
||||||
|
@ -116,9 +116,8 @@ class AppUpdateService : Service() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Download the new update.
|
// Download the new update.
|
||||||
val call = network.client.newCachelessCallWithProgress(GET(url), progressListener)
|
val response = network.client.newCachelessCallWithProgress(GET(url), progressListener)
|
||||||
runningCall = call
|
.await()
|
||||||
val response = call.await()
|
|
||||||
|
|
||||||
// File where the apk will be saved.
|
// File where the apk will be saved.
|
||||||
val apkFile = File(externalCacheDir, "update.apk")
|
val apkFile = File(externalCacheDir, "update.apk")
|
||||||
|
@ -131,10 +130,9 @@ class AppUpdateService : Service() {
|
||||||
}
|
}
|
||||||
notifier.promptInstall(apkFile.getUriCompat(this))
|
notifier.promptInstall(apkFile.getUriCompat(this))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
val shouldCancel = e is CancellationException ||
|
||||||
if (e is CancellationException ||
|
|
||||||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
|
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
|
||||||
) {
|
if (shouldCancel) {
|
||||||
notifier.cancel()
|
notifier.cancel()
|
||||||
} else {
|
} else {
|
||||||
notifier.onDownloadError(url)
|
notifier.onDownloadError(url)
|
||||||
|
@ -165,11 +163,11 @@ class AppUpdateService : Service() {
|
||||||
fun start(context: Context, url: String, title: String? = context.getString(R.string.app_name)) {
|
fun start(context: Context, url: String, title: String? = context.getString(R.string.app_name)) {
|
||||||
if (isRunning(context)) return
|
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_TITLE, title)
|
||||||
putExtra(EXTRA_DOWNLOAD_URL, url)
|
putExtra(EXTRA_DOWNLOAD_URL, url)
|
||||||
|
ContextCompat.startForegroundService(context, this)
|
||||||
}
|
}
|
||||||
ContextCompat.startForegroundService(context, intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,10 +186,10 @@ class AppUpdateService : Service() {
|
||||||
* @return [PendingIntent]
|
* @return [PendingIntent]
|
||||||
*/
|
*/
|
||||||
internal fun downloadApkPendingService(context: Context, url: String): 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)
|
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.api.AnimeExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
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.AnimeExtensionInstallReceiver
|
||||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller
|
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller
|
||||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
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.preference.plusAssign
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
@ -22,7 +22,7 @@ import rx.Observable
|
||||||
import tachiyomi.core.util.lang.launchNow
|
import tachiyomi.core.util.lang.launchNow
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
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.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -73,12 +73,12 @@ class AnimeExtensionManager(
|
||||||
private val _availableAnimeExtensionsFlow = MutableStateFlow(emptyList<AnimeExtension.Available>())
|
private val _availableAnimeExtensionsFlow = MutableStateFlow(emptyList<AnimeExtension.Available>())
|
||||||
val availableExtensionsFlow = _availableAnimeExtensionsFlow.asStateFlow()
|
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>) {
|
private fun setupAvailableAnimeExtensionsSourcesDataMap(animeextensions: List<AnimeExtension.Available>) {
|
||||||
if (animeextensions.isEmpty()) return
|
if (animeextensions.isEmpty()) return
|
||||||
availableAnimeExtensionsSourcesData = animeextensions
|
availableAnimeExtensionsSourcesData = animeextensions
|
||||||
.flatMap { ext -> ext.sources.map { it.toAnimeSourceData() } }
|
.flatMap { ext -> ext.sources.map { it.toStubSource() } }
|
||||||
.associateBy { it.id }
|
.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,8 +145,8 @@ class AnimeExtensionManager(
|
||||||
// Use the source lang as some aren't present on the animeextension level.
|
// Use the source lang as some aren't present on the animeextension level.
|
||||||
val availableLanguages = animeextensions
|
val availableLanguages = animeextensions
|
||||||
.flatMap(AnimeExtension.Available::sources)
|
.flatMap(AnimeExtension.Available::sources)
|
||||||
.distinctBy(AvailableAnimeSources::lang)
|
.distinctBy(AnimeExtension.Available.AnimeSource::lang)
|
||||||
.map(AvailableAnimeSources::lang)
|
.map(AnimeExtension.Available.AnimeSource::lang)
|
||||||
|
|
||||||
val deviceLanguage = Locale.getDefault().language
|
val deviceLanguage = Locale.getDefault().language
|
||||||
val defaultLanguages = preferences.enabledLanguages().defaultValue()
|
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.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
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.extension.anime.util.AnimeExtensionLoader
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
@ -126,24 +125,13 @@ internal class AnimeExtensionGithubApi {
|
||||||
isNsfw = it.nsfw == 1,
|
isNsfw = it.nsfw == 1,
|
||||||
hasReadme = it.hasReadme == 1,
|
hasReadme = it.hasReadme == 1,
|
||||||
hasChangelog = it.hasChangelog == 1,
|
hasChangelog = it.hasChangelog == 1,
|
||||||
sources = it.sources?.toAnimeExtensionSources().orEmpty(),
|
sources = it.sources?.map(extensionAnimeSourceMapper).orEmpty(),
|
||||||
apkName = it.apk,
|
apkName = it.apk,
|
||||||
iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}",
|
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 {
|
fun getApkUrl(extension: AnimeExtension.Available): String {
|
||||||
return "${getUrlPrefix()}apk/${extension.apkName}"
|
return "${getUrlPrefix()}apk/${extension.apkName}"
|
||||||
}
|
}
|
||||||
|
@ -185,3 +173,12 @@ private data class AnimeExtensionSourceJsonObject(
|
||||||
val name: String,
|
val name: String,
|
||||||
val baseUrl: 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 android.graphics.drawable.Drawable
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSourceData
|
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||||
|
|
||||||
sealed class AnimeExtension {
|
sealed class AnimeExtension {
|
||||||
|
|
||||||
|
@ -44,10 +44,26 @@ sealed class AnimeExtension {
|
||||||
override val isNsfw: Boolean,
|
override val isNsfw: Boolean,
|
||||||
override val hasReadme: Boolean,
|
override val hasReadme: Boolean,
|
||||||
override val hasChangelog: Boolean,
|
override val hasChangelog: Boolean,
|
||||||
val sources: List<AvailableAnimeSources>,
|
val sources: List<AnimeSource>,
|
||||||
val apkName: String,
|
val apkName: String,
|
||||||
val iconUrl: 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(
|
data class Untrusted(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
|
@ -62,18 +78,3 @@ sealed class AnimeExtension {
|
||||||
override val hasChangelog: Boolean = false,
|
override val hasChangelog: Boolean = false,
|
||||||
) : AnimeExtension()
|
) : 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.R
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi
|
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.MangaExtension
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
|
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
|
||||||
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver
|
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver
|
||||||
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller
|
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller
|
||||||
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader
|
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.preference.plusAssign
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
@ -22,7 +22,7 @@ import rx.Observable
|
||||||
import tachiyomi.core.util.lang.launchNow
|
import tachiyomi.core.util.lang.launchNow
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
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.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -73,12 +73,12 @@ class MangaExtensionManager(
|
||||||
private val _availableExtensionsFlow = MutableStateFlow(emptyList<MangaExtension.Available>())
|
private val _availableExtensionsFlow = MutableStateFlow(emptyList<MangaExtension.Available>())
|
||||||
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow()
|
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>) {
|
private fun setupAvailableExtensionsSourcesDataMap(extensions: List<MangaExtension.Available>) {
|
||||||
if (extensions.isEmpty()) return
|
if (extensions.isEmpty()) return
|
||||||
availableExtensionsSourcesData = extensions
|
availableExtensionsSourcesData = extensions
|
||||||
.flatMap { ext -> ext.sources.map { it.toSourceData() } }
|
.flatMap { ext -> ext.sources.map { it.toStubSource() } }
|
||||||
.associateBy { it.id }
|
.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,8 +145,8 @@ class MangaExtensionManager(
|
||||||
// Use the source lang as some aren't present on the extension level.
|
// Use the source lang as some aren't present on the extension level.
|
||||||
val availableLanguages = extensions
|
val availableLanguages = extensions
|
||||||
.flatMap(MangaExtension.Available::sources)
|
.flatMap(MangaExtension.Available::sources)
|
||||||
.distinctBy(AvailableMangaSources::lang)
|
.distinctBy(MangaExtension.Available.MangaSource::lang)
|
||||||
.map(AvailableMangaSources::lang)
|
.map(MangaExtension.Available.MangaSource::lang)
|
||||||
|
|
||||||
val deviceLanguage = Locale.getDefault().language
|
val deviceLanguage = Locale.getDefault().language
|
||||||
val defaultLanguages = preferences.enabledLanguages().defaultValue()
|
val defaultLanguages = preferences.enabledLanguages().defaultValue()
|
||||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.manga.api
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
|
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
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.MangaExtension
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
|
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
|
||||||
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader
|
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader
|
||||||
|
@ -125,24 +124,13 @@ internal class MangaExtensionGithubApi {
|
||||||
isNsfw = it.nsfw == 1,
|
isNsfw = it.nsfw == 1,
|
||||||
hasReadme = it.hasReadme == 1,
|
hasReadme = it.hasReadme == 1,
|
||||||
hasChangelog = it.hasChangelog == 1,
|
hasChangelog = it.hasChangelog == 1,
|
||||||
sources = it.sources?.toExtensionSources().orEmpty(),
|
sources = it.sources?.map(extensionSourceMapper).orEmpty(),
|
||||||
apkName = it.apk,
|
apkName = it.apk,
|
||||||
iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}",
|
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 {
|
fun getApkUrl(extension: MangaExtension.Available): String {
|
||||||
return "${getUrlPrefix()}apk/${extension.apkName}"
|
return "${getUrlPrefix()}apk/${extension.apkName}"
|
||||||
}
|
}
|
||||||
|
@ -184,3 +172,12 @@ private data class ExtensionSourceJsonObject(
|
||||||
val name: String,
|
val name: String,
|
||||||
val baseUrl: 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 android.graphics.drawable.Drawable
|
||||||
import eu.kanade.tachiyomi.source.MangaSource
|
import eu.kanade.tachiyomi.source.MangaSource
|
||||||
import tachiyomi.domain.source.manga.model.MangaSourceData
|
import tachiyomi.domain.source.manga.model.StubMangaSource
|
||||||
|
|
||||||
sealed class MangaExtension {
|
sealed class MangaExtension {
|
||||||
|
|
||||||
|
@ -44,10 +44,26 @@ sealed class MangaExtension {
|
||||||
override val isNsfw: Boolean,
|
override val isNsfw: Boolean,
|
||||||
override val hasReadme: Boolean,
|
override val hasReadme: Boolean,
|
||||||
override val hasChangelog: Boolean,
|
override val hasChangelog: Boolean,
|
||||||
val sources: List<AvailableMangaSources>,
|
val sources: List<MangaSource>,
|
||||||
val apkName: String,
|
val apkName: String,
|
||||||
val iconUrl: 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(
|
data class Untrusted(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
|
@ -62,18 +78,3 @@ sealed class MangaExtension {
|
||||||
override val hasChangelog: Boolean = false,
|
override val hasChangelog: Boolean = false,
|
||||||
) : MangaExtension()
|
) : 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.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSourceData
|
|
||||||
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
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.domain.source.anime.service.AnimeSourceManager
|
||||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -28,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||||
class AndroidAnimeSourceManager(
|
class AndroidAnimeSourceManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val extensionManager: AnimeExtensionManager,
|
private val extensionManager: AnimeExtensionManager,
|
||||||
private val sourceRepository: AnimeSourceDataRepository,
|
private val sourceRepository: AnimeStubSourceRepository,
|
||||||
) : AnimeSourceManager {
|
) : AnimeSourceManager {
|
||||||
private val downloadManager: AnimeDownloadManager by injectLazy()
|
private val downloadManager: AnimeDownloadManager by injectLazy()
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ class AndroidAnimeSourceManager(
|
||||||
extensions.forEach { extension ->
|
extensions.forEach { extension ->
|
||||||
extension.sources.forEach {
|
extension.sources.forEach {
|
||||||
mutableMap[it.id] = it
|
mutableMap[it.id] = it
|
||||||
registerStubSource(it.toSourceData())
|
registerStubSource(it.toStubSource())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourcesMapFlow.value = mutableMap
|
sourcesMapFlow.value = mutableMap
|
||||||
|
@ -68,7 +67,7 @@ class AndroidAnimeSourceManager(
|
||||||
.collectLatest { sources ->
|
.collectLatest { sources ->
|
||||||
val mutableMap = stubSourcesMap.toMutableMap()
|
val mutableMap = stubSourcesMap.toMutableMap()
|
||||||
sources.forEach {
|
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 }
|
return stubSourcesMap.values.filterNot { it.id in onlineSourceIds }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerStubSource(sourceData: AnimeSourceData) {
|
private fun registerStubSource(source: StubAnimeSource) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val (id, lang, name) = sourceData
|
val dbSource = sourceRepository.getStubAnimeSource(source.id)
|
||||||
val dbSourceData = sourceRepository.getAnimeSourceData(id)
|
if (dbSource == source) return@launch
|
||||||
if (dbSourceData == sourceData) return@launch
|
sourceRepository.upsertStubAnimeSource(source.id, source.lang, source.name)
|
||||||
sourceRepository.upsertAnimeSourceData(id, lang, name)
|
if (dbSource != null) {
|
||||||
if (dbSourceData != null) {
|
downloadManager.renameSource(dbSource, source)
|
||||||
downloadManager.renameSource(
|
|
||||||
StubAnimeSource(dbSourceData),
|
|
||||||
StubAnimeSource(sourceData),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createStubSource(id: Long): StubAnimeSource {
|
private suspend fun createStubSource(id: Long): StubAnimeSource {
|
||||||
sourceRepository.getAnimeSourceData(id)?.let {
|
sourceRepository.getStubAnimeSource(id)?.let {
|
||||||
return StubAnimeSource(it)
|
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.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSourceData
|
|
||||||
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||||
import tachiyomi.source.local.entries.anime.isLocal
|
import tachiyomi.source.local.entries.anime.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
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.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 {
|
fun AnimeSource.getNameForAnimeInfo(): String {
|
||||||
val preferences = Injekt.get<SourcePreferences>()
|
val preferences = Injekt.get<SourcePreferences>()
|
||||||
|
|
|
@ -15,9 +15,8 @@ import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import tachiyomi.domain.source.manga.model.MangaSourceData
|
|
||||||
import tachiyomi.domain.source.manga.model.StubMangaSource
|
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.domain.source.manga.service.MangaSourceManager
|
||||||
import tachiyomi.source.local.entries.manga.LocalMangaSource
|
import tachiyomi.source.local.entries.manga.LocalMangaSource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -28,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||||
class AndroidMangaSourceManager(
|
class AndroidMangaSourceManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val extensionManager: MangaExtensionManager,
|
private val extensionManager: MangaExtensionManager,
|
||||||
private val sourceRepository: MangaSourceDataRepository,
|
private val sourceRepository: MangaStubSourceRepository,
|
||||||
) : MangaSourceManager {
|
) : MangaSourceManager {
|
||||||
private val downloadManager: MangaDownloadManager by injectLazy()
|
private val downloadManager: MangaDownloadManager by injectLazy()
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ class AndroidMangaSourceManager(
|
||||||
extensions.forEach { extension ->
|
extensions.forEach { extension ->
|
||||||
extension.sources.forEach {
|
extension.sources.forEach {
|
||||||
mutableMap[it.id] = it
|
mutableMap[it.id] = it
|
||||||
registerStubSource(it.toSourceData())
|
registerStubSource(it.toStubSource())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourcesMapFlow.value = mutableMap
|
sourcesMapFlow.value = mutableMap
|
||||||
|
@ -68,7 +67,7 @@ class AndroidMangaSourceManager(
|
||||||
.collectLatest { sources ->
|
.collectLatest { sources ->
|
||||||
val mutableMap = stubSourcesMap.toMutableMap()
|
val mutableMap = stubSourcesMap.toMutableMap()
|
||||||
sources.forEach {
|
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 }
|
return stubSourcesMap.values.filterNot { it.id in onlineSourceIds }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerStubSource(sourceData: MangaSourceData) {
|
private fun registerStubSource(source: StubMangaSource) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val (id, lang, name) = sourceData
|
val dbSource = sourceRepository.getStubMangaSource(source.id)
|
||||||
val dbSourceData = sourceRepository.getMangaSourceData(id)
|
if (dbSource == source) return@launch
|
||||||
if (dbSourceData == sourceData) return@launch
|
sourceRepository.upsertStubMangaSource(source.id, source.lang, source.name)
|
||||||
sourceRepository.upsertMangaSourceData(id, lang, name)
|
if (dbSource != null) {
|
||||||
if (dbSourceData != null) {
|
downloadManager.renameSource(dbSource, source)
|
||||||
downloadManager.renameSource(StubMangaSource(dbSourceData), StubMangaSource(sourceData))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createStubSource(id: Long): StubMangaSource {
|
private suspend fun createStubSource(id: Long): StubMangaSource {
|
||||||
sourceRepository.getMangaSourceData(id)?.let {
|
sourceRepository.getStubMangaSource(id)?.let {
|
||||||
return StubMangaSource(it)
|
return it
|
||||||
}
|
}
|
||||||
extensionManager.getSourceData(id)?.let {
|
extensionManager.getSourceData(id)?.let {
|
||||||
registerStubSource(it)
|
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.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.MangaSource
|
import eu.kanade.tachiyomi.source.MangaSource
|
||||||
import tachiyomi.domain.source.manga.model.MangaSourceData
|
|
||||||
import tachiyomi.domain.source.manga.model.StubMangaSource
|
import tachiyomi.domain.source.manga.model.StubMangaSource
|
||||||
import tachiyomi.source.local.entries.manga.isLocal
|
import tachiyomi.source.local.entries.manga.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
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.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 {
|
fun MangaSource.getNameForMangaInfo(): String {
|
||||||
val preferences = Injekt.get<SourcePreferences>()
|
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.download.manga.MangaDownloadCache
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
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.data.updater.RELEASE_URL
|
||||||
import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi
|
||||||
|
@ -110,6 +109,7 @@ import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
@ -352,7 +352,7 @@ class MainActivity : BaseActivity() {
|
||||||
if (BuildConfig.INCLUDE_UPDATER) {
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
try {
|
try {
|
||||||
val result = AppUpdateChecker().checkForUpdate(context)
|
val result = AppUpdateChecker().checkForUpdate(context)
|
||||||
if (result is AppUpdateResult.NewUpdate) {
|
if (result is GetApplicationRelease.Result.NewUpdate) {
|
||||||
val updateScreen = NewUpdateScreen(
|
val updateScreen = NewUpdateScreen(
|
||||||
versionName = result.release.version,
|
versionName = result.release.version,
|
||||||
changelogInfo = result.release.info,
|
changelogInfo = result.release.info,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
kotlin("android")
|
kotlin("android")
|
||||||
|
kotlin("plugin.serialization")
|
||||||
id("com.squareup.sqldelight")
|
id("com.squareup.sqldelight")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,3 +35,12 @@ dependencies {
|
||||||
api(libs.sqldelight.coroutines)
|
api(libs.sqldelight.coroutines)
|
||||||
api(libs.sqldelight.android.paging)
|
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
|
package tachiyomi.data.source.anime
|
||||||
|
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
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 ->
|
val animeSourceMapper: (eu.kanade.tachiyomi.animesource.AnimeSource) -> AnimeSource = { source ->
|
||||||
AnimeSource(
|
AnimeSource(
|
||||||
|
@ -13,6 +13,6 @@ val animeSourceMapper: (eu.kanade.tachiyomi.animesource.AnimeSource) -> AnimeSou
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val animeSourceDataMapper: (Long, String, String) -> AnimeSourceData = { id, lang, name ->
|
val animeSourceDataMapper: (Long, String, String) -> StubAnimeSource = { id, lang, name ->
|
||||||
AnimeSourceData(id, lang, name)
|
StubAnimeSource(id, lang, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,18 @@ package tachiyomi.data.source.anime
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import tachiyomi.data.handlers.anime.AnimeDatabaseHandler
|
import tachiyomi.data.handlers.anime.AnimeDatabaseHandler
|
||||||
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
|
||||||
|
|
||||||
class AnimeSourceDataRepositoryImpl(
|
class AnimeStubSourceRepositoryImpl(
|
||||||
private val handler: AnimeDatabaseHandler,
|
private val handler: AnimeDatabaseHandler,
|
||||||
) : AnimeSourceDataRepository {
|
) : AnimeStubSourceRepository {
|
||||||
|
|
||||||
override fun subscribeAllAnime(): Flow<List<AnimeSourceData>> {
|
override fun subscribeAllAnime(): Flow<List<StubAnimeSource>> {
|
||||||
return handler.subscribeToList { animesourcesQueries.findAll(animeSourceDataMapper) }
|
return handler.subscribeToList { animesourcesQueries.findAll(animeSourceDataMapper) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAnimeSourceData(id: Long): AnimeSourceData? {
|
override suspend fun getStubAnimeSource(id: Long): StubAnimeSource? {
|
||||||
return handler.awaitOneOrNull {
|
return handler.awaitOneOrNull {
|
||||||
animesourcesQueries.findOne(
|
animesourcesQueries.findOne(
|
||||||
id,
|
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) }
|
handler.await { animesourcesQueries.upsert(id, lang, name) }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package tachiyomi.data.source.manga
|
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.Source
|
||||||
|
import tachiyomi.domain.source.manga.model.StubMangaSource
|
||||||
|
|
||||||
val mangaSourceMapper: (eu.kanade.tachiyomi.source.MangaSource) -> Source = { source ->
|
val mangaSourceMapper: (eu.kanade.tachiyomi.source.MangaSource) -> Source = { 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 ->
|
val mangaSourceDataMapper: (Long, String, String) -> StubMangaSource = { id, lang, name ->
|
||||||
MangaSourceData(id, lang, name)
|
StubMangaSource(id, lang, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,18 @@ package tachiyomi.data.source.manga
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
||||||
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
|
||||||
|
|
||||||
class MangaSourceDataRepositoryImpl(
|
class MangaStubSourceRepositoryImpl(
|
||||||
private val handler: MangaDatabaseHandler,
|
private val handler: MangaDatabaseHandler,
|
||||||
) : MangaSourceDataRepository {
|
) : MangaStubSourceRepository {
|
||||||
|
|
||||||
override fun subscribeAllManga(): Flow<List<MangaSourceData>> {
|
override fun subscribeAllManga(): Flow<List<StubMangaSource>> {
|
||||||
return handler.subscribeToList { sourcesQueries.findAll(mangaSourceDataMapper) }
|
return handler.subscribeToList { sourcesQueries.findAll(mangaSourceDataMapper) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMangaSourceData(id: Long): MangaSourceData? {
|
override suspend fun getStubMangaSource(id: Long): StubMangaSource? {
|
||||||
return handler.awaitOneOrNull {
|
return handler.awaitOneOrNull {
|
||||||
sourcesQueries.findOne(
|
sourcesQueries.findOne(
|
||||||
id,
|
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) }
|
handler.await { sourcesQueries.upsert(id, lang, name) }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,4 +23,13 @@ dependencies {
|
||||||
api(libs.sqldelight.android.paging)
|
api(libs.sqldelight.android.paging)
|
||||||
|
|
||||||
testImplementation(libs.bundles.test)
|
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
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
|
||||||
@Suppress("OverridingDeprecatedMember")
|
@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
|
val isInvalid: Boolean = name.isBlank() || lang.isBlank()
|
||||||
|
|
||||||
override val name: String = sourceData.name.ifBlank { id.toString() }
|
|
||||||
|
|
||||||
override val lang: String = sourceData.lang
|
|
||||||
|
|
||||||
override suspend fun getAnimeDetails(anime: SAnime): SAnime {
|
override suspend fun getAnimeDetails(anime: SAnime): SAnime {
|
||||||
throw AnimeSourceNotInstalledException()
|
throw AnimeSourceNotInstalledException()
|
||||||
|
@ -27,7 +27,7 @@ class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
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()
|
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
|
import rx.Observable
|
||||||
|
|
||||||
@Suppress("OverridingDeprecatedMember")
|
@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
|
val isInvalid: Boolean = name.isBlank() || lang.isBlank()
|
||||||
|
|
||||||
override val name: String = sourceData.name.ifBlank { id.toString() }
|
|
||||||
|
|
||||||
override val lang: String = sourceData.lang
|
|
||||||
|
|
||||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||||
throw SourceNotInstalledException()
|
throw SourceNotInstalledException()
|
||||||
|
@ -43,7 +43,7 @@ class StubMangaSource(private val sourceData: MangaSourceData) : MangaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
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-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
|
||||||
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
|
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
|
||||||
coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava" }
|
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 = { 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" }
|
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"
|
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
||||||
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||||
compose-cascade = "me.saket.cascade:cascade-compose:2.0.0-rc02"
|
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"
|
compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0"
|
||||||
|
|
||||||
logcat = "com.squareup.logcat:logcat:0.1"
|
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"
|
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"
|
aniyomi-mpv = "com.github.aniyomiorg:aniyomi-mpv-lib:1.10.n"
|
||||||
ffmpeg-kit = "com.github.jmir1:ffmpeg-kit:1.10"
|
ffmpeg-kit = "com.github.jmir1:ffmpeg-kit:1.10"
|
||||||
arthenica-smartexceptions = "com.arthenica:smart-exception-java:0.1.1"
|
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"]
|
shizuku = ["shizuku-api", "shizuku-provider"]
|
||||||
voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"]
|
voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"]
|
||||||
richtext = ["richtext-commonmark", "richtext-m3"]
|
richtext = ["richtext-commonmark", "richtext-m3"]
|
||||||
test = ["junit", "kotest-assertions"]
|
test = ["junit", "kotest-assertions", "mockk"]
|
||||||
|
|
Loading…
Reference in a new issue