From ed6744f768c615766411a7756590b32f35b9f10b Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:47:39 +0200 Subject: [PATCH] feat: Add data saver (#1084) --- app/src/main/java/aniyomi/util/DataSaver.kt | 154 ++++++++++++++++++ .../java/eu/kanade/domain/SYDomainModule.kt | 14 ++ .../interactor/GetEnabledMangaSources.kt | 18 +- .../ToggleExcludeFromMangaDataSaver.kt | 20 +++ .../source/service/SourcePreferences.kt | 36 ++++ .../browse/manga/MangaSourcesScreen.kt | 18 ++ .../settings/screen/SettingsAdvancedScreen.kt | 84 ++++++++++ app/src/main/java/eu/kanade/tachiyomi/App.kt | 4 + .../data/download/manga/MangaDownloader.kt | 22 ++- .../manga/source/MangaSourcesScreenModel.kt | 27 +++ .../ui/browse/manga/source/MangaSourcesTab.kt | 4 + .../ui/reader/loader/HttpPageLoader.kt | 12 +- .../domain/source/manga/model/Source.kt | 3 + i18n/src/main/res/values/strings-aniyomi.xml | 19 +++ .../tachiyomi/source/online/HttpSource.kt | 2 +- 15 files changed, 426 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/aniyomi/util/DataSaver.kt create mode 100644 app/src/main/java/eu/kanade/domain/SYDomainModule.kt create mode 100644 app/src/main/java/eu/kanade/domain/source/manga/interactor/ToggleExcludeFromMangaDataSaver.kt diff --git a/app/src/main/java/aniyomi/util/DataSaver.kt b/app/src/main/java/aniyomi/util/DataSaver.kt new file mode 100644 index 0000000000..ddefbf2621 --- /dev/null +++ b/app/src/main/java/aniyomi/util/DataSaver.kt @@ -0,0 +1,154 @@ +package aniyomi.util + +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.domain.source.service.SourcePreferences.DataSaver.BANDWIDTH_HERO +import eu.kanade.domain.source.service.SourcePreferences.DataSaver.NONE +import eu.kanade.domain.source.service.SourcePreferences.DataSaver.RESMUSH_IT +import eu.kanade.domain.source.service.SourcePreferences.DataSaver.WSRV_NL +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.source.MangaSource +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.online.HttpSource +import okhttp3.OkHttpClient +import okhttp3.Response +import rx.Observable +import tachiyomi.core.preference.Preference +import uy.kohesive.injekt.injectLazy + +interface DataSaver { + + fun compress(imageUrl: String): String + + companion object { + val NoOp = object : DataSaver { + override fun compress(imageUrl: String): String { + return imageUrl + } + } + + fun HttpSource.fetchImage(page: Page, dataSaver: DataSaver): Observable { + val imageUrl = page.imageUrl ?: return fetchImage(page) + page.imageUrl = dataSaver.compress(imageUrl) + return fetchImage(page) + .doOnNext { + page.imageUrl = imageUrl + } + } + + suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response { + val imageUrl = page.imageUrl ?: return getImage(page) + page.imageUrl = dataSaver.compress(imageUrl) + return try { + getImage(page) + } finally { + page.imageUrl = imageUrl + } + } + } +} + +fun DataSaver(source: MangaSource, preferences: SourcePreferences): DataSaver { + val dataSaver = preferences.dataSaver().get() + if (dataSaver != NONE && source.id.toString() in preferences.dataSaverExcludedSources().get()) { + return DataSaver.NoOp + } + return when (dataSaver) { + NONE -> DataSaver.NoOp + BANDWIDTH_HERO -> BandwidthHeroDataSaver(preferences) + WSRV_NL -> WsrvNlDataSaver(preferences) + RESMUSH_IT -> ReSmushItDataSaver(preferences) + } +} + +private class BandwidthHeroDataSaver(preferences: SourcePreferences) : DataSaver { + private val dataSavedServer = preferences.dataSaverServer().get().trimEnd('/') + + private val ignoreJpg = preferences.dataSaverIgnoreJpeg().get() + private val ignoreGif = preferences.dataSaverIgnoreGif().get() + + private val format = preferences.dataSaverImageFormatJpeg().toIntRepresentation() + private val quality = preferences.dataSaverImageQuality().get() + private val colorBW = preferences.dataSaverColorBW().toIntRepresentation() + + override fun compress(imageUrl: String): String { + return if (dataSavedServer.isNotBlank() && !imageUrl.contains(dataSavedServer)) { + when { + imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl) + imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl) + else -> getUrl(imageUrl) + } + } else { + imageUrl + } + } + + private fun getUrl(imageUrl: String): String { + // Network Request sent for the Bandwidth Hero Proxy server + return "$dataSavedServer/?jpg=$format&l=$quality&bw=$colorBW&url=$imageUrl" + } + + private fun Preference.toIntRepresentation() = if (get()) "1" else "0" +} + +private class WsrvNlDataSaver(preferences: SourcePreferences) : DataSaver { + private val ignoreJpg = preferences.dataSaverIgnoreJpeg().get() + private val ignoreGif = preferences.dataSaverIgnoreGif().get() + + private val format = preferences.dataSaverImageFormatJpeg().get() + private val quality = preferences.dataSaverImageQuality().get() + + override fun compress(imageUrl: String): String { + return when { + imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl) + imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl) + else -> getUrl(imageUrl) + } + } + + private fun getUrl(imageUrl: String): String { + // Network Request sent to wsrv + return "https://wsrv.nl/?url=$imageUrl" + if (imageUrl.contains(".webp", true) || imageUrl.contains(".gif", true)) { + if (!format) { + // Preserve output image extension for animated images(.webp and .gif) + "&q=$quality&n=-1" + } else { + // Do not preserve output Extension if User asked to convert into Jpeg + "&output=jpg&q=$quality&n=-1" + } + } else { + if (format) { + "&output=jpg&q=$quality" + } else { + "&output=webp&q=$quality" + } + } + } +} + +private class ReSmushItDataSaver(preferences: SourcePreferences) : DataSaver { + + private val network: NetworkHelper by injectLazy() + + private val client: OkHttpClient + get() = network.client + + private val ignoreJpg = preferences.dataSaverIgnoreJpeg().get() + private val ignoreGif = preferences.dataSaverIgnoreGif().get() + + private val quality = preferences.dataSaverImageQuality().get() + + override fun compress(imageUrl: String): String { + return when { + imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl) + imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl) + else -> getUrl(imageUrl) + } + } + + private fun getUrl(imageUrl: String): String { + // Network Request sent to resmush + return client.newCall(GET("http://api.resmush.it/ws.php?img=$imageUrl&qlty=$quality")).execute() + .body.string().substringAfter("\"dest\":\"").substringBefore("\",") + } +} diff --git a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt new file mode 100644 index 0000000000..2315a60bc4 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt @@ -0,0 +1,14 @@ +package eu.kanade.domain + +import eu.kanade.domain.source.manga.interactor.ToggleExcludeFromMangaDataSaver +import uy.kohesive.injekt.api.InjektModule +import uy.kohesive.injekt.api.InjektRegistrar +import uy.kohesive.injekt.api.addFactory +import uy.kohesive.injekt.api.get + +class SYDomainModule : InjektModule { + + override fun InjektRegistrar.registerInjectables() { + addFactory { ToggleExcludeFromMangaDataSaver(get()) } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt index 2c8a2b63ff..ac22e7fbbf 100644 --- a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt +++ b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt @@ -19,17 +19,27 @@ class GetEnabledMangaSources( return combine( preferences.pinnedMangaSources().changes(), preferences.enabledLanguages().changes(), - preferences.disabledMangaSources().changes(), - preferences.lastUsedMangaSource().changes(), + combine( + preferences.disabledMangaSources().changes(), + preferences.lastUsedMangaSource().changes(), + // SY --> + preferences.dataSaverExcludedSources().changes(), + // SY <-- + ) { a, b, c -> Triple(a, b, c) }, repository.getMangaSources(), - ) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources -> + ) { pinnedSourceIds, enabledLanguages, (disabledSources, lastUsedSource, excludedFromDataSaver), sources -> sources .filter { it.lang in enabledLanguages || it.id == LocalMangaSource.ID } .filterNot { it.id.toString() in disabledSources } .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }) .flatMap { val flag = if ("${it.id}" in pinnedSourceIds) Pins.pinned else Pins.unpinned - val source = it.copy(pin = flag) + val source = it.copy( + pin = flag, + // SY --> + isExcludedFromDataSaver = it.id.toString() in excludedFromDataSaver, + // SY <-- + ) val toFlatten = mutableListOf(source) if (source.id == lastUsedSource) { toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual)) diff --git a/app/src/main/java/eu/kanade/domain/source/manga/interactor/ToggleExcludeFromMangaDataSaver.kt b/app/src/main/java/eu/kanade/domain/source/manga/interactor/ToggleExcludeFromMangaDataSaver.kt new file mode 100644 index 0000000000..409f8a8baa --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/manga/interactor/ToggleExcludeFromMangaDataSaver.kt @@ -0,0 +1,20 @@ +package eu.kanade.domain.source.manga.interactor + +import eu.kanade.domain.source.service.SourcePreferences +import tachiyomi.core.preference.getAndSet +import tachiyomi.domain.source.manga.model.Source + +class ToggleExcludeFromMangaDataSaver( + private val preferences: SourcePreferences, +) { + + fun await(source: Source) { + preferences.dataSaverExcludedSources().getAndSet { + if (source.id.toString() in it) { + it - source.id.toString() + } else { + it + source.id.toString() + } + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index cc5ef87211..22a24479b2 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -43,4 +43,40 @@ class SourcePreferences( fun hideInAnimeLibraryItems() = preferenceStore.getBoolean("browse_hide_in_anime_library_items", false) fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false) + + // SY --> + + // fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true) + + // fun sourcesTabCategories() = preferenceStore.getStringSet("sources_tab_categories", mutableSetOf()) + + // fun sourcesTabCategoriesFilter() = preferenceStore.getBoolean("sources_tab_categories_filter", false) + + // fun sourcesTabSourcesInCategories() = preferenceStore.getStringSet("sources_tab_source_categories", mutableSetOf()) + + fun dataSaver() = preferenceStore.getEnum("data_saver", DataSaver.NONE) + + fun dataSaverIgnoreJpeg() = preferenceStore.getBoolean("ignore_jpeg", false) + + fun dataSaverIgnoreGif() = preferenceStore.getBoolean("ignore_gif", true) + + fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80) + + fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean("data_saver_image_format_jpeg", false) + + fun dataSaverServer() = preferenceStore.getString("data_saver_server", "") + + fun dataSaverColorBW() = preferenceStore.getBoolean("data_saver_color_bw", false) + + fun dataSaverExcludedSources() = preferenceStore.getStringSet("data_saver_excluded", emptySet()) + + fun dataSaverDownloader() = preferenceStore.getBoolean("data_saver_downloader", true) + + enum class DataSaver { + NONE, + BANDWIDTH_HERO, + WSRV_NL, + RESMUSH_IT, + } + // SY <-- } diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt index 8d97d67559..0aff550b9f 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt @@ -160,6 +160,9 @@ fun MangaSourceOptionsDialog( source: Source, onClickPin: () -> Unit, onClickDisable: () -> Unit, + // SY --> + onClickToggleDataSaver: (() -> Unit)?, + // SY <-- onDismiss: () -> Unit, ) { AlertDialog( @@ -185,6 +188,21 @@ fun MangaSourceOptionsDialog( .padding(vertical = 16.dp), ) } + // SY --> + if (onClickToggleDataSaver != null) { + Text( + text = if (source.isExcludedFromDataSaver) { + stringResource(id = R.string.data_saver_stop_exclude) + } else { + stringResource(id = R.string.data_saver_exclude) + }, + modifier = Modifier + .clickable(onClick = onClickToggleDataSaver) + .fillMaxWidth() + .padding(vertical = 16.dp), + ) + } + // SY <-- } }, onDismissRequest = onDismiss, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index 12109eef70..4953c7e0f1 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -25,6 +25,8 @@ import androidx.core.net.toUri import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.base.BasePreferences +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.domain.source.service.SourcePreferences.DataSaver import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen import eu.kanade.presentation.util.collectAsState @@ -115,6 +117,9 @@ object SettingsAdvancedScreen : SearchableSettings { getNetworkGroup(networkPreferences = networkPreferences), getLibraryGroup(), getExtensionsGroup(basePreferences = basePreferences), + // SY --> + getDataSaverGroup(), + // SY <-- ) } @@ -400,4 +405,83 @@ object SettingsAdvancedScreen : SearchableSettings { ), ) } + + // SY --> + @Composable + private fun getDataSaverGroup(): Preference.PreferenceGroup { + val sourcePreferences = remember { Injekt.get() } + val dataSaver by sourcePreferences.dataSaver().collectAsState() + return Preference.PreferenceGroup( + title = stringResource(R.string.data_saver), + preferenceItems = listOf( + Preference.PreferenceItem.ListPreference( + pref = sourcePreferences.dataSaver(), + title = stringResource(R.string.data_saver), + subtitle = stringResource(R.string.data_saver_summary), + entries = mapOf( + DataSaver.NONE to stringResource(R.string.disabled), + DataSaver.BANDWIDTH_HERO to stringResource(R.string.bandwidth_hero), + DataSaver.WSRV_NL to stringResource(R.string.wsrv), + DataSaver.RESMUSH_IT to stringResource(R.string.resmush), + ), + ), + Preference.PreferenceItem.EditTextPreference( + pref = sourcePreferences.dataSaverServer(), + title = stringResource(R.string.bandwidth_data_saver_server), + subtitle = stringResource(R.string.data_saver_server_summary), + enabled = dataSaver == DataSaver.BANDWIDTH_HERO, + ), + Preference.PreferenceItem.SwitchPreference( + pref = sourcePreferences.dataSaverDownloader(), + title = stringResource(R.string.data_saver_downloader), + enabled = dataSaver != DataSaver.NONE, + ), + Preference.PreferenceItem.SwitchPreference( + pref = sourcePreferences.dataSaverIgnoreJpeg(), + title = stringResource(R.string.data_saver_ignore_jpeg), + enabled = dataSaver != DataSaver.NONE, + ), + Preference.PreferenceItem.SwitchPreference( + pref = sourcePreferences.dataSaverIgnoreGif(), + title = stringResource(R.string.data_saver_ignore_gif), + enabled = dataSaver != DataSaver.NONE, + ), + Preference.PreferenceItem.ListPreference( + pref = sourcePreferences.dataSaverImageQuality(), + title = stringResource(R.string.data_saver_image_quality), + subtitle = stringResource(R.string.data_saver_image_quality_summary), + entries = listOf( + "10%", + "20%", + "40%", + "50%", + "70%", + "80%", + "90%", + "95%", + ).associateBy { it.trimEnd('%').toInt() }, + enabled = dataSaver != DataSaver.NONE, + ), + kotlin.run { + val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg().collectAsState() + Preference.PreferenceItem.SwitchPreference( + pref = sourcePreferences.dataSaverImageFormatJpeg(), + title = stringResource(R.string.data_saver_image_format), + subtitle = if (dataSaverImageFormatJpeg) { + stringResource(R.string.data_saver_image_format_summary_on) + } else { + stringResource(R.string.data_saver_image_format_summary_off) + }, + enabled = dataSaver != DataSaver.NONE && dataSaver != DataSaver.RESMUSH_IT, + ) + }, + Preference.PreferenceItem.SwitchPreference( + pref = sourcePreferences.dataSaverColorBW(), + title = stringResource(R.string.data_saver_color_bw), + enabled = dataSaver == DataSaver.BANDWIDTH_HERO, + ), + ), + ) + } + // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 5b754c0573..3ae2619248 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -23,6 +23,7 @@ import coil.decode.ImageDecoderDecoder import coil.disk.DiskCache import coil.util.DebugLogger import eu.kanade.domain.DomainModule +import eu.kanade.domain.SYDomainModule import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode @@ -85,6 +86,9 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { Injekt.importModule(AppModule(this)) Injekt.importModule(PreferenceModule(this)) Injekt.importModule(DomainModule()) + // SY --> + Injekt.importModule(SYDomainModule()) + // SY <-- setupAcra() setupNotificationChannels() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 9baa850bd0..d4c021987d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -1,10 +1,13 @@ package eu.kanade.tachiyomi.data.download.manga import android.content.Context +import aniyomi.util.DataSaver +import aniyomi.util.DataSaver.Companion.getImage import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.PublishRelay import eu.kanade.domain.entries.manga.model.getComicInfo import eu.kanade.domain.items.chapter.model.toSChapter +import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload @@ -75,6 +78,9 @@ class MangaDownloader( private val chapterCache: ChapterCache = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(), private val xml: XML = Injekt.get(), + // SY --> + private val sourcePreferences: SourcePreferences = Injekt.get(), + // SY <-- ) { /** @@ -331,6 +337,12 @@ class MangaDownloader( reIndexedPages } + val dataSaver = if (sourcePreferences.dataSaverDownloader().get()) { + DataSaver(download.source, sourcePreferences) + } else { + DataSaver.NoOp + } + // Delete all temporary (unfinished) files tmpDir.listFiles() ?.filter { it.name!!.endsWith(".tmp") } @@ -353,7 +365,7 @@ class MangaDownloader( pageList.asFlow() .flatMapMerge(concurrency = 2) { page -> flow { - withIOContext { getOrDownloadImage(page, download, tmpDir) } + withIOContext { getOrDownloadImage(page, download, tmpDir, dataSaver) } emit(page) }.flowOn(Dispatchers.IO) } @@ -380,7 +392,7 @@ class MangaDownloader( * @param download the download of the page. * @param tmpDir the temporary directory of the download. */ - private suspend fun getOrDownloadImage(page: Page, download: MangaDownload, tmpDir: UniFile) { + private suspend fun getOrDownloadImage(page: Page, download: MangaDownload, tmpDir: UniFile, dataSaver: DataSaver) { // If the image URL is empty, do nothing if (page.imageUrl == null) { return @@ -401,7 +413,7 @@ class MangaDownloader( val file = when { imageFile != null -> imageFile chapterCache.isImageInCache(page.imageUrl!!) -> copyImageFromCache(chapterCache.getImageFile(page.imageUrl!!), tmpDir, filename) - else -> downloadImage(page, download.source, tmpDir, filename) + else -> downloadImage(page, download.source, tmpDir, filename, dataSaver) } // When the page is ready, set page path, progress (just in case) and status @@ -426,11 +438,11 @@ class MangaDownloader( * @param tmpDir the temporary directory of the download. * @param filename the filename of the image. */ - private suspend fun downloadImage(page: Page, source: HttpSource, tmpDir: UniFile, filename: String): UniFile { + private suspend fun downloadImage(page: Page, source: HttpSource, tmpDir: UniFile, filename: String, dataSaver: DataSaver): UniFile { page.status = Page.State.DOWNLOAD_IMAGE page.progress = 0 return flow { - val response = source.getImage(page) + val response = source.getImage(page, dataSaver) val file = tmpDir.createFile("$filename.tmp") try { response.body.source().saveTo(file.openOutputStream()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesScreenModel.kt index b9705bd295..64fbfff5d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesScreenModel.kt @@ -5,15 +5,19 @@ import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.manga.interactor.GetEnabledMangaSources +import eu.kanade.domain.source.manga.interactor.ToggleExcludeFromMangaDataSaver import eu.kanade.domain.source.manga.interactor.ToggleMangaSource import eu.kanade.domain.source.manga.interactor.ToggleMangaSourcePin import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.domain.source.service.SourcePreferences.DataSaver import eu.kanade.presentation.browse.manga.MangaSourceUiModel import eu.kanade.tachiyomi.util.system.LAST_USED_KEY import eu.kanade.tachiyomi.util.system.PINNED_KEY import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import logcat.LogPriority @@ -31,6 +35,9 @@ class MangaSourcesScreenModel( private val getEnabledSources: GetEnabledMangaSources = Injekt.get(), private val toggleSource: ToggleMangaSource = Injekt.get(), private val toggleSourcePin: ToggleMangaSourcePin = Injekt.get(), + // SY --> + private val toggleExcludeFromMangaDataSaver: ToggleExcludeFromMangaDataSaver = Injekt.get(), + // SY <-- ) : StateScreenModel(MangaSourcesState()) { private val _events = Channel(Int.MAX_VALUE) @@ -45,6 +52,17 @@ class MangaSourcesScreenModel( } .collectLatest(::collectLatestSources) } + // SY --> + sourcePreferences.dataSaver().changes() + .onEach { + mutableState.update { + it.copy( + dataSaverEnabled = sourcePreferences.dataSaver().get() != DataSaver.NONE, + ) + } + } + .launchIn(coroutineScope) + // SY <-- } private fun collectLatestSources(sources: List) { @@ -97,6 +115,12 @@ class MangaSourcesScreenModel( toggleSourcePin.await(source) } + // SY --> + fun toggleExcludeFromMangaDataSaver(source: Source) { + toggleExcludeFromMangaDataSaver.await(source) + } + // SY <-- + fun showSourceDialog(source: Source) { mutableState.update { it.copy(dialog = Dialog(source)) } } @@ -117,6 +141,9 @@ data class MangaSourcesState( val dialog: MangaSourcesScreenModel.Dialog? = null, val isLoading: Boolean = true, val items: List = emptyList(), + // SY --> + val dataSaverEnabled: Boolean = false, + // SY <-- ) { val isEmpty = items.isEmpty() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesTab.kt index 553ac32544..2a5793febd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/MangaSourcesTab.kt @@ -66,6 +66,10 @@ fun Screen.mangaSourcesTab(): TabContent { screenModel.toggleSource(source) screenModel.closeDialog() }, + onClickToggleDataSaver = { + screenModel.toggleExcludeFromMangaDataSaver(source) + screenModel.closeDialog() + }.takeIf { state.dataSaverEnabled }, onDismiss = screenModel::closeDialog, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 8f9607be24..9a764cdcb4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.ui.reader.loader +import aniyomi.util.DataSaver +import aniyomi.util.DataSaver.Companion.getImage +import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.models.manga.toDomainChapter import eu.kanade.tachiyomi.source.model.Page @@ -31,6 +34,9 @@ class HttpPageLoader( private val chapter: ReaderChapter, private val source: HttpSource, private val chapterCache: ChapterCache = Injekt.get(), + // SY --> + private val sourcePreferences: SourcePreferences = Injekt.get(), + // SY <-- ) : PageLoader() { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) @@ -42,6 +48,10 @@ class HttpPageLoader( private val preloadSize = 4 + // SY --> + private val dataSaver = DataSaver(source, sourcePreferences) + // SY <-- + init { scope.launchIO { flow { @@ -203,7 +213,7 @@ class HttpPageLoader( if (!chapterCache.isImageInCache(imageUrl)) { page.status = Page.State.DOWNLOAD_IMAGE - val imageResponse = source.getImage(page) + val imageResponse = source.getImage(page, dataSaver) chapterCache.putImageToCache(imageUrl, imageResponse) } diff --git a/domain/src/main/java/tachiyomi/domain/source/manga/model/Source.kt b/domain/src/main/java/tachiyomi/domain/source/manga/model/Source.kt index fe1991b7ca..daaa08ddf5 100644 --- a/domain/src/main/java/tachiyomi/domain/source/manga/model/Source.kt +++ b/domain/src/main/java/tachiyomi/domain/source/manga/model/Source.kt @@ -8,6 +8,9 @@ data class Source( val isStub: Boolean, val pin: Pins = Pins.unpinned, val isUsedLast: Boolean = false, + // SY --> + val isExcludedFromDataSaver: Boolean = false, + // SY <-- ) { val visualName: String diff --git a/i18n/src/main/res/values/strings-aniyomi.xml b/i18n/src/main/res/values/strings-aniyomi.xml index fa0ffbf6e6..adb93ab0e4 100644 --- a/i18n/src/main/res/values/strings-aniyomi.xml +++ b/i18n/src/main/res/values/strings-aniyomi.xml @@ -292,4 +292,23 @@ Choose video quality: Extension settings Save screenshot + + Exclude from data saver + Stop excluding from data saver + Data Saver + Compress images before downloading or loading in reader + Use data saver in the downloader + Ignore Jpeg Images + Ignore Gif Animations + Image Quality + Higher values mean that a higher percentage of the image quality is saved, but it also means the file size is larger, 80 percent is a good median between file size and image quality + Compress to Jpeg + The Jpeg file size is considerably smaller then Webp is(meaning more data is saved), but it makes the images lose more quality as well.\nCurrently compresses to Jpeg + The Jpeg file size is considerably smaller then Webp is(meaning more data is saved), but it makes the images lose more quality as well.\nCurrently compresses to Webp + Convert to Black And White + Bandwidth Hero (requires a Bandwidth Hero Proxy server) + wsrv.nl + resmush.it + Bandwidth Hero Proxy Server + Put Bandwidth Hero Proxy server url here \ No newline at end of file diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index f9e616dcdf..d0aa5b2da3 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -311,7 +311,7 @@ abstract class HttpSource : CatalogueSource { * * @param page the page whose source image has to be downloaded. */ - suspend fun getImage(page: Page): Response { + open suspend fun getImage(page: Page): Response { // images will be cached or saved manually, so don't take up network cache return client.newCachelessCallWithProgress(imageRequest(page), page) .awaitSuccess()