From 24ce2683d49bf75d65a1e999dd00df9502cf5791 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> Date: Sun, 16 Jun 2024 09:34:02 +0700 Subject: [PATCH] refactor: Modularize the project (#97) * refactor: Modularize the project * chore: Move okhttp stuff back to androidMain OkHttp decided to cancel multiplatform plan on 5.0 REF: https://square.github.io/okhttp/changelogs/changelog/#version-500-alpha13 * feat: Start using moko for i18n * fix: Solve some errors * chore: Remove manga from domain module We'll do this later * fix: Duplicate error * fix: Conflict function name error * fix: Target SManga * fix: Breaking changes after the split * fix: Not enough heap memory * chore: Update proguard rules Sorta similar to upstream * refactor: Fix namespace --- app/build.gradle.kts | 32 +---- app/proguard-rules.pro | 13 +- .../main/java/dev/yokai/core/di/AppModule.kt | 22 ++- .../dev/yokai/core/di/PreferenceModule.kt | 3 + .../core/migration/migrations/Migrations.kt | 1 + .../migrations/NetworkPrefsMigration.kt | 23 ++++ .../component/preference/PreferenceCommon.kt | 2 +- .../component/preference/PreferenceItem.kt | 2 +- .../onboarding/OnboardingController.kt | 2 +- .../onboarding/steps/ThemeStep.kt | 4 +- .../settings/SettingsCommonWidget.kt | 2 +- .../storage/preference/PreferenceExtension.kt | 13 ++ .../data/database/models/ChapterImpl.kt | 13 ++ .../tachiyomi/data/database/models/Manga.kt | 51 ++++++- .../data/preference/PreferencesHelper.kt | 5 - .../tachiyomi/source/SourceExtensions.kt | 29 ++++ .../kanade/tachiyomi/source/model/SManga.kt | 100 -------------- .../source/models/SMangaExtensions.kt | 7 + .../tachiyomi/source/online/all/Cubari.kt | 3 +- .../tachiyomi/source/online/all/MangaDex.kt | 3 +- .../source/online/english/FoolSlide.kt | 1 + .../source/online/english/MangaPlus.kt | 1 + .../ui/manga/MangaDetailsPresenter.kt | 4 +- .../tachiyomi/ui/manga/MangaHeaderHolder.kt | 5 +- .../manga/design/MigrationSourceHolder.kt | 1 + .../stats/details/StatsDetailsController.kt | 4 +- .../stats/details/StatsDetailsPresenter.kt | 6 +- .../controllers/SettingsAdvancedController.kt | 4 +- .../tachiyomi/ui/source/SourceHolder.kt | 1 + .../util/chapter/ChapterRecognition.kt | 1 + .../util/system/ContextExtensions.kt | 47 ------- app/src/main/res/values/strings.xml | 6 - build.gradle.kts | 44 ++++++ .../src/main/kotlin/LocalesConfigPlugin.kt | 41 ++++++ core/build.gradle.kts | 49 +++++++ core/src/androidMain/AndroidManifest.xml | 2 + .../core/preference/AndroidPreference.kt | 0 .../core/preference/AndroidPreferenceStore.kt | 0 .../tachiyomi/network/AndroidCookieJar.kt | 0 .../kanade/tachiyomi/network/DohProviders.kt | 0 .../tachiyomi/network/JavaScriptEngine.kt | 0 .../kanade/tachiyomi/network/NetworkHelper.kt | 30 ++-- .../tachiyomi/network/OkHttpExtensions.kt | 2 +- .../tachiyomi/network/ProgressResponseBody.kt | 0 .../eu/kanade/tachiyomi/network/Requests.kt | 2 +- .../interceptor/CloudflareInterceptor.kt | 9 +- .../interceptor/IgnoreGzipInterceptor.kt | 0 .../interceptor/RateLimitInterceptor.kt | 2 +- .../SpecificHostRateLimitInterceptor.kt | 2 +- .../UncaughtExceptionInterceptor.kt | 0 .../interceptor/UserAgentInterceptor.kt | 0 .../network/interceptor/WebViewInterceptor.kt | 9 +- .../util/system/CoroutinesExtensions.kt | 0 .../util/system/DensityExtensions.kt | 28 ++++ .../tachiyomi/util/system/DeviceUtil.kt | 0 .../tachiyomi/util/system/KermitExtensions.kt | 0 .../tachiyomi/util/system/ToastExtensions.kt | 37 +++++ .../util/system/WebViewClientCompat.kt | 0 .../tachiyomi/util/system/WebViewUtil.kt | 0 .../kotlin/yokai/util/lang/MokoExtensions.kt | 8 ++ .../yokai/util/lang}/RxCoroutineBridge.kt | 6 +- .../tachiyomi/core/preference/Preference.kt | 10 -- .../core/preference/PreferenceStore.kt | 0 .../tachiyomi/network/NetworkPreferences.kt | 10 ++ .../tachiyomi/network/ProgressListener.kt | 0 domain/build.gradle.kts | 11 ++ gradle.properties | 2 +- gradle/kotlinx.versions.toml | 9 +- gradle/libs.versions.toml | 5 + i18n/.gitignore | 2 + i18n/build.gradle.kts | 43 ++++++ i18n/src/androidMain/AndroidManifest.xml | 2 + .../moko-resources/base/strings.xml | 8 ++ presentation-core/build.gradle.kts | 17 +++ presentation-core/consumer-rules.pro | 0 presentation-core/proguard-rules.pro | 21 +++ presentation-widget/build.gradle.kts | 31 +++++ presentation-widget/consumer-rules.pro | 0 presentation-widget/proguard-rules.pro | 21 +++ .../appwidget/TachiyomiWidgetManager.kt | 15 ++ .../appwidget/UpdatesGridGlanceReceiver.kt | 8 ++ .../appwidget/UpdatesGridGlanceWidget.kt | 128 ++++++++++++++++++ .../appwidget/components/LockedWidget.kt | 44 ++++++ .../appwidget/components/UpdatesMangaCover.kt | 48 +++++++ .../appwidget/components/UpdatesWidget.kt | 74 ++++++++++ .../tachiyomi/appwidget/util/GlanceUtils.kt | 43 ++++++ settings.gradle.kts | 8 ++ source-api/build.gradle.kts | 42 ++++++ source-api/consumer-proguard.pro | 5 + .../src/androidMain/AndroidManifest.xml | 2 + .../eu/kanade/tachiyomi/util/RxExtension.kt | 6 + .../tachiyomi/source/CatalogueSource.kt | 2 +- .../tachiyomi/source/ConfigurableSource.kt | 0 .../eu/kanade/tachiyomi/source/Source.kt | 22 +-- .../kanade/tachiyomi/source/SourceFactory.kt | 0 .../tachiyomi/source/UnmeteredSource.kt | 0 .../kanade/tachiyomi/source/model/Filter.kt | 0 .../tachiyomi/source/model/FilterList.kt | 0 .../tachiyomi/source/model/MangasPage.kt | 0 .../eu/kanade/tachiyomi/source/model/Page.kt | 0 .../kanade/tachiyomi/source/model/SChapter.kt | 11 -- .../tachiyomi/source/model/SChapterImpl.kt | 0 .../kanade/tachiyomi/source/model/SManga.kt | 52 +++++++ .../tachiyomi/source/model/SMangaImpl.kt | 24 ++++ .../tachiyomi/source/model/UpdateStrategy.kt | 0 .../tachiyomi/source/online/HttpSource.kt | 15 +- .../source/online/ParsedHttpSource.kt | 0 .../kanade/tachiyomi/util/JsoupExtensions.kt | 26 ++++ .../eu/kanade/tachiyomi/util/RxExtension.kt | 5 + 109 files changed, 1146 insertions(+), 308 deletions(-) create mode 100644 app/src/main/java/dev/yokai/core/migration/migrations/NetworkPrefsMigration.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/models/SMangaExtensions.kt create mode 100644 buildSrc/src/main/kotlin/LocalesConfigPlugin.kt create mode 100644 core/build.gradle.kts create mode 100644 core/src/androidMain/AndroidManifest.xml rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/AndroidCookieJar.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/DohProviders.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/JavaScriptEngine.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/NetworkHelper.kt (68%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/OkHttpExtensions.kt (99%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/ProgressResponseBody.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/Requests.kt (97%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt (94%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt (98%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt (98%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt (94%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt (100%) create mode 100644 core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/util/system/DeviceUtil.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/util/system/KermitExtensions.kt (100%) create mode 100644 core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt (100%) rename {app/src/main/java => core/src/androidMain/kotlin}/eu/kanade/tachiyomi/util/system/WebViewUtil.kt (100%) create mode 100644 core/src/androidMain/kotlin/yokai/util/lang/MokoExtensions.kt rename {app/src/main/java/eu/kanade/tachiyomi/util/system => core/src/androidMain/kotlin/yokai/util/lang}/RxCoroutineBridge.kt (94%) rename {app/src/main/java => core/src/commonMain/kotlin}/eu/kanade/tachiyomi/core/preference/Preference.kt (84%) rename {app/src/main/java => core/src/commonMain/kotlin}/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt (100%) create mode 100644 core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt rename {app/src/main/java => core/src/commonMain/kotlin}/eu/kanade/tachiyomi/network/ProgressListener.kt (100%) create mode 100644 domain/build.gradle.kts create mode 100644 i18n/.gitignore create mode 100644 i18n/build.gradle.kts create mode 100644 i18n/src/androidMain/AndroidManifest.xml create mode 100644 i18n/src/commonMain/moko-resources/base/strings.xml create mode 100644 presentation-core/build.gradle.kts create mode 100644 presentation-core/consumer-rules.pro create mode 100644 presentation-core/proguard-rules.pro create mode 100644 presentation-widget/build.gradle.kts create mode 100644 presentation-widget/consumer-rules.pro create mode 100644 presentation-widget/proguard-rules.pro create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt create mode 100644 presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt create mode 100644 source-api/build.gradle.kts create mode 100644 source-api/consumer-proguard.pro create mode 100644 source-api/src/androidMain/AndroidManifest.xml create mode 100644 source-api/src/androidMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/CatalogueSource.kt (97%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/ConfigurableSource.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/Source.kt (67%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/SourceFactory.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/UnmeteredSource.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/Filter.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/FilterList.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/MangasPage.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/Page.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/SChapter.kt (59%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/SChapterImpl.kt (100%) create mode 100644 source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt create mode 100644 source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt (100%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/online/HttpSource.kt (96%) rename {app/src/main/java => source-api/src/commonMain/kotlin}/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt (100%) create mode 100644 source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt create mode 100644 source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efd653ad48..7aed59ddcb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,14 +39,9 @@ val buildTime: String by lazy { val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") android { - compileSdk = AndroidConfig.compileSdk - ndkVersion = AndroidConfig.ndk - defaultConfig { - minSdk = AndroidConfig.minSdk - targetSdk = AndroidConfig.targetSdk applicationId = "eu.kanade.tachiyomi" - versionCode = 136 + versionCode = 137 versionName = "1.8.4" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled = true @@ -145,14 +140,6 @@ android { kotlinCompilerExtensionVersion = compose.versions.compose.compiler.get() } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - isCoreLibraryDesugaringEnabled = true - } - kotlinOptions { - jvmTarget = "17" - } namespace = "eu.kanade.tachiyomi" sqldelight { @@ -167,6 +154,10 @@ android { } dependencies { + implementation(projects.core) + implementation(projects.i18n) + implementation(projects.sourceApi) + // Compose implementation(compose.bundles.compose) debugImplementation(compose.ui.tooling) @@ -280,8 +271,8 @@ dependencies { implementation(kotlin("stdlib", org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)) - implementation(kotlinx.coroutines.core) - implementation(kotlinx.coroutines.android) + implementation(platform(kotlinx.coroutines.bom)) + implementation(kotlinx.bundles.coroutines) // Text distance implementation(libs.java.string.similarity) @@ -296,8 +287,6 @@ dependencies { implementation(kotlinx.immutable) - "coreLibraryDesugaring"(libs.desugar) - // Tests testImplementation(libs.bundles.test) testRuntimeOnly(libs.bundles.test.runtime) @@ -306,13 +295,6 @@ dependencies { } tasks { - withType { - useJUnitPlatform() - testLogging { - events("passed", "skipped", "failed") - } - } - // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) withType { kotlinOptions.freeCompilerArgs += listOf( diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7d18bb92e9..440f0a528e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,19 +1,20 @@ -dontobfuscate --keep class eu.kanade.tachiyomi.source.** { public protected *; } # Avoid access modification -keep,allowoptimization class eu.kanade.** { public protected *; } -keep,allowoptimization class tachiyomi.** { public protected *; } -keep,allowoptimization class dev.yokai.** { public protected *; } +-keep,allowoptimization class yokai.** { public protected *; } # Keep common dependencies used in extensions --keep class androidx.preference.** { public protected *; } --keep class kotlin.** { public protected *; } +-keep,allowoptimization class androidx.preference.** { public protected *; } +-keep,allowoptimization class kotlin.** { public protected *; } -keep,allowoptimization class kotlinx.coroutines.** { public protected *; } --keep class kotlinx.serialization.** { public protected *; } --keep class okhttp3.** { public protected *; } +-keep,allowoptimization class kotlinx.serialization.** { public protected *; } +-keep,allowoptimization class kotlin.time.** { public protected *; } +-keep,allowoptimization class okhttp3.** { public protected *; } -keep,allowoptimization class okio.** { public protected *; } -keep,allowoptimization class rx.** { public protected *; } --keep class org.jsoup.** { public protected *; } +-keep,allowoptimization class org.jsoup.** { public protected *; } -keep,allowoptimization class com.google.gson.** { public protected *; } -keep,allowoptimization class app.cash.quickjs.** { public protected *; } -keep,allowoptimization class uy.kohesive.injekt.** { public protected *; } diff --git a/app/src/main/java/dev/yokai/core/di/AppModule.kt b/app/src/main/java/dev/yokai/core/di/AppModule.kt index fd2081855f..3edafc645a 100644 --- a/app/src/main/java/dev/yokai/core/di/AppModule.kt +++ b/app/src/main/java/dev/yokai/core/di/AppModule.kt @@ -5,11 +5,13 @@ import androidx.core.content.ContextCompat import androidx.sqlite.db.SupportSQLiteOpenHelper import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver +import com.chuckerteam.chucker.api.ChuckerCollector +import com.chuckerteam.chucker.api.ChuckerInterceptor import dev.yokai.data.AndroidDatabaseHandler import dev.yokai.data.DatabaseHandler import dev.yokai.domain.SplashState -import dev.yokai.domain.extension.interactor.TrustExtension import dev.yokai.domain.storage.StorageManager +import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.CoverCache @@ -89,7 +91,23 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { CoverCache(app) } - addSingletonFactory { NetworkHelper(app) } + addSingletonFactory { + NetworkHelper( + app, + get(), + ) { builder -> + if (BuildConfig.DEBUG) { + builder.addInterceptor( + ChuckerInterceptor.Builder(app) + .collector(ChuckerCollector(app)) + .maxContentLength(250000L) + .redactHeaders(emptySet()) + .alwaysReadResponseBody(false) + .build(), + ) + } + } + } addSingletonFactory { JavaScriptEngine(app) } diff --git a/app/src/main/java/dev/yokai/core/di/PreferenceModule.kt b/app/src/main/java/dev/yokai/core/di/PreferenceModule.kt index 10698079c0..f926828a56 100644 --- a/app/src/main/java/dev/yokai/core/di/PreferenceModule.kt +++ b/app/src/main/java/dev/yokai/core/di/PreferenceModule.kt @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.TrackPreferences +import eu.kanade.tachiyomi.network.NetworkPreferences import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.addSingletonFactory @@ -36,6 +37,8 @@ class PreferenceModule(val application: Application) : InjektModule { addSingletonFactory { DownloadPreferences(get()) } + addSingletonFactory { NetworkPreferences(get()) } + addSingletonFactory { PreferencesHelper( context = application, diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt b/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt index db933c0c41..4acbc83755 100644 --- a/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt +++ b/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt @@ -36,4 +36,5 @@ val migrations: ImmutableList = persistentListOf( ExtensionInstallerEnumMigration(), CutoutMigration(), RepoJsonMigration(), + NetworkPrefsMigration(), ) diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/NetworkPrefsMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/NetworkPrefsMigration.kt new file mode 100644 index 0000000000..f52aed4ddd --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/NetworkPrefsMigration.kt @@ -0,0 +1,23 @@ +package dev.yokai.core.migration.migrations + +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.network.NetworkPreferences + +class NetworkPrefsMigration : Migration { + override val version: Float = 137f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val preferences: NetworkPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val dohProvider = prefs.getInt("doh_provider", -1) + if (dohProvider > -1) { + preferences.dohProvider().set(dohProvider) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceCommon.kt b/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceCommon.kt index 7a845deb05..90c03d3ab7 100644 --- a/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceCommon.kt +++ b/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceCommon.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.res.stringResource import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.core.preference.collectAsState +import eu.kanade.tachiyomi.core.storage.preference.collectAsState @Composable fun storageLocationText( diff --git a/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceItem.kt b/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceItem.kt index bdf5a58c51..5d276011a1 100644 --- a/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceItem.kt +++ b/app/src/main/java/dev/yokai/presentation/component/preference/PreferenceItem.kt @@ -20,7 +20,7 @@ import dev.yokai.presentation.component.preference.widget.SliderPreferenceWidget import dev.yokai.presentation.component.preference.widget.SwitchPreferenceWidget import dev.yokai.presentation.component.preference.widget.TextPreferenceWidget import dev.yokai.presentation.component.preference.widget.TrackingPreferenceWidget -import eu.kanade.tachiyomi.core.preference.collectAsState +import eu.kanade.tachiyomi.core.storage.preference.collectAsState import eu.kanade.tachiyomi.data.track.TrackPreferences import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/dev/yokai/presentation/onboarding/OnboardingController.kt b/app/src/main/java/dev/yokai/presentation/onboarding/OnboardingController.kt index 90284afc28..37f5c6a11c 100644 --- a/app/src/main/java/dev/yokai/presentation/onboarding/OnboardingController.kt +++ b/app/src/main/java/dev/yokai/presentation/onboarding/OnboardingController.kt @@ -4,7 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import dev.yokai.domain.base.BasePreferences -import eu.kanade.tachiyomi.core.preference.collectAsState +import eu.kanade.tachiyomi.core.storage.preference.collectAsState import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController import uy.kohesive.injekt.injectLazy diff --git a/app/src/main/java/dev/yokai/presentation/onboarding/steps/ThemeStep.kt b/app/src/main/java/dev/yokai/presentation/onboarding/steps/ThemeStep.kt index 1e2cde5eec..d793937e83 100644 --- a/app/src/main/java/dev/yokai/presentation/onboarding/steps/ThemeStep.kt +++ b/app/src/main/java/dev/yokai/presentation/onboarding/steps/ThemeStep.kt @@ -22,14 +22,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat import com.google.android.material.color.DynamicColors import dev.yokai.presentation.component.ThemeItem import dev.yokai.presentation.theme.Size import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.core.preference.collectAsState +import eu.kanade.tachiyomi.core.storage.preference.collectAsState import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.system.Themes import eu.kanade.tachiyomi.util.system.appDelegateNightMode diff --git a/app/src/main/java/dev/yokai/presentation/settings/SettingsCommonWidget.kt b/app/src/main/java/dev/yokai/presentation/settings/SettingsCommonWidget.kt index cdb26bc9ce..d12149e545 100644 --- a/app/src/main/java/dev/yokai/presentation/settings/SettingsCommonWidget.kt +++ b/app/src/main/java/dev/yokai/presentation/settings/SettingsCommonWidget.kt @@ -21,7 +21,7 @@ import dev.yokai.presentation.component.Gap import dev.yokai.presentation.component.preference.Preference import dev.yokai.presentation.component.preference.PreferenceItem import dev.yokai.presentation.component.preference.widget.PreferenceGroupHeader -import eu.kanade.tachiyomi.core.preference.collectAsState +import eu.kanade.tachiyomi.core.storage.preference.collectAsState import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.compose.LocalAlertDialog import eu.kanade.tachiyomi.util.compose.LocalBackPress diff --git a/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt new file mode 100644 index 0000000000..be0b8e9773 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.core.storage.preference + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import eu.kanade.tachiyomi.core.preference.Preference + +@Composable +fun Preference.collectAsState(): State { + val flow = remember(this) { changes() } + return flow.collectAsState(initial = get()) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt index 0daae16f5d..7017d0d14c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt @@ -1,5 +1,18 @@ package eu.kanade.tachiyomi.data.database.models +import eu.kanade.tachiyomi.source.model.SChapter + +fun SChapter.toChapter(): ChapterImpl { + return ChapterImpl().apply { + name = this@SChapter.name + url = this@SChapter.url + date_upload = this@SChapter.date_upload + chapter_number = this@SChapter.chapter_number + scanlator = this@SChapter.scanlator + } +} + + class ChapterImpl : Chapter { override var id: Long? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index 4c63956f70..62507cf4e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.Locale +import java.util.* interface Manga : SManga { @@ -34,6 +34,55 @@ interface Manga : SManga { var filtered_scanlators: String? + val originalTitle: String + get() = (this as? MangaImpl)?.ogTitle ?: title + val originalAuthor: String? + get() = (this as? MangaImpl)?.ogAuthor ?: author + val originalArtist: String? + get() = (this as? MangaImpl)?.ogArtist ?: artist + val originalDescription: String? + get() = (this as? MangaImpl)?.ogDesc ?: description + val originalGenre: String? + get() = (this as? MangaImpl)?.ogGenre ?: genre + val originalStatus: Int + get() = (this as? MangaImpl)?.ogStatus ?: status + + val hasSameAuthorAndArtist: Boolean + get() = author == artist || artist.isNullOrBlank() || + author?.contains(artist ?: "", true) == true + + fun copyFrom(other: SManga) { + if (other is Manga) { + if (other.author != null) { + author = other.originalAuthor + } + + if (other.artist != null) { + artist = other.originalArtist + } + + if (other.description != null) { + description = other.originalDescription + } + + if (other.genre != null) { + genre = other.originalGenre + } + + if (other.thumbnail_url != null) { + thumbnail_url = other.thumbnail_url + } + + status = other.originalStatus + } + + update_strategy = other.update_strategy + + if (!initialized) { + initialized = other.initialized + } + } + fun isBlank() = id == Long.MIN_VALUE fun isHidden() = status == -1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 02e6693495..e86b2da262 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.core.preference.getEnum import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder -import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet import eu.kanade.tachiyomi.ui.reader.settings.OrientationType @@ -401,10 +400,6 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto fun useLargeToolbar() = preferenceStore.getBoolean("use_large_toolbar", true) - fun dohProvider() = prefs.getInt(Keys.dohProvider, -1) - - fun defaultUserAgent() = preferenceStore.getString("default_user_agent", NetworkHelper.DEFAULT_USER_AGENT) - fun showSeriesInShortcuts() = prefs.getBoolean(Keys.showSeriesInShortcuts, true) fun showSourcesInShortcuts() = prefs.getBoolean(Keys.showSourcesInShortcuts, true) fun openChapterInShortcuts() = prefs.getBoolean(Keys.openChapterInShortcuts, true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt new file mode 100644 index 0000000000..4cbf8218fc --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt @@ -0,0 +1,29 @@ +package eu.kanade.tachiyomi.source + +import android.graphics.drawable.Drawable +import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.extension.model.Extension +import eu.kanade.tachiyomi.source.online.HttpSource +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +fun Source.includeLangInName(enabledLanguages: Set, extensionManager: ExtensionManager? = null): Boolean { + val httpSource = this as? HttpSource ?: return true + val extManager = extensionManager ?: Injekt.get() + val allExt = httpSource.getExtension(extManager)?.lang == "all" + val onlyAll = httpSource.extOnlyHasAllLanguage(extManager) + val isMultiLingual = enabledLanguages.filterNot { it == "all" }.size > 1 + return (isMultiLingual && allExt) || (lang == "all" && !onlyAll) +} + +fun Source.nameBasedOnEnabledLanguages(enabledLanguages: Set, extensionManager: ExtensionManager? = null): String { + return if (includeLangInName(enabledLanguages, extensionManager)) toString() else name +} + +fun Source.icon(): Drawable? = Injekt.get().getAppIconForSource(this) + +fun HttpSource.getExtension(extensionManager: ExtensionManager? = null): Extension.Installed? = + (extensionManager ?: Injekt.get()).installedExtensionsFlow.value.find { it.sources.contains(this) } + +fun HttpSource.extOnlyHasAllLanguage(extensionManager: ExtensionManager? = null) = + getExtension(extensionManager)?.sources?.all { it.lang == "all" } ?: true diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt deleted file mode 100644 index 681cf126f4..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt +++ /dev/null @@ -1,100 +0,0 @@ -package eu.kanade.tachiyomi.source.model - -import eu.kanade.tachiyomi.data.database.models.MangaImpl -import java.io.Serializable - -interface SManga : Serializable { - - var url: String - - var title: String - - var artist: String? - - var author: String? - - var description: String? - - var genre: String? - - var status: Int - - var thumbnail_url: String? - - var update_strategy: UpdateStrategy - - var initialized: Boolean - - val originalTitle: String - get() = (this as? MangaImpl)?.ogTitle ?: title - val originalAuthor: String? - get() = (this as? MangaImpl)?.ogAuthor ?: author - val originalArtist: String? - get() = (this as? MangaImpl)?.ogArtist ?: artist - val originalDescription: String? - get() = (this as? MangaImpl)?.ogDesc ?: description - val originalGenre: String? - get() = (this as? MangaImpl)?.ogGenre ?: genre - val originalStatus: Int - get() = (this as? MangaImpl)?.ogStatus ?: status - - val hasSameAuthorAndArtist: Boolean - get() = author == artist || artist.isNullOrBlank() || - author?.contains(artist ?: "", true) == true - - fun copyFrom(other: SManga) { - if (other.author != null) { - author = other.originalAuthor - } - - if (other.artist != null) { - artist = other.originalArtist - } - - if (other.description != null) { - description = other.originalDescription - } - - if (other.genre != null) { - genre = other.originalGenre - } - - if (other.thumbnail_url != null) { - thumbnail_url = other.thumbnail_url - } - - status = other.originalStatus - - update_strategy = other.update_strategy - - if (!initialized) { - initialized = other.initialized - } - } - - fun copy() = create().also { - it.url = url - it.title = title - it.artist = artist - it.author = author - it.description = description - it.genre = genre - it.status = status - it.thumbnail_url = thumbnail_url - it.initialized = initialized - } - - companion object { - const val UNKNOWN = 0 - const val ONGOING = 1 - const val COMPLETED = 2 - const val LICENSED = 3 - const val PUBLISHING_FINISHED = 4 - const val CANCELLED = 5 - const val ON_HIATUS = 6 - - fun create(): SManga { - return MangaImpl() - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/models/SMangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/models/SMangaExtensions.kt new file mode 100644 index 0000000000..352fc93f30 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/models/SMangaExtensions.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.source.models + +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.source.model.SManga + +val SManga.originalTitle: String + get() = if (this is Manga) this.originalTitle else title diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt index 79b24f74a0..fa460c9e87 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt @@ -4,6 +4,7 @@ import android.net.Uri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.online.DelegatedHttpSource @@ -13,7 +14,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.Locale +import java.util.* class Cubari : DelegatedHttpSource() { override val domainName: String = "cubari" diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt index 1b98e01ad5..0f80b5b601 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt @@ -4,6 +4,7 @@ import android.net.Uri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.await @@ -19,7 +20,7 @@ import okhttp3.CacheControl import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.util.Locale +import java.util.* class MangaDex : DelegatedHttpSource() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt index 3f7b595e68..0f006897af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaImpl +import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt index 631e0efdf1..7a03655487 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt @@ -4,6 +4,7 @@ import android.net.Uri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.await diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index e5eec22bc9..bc3376c189 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -36,6 +36,7 @@ import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceNotFoundException +import eu.kanade.tachiyomi.source.getExtension import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter @@ -77,8 +78,7 @@ import uy.kohesive.injekt.injectLazy import java.io.File import java.io.FileOutputStream import java.io.OutputStream -import java.util.Date -import java.util.Locale +import java.util.* class MangaDetailsPresenter( val manga: Manga, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index 7a58bb1b80..b121bb7906 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -26,17 +26,18 @@ import androidx.core.widget.TextViewCompat import androidx.transition.TransitionSet import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import coil3.request.CachePolicy -import coil3.request.placeholder import coil3.request.error +import coil3.request.placeholder import com.google.android.material.button.MaterialButton import com.google.android.material.chip.Chip import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.coil.loadManga +import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.ChapterHeaderItemBinding import eu.kanade.tachiyomi.databinding.MangaHeaderItemBinding import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.nameBasedOnEnabledLanguages import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.lang.toNormalized diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt index e48bdbc270..0bff2ed5ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt @@ -4,6 +4,7 @@ import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.view.View import eu.kanade.tachiyomi.databinding.MigrationSourceItemBinding import eu.kanade.tachiyomi.source.icon +import eu.kanade.tachiyomi.source.nameBasedOnEnabledLanguages import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt index bba1a44ae3..427f418ee6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt @@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.databinding.StatsDetailsChartBinding import eu.kanade.tachiyomi.databinding.StatsDetailsControllerBinding import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.nameBasedOnEnabledLanguages import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController import eu.kanade.tachiyomi.ui.library.FilteredLibraryController @@ -59,8 +60,7 @@ import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.withFadeTransaction import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import java.util.Calendar -import java.util.Locale +import java.util.* class StatsDetailsController : BaseCoroutineController(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt index 5f41b95a4e..39e6381dfa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt @@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.icon +import eu.kanade.tachiyomi.source.nameBasedOnEnabledLanguages import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter import eu.kanade.tachiyomi.ui.more.stats.StatsHelper import eu.kanade.tachiyomi.util.isLocal @@ -32,9 +33,8 @@ import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.util.Calendar -import java.util.Locale -import java.util.concurrent.TimeUnit +import java.util.* +import java.util.concurrent.* import kotlin.math.roundToInt class StatsDetailsPresenter( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt index 55b8d93f23..ea27eca7bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys import eu.kanade.tachiyomi.data.preference.changesIn import eu.kanade.tachiyomi.extension.ShizukuInstaller import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.PREF_DOH_360 import eu.kanade.tachiyomi.network.PREF_DOH_ADGUARD import eu.kanade.tachiyomi.network.PREF_DOH_ALIDNS @@ -83,6 +84,7 @@ import java.io.File class SettingsAdvancedController : SettingsLegacyController() { private val network: NetworkHelper by injectLazy() + private val networkPreferences: NetworkPreferences by injectLazy() private val db: DatabaseHelper by injectLazy() @@ -278,7 +280,7 @@ class SettingsAdvancedController : SettingsLegacyController() { } } editTextPreference(activity) { - bindTo(preferences.defaultUserAgent()) + bindTo(networkPreferences.defaultUserAgent()) titleRes = R.string.user_agent_string onChange { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt index 8571e675a9..79aa56c742 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.SourceItemBinding import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.icon +import eu.kanade.tachiyomi.source.includeLangInName import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.compatToolTipText diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt index 33df08f29d..3e6292406e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util.chapter import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.models.originalTitle /** * -R> = regex conversion. diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 621f3f3ab0..b6f85365cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -17,14 +17,11 @@ import android.net.wifi.WifiManager import android.os.Build import android.os.PowerManager import android.provider.Settings -import android.util.TypedValue import android.view.View -import android.widget.Toast import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatDelegate import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent @@ -54,26 +51,6 @@ import kotlin.math.max private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720 -/** - * Display a toast in this context. - * - * @param resource the text resource. - * @param duration the duration of the toast. Defaults to short. - */ -fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) { - Toast.makeText(this, resource, duration).show() -} - -/** - * Display a toast in this context. - * - * @param text the text to display. - * @param duration the duration of the toast. Defaults to short. - */ -fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT) { - Toast.makeText(this, text.orEmpty(), duration).show() -} - /** * Helper method to create a notification. * @@ -125,30 +102,6 @@ fun Context.contextCompatDrawable(@DrawableRes resource: Int): Drawable? { return ContextCompat.getDrawable(this, resource) } -/** - * Converts to dp. - */ -val Int.pxToDp: Int - get() = (this / Resources.getSystem().displayMetrics.density).toInt() - -val Float.pxToDp: Float - get() = (this / Resources.getSystem().displayMetrics.density) - -/** - * Converts to px. - */ -val Int.dpToPx: Int - get() = this.toFloat().dpToPx.toInt() - -val Int.spToPx: Int - get() = this.toFloat().spToPx.toInt() - -val Float.spToPx: Float - get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics) - -val Float.dpToPx: Float - get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics) - /** Converts to px and takes into account LTR/RTL layout */ fun Float.dpToPxEnd(resources: Resources): Float { return this * resources.displayMetrics.density * if (resources.isLTR) 1 else -1 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 060454eb11..c05e49c91b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1116,12 +1116,6 @@ No file picker app found Failed to acquire persistent folder access. The app may behave unexpectedly. - - Failed to bypass Cloudflare - Please update the WebView app for better compatibility - - WebView is required for Tachiyomi - See your recently updated library entries Widget not available when app lock is enabled diff --git a/build.gradle.kts b/build.gradle.kts index d97f392324..7cbeb0930d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,7 @@ +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.BasePlugin +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile import java.util.* plugins { @@ -15,6 +19,7 @@ buildscript { classpath(kotlinx.serialization.gradle) classpath(libs.firebase.crashlytics.gradle) classpath(libs.sqldelight.gradle) + classpath(libs.moko.generator) } repositories { gradlePluginPortal() @@ -23,6 +28,45 @@ buildscript { } } +subprojects { + tasks.withType { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + } + + tasks.withType { + useJUnitPlatform() + testLogging { + events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + } + + plugins.withType { + configure { + compileSdkVersion(AndroidConfig.compileSdk) + + defaultConfig { + minSdk = AndroidConfig.minSdk + targetSdk = AndroidConfig.targetSdk + ndk { + version = AndroidConfig.ndk + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + isCoreLibraryDesugaringEnabled = true + } + + dependencies { + add("coreLibraryDesugaring", libs.desugar) + } + } + } +} + tasks.named("dependencyUpdates", com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask::class.java).configure { rejectVersionIf { val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { candidate.version.uppercase(Locale.ROOT).contains(it) } diff --git a/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt b/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt new file mode 100644 index 0000000000..c6a13f7c42 --- /dev/null +++ b/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt @@ -0,0 +1,41 @@ +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.TaskContainerScope + +fun TaskContainerScope.registerLocalesConfigTask(project: Project): TaskProvider { + return with(project) { + register("generateLocalesConfig") { + val emptyResourcesElement = "\\s*|".toRegex() + val valuesPrefix = "values-?".toRegex() + + val languages = fileTree("$projectDir/src/main/res/") + .matching { + include("**/strings.xml") + } + .filterNot { + it.readText().contains(emptyResourcesElement) + } + .map { it.parentFile.name } + .sorted() + .joinToString(separator = "\n") { + val language = it + .replace(valuesPrefix, "") + .replace("-r", "-") + .takeIf(String::isNotBlank) ?: "en" + " " + } + + val content = """ + + +$languages + + """.trimIndent() + + val localeFile = file("$projectDir/src/main/res/xml/locales_config.xml") + localeFile.parentFile.mkdirs() + localeFile.writeText(content) + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000000..3eda79fb66 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") + id("com.android.library") +} + +kotlin { + androidTarget() + sourceSets { + val commonMain by getting { + dependencies { + implementation(projects.i18n) + api(libs.bundles.logging) + } + } + val androidMain by getting { + dependencies { + api(libs.okhttp) + api(libs.okhttp.logging.interceptor) + api(libs.okhttp.dnsoverhttps) + api(libs.okhttp.brotli) + api(libs.okio) + + api(androidx.preference) + api(libs.rxjava) + api(project.dependencies.enforcedPlatform(kotlinx.coroutines.bom)) + api(kotlinx.coroutines.core) + api(kotlinx.serialization.json) + api(kotlinx.serialization.json.okio) + + implementation(libs.quickjs.android) + } + } + } +} + +android { + namespace = "yokai.core" +} + +tasks { + withType { + kotlinOptions.freeCompilerArgs += listOf( + "-Xcontext-receivers", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", + ) + } +} diff --git a/core/src/androidMain/AndroidManifest.xml b/core/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/core/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt similarity index 68% rename from app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index b73ca6c50a..5fb1cd3703 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -1,10 +1,6 @@ package eu.kanade.tachiyomi.network import android.content.Context -import com.chuckerteam.chucker.api.ChuckerCollector -import com.chuckerteam.chucker.api.ChuckerInterceptor -import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.IgnoreGzipInterceptor import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor @@ -12,13 +8,14 @@ import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.brotli.BrotliInterceptor -import uy.kohesive.injekt.injectLazy import java.io.File -import java.util.concurrent.TimeUnit +import java.util.concurrent.* -class NetworkHelper(val context: Context) { - - private val preferences: PreferencesHelper by injectLazy() +class NetworkHelper( + val context: Context, + private val networkPreferences: NetworkPreferences, + private val block: (OkHttpClient.Builder) -> Unit, +) { private val cacheDir = File(context.cacheDir, "network_cache") @@ -43,18 +40,9 @@ class NetworkHelper(val context: Context) { .addNetworkInterceptor(IgnoreGzipInterceptor()) .addNetworkInterceptor(BrotliInterceptor) .apply { - if (BuildConfig.DEBUG) { - addInterceptor( - ChuckerInterceptor.Builder(context) - .collector(ChuckerCollector(context)) - .maxContentLength(250000L) - .redactHeaders(emptySet()) - .alwaysReadResponseBody(false) - .build(), - ) - } + block(this) - when (preferences.dohProvider()) { + when (networkPreferences.dohProvider().get()) { PREF_DOH_CLOUDFLARE -> dohCloudflare() PREF_DOH_GOOGLE -> dohGoogle() PREF_DOH_ADGUARD -> dohAdGuard() @@ -75,7 +63,7 @@ class NetworkHelper(val context: Context) { } val defaultUserAgent - get() = preferences.defaultUserAgent().get().replace("\n", " ").trim() + get() = networkPreferences.defaultUserAgent().get().replace("\n", " ").trim() companion object { const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt similarity index 99% rename from app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index e6eec02f4a..15c8839e5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -16,7 +16,7 @@ import rx.Observable import rx.Producer import rx.Subscription import java.io.IOException -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.* import kotlin.coroutines.resumeWithException val jsonMime = "application/json; charset=utf-8".toMediaType() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt index 6adb0de8ef..dedc62fea3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt @@ -7,7 +7,7 @@ import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.RequestBody -import java.util.concurrent.TimeUnit.MINUTES +import java.util.concurrent.TimeUnit.* private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() private val DEFAULT_HEADERS = Headers.Builder().build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 875bbc2c73..ca3d155395 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -5,7 +5,6 @@ import android.content.Context import android.webkit.WebView import android.widget.Toast import androidx.core.content.ContextCompat -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.network.AndroidCookieJar import eu.kanade.tachiyomi.util.system.WebViewClientCompat import eu.kanade.tachiyomi.util.system.isOutdated @@ -15,8 +14,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response +import yokai.i18n.MR +import yokai.util.lang.getMString import java.io.IOException -import java.util.concurrent.CountDownLatch +import java.util.concurrent.* class CloudflareInterceptor( private val context: Context, @@ -48,7 +49,7 @@ class CloudflareInterceptor( // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that // we don't crash the entire app catch (e: CloudflareBypassException) { - throw IOException(context.getString(R.string.failed_to_bypass_cloudflare)) + throw IOException(context.getMString(MR.strings.failed_to_bypass_cloudflare)) } catch (e: Exception) { throw IOException(e) } @@ -130,7 +131,7 @@ class CloudflareInterceptor( if (!cloudflareBypassed) { // Prompt user to update WebView if it seems too outdated if (isWebViewOutdated) { - context.toast(R.string.please_update_webview, Toast.LENGTH_LONG) + context.toast(MR.strings.please_update_webview, Toast.LENGTH_LONG) } throw CloudflareBypassException() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt index 994c3c032d..e059f2b742 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -5,7 +5,7 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import java.io.IOException -import java.util.concurrent.TimeUnit +import java.util.concurrent.* /** * An OkHttp interceptor that handles rate limiting. diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt index b10b6904b5..a1307de1f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -6,7 +6,7 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import java.io.IOException -import java.util.concurrent.TimeUnit +import java.util.concurrent.* /** * An OkHttp interceptor that handles given url host's rate limiting. diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index be8db9ee5e..47018c7443 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -5,7 +5,6 @@ import android.os.Build import android.webkit.WebSettings import android.webkit.WebView import android.widget.Toast -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.launchUI @@ -15,9 +14,9 @@ import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response -import java.util.Locale -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit +import yokai.i18n.MR +import java.util.* +import java.util.concurrent.* abstract class WebViewInterceptor( private val context: Context, @@ -57,7 +56,7 @@ abstract class WebViewInterceptor( if (!WebViewUtil.supportsWebView(context)) { launchUI { - context.toast(R.string.webview_is_required, Toast.LENGTH_LONG) + context.toast(MR.strings.webview_is_required, Toast.LENGTH_LONG) } return response } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt new file mode 100644 index 0000000000..2fc2ef21d4 --- /dev/null +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt @@ -0,0 +1,28 @@ +package eu.kanade.tachiyomi.util.system + +import android.content.res.Resources +import android.util.TypedValue + +/** + * Converts to dp. + */ +val Int.pxToDp: Int + get() = (this / Resources.getSystem().displayMetrics.density).toInt() + +val Float.pxToDp: Float + get() = (this / Resources.getSystem().displayMetrics.density) + +/** + * Converts to px. + */ +val Int.dpToPx: Int + get() = this.toFloat().dpToPx.toInt() + +val Int.spToPx: Int + get() = this.toFloat().spToPx.toInt() + +val Float.spToPx: Float + get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics) + +val Float.dpToPx: Float + get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/KermitExtensions.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/util/system/KermitExtensions.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt new file mode 100644 index 0000000000..1930e6eae7 --- /dev/null +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt @@ -0,0 +1,37 @@ +package eu.kanade.tachiyomi.util.system + +import android.content.Context +import android.widget.Toast +import androidx.annotation.StringRes +import dev.icerock.moko.resources.StringResource +import yokai.util.lang.getMString + +/** + * Display a toast in this context. + * + * @param resource the text resource. + * @param duration the duration of the toast. Defaults to short. + */ +fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) { + Toast.makeText(this, resource, duration).show() +} + +/** + * Display a toast in this context. + * + * @param resource the text resource. + * @param duration the duration of the toast. Defaults to short. + */ +fun Context.toast(resource: StringResource, duration: Int = Toast.LENGTH_SHORT) { + toast(getMString(resource), duration) +} + +/** + * Display a toast in this context. + * + * @param text the text to display. + * @param duration the duration of the toast. Defaults to short. + */ +fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT) { + Toast.makeText(this, text.orEmpty(), duration).show() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt rename to core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt diff --git a/core/src/androidMain/kotlin/yokai/util/lang/MokoExtensions.kt b/core/src/androidMain/kotlin/yokai/util/lang/MokoExtensions.kt new file mode 100644 index 0000000000..4d431d9f6c --- /dev/null +++ b/core/src/androidMain/kotlin/yokai/util/lang/MokoExtensions.kt @@ -0,0 +1,8 @@ +package yokai.util.lang + +import android.content.Context +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.desc.Resource +import dev.icerock.moko.resources.desc.StringDesc + +fun Context.getMString(stringRes: StringResource): String = StringDesc.Resource(stringRes).toString(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/RxCoroutineBridge.kt b/core/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/util/system/RxCoroutineBridge.kt rename to core/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt index bba89d1d8e..a4909a77ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/RxCoroutineBridge.kt +++ b/core/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt @@ -1,17 +1,18 @@ -package eu.kanade.tachiyomi.util.system +package yokai.util.lang import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import rx.Emitter import rx.Observable import rx.Subscriber import rx.Subscription +import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -21,6 +22,7 @@ import kotlin.coroutines.resumeWithException suspend fun Observable.awaitSingle(): T = single().awaitOne() +@OptIn(InternalCoroutinesApi::class) private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutine { cont -> cont.unsubscribeOnCancellation( subscribe( diff --git a/app/src/main/java/eu/kanade/tachiyomi/core/preference/Preference.kt b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt similarity index 84% rename from app/src/main/java/eu/kanade/tachiyomi/core/preference/Preference.kt rename to core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt index 6addefaa11..2950d0db48 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/core/preference/Preference.kt +++ b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt @@ -1,9 +1,5 @@ package eu.kanade.tachiyomi.core.preference -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -77,9 +73,3 @@ fun Preference.toggle(): Boolean { set(!get()) return get() } - -@Composable -fun Preference.collectAsState(): State { - val flow = remember(this) { changes() } - return flow.collectAsState(initial = get()) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt rename to core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt new file mode 100644 index 0000000000..8a1023b082 --- /dev/null +++ b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.network + +import eu.kanade.tachiyomi.core.preference.PreferenceStore + +class NetworkPreferences(private val preferenceStore: PreferenceStore) { + + fun dohProvider() = preferenceStore.getInt("doh_provider", -1) + + fun defaultUserAgent() = preferenceStore.getString("default_user_agent", NetworkHelper.DEFAULT_USER_AGENT) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt b/core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt rename to core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts new file mode 100644 index 0000000000..c35a38c744 --- /dev/null +++ b/domain/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "yokai.domain" +} + +dependencies { +} diff --git a/gradle.properties b/gradle.properties index be89a213a4..a3c529f420 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,7 +20,7 @@ # AndroidX support android.enableJetifier=true android.useAndroidX=true -org.gradle.jvmargs=-Xmx2048M +org.gradle.jvmargs=-Xmx4G org.gradle.caching=true android.nonTransitiveRClass=false android.nonFinalResIds=false diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index b6ef6bf683..8c4afaf538 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,13 +1,13 @@ [versions] kotlin = "1.9.24" -coroutines = "1.8.0" serialization = "1.6.2" xml_serialization = "0.86.3" [libraries] -coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } -coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } -coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } +coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.8.0" } +coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } +coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } +coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } serialization-gradle = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } @@ -22,6 +22,7 @@ serialization = [ "serialization-gradle", "serialization-json", "serialization-json-okio", "serialization-protobuf", "serialization-xml", "serialization-xml-core" ] +coroutines = [ "coroutines-android", "coroutines-core" ] [plugins] android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6ef3370c25..9335bac840 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ chucker = "3.5.2" coil3 = "3.0.0-alpha06" flexible-adapter = "c8013533" fast_adapter = "5.6.0" +moko = "0.24.0" nucleus = "3.0.0" okhttp = "5.0.0-alpha.14" shizuku = "12.1.0" @@ -59,6 +60,10 @@ junit-android = { module = "androidx.test.ext:junit", version = "1.1.5" } junrar = { module = "com.github.junrar:junrar", version = "7.5.5" } loading-button = { module = "br.com.simplepass:loading-button-android", version = "2.2.0" } mockk = { module = "io.mockk:mockk", version = "1.13.11" } + +moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko" } +moko-generator = { module = "dev.icerock.moko:resources-generator", version.ref = "moko" } + okio = { module = "com.squareup.okio:okio", version = "3.9.0" } okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } diff --git a/i18n/.gitignore b/i18n/.gitignore new file mode 100644 index 0000000000..4d99b14242 --- /dev/null +++ b/i18n/.gitignore @@ -0,0 +1,2 @@ +# Generated +locales_config.xml diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts new file mode 100644 index 0000000000..f05de8ca2c --- /dev/null +++ b/i18n/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + kotlin("multiplatform") + id("com.android.library") + id("dev.icerock.mobile.multiplatform-resources") +} + +kotlin { + androidTarget() + + applyDefaultHierarchyTemplate() + + sourceSets { + val commonMain by getting { + dependencies { + api(libs.moko.resources) + } + } + val androidMain by getting { + dependsOn(commonMain) + } + } +} + +android { + namespace = "yokai.i18n" +} + +multiplatformResources { + resourcesPackage.set("yokai.i18n") +} + +tasks { + val localesConfigTask = registerLocalesConfigTask(project) + preBuild { + dependsOn(localesConfigTask) + } + + withType { + kotlinOptions.freeCompilerArgs += listOf( + "-Xexpect-actual-classes", + ) + } +} diff --git a/i18n/src/androidMain/AndroidManifest.xml b/i18n/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/i18n/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml new file mode 100644 index 0000000000..28f17080a6 --- /dev/null +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -0,0 +1,8 @@ + + + + Failed to bypass Cloudflare + + Please update the WebView app for better compatibility + WebView is required for Tachiyomi + diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts new file mode 100644 index 0000000000..7c080dcdb5 --- /dev/null +++ b/presentation-core/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "yokai.presentation.core" + + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } +} + +dependencies { + +} diff --git a/presentation-core/consumer-rules.pro b/presentation-core/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/presentation-core/proguard-rules.pro b/presentation-core/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/presentation-core/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/presentation-widget/build.gradle.kts b/presentation-widget/build.gradle.kts new file mode 100644 index 0000000000..b89a812723 --- /dev/null +++ b/presentation-widget/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "yokai.presentation.widget" + + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = compose.versions.compose.compiler.get() + } +} + +dependencies { + implementation(projects.core) + implementation(projects.domain) + implementation(projects.presentationCore) + + implementation(androidx.glance.appwidget) + + implementation(libs.coil3) +} diff --git a/presentation-widget/consumer-rules.pro b/presentation-widget/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/presentation-widget/proguard-rules.pro b/presentation-widget/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/presentation-widget/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt new file mode 100644 index 0000000000..a37fa78e07 --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/TachiyomiWidgetManager.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.appwidget + +import android.content.Context +import androidx.glance.appwidget.GlanceAppWidgetManager +import eu.kanade.tachiyomi.appwidget.UpdatesGridGlanceWidget + +class TachiyomiWidgetManager { + + suspend fun Context.init() { + val manager = GlanceAppWidgetManager(this) + if (manager.getGlanceIds(UpdatesGridGlanceWidget::class.java).isNotEmpty()) { + UpdatesGridGlanceWidget().loadData() + } + } +} diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt new file mode 100644 index 0000000000..4423aeebf5 --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceReceiver.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.appwidget + +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver + +class UpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = UpdatesGridGlanceWidget().apply { loadData() } +} diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt new file mode 100644 index 0000000000..dda4a6a6d6 --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt @@ -0,0 +1,128 @@ +package eu.kanade.tachiyomi.appwidget + +import android.app.Application +import android.content.Context +import android.graphics.Bitmap +import android.os.Build +import androidx.core.graphics.drawable.toBitmap +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.ImageProvider +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager +import androidx.glance.appwidget.SizeMode +import androidx.glance.appwidget.appWidgetBackground +import androidx.glance.appwidget.provideContent +import androidx.glance.appwidget.updateAll +import androidx.glance.background +import androidx.glance.layout.fillMaxSize +import coil3.executeBlocking +import coil3.imageLoader +import coil3.request.CachePolicy +import coil3.request.ImageRequest +import coil3.request.transformations +import coil3.size.Precision +import coil3.size.Scale +import coil3.transform.RoundedCornersTransformation +import eu.kanade.tachiyomi.appwidget.components.CoverHeight +import eu.kanade.tachiyomi.appwidget.components.CoverWidth +import eu.kanade.tachiyomi.appwidget.components.LockedWidget +import eu.kanade.tachiyomi.appwidget.components.UpdatesWidget +import eu.kanade.tachiyomi.appwidget.util.appWidgetBackgroundRadius +import eu.kanade.tachiyomi.appwidget.util.calculateRowAndColumnCount +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.recents.RecentsPresenter +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.launchIO +import kotlinx.coroutines.MainScope +import uy.kohesive.injekt.injectLazy +import java.util.* +import kotlin.math.min + +class UpdatesGridGlanceWidget : GlanceAppWidget() { + private val app: Application by injectLazy() + private val preferences: PreferencesHelper by injectLazy() + + private val coroutineScope = MainScope() + + private var data: List>? = null + + override val sizeMode = SizeMode.Exact + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + // If app lock enabled, don't do anything + if (preferences.useBiometrics().get()) { + LockedWidget() + } else { + UpdatesWidget(data) + } + } + } + + fun loadData(list: List>? = null) { + coroutineScope.launchIO { + // Don't show anything when lock is active + if (preferences.useBiometrics().get()) { + updateAll(app) + return@launchIO + } + + val manager = GlanceAppWidgetManager(app) + val ids = manager.getGlanceIds(this@UpdatesGridGlanceWidget::class.java) + if (ids.isEmpty()) return@launchIO + + val (rowCount, columnCount) = ids + .flatMap { manager.getAppWidgetSizes(it) } + .maxBy { it.height.value * it.width.value } + .calculateRowAndColumnCount() + val processList = list ?: RecentsPresenter.getRecentManga(customAmount = min(50, rowCount * columnCount)) + + data = prepareList(processList, rowCount * columnCount) + ids.forEach { update(app, it) } + } + } + + private fun prepareList(processList: List>, take: Int): List> { + // Resize to cover size + val widthPx = CoverWidth.value.toInt().dpToPx + val heightPx = CoverHeight.value.toInt().dpToPx + val roundPx = app.resources.getDimension(R.dimen.appwidget_inner_radius) + return processList +// .distinctBy { it.first.id } + .sortedByDescending { it.second } + .take(take) + .map { it.first } + .map { updatesView -> + val request = ImageRequest.Builder(app) + .data(updatesView) + .memoryCachePolicy(CachePolicy.DISABLED) + .precision(Precision.EXACT) + .size(widthPx, heightPx) + .scale(Scale.FILL) + .let { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + it.transformations(RoundedCornersTransformation(roundPx)) + } else { + it // Handled by system + } + } + .build() + Pair(updatesView.id!!, app.imageLoader.executeBlocking(request).image?.asDrawable(app.resources)?.toBitmap()) + } + } + + companion object { + val DateLimit: Calendar + get() = Calendar.getInstance().apply { + time = Date() + add(Calendar.MONTH, -3) + } + } +} + +val ContainerModifier = GlanceModifier + .fillMaxSize() + .background(ImageProvider(R.drawable.appwidget_background)) + .appWidgetBackground() + .appWidgetBackgroundRadius() diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt new file mode 100644 index 0000000000..8911445446 --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt @@ -0,0 +1,44 @@ +package eu.kanade.tachiyomi.appwidget.components + +import android.content.Intent +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceModifier +import androidx.glance.LocalContext +import androidx.glance.action.clickable +import androidx.glance.appwidget.action.actionStartActivity +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.padding +import androidx.glance.text.Text +import androidx.glance.text.TextAlign +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.appwidget.ContainerModifier +import eu.kanade.tachiyomi.appwidget.util.stringResource +import eu.kanade.tachiyomi.ui.main.MainActivity + +@Composable +fun LockedWidget() { + val intent = Intent(LocalContext.current, Class.forName(MainActivity.MAIN_ACTIVITY)).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + Box( + modifier = GlanceModifier + .clickable(actionStartActivity(intent)) + .then(ContainerModifier) + .padding(8.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(R.string.appwidget_unavailable_locked), + style = TextStyle( + color = ColorProvider(R.color.appwidget_on_secondary_container), + fontSize = 12.sp, + textAlign = TextAlign.Center, + ), + ) + } +} diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt new file mode 100644 index 0000000000..a9aa232d08 --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesMangaCover.kt @@ -0,0 +1,48 @@ +package eu.kanade.tachiyomi.appwidget.components + +import android.graphics.Bitmap +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.layout.Box +import androidx.glance.layout.ContentScale +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.size +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.appwidget.util.appWidgetInnerRadius + +val CoverWidth = 58.dp +val CoverHeight = 87.dp + +@Composable +fun UpdatesMangaCover( + modifier: GlanceModifier = GlanceModifier, + cover: Bitmap?, +) { + Box( + modifier = modifier + .size(width = CoverWidth, height = CoverHeight) + .appWidgetInnerRadius(), + ) { + if (cover != null) { + Image( + provider = ImageProvider(cover), + contentDescription = null, + modifier = GlanceModifier + .fillMaxSize() + .appWidgetInnerRadius(), + contentScale = ContentScale.Crop, + ) + } else { + // Enjoy placeholder + Image( + provider = ImageProvider(R.drawable.appwidget_cover_error), + contentDescription = null, + modifier = GlanceModifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + } + } +} diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt new file mode 100644 index 0000000000..e447e47d4e --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt @@ -0,0 +1,74 @@ +package eu.kanade.tachiyomi.appwidget.components + +import android.content.Intent +import android.graphics.Bitmap +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.glance.GlanceModifier +import androidx.glance.LocalContext +import androidx.glance.LocalSize +import androidx.glance.action.clickable +import androidx.glance.appwidget.CircularProgressIndicator +import androidx.glance.appwidget.action.actionStartActivity +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.Column +import androidx.glance.layout.Row +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.padding +import androidx.glance.text.Text +import eu.kanade.tachiyomi.appwidget.ContainerModifier +import eu.kanade.tachiyomi.appwidget.util.calculateRowAndColumnCount +import eu.kanade.tachiyomi.appwidget.util.stringResource +import eu.kanade.tachiyomi.ui.main.SearchActivity + +@Composable +fun UpdatesWidget(data: List>?) { + val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount() + val clazz = Class.forName("eu.kanade.tachiyomi.ui.main.MainActivity") + val mainIntent = Intent(LocalContext.current, clazz).setAction("eu.kanade.tachiyomi.SHOW_RECENTS") + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + Column( + modifier = ContainerModifier.clickable(actionStartActivity(mainIntent)), + verticalAlignment = Alignment.CenterVertically, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (data == null) { + CircularProgressIndicator() + } else if (data.isEmpty()) { + Text(text = stringResource(R.string.no_recent_read_updated_manga)) + } else { + (0 until rowCount).forEach { i -> + val coverRow = (0 until columnCount).mapNotNull { j -> + data.getOrNull(j + (i * columnCount)) + } + if (coverRow.isNotEmpty()) { + Row( + modifier = GlanceModifier + .padding(vertical = 4.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalAlignment = Alignment.CenterVertically, + ) { + coverRow.forEach { (mangaId, cover) -> + Box( + modifier = GlanceModifier + .padding(horizontal = 3.dp), + contentAlignment = Alignment.Center, + ) { + val intent = SearchActivity.openMangaIntent(LocalContext.current, mangaId, true) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + // https://issuetracker.google.com/issues/238793260 + .addCategory(mangaId.toString()) + UpdatesMangaCover( + modifier = GlanceModifier.clickable(actionStartActivity(intent)), + cover = cover, + ) + } + } + } + } + } + } + } +} diff --git a/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt new file mode 100644 index 0000000000..5f56877211 --- /dev/null +++ b/presentation-widget/src/main/java/eu/kanade/tachiyomi/appwidget/util/GlanceUtils.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.appwidget.util + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.DpSize +import androidx.glance.GlanceModifier +import androidx.glance.LocalContext +import androidx.glance.appwidget.cornerRadius +import eu.kanade.tachiyomi.R + +fun GlanceModifier.appWidgetBackgroundRadius(): GlanceModifier { + return this.cornerRadius(R.dimen.appwidget_background_radius) +} + +fun GlanceModifier.appWidgetInnerRadius(): GlanceModifier { + return this.cornerRadius(R.dimen.appwidget_inner_radius) +} + +@Composable +fun stringResource(@StringRes id: Int): String { + return LocalContext.current.getString(id) +} + +/** + * Calculates row-column count. + * + * Row + * Numerator: Container height - container vertical padding + * Denominator: Cover height + cover vertical padding + * + * Column + * Numerator: Container width - container horizontal padding + * Denominator: Cover width + cover horizontal padding + * + * @return pair of row and column count + */ +fun DpSize.calculateRowAndColumnCount(): Pair { + // Hack: Size provided by Glance manager is not reliable so take at least 1 row and 1 column + // Set max to 10 children each direction because of Glance limitation + val rowCount = (height.value / 95).toInt().coerceIn(1, 10) + val columnCount = (width.value / 64).toInt().coerceIn(1, 10) + return Pair(rowCount, columnCount) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c0a5987077..0be46720cc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,5 +27,13 @@ dependencyResolutionManagement { } } +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + rootProject.name = "Yokai" include(":app") +include(":core") +include(":domain") +include(":i18n") +include(":presentation-core") +include(":presentation-widget") +include(":source-api") diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts new file mode 100644 index 0000000000..0cbb0380dc --- /dev/null +++ b/source-api/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") + id("com.android.library") +} + +kotlin { + androidTarget() + sourceSets { + val commonMain by getting { + dependencies { + api(kotlinx.serialization.json) + api(libs.injekt.core) + api(libs.rxjava) + api(libs.jsoup) + } + } + val androidMain by getting { + dependencies { + implementation(projects.core) + api(androidx.preference) + + // Workaround for https://youtrack.jetbrains.com/issue/KT-57605 + implementation(kotlinx.coroutines.android) + implementation(project.dependencies.platform(kotlinx.coroutines.bom)) + } + } + } +} +android { + namespace = "eu.kanade.tachiyomi.source" + defaultConfig { + consumerProguardFile("consumer-proguard.pro") + } +} +tasks { + withType { + kotlinOptions.freeCompilerArgs += listOf( + "-Xexpect-actual-classes", + ) + } +} diff --git a/source-api/consumer-proguard.pro b/source-api/consumer-proguard.pro new file mode 100644 index 0000000000..aa81da4bfd --- /dev/null +++ b/source-api/consumer-proguard.pro @@ -0,0 +1,5 @@ +-keep class eu.kanade.tachiyomi.source.model.** { public protected *; } +-keep class eu.kanade.tachiyomi.source.online.** { public protected *; } +-keep class eu.kanade.tachiyomi.source.** extends eu.kanade.tachiyomi.source.Source { public protected *; } + +-keep,allowoptimization class eu.kanade.tachiyomi.util.JsoupExtensionsKt { public protected *; } diff --git a/source-api/src/androidMain/AndroidManifest.xml b/source-api/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/source-api/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/source-api/src/androidMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt b/source-api/src/androidMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt new file mode 100644 index 0000000000..9a76345cb9 --- /dev/null +++ b/source-api/src/androidMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt @@ -0,0 +1,6 @@ +package eu.kanade.tachiyomi.util + +import rx.Observable +import yokai.util.lang.awaitSingle + +actual suspend fun Observable.awaitSingle(): T = awaitSingle() diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt index 01cc4a4fc9..f65c2662a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.source import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.util.system.awaitSingle +import eu.kanade.tachiyomi.util.awaitSingle import rx.Observable interface CatalogueSource : Source { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt similarity index 67% rename from app/src/main/java/eu/kanade/tachiyomi/source/Source.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt index b2167ee94c..fec2f89674 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt @@ -1,15 +1,10 @@ package eu.kanade.tachiyomi.source -import android.graphics.drawable.Drawable -import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.system.awaitSingle +import eu.kanade.tachiyomi.util.awaitSingle import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get /** * A basic interface for creating a source. It could be an online source, a local source, etc. @@ -66,19 +61,6 @@ interface Source { return fetchPageList(chapter).awaitSingle() } - fun includeLangInName(enabledLanguages: Set, extensionManager: ExtensionManager? = null): Boolean { - val httpSource = this as? HttpSource ?: return true - val extManager = extensionManager ?: Injekt.get() - val allExt = httpSource.getExtension(extManager)?.lang == "all" - val onlyAll = httpSource.extOnlyHasAllLanguage(extManager) - val isMultiLingual = enabledLanguages.filterNot { it == "all" }.size > 1 - return (isMultiLingual && allExt) || (lang == "all" && !onlyAll) - } - - fun nameBasedOnEnabledLanguages(enabledLanguages: Set, extensionManager: ExtensionManager? = null): String { - return if (includeLangInName(enabledLanguages, extensionManager)) toString() else name - } - @Deprecated( "Use the non-RxJava API instead", ReplaceWith("getMangaDetails"), @@ -101,6 +83,4 @@ interface Source { throw IllegalStateException("Not used") } -fun Source.icon(): Drawable? = Injekt.get().getAppIconForSource(this) - fun Source.preferenceKey(): String = "source_$id" diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/SourceFactory.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/SourceFactory.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/UnmeteredSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/UnmeteredSource.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/UnmeteredSource.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/UnmeteredSource.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Page.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Page.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt similarity index 59% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt index 756b8f2c84..f53bbe8f0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.source.model -import eu.kanade.tachiyomi.data.database.models.ChapterImpl import java.io.Serializable interface SChapter : Serializable { @@ -23,16 +22,6 @@ interface SChapter : Serializable { scanlator = other.scanlator } - fun toChapter(): ChapterImpl { - return ChapterImpl().apply { - name = this@SChapter.name - url = this@SChapter.url - date_upload = this@SChapter.date_upload - chapter_number = this@SChapter.chapter_number - scanlator = this@SChapter.scanlator - } - } - companion object { fun create(): SChapter { return SChapterImpl() diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt new file mode 100644 index 0000000000..c0f8ef8281 --- /dev/null +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.source.model + +import java.io.Serializable + +interface SManga : Serializable { + + var url: String + + var title: String + + var artist: String? + + var author: String? + + var description: String? + + var genre: String? + + var status: Int + + var thumbnail_url: String? + + var update_strategy: UpdateStrategy + + var initialized: Boolean + + fun copy() = create().also { + it.url = url + it.title = title + it.artist = artist + it.author = author + it.description = description + it.genre = genre + it.status = status + it.thumbnail_url = thumbnail_url + it.initialized = initialized + } + + companion object { + const val UNKNOWN = 0 + const val ONGOING = 1 + const val COMPLETED = 2 + const val LICENSED = 3 + const val PUBLISHING_FINISHED = 4 + const val CANCELLED = 5 + const val ON_HIATUS = 6 + + fun create(): SManga { + return SMangaImpl() + } + } +} diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt new file mode 100644 index 0000000000..91a7711cce --- /dev/null +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.source.model + +class SMangaImpl : SManga { + + override lateinit var url: String + + override lateinit var title: String + + override var artist: String? = null + + override var author: String? = null + + override var description: String? = null + + override var genre: String? = null + + override var status: Int = 0 + + override var thumbnail_url: String? = null + + override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE + + override var initialized: Boolean = false +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt similarity index 96% rename from app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index 5fc6ba155b..5ee2052c4a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -1,7 +1,5 @@ package eu.kanade.tachiyomi.source.online -import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.asObservableSuccess @@ -13,15 +11,12 @@ import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.util.lang.getUrlWithoutDomain -import eu.kanade.tachiyomi.util.system.awaitSingle +import eu.kanade.tachiyomi.util.awaitSingle import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.net.URI import java.net.URISyntaxException @@ -121,12 +116,6 @@ abstract class HttpSource : CatalogueSource { } } - fun getExtension(extensionManager: ExtensionManager? = null): Extension.Installed? = - (extensionManager ?: Injekt.get()).installedExtensionsFlow.value.find { it.sources.contains(this) } - - fun extOnlyHasAllLanguage(extensionManager: ExtensionManager? = null) = - getExtension(extensionManager)?.sources?.all { it.lang == "all" } ?: true - /** * Returns the request for the popular manga given the page. * @@ -462,7 +451,7 @@ abstract class HttpSource : CatalogueSource { fun getChapterUrl(manga: SManga?, chapter: SChapter): String? { manga ?: return null - val chapterUrl = chapter.url.getUrlWithoutDomain() + val chapterUrl = getUrlWithoutDomain(chapter.url) val mangaUrl = getMangaUrl(manga) return if (chapterUrl.isBlank()) { mangaUrl diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt similarity index 100% rename from app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt rename to source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt new file mode 100644 index 0000000000..6c166448a5 --- /dev/null +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.util + +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +fun Element.selectText(css: String, defaultValue: String? = null): String? { + return select(css).first()?.text() ?: defaultValue +} + +fun Element.selectInt(css: String, defaultValue: Int = 0): Int { + return select(css).first()?.text()?.toInt() ?: defaultValue +} + +fun Element.attrOrText(css: String): String { + return if (css != "text") attr(css) else text() +} + +/** + * Returns a Jsoup document for this response. + * @param html the body of the response. Use only if the body was read before calling this method. + */ +fun Response.asJsoup(html: String? = null): Document { + return Jsoup.parse(html ?: body.string(), request.url.toString()) +} diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt new file mode 100644 index 0000000000..5b8420d412 --- /dev/null +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/util/RxExtension.kt @@ -0,0 +1,5 @@ +package eu.kanade.tachiyomi.util + +import rx.Observable + +expect suspend fun Observable.awaitSingle(): T