diff --git a/.editorconfig b/.editorconfig
index 1b1fadfbcb..7bee4501e9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,28 +1,13 @@
-root = true
-
[*]
charset = utf-8
-indent_size = 2
-indent_style = space
-insert_final_newline = true
-trim_trailing_whitespace = true
+end_of_line = lf
+indent_style=space
+insert_final_newline=true
-[*.xml]
-indent_size = 4
+[*.{json,json5}]
+indent_size=2
-# noinspection EditorConfigKeyCorrectness
[*.{kt,kts}]
-indent_size = 4
-max_line_length = 120
-
-ij_kotlin_allow_trailing_comma = true
-ij_kotlin_allow_trailing_comma_on_call_site = true
-ij_kotlin_name_count_to_use_star_import = 2147483647
-ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
-
-ktlint_code_style = intellij_idea
-ktlint_function_naming_ignore_when_annotated_with = Composable
-ktlint_standard_class-signature = disabled
-ktlint_standard_discouraged-comment-location = disabled
-ktlint_standard_function-expression-body = disabled
-ktlint_standard_function-signature = disabled
+indent_size=4
+ij_kotlin_allow_trailing_comma=true
+ij_kotlin_allow_trailing_comma_on_call_site=true
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 545636880b..bc57d11bf2 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -35,24 +35,9 @@ body:
required: true
- label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help.
required: true
- - label: I have updated the app to version **[1.9.7.3](https://github.com/null2264/yokai/releases/latest)**.
+ - label: I have updated the app to version **[1.9.7](https://github.com/null2264/yokai/releases/latest)**.
required: true
- label: I have checked through the app settings for my feature.
required: true
- label: I will fill out all of the requested information in this form.
required: true
-
- - type: "textarea"
- id: "prioritisation"
- attributes:
- label: "Is this issue important to you?"
- description: |
- **Please do not modify this text area!**
-
- This template let users to vote with a :+1: reaction if they find it important.
- This is not a guarantee that highly-requested issues will be fixed first, but it helps us to figure out what's important to users. Please react on other users' issues if you find them important.
- value: |
- Add a :+1: [reaction] to [issues you find important].
-
- [reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
- [issues you find important]: https://github.com/null2264/yokai/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc
diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml
index a1f0593f0d..d313d377e4 100644
--- a/.github/ISSUE_TEMPLATE/issue_report.yml
+++ b/.github/ISSUE_TEMPLATE/issue_report.yml
@@ -94,30 +94,15 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help ([Browse → Extensions → Find the extension → Settings → Tap the `[<>]` icon](https://cdn.aap.my.id/extension-repo-link.png), it *should* redirect you to the maintainer).
+ - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help.
required: true
- - label: I am reporting an issue exclusive to this fork. I have also checked that is not an issue on the [main version of Mihon](https://github.com/mihonapp/mihon).
+ - label: I am reporting an issue exclusive to this fork. I have also checked that is not an issue on the [main version of Mihon](https://github.com/mihonapp/mihon)
required: true
- - label: I have tried the [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
+ - label: I have tried the [troubleshooting guide](https://mihon.app/help/).
required: true
- - label: I have updated the app to version **[1.9.7.3](https://github.com/null2264/yokai/releases/latest)**.
+ - label: I have updated the app to version **[1.9.7](https://github.com/null2264/yokai/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
- label: I have filled out all of the requested information in this form.
required: true
-
- - type: "textarea"
- id: "prioritisation"
- attributes:
- label: "Is this issue important to you?"
- description: |
- **Please do not modify this text area!**
-
- This template let users to vote with a :+1: reaction if they find it important.
- This is not a guarantee that highly-requested issues will be fixed first, but it helps us to figure out what's important to users. Please react on other users' issues if you find them important.
- value: |
- Add a :+1: [reaction] to [issues you find important].
-
- [reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
- [issues you find important]: https://github.com/null2264/yokai/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
deleted file mode 100644
index f771cb985a..0000000000
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
----
-
-Add a :+1: [reaction] to [pull requests you find important].
-
-[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/
-[pull requests you find important]: https://github.com/null2264/yokai/pulls?q=is%3Aopen+sort%3Areactions-%2B1-desc
diff --git a/.github/workflows/build_check.yml b/.github/workflows/build_check.yml
index 74bb37273e..abb7c971a7 100644
--- a/.github/workflows/build_check.yml
+++ b/.github/workflows/build_check.yml
@@ -4,15 +4,7 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
-on:
- pull_request:
- paths:
- - '**'
- - '!**.md'
- - '!i18n/src/commonMain/moko-resources/**/strings.xml'
- - '!i18n/src/commonMain/moko-resources/**/plurals.xml'
- - 'i18n/src/commonMain/moko-resources/base/strings.xml'
- - 'i18n/src/commonMain/moko-resources/base/plurals.xml'
+on: [pull_request]
jobs:
build:
@@ -30,21 +22,18 @@ jobs:
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;35.0.0"
- name: Setup Gradle
- uses: null2264/actions/gradle-java-setup@363cb9cf3d66bd9c72ed6860142c6b2c121d7e94
+ uses: null2264/actions/gradle-setup@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29
with:
java: 17
- distro: temurin
+ distro: adopt
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- - name: Build the app
- run: ./gradlew assembleStandardRelease
-
- - name: Run unit tests
- run: ./gradlew testReleaseUnitTest testStandardReleaseUnitTest
+ - name: Build and run tests
+ run: ./gradlew assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
- name: Publish test report
uses: mikepenz/action-junit-report@v5
diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml
index 57f13cf1de..c81a5504cf 100644
--- a/.github/workflows/build_push.yml
+++ b/.github/workflows/build_push.yml
@@ -43,10 +43,10 @@ jobs:
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;35.0.0"
- name: Setup Gradle
- uses: null2264/actions/gradle-java-setup@363cb9cf3d66bd9c72ed6860142c6b2c121d7e94
+ uses: null2264/actions/gradle-setup@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29
with:
java: 17
- distro: temurin
+ distro: adopt
- name: Setup CHANGELOG parser
uses: taiki-e/install-action@parse-changelog
@@ -80,7 +80,6 @@ jobs:
run: |
set -x
echo "VERSION_TAG=v${{github.event.inputs.version}}" >> $GITHUB_ENV
- echo "BUILD_TYPE=StandardRelease" >> $GITHUB_ENV
# BETA
- name: Prepare beta build
@@ -89,14 +88,9 @@ jobs:
set -x
BETA_COUNT=$(git tag -l --sort=refname "v${{github.event.inputs.version}}-b*" | tail -n1 | sed "s/^\S*-b//g")
- if [ -z "$BETA_COUNT" ]; then
- BETA_COUNT="1"
- else
- BETA_COUNT=$((BETA_COUNT+1))
- fi
+ [ "$BETA_COUNT" = "" ] && BETA_COUNT="1" || BETA_COUNT=$((BETA_COUNT+1))
echo "VERSION_TAG=v${{github.event.inputs.version}}-b${BETA_COUNT}" >> $GITHUB_ENV
- echo "BUILD_TYPE=StandardBeta" >> $GITHUB_ENV
# NIGHTLY
- name: Prepare nightly build
@@ -104,17 +98,23 @@ jobs:
run: |
set -x
echo "VERSION_TAG=r$(git rev-list --count HEAD)" >> $GITHUB_ENV
- echo "BUILD_TYPE=StandardNightly" >> $GITHUB_ENV
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "COMMIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- - name: Build the app
- if: startsWith(env.BUILD_TYPE, 'Standard')
- run: ./gradlew assemble${{ env.BUILD_TYPE }}
+ # PROD
+ - name: Build release build and run tests
+ if: startsWith(env.VERSION_TAG, 'v') && github.event.inputs.beta != 'true'
+ run: ./gradlew assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
- - name: Run unit tests
- if: startsWith(env.BUILD_TYPE, 'Standard')
- run: ./gradlew testReleaseUnitTest test${{ env.BUILD_TYPE }}UnitTest
+ # BETA
+ - name: Build beta build and run tests
+ if: startsWith(env.VERSION_TAG, 'v') && github.event.inputs.beta == 'true'
+ run: ./gradlew assembleStandardBeta testReleaseUnitTest testStandardBetaUnitTest
+
+ # NIGHTLY
+ - name: Build nightly build and run tests
+ if: startsWith(env.VERSION_TAG, 'r')
+ run: ./gradlew assembleStandardNightly testReleaseUnitTest testStandardNightlyUnitTest
- name: Upload R8 APK to artifact
uses: actions/upload-artifact@v4
@@ -154,7 +154,7 @@ jobs:
echo "STAGE=${stage}" >> $GITHUB_OUTPUT
- name: Sign APK
- uses: null2264/actions/android-signer@363cb9cf3d66bd9c72ed6860142c6b2c121d7e94
+ uses: null2264/actions/android-signer@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29
if: env.VERSION_TAG != ''
with:
releaseDir: app/build/outputs/apk/standard/${{ steps.version_stage.outputs.STAGE }}
diff --git a/.gitignore b/.gitignore
index fa0048f78b..094f3c64de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,3 @@
*/*/build
.kotlin/
kls_database.db
-weblate.conf
diff --git a/.renovaterc.json5 b/.renovaterc.json5
index 6808816504..c28595b97f 100644
--- a/.renovaterc.json5
+++ b/.renovaterc.json5
@@ -24,8 +24,6 @@
'com.github.tachiyomiorg:image-decoder',
'com.github.tachiyomiorg:unifile',
'com.github.tachiyomiorg:conductor-support-preference',
- 'com.github.chrisbanes:PhotoView',
- 'com.github.PhilJay:MPAndroidChart',
],
enabled: false,
},
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95cea8eab0..18e7a58072 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,101 +6,9 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co
- `Additions` - New features
- `Changes` - Behaviour/visual changes
- `Fixes` - Bugfixes
+- `Translation` - Translation changes/updates
- `Other` - Technical changes/updates
-## [Unreleased]
-
-### Additions
-- Add random library sort
-- Add the ability to save search queries
-- Add toggle to enable/disable hide source on swipe (@Hiirbaf)
-- Add the ability to mark duplicate read chapters as read (@AntsyLich)
-
-### Changes
-- Temporarily disable log file
-- Categories' header now show filtered count when you search the library when you have "Show number of items" enabled (@LeeSF03)
-- Chapter progress now saved everything the page is changed
-
-### Fixes
-- Allow users to bypass onboarding's permission step if Shizuku is installed
-- Fix Recents page shows "No recent chapters" instead of a loading screen
-- Fix not fully loaded entries can't be selected on Library page
-- Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya)
-- Fix source filter bottom sheet unable to be fully scrolled to the bottom
-- Prevent potential "Comparison method violates its general contract!" crash
-- Fix staggered grid cover being squashed for local source (@AwkwardPeak7)
-
-### Translation
-- Update translations from Weblate
-
-### Other
-- Refactor Library to utilize Flow even more
-- Refactor EmptyView to use Compose
-- Refactor Reader ChapterTransition to use Compose (@arkon)
-- [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout
-- Refactor About page to use Compose
-- Adjust Compose-based pages' transition to match J2K's Conductor transition
-- Resolve deprecation warnings
- - Kotlin's context-receiver, schedule for removal on Kotlin v2.1.x and planned to be replaced by context-parameters on Kotlin v2.2
- - Project.exec -> Providers.exec
- - Remove internal API usage to retrieve Kotlin version for kotlin-stdlib
-- Move :core module to :core:main
- - Move archive related code to :core:archive (@AntsyLich)
-- Refactor Library to store LibraryMap instead of flatten list of LibraryItem
- - LibraryItem abstraction to make it easier to manage
- - LibraryManga no longer extend MangaImpl
-- Update dependency gradle to v8.12
-- Update user agent (@Hiirbaf)
-- Update serialization to v1.8.1
-- Update dependency io.github.fornewid:material-motion-compose-core to v1.2.1
-- Update lifecycle to v2.9.0
-- Update dependency org.jsoup:jsoup to v1.20.1
-- Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.4.0
-- Update dependency io.mockk:mockk to v1.14.2
-- Update dependency io.coil-kt.coil3:coil-bom to v3.2.0
-- Update dependency com.squareup.okio:okio to v3.12.0
-- Update dependency com.google.firebase:firebase-bom to v33.14.0
-- Update dependency com.google.accompanist:accompanist-themeadapter-material3 to v0.36.0
-- Update dependency com.github.requery:sqlite-android to v3.49.0
-- Update dependency com.getkeepsafe.taptargetview:taptargetview to v1.15.0
-- Update dependency androidx.window:window to v1.4.0
-- Update dependency androidx.webkit:webkit to v1.13.0
-- Update dependency androidx.sqlite:sqlite-ktx to v2.5.1
-- Update dependency androidx.sqlite:sqlite to v2.5.1
-- Update dependency androidx.recyclerview:recyclerview to v1.4.0
-- Update dependency androidx.core:core-ktx to v1.16.0
-- Update dependency androidx.compose:compose-bom to v2025.05.01
-- Update aboutlibraries to v11.6.3
-- Update plugin kotlinter to v5.1.0
-- Update plugin gradle-versions to v0.52.0
-- Update okhttp monorepo to v5.0.0-alpha.16
-- Update moko to v0.24.5
-- Update kotlin monorepo to v2.1.21
-- Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.2
-- Update dependency me.zhanghai.android.libarchive:library to v1.1.5
-- Update dependency io.insert-koin:koin-bom to v4.0.4
-- Update dependency com.android.tools:desugar_jdk_libs to v2.1.5
-- Update dependency androidx.work:work-runtime-ktx to v2.10.1
-- Update dependency androidx.constraintlayout:constraintlayout to v2.2.1
-- Update plugin firebase-crashlytics to v3.0.3
-- Update null2264/actions digest to 363cb9c
-- Update dependency io.github.pdvrieze.xmlutil:core-android to v0.91.1
-
-## [1.9.7.3]
-
-### Fixes
-- More `Comparison method violates its general contract!` crash prevention
-
-## [1.9.7.2]
-
-### Fixes
-- Fix MyAnimeList timeout issue
-
-## [1.9.7.1]
-
-### Fixes
-- Prevent `Comparison method violates its general contract!` crashes
-
## [1.9.7]
### Changes
diff --git a/README.md b/README.md
index d4ba494c0d..33349a0c9b 100644
--- a/README.md
+++ b/README.md
@@ -12,13 +12,10 @@
A free and open source manga reader
+[](https://github.com/null2264/yokai/actions/workflows/build_push.yml)
+[](/LICENSE)
[](https://discord.gg/mihon)
-[](https://gitlab.com/null2264/yokai)
-[](https://git.aap.my.id/null2264/yokai)
-
-[](https://github.com/null2264/yokai/actions/workflows/build_push.yml)
-[](/LICENSE)
-[](https://hosted.weblate.org/engage/yokai/)
+[](https://gitlab.com/null2264/yokai)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index dc23c131b0..00ba2c765b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,13 +1,15 @@
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsPlugin
import com.google.gms.googleservices.GoogleServicesPlugin
+import java.io.ByteArrayOutputStream
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
- id("yokai.android.application")
- id("yokai.android.application.compose")
+ alias(androidx.plugins.application)
+ alias(kotlinx.plugins.android)
+ alias(kotlinx.plugins.compose.compiler)
alias(kotlinx.plugins.serialization)
alias(kotlinx.plugins.parcelize)
alias(libs.plugins.aboutlibraries)
@@ -21,12 +23,15 @@ if (gradle.startParameter.taskRequests.toString().contains("standard", true)) {
}
fun runCommand(command: String): String {
- val result = providers.exec { commandLine(command.split(" ")) }
- return result.standardOutput.asText.get().trim()
+ val byteOut = ByteArrayOutputStream()
+ project.exec {
+ commandLine = command.split(" ")
+ standardOutput = byteOut
+ }
+ return String(byteOut.toByteArray()).trim()
}
-@Suppress("PropertyName")
-val _versionName = "1.10.0"
+val _versionName = "1.9.8"
val betaCount by lazy {
val betaTags = runCommand("git tag -l --sort=refname v${_versionName}-b*")
@@ -49,7 +54,7 @@ val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi"
- versionCode = 158
+ versionCode = 157
versionName = _versionName
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled = true
@@ -67,6 +72,11 @@ android {
//noinspection ChromeOsAbiSupport
abiFilters += supportedAbis
}
+ externalNativeBuild {
+ cmake {
+ this.arguments("-DHAVE_LIBJXL=FALSE")
+ }
+ }
}
splits {
@@ -112,6 +122,7 @@ android {
buildFeatures {
viewBinding = true
+ compose = true
// If you're here because there's not BuildConfig, build the app first, it'll generate it for you
buildConfig = true
@@ -145,8 +156,7 @@ android {
}
dependencies {
- implementation(projects.core.archive)
- implementation(projects.core.main)
+ implementation(projects.core)
implementation(projects.data)
implementation(projects.domain)
implementation(projects.i18n)
@@ -241,6 +251,8 @@ dependencies {
implementation(libs.shizuku.api)
implementation(libs.shizuku.provider)
+ implementation(kotlin("stdlib", org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION))
+
implementation(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines)
@@ -268,15 +280,17 @@ tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType {
compilerOptions.freeCompilerArgs.addAll(
+ "-Xcontext-receivers",
// "-opt-in=kotlin.Experimental",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalStdlibApi",
- "-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
+ // "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
+ "-opt-in=coil3.annotation.ExperimentalCoilApi",
// "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
@@ -284,6 +298,19 @@ tasks {
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
+
+ if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
+ compilerOptions.freeCompilerArgs.addAll(
+ "-P",
+ "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
+ (project.layout.buildDirectory.asFile.orNull?.absolutePath ?: "/tmp/yokai") + "/compose_metrics",
+ )
+ compilerOptions.freeCompilerArgs.addAll(
+ "-P",
+ "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
+ (project.layout.buildDirectory.asFile.orNull?.absolutePath ?: "/tmp/yokai") + "/compose_metrics",
+ )
+ }
}
// Duplicating Hebrew string assets due to some locale code issues on different devices
diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt
index 8eb0338127..b929105a1c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/App.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt
@@ -300,7 +300,7 @@ fun buildLogWritersToAdd(
) = buildList {
if (!BuildConfig.DEBUG) add(CrashlyticsLogWriter())
- // if (logPath != null && !BuildConfig.DEBUG) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose))
+ if (logPath != null) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose))
}
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt
index 421b83e367..0cd9f75e50 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt
@@ -40,13 +40,9 @@ import eu.kanade.tachiyomi.util.system.launchIO
import java.util.Calendar
import java.util.Date
import kotlin.math.min
-import kotlin.math.roundToLong
import kotlinx.coroutines.MainScope
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.domain.manga.models.cover
-import yokai.domain.recents.interactor.GetRecents
class UpdatesGridGlanceWidget : GlanceAppWidget() {
private val app: Application by injectLazy()
@@ -68,33 +64,6 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
- // FIXME: Don't depends on RecentsPresenter
- private suspend fun getUpdates(customAmount: Int = 0, getRecents: GetRecents = Injekt.get()): List> {
- return getRecents
- .awaitUpdates(
- limit = when {
- customAmount > 0 -> (customAmount * 1.5).roundToLong()
- else -> 25L
- }
- )
- .mapNotNull {
- when {
- it.chapter.read || it.chapter.id == null -> RecentsPresenter.getNextChapter(it.manga)
- it.history.id == null -> RecentsPresenter.getFirstUpdatedChapter(it.manga, it.chapter)
- else -> it.chapter
- } ?: return@mapNotNull null
- it
- }
- .asSequence()
- .distinctBy { it.manga.id }
- .sortedByDescending { it.history.last_read }
- // nChapterItems + nAdditionalItems + cReadingItems
- .take((RecentsPresenter.UPDATES_CHAPTER_LIMIT * 2) + RecentsPresenter.UPDATES_READING_LIMIT_LOWER)
- .filter { it.manga.id != null }
- .map { it.manga to it.history.last_read }
- .toList()
- }
-
fun loadData(list: List>? = null) {
coroutineScope.launchIO {
// Don't show anything when lock is active
@@ -111,7 +80,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
.flatMap { manager.getAppWidgetSizes(it) }
.maxBy { it.height.value * it.width.value }
.calculateRowAndColumnCount()
- val processList = list ?: getUpdates(customAmount = min(50, rowCount * columnCount))
+ val processList = list ?: RecentsPresenter.getRecentManga(customAmount = min(50, rowCount * columnCount))
data = prepareList(processList, rowCount * columnCount)
ids.forEach { update(app, it) }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt
index 56a49cd16d..7b89f51a37 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt
@@ -17,15 +17,15 @@ import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import eu.kanade.tachiyomi.R
+import yokai.i18n.MR
import eu.kanade.tachiyomi.appwidget.ContainerModifier
import eu.kanade.tachiyomi.appwidget.util.stringResource
-import yokai.i18n.MR
-import yokai.presentation.core.Constants
+import eu.kanade.tachiyomi.ui.main.MainActivity
@Composable
fun LockedWidget() {
val context = LocalContext.current
- val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply {
+ val intent = Intent(LocalContext.current, Class.forName(MainActivity.MAIN_ACTIVITY)).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
Box(
diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt
index 542b76d3fc..d9786f70ad 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt
@@ -17,18 +17,19 @@ import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.text.Text
+import eu.kanade.tachiyomi.R
+import yokai.i18n.MR
+import yokai.util.lang.getString
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.MainActivity
import eu.kanade.tachiyomi.ui.main.SearchActivity
-import yokai.i18n.MR
-import yokai.presentation.core.Constants
@Composable
fun UpdatesWidget(data: List>?) {
val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount()
- val mainIntent = Intent(LocalContext.current, MainActivity::class.java).setAction(Constants.SHORTCUT_RECENTS)
+ val mainIntent = Intent(LocalContext.current, MainActivity::class.java).setAction(MainActivity.SHORTCUT_RECENTS)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
Column(
modifier = ContainerModifier.clickable(actionStartActivity(mainIntent)),
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
index 806b80ea22..be0b8e9773 100644
--- 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
@@ -5,17 +5,9 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import eu.kanade.tachiyomi.core.preference.Preference
-import java.text.DateFormat
-import java.text.SimpleDateFormat
-import java.util.Locale
@Composable
fun Preference.collectAsState(): State {
val flow = remember(this) { changes() }
return flow.collectAsState(initial = get())
}
-
-fun String.asDateFormat(): DateFormat = when (this) {
- "" -> DateFormat.getDateInstance(DateFormat.SHORT)
- else -> SimpleDateFormat(this, Locale.getDefault())
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt
index fc2f87bacf..00521bafe0 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt
@@ -57,10 +57,8 @@ data class BackupManga(
@ProtoNumber(805) var customGenre: List? = null,
) {
fun getMangaImpl(): MangaImpl {
- return MangaImpl(
- source = this.source,
- url = this.url,
- ).apply {
+ return MangaImpl().apply {
+ url = this@BackupManga.url
title = this@BackupManga.title
artist = this@BackupManga.artist
author = this@BackupManga.author
@@ -69,6 +67,7 @@ data class BackupManga(
status = this@BackupManga.status
thumbnail_url = this@BackupManga.thumbnailUrl
favorite = this@BackupManga.favorite
+ source = this@BackupManga.source
date_added = this@BackupManga.dateAdded
viewer_flags = (
this@BackupManga.viewer_flags
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
index d9969b931b..a0022e5729 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
@@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.database.models
import android.content.Context
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.ui.library.LibrarySort
-import java.io.Serializable
import yokai.i18n.MR
import yokai.util.lang.getString
+import java.io.Serializable
interface Category : Serializable {
@@ -37,7 +37,8 @@ interface Category : Serializable {
return ((mangaSort?.minus('a') ?: 0) % 2) != 1
}
- fun sortingMode(): LibrarySort? = LibrarySort.valueOf(mangaSort)
+ fun sortingMode(nullAsDND: Boolean = false): LibrarySort? = LibrarySort.valueOf(mangaSort)
+ ?: if (nullAsDND && !isDynamic) LibrarySort.DragAndDrop else null
val isDragAndDrop
get() = (
@@ -55,21 +56,7 @@ interface Category : Serializable {
fun mangaOrderToString(): String =
if (mangaSort != null) mangaSort.toString() else mangaOrder.joinToString("/")
- // For dynamic categories
- fun dynamicHeaderKey(): String {
- if (!isDynamic) throw IllegalStateException("This category is not a dynamic category")
-
- return when {
- sourceId != null -> "${name}$sourceSplitter${sourceId}"
- langId != null -> "${langId}$langSplitter${name}"
- else -> name
- }
- }
-
companion object {
- const val sourceSplitter = "◘•◘"
- const val langSplitter = "⨼⨦⨠"
-
var lastCategoriesAddedTo = emptySet()
fun create(name: String): Category = CategoryImpl().apply {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
index de3e30df4e..6e685036ca 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
@@ -32,14 +32,10 @@ class CategoryImpl : Category {
val category = other as Category
- if (isDynamic && category.isDynamic) return dynamicHeaderKey() == category.dynamicHeaderKey()
-
return name == category.name
}
override fun hashCode(): Int {
- if (isDynamic) return dynamicHeaderKey().hashCode()
-
return name.hashCode()
}
}
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 e9a27c7c53..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
@@ -4,11 +4,11 @@ import eu.kanade.tachiyomi.source.model.SChapter
fun SChapter.toChapter(): ChapterImpl {
return ChapterImpl().apply {
- name = this@toChapter.name
- url = this@toChapter.url
- date_upload = this@toChapter.date_upload
- chapter_number = this@toChapter.chapter_number
- scanlator = this@toChapter.scanlator
+ name = this@SChapter.name
+ url = this@SChapter.url
+ date_upload = this@SChapter.date_upload
+ chapter_number = this@SChapter.chapter_number
+ scanlator = this@SChapter.scanlator
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt
index 7631836fcf..e017568209 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt
@@ -1,10 +1,10 @@
package eu.kanade.tachiyomi.data.database.models
-import eu.kanade.tachiyomi.domain.manga.models.Manga
+import eu.kanade.tachiyomi.ui.library.LibraryItem
import kotlin.math.roundToInt
+import yokai.data.updateStrategyAdapter
data class LibraryManga(
- val manga: Manga,
var unread: Int = 0,
var read: Int = 0,
var category: Int = 0,
@@ -13,11 +13,41 @@ data class LibraryManga(
var latestUpdate: Long = 0,
var lastRead: Long = 0,
var lastFetch: Long = 0,
-) {
+) : MangaImpl() {
+
+ var realMangaCount = 0
+ get() = if (isBlank()) field else throw IllegalStateException("realMangaCount is only accessible by placeholders")
+ set(value) {
+ if (!isBlank()) throw IllegalStateException("realMangaCount can only be set by placeholders")
+ field = value
+ }
+
val hasRead
get() = read > 0
+ @Transient
+ var items: List? = null
+ get() = if (isHidden()) field else throw IllegalStateException("items only accessible by placeholders")
+ set(value) {
+ if (!isHidden()) throw IllegalStateException("items can only be set by placeholders")
+ field = value
+ }
+
companion object {
+ fun createBlank(categoryId: Int): LibraryManga = LibraryManga().apply {
+ title = ""
+ id = Long.MIN_VALUE
+ category = categoryId
+ }
+
+ fun createHide(categoryId: Int, title: String, hiddenItems: List): LibraryManga =
+ createBlank(categoryId).apply {
+ this.title = title
+ this.status = -1
+ this.read = hiddenItems.size
+ this.items = hiddenItems
+ }
+
fun mapper(
// manga
id: Long,
@@ -48,37 +78,34 @@ data class LibraryManga(
latestUpdate: Long,
lastRead: Long,
lastFetch: Long,
- ): LibraryManga = LibraryManga(
- manga = Manga.mapper(
- id = id,
- source = source,
- url = url,
- artist = artist,
- author = author,
- description = description,
- genre = genre,
- title = title,
- status = status,
- thumbnailUrl = thumbnailUrl,
- favorite = favorite,
- lastUpdate = lastUpdate,
- initialized = initialized,
- viewerFlags = viewerFlags,
- hideTitle = hideTitle,
- chapterFlags = chapterFlags,
- dateAdded = dateAdded,
- filteredScanlators = filteredScanlators,
- updateStrategy = updateStrategy,
- coverLastModified = coverLastModified,
- ),
- read = readCount.roundToInt(),
- unread = maxOf((total - readCount).roundToInt(), 0),
- totalChapters = total.toInt(),
- bookmarkCount = bookmarkCount.roundToInt(),
- category = categoryId.toInt(),
- latestUpdate = latestUpdate,
- lastRead = lastRead,
- lastFetch = lastFetch,
- )
+ ): LibraryManga = createBlank(categoryId.toInt()).apply {
+ this.id = id
+ this.source = source
+ this.url = url
+ this.artist = artist
+ this.author = author
+ this.description = description
+ this.genre = genre
+ this.title = title
+ this.status = status.toInt()
+ this.thumbnail_url = thumbnailUrl
+ this.favorite = favorite
+ this.last_update = lastUpdate ?: 0L
+ this.initialized = initialized
+ this.viewer_flags = viewerFlags.toInt()
+ this.hide_title = hideTitle
+ this.chapter_flags = chapterFlags.toInt()
+ this.date_added = dateAdded ?: 0L
+ this.filtered_scanlators = filteredScanlators
+ this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode)
+ this.cover_last_modified = coverLastModified
+ this.read = readCount.roundToInt()
+ this.unread = maxOf((total - readCount).roundToInt(), 0)
+ this.totalChapters = total.toInt()
+ this.bookmarkCount = bookmarkCount.roundToInt()
+ this.latestUpdate = latestUpdate
+ this.lastRead = lastRead
+ this.lastFetch = lastFetch
+ }
}
}
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 42a0c038eb..fb560da49c 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
@@ -182,13 +182,15 @@ var Manga.vibrantCoverColor: Int?
id?.let { MangaCoverMetadata.setVibrantColor(it, value) }
}
-fun Manga.Companion.create(url: String, title: String, source: Long = 0) =
- MangaImpl(
- source = source,
- url = url,
- ).apply {
- this.title = title
- }
+fun Manga.Companion.create(source: Long) = MangaImpl().apply {
+ this.source = source
+}
+
+fun Manga.Companion.create(pathUrl: String, title: String, source: Long = 0) = MangaImpl().apply {
+ url = pathUrl
+ this.title = title
+ this.source = source
+}
fun Manga.Companion.mapper(
id: Long,
@@ -211,12 +213,14 @@ fun Manga.Companion.mapper(
filteredScanlators: String?,
updateStrategy: Long,
coverLastModified: Long,
-) = create(url, title, source).apply {
+) = create(source).apply {
this.id = id
+ this.url = url
this.artist = artist
this.author = author
this.description = description
this.genre = genre
+ this.title = title
this.status = status.toInt()
this.thumbnail_url = thumbnailUrl
this.favorite = favorite
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
index ff0677e9c6..afe855bff3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
@@ -12,11 +12,7 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga
data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History, var extraChapters: List = emptyList()) {
companion object {
- fun createBlank() = MangaChapterHistory(
- MangaImpl(null, -1, ""),
- ChapterImpl(),
- HistoryImpl(),
- )
+ fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl())
fun mapper(
// manga
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
index 6046ddab15..df0342a5d0 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
@@ -8,11 +8,13 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import uy.kohesive.injekt.injectLazy
-open class MangaImpl(
- override var id: Long? = null,
- override var source: Long = -1,
- override var url: String = "",
-) : Manga {
+open class MangaImpl : Manga {
+
+ override var id: Long? = null
+
+ override var source: Long = -1
+
+ override lateinit var url: String
private val customMangaManager: CustomMangaManager by injectLazy()
@@ -105,7 +107,7 @@ open class MangaImpl(
}
override fun hashCode(): Int {
- return if (url.isNotBlank()) {
+ return if (::url.isInitialized) {
url.hashCode()
} else {
(id ?: 0L).hashCode()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
index 8371e1c3b9..830c9b071e 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
@@ -21,8 +21,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
+import uy.kohesive.injekt.api.get
import yokai.domain.download.DownloadPreferences
import yokai.i18n.MR
import yokai.util.lang.getString
@@ -171,7 +171,7 @@ class DownloadManager(
return files.sortedBy { it.name }
.mapIndexed { i, file ->
- Page(i, uri = file.uri).apply { status = Page.State.Ready }
+ Page(i, uri = file.uri).apply { status = Page.State.READY }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
index 23ac9773b7..fd0960f179 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.download
import android.content.Context
+import android.os.Looper
import co.touchlab.kermit.Logger
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.cache.ChapterCache
@@ -54,8 +55,8 @@ import kotlinx.coroutines.supervisorScope
import nl.adaptivity.xmlutil.serialization.XML
import okhttp3.Response
import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
+import uy.kohesive.injekt.api.get
import yokai.core.archive.ZipWriter
import yokai.core.metadata.COMIC_INFO_FILE
import yokai.core.metadata.ComicInfo
@@ -364,11 +365,11 @@ class Downloader(
// Get all the URLs to the source images, fetch pages if necessary
pageList.filter { it.imageUrl.isNullOrEmpty() }.forEach { page ->
- page.status = Page.State.LoadPage
+ page.status = Page.State.LOAD_PAGE
try {
page.imageUrl = download.source.getImageUrl(page)
} catch (e: Throwable) {
- page.status = Page.State.Error
+ page.status = Page.State.ERROR
}
}
@@ -493,12 +494,12 @@ class Downloader(
page.uri = file.uri
page.progress = 100
- page.status = Page.State.Ready
+ page.status = Page.State.READY
} catch (e: Throwable) {
if (e is CancellationException) throw e
// Mark this page as error and allow to download the remaining
page.progress = 0
- page.status = Page.State.Error
+ page.status = Page.State.ERROR
notifier.onError(e.message, chapName, download.manga.title)
}
}
@@ -517,7 +518,7 @@ class Downloader(
tmpDir: UniFile,
filename: String,
): UniFile {
- page.status = Page.State.DownloadImage
+ page.status = Page.State.DOWNLOAD_IMAGE
page.progress = 0
return flow {
val response = source.getImage(page)
@@ -603,9 +604,8 @@ class Downloader(
dirname: String,
tmpDir: UniFile,
) {
- val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")
- if (zip?.isFile != true) throw Exception("Failed to create CBZ file for downloaded chapter")
- ZipWriter(context, zip!!).use { writer ->
+ val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!!
+ ZipWriter(context, zip).use { writer ->
tmpDir.listFiles()?.forEach { file ->
writer.write(file)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
index 4386bfd43b..de3264b16c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
@@ -22,7 +22,7 @@ class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) {
get() = pages?.sumOf(Page::progress) ?: 0
val downloadedImages: Int
- get() = pages?.count { it.status is Page.State.Ready } ?: 0
+ get() = pages?.count { it.status == Page.State.READY } ?: 0
@Transient
private val _statusFlow = MutableStateFlow(State.NOT_DOWNLOADED)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt
index 8f686664a9..cdcf00262a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt
@@ -4,7 +4,6 @@ import android.content.Context
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.domain.manga.models.Manga
-import java.nio.charset.StandardCharsets
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
@@ -27,6 +26,7 @@ import yokai.domain.library.custom.interactor.GetCustomManga
import yokai.domain.library.custom.interactor.RelinkCustomManga
import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo
+import java.nio.charset.StandardCharsets
class CustomMangaManager(val context: Context) {
private val scope = CoroutineScope(Dispatchers.IO)
@@ -176,7 +176,8 @@ class CustomMangaManager(val context: Context) {
val status: Int? = null,
) {
- fun toManga() = MangaImpl(id = this.id).apply {
+ fun toManga() = MangaImpl().apply {
+ id = this@MangaJson.id
title = this@MangaJson.title ?: ""
author = this@MangaJson.author
artist = this@MangaJson.artist
@@ -271,6 +272,9 @@ class CustomMangaManager(val context: Context) {
}
}
- private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) =
- MangaImpl(id = id).apply { this.copyFromComicInfo(comicInfo) }
+ private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) = MangaImpl().apply {
+ this.id = id
+ this.copyFromComicInfo(comicInfo)
+ this.title = comicInfo.series?.value ?: ""
+ }
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
index 105d61c6a9..b2e8eb44c1 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
@@ -173,17 +173,16 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val mangaList = (
if (savedMangasList != null) {
- val mangas =
- getLibraryManga.await()
- .filter { it.manga.id in savedMangasList }
- .distinctBy { it.manga.id }
+ val mangas = getLibraryManga.await().filter {
+ it.id in savedMangasList
+ }.distinctBy { it.id }
val categoryId = inputData.getInt(KEY_CATEGORY, -1)
if (categoryId > -1) categoryIds.add(categoryId)
mangas
} else {
getMangaToUpdate()
}
- ).sortedBy { it.manga.title }
+ ).sortedBy { it.title }
return withIOContext {
try {
@@ -228,7 +227,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private suspend fun updateChaptersJob(mangaToAdd: List) {
// Initialize the variables holding the progress of the updates.
mangaToUpdate.addAll(mangaToAdd)
- mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.manga.source })
+ mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.source })
checkIfMassiveUpdate()
coroutineScope {
val list = mangaToUpdateMap.keys.map { source ->
@@ -258,42 +257,42 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private suspend fun updateDetails(mangaToUpdate: List) = coroutineScope {
// Initialize the variables holding the progress of the updates.
val count = AtomicInteger(0)
- val asyncList = mangaToUpdate.groupBy { it.manga.source }.values.map { list ->
+ val asyncList = mangaToUpdate.groupBy { it.source }.values.map { list ->
async {
requestSemaphore.withPermit {
list.forEach { manga ->
ensureActive()
- val source = sourceManager.get(manga.manga.source) as? HttpSource ?: return@async
+ val source = sourceManager.get(manga.source) as? HttpSource ?: return@async
notifier.showProgressNotification(
- manga.manga,
+ manga,
count.andIncrement,
mangaToUpdate.size,
)
ensureActive()
val networkManga = try {
- source.getMangaDetails(manga.manga.copy())
+ source.getMangaDetails(manga.copy())
} catch (e: java.lang.Exception) {
Logger.e(e)
null
}
if (networkManga != null) {
- manga.manga.prepareCoverUpdate(coverCache, networkManga, false)
- val thumbnailUrl = manga.manga.thumbnail_url
- manga.manga.copyFrom(networkManga)
- manga.manga.initialized = true
+ manga.prepareCoverUpdate(coverCache, networkManga, false)
+ val thumbnailUrl = manga.thumbnail_url
+ manga.copyFrom(networkManga)
+ manga.initialized = true
val request: ImageRequest =
- if (thumbnailUrl != manga.manga.thumbnail_url) {
+ if (thumbnailUrl != manga.thumbnail_url) {
// load new covers in background
- ImageRequest.Builder(context).data(manga.manga.cover())
+ ImageRequest.Builder(context).data(manga.cover())
.memoryCachePolicy(CachePolicy.DISABLED).build()
} else {
- ImageRequest.Builder(context).data(manga.manga.cover())
+ ImageRequest.Builder(context).data(manga.cover())
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.WRITE_ONLY)
.build()
}
context.imageLoader.execute(request)
- updateManga.await(manga.manga.toMangaUpdate())
+ updateManga.await(manga.toMangaUpdate())
}
}
}
@@ -314,9 +313,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val loggedServices = trackManager.services.filter { it.isLogged }
mangaToUpdate.forEach { manga ->
- notifier.showProgressNotification(manga.manga, count++, mangaToUpdate.size)
+ notifier.showProgressNotification(manga, count++, mangaToUpdate.size)
- val tracks = getTrack.awaitAllByMangaId(manga.manga.id!!)
+ val tracks = getTrack.awaitAllByMangaId(manga.id!!)
tracks.forEach { track ->
val service = trackManager.getService(track.sync_id)
@@ -325,7 +324,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val newTrack = service.refresh(track)
insertTrack.await(newTrack)
- syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.manga.id!!, false), track, service)
+ syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.id!!, false), track, service)
} catch (e: Exception) {
Logger.e(e)
}
@@ -377,7 +376,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private fun checkIfMassiveUpdate() {
val largestSourceSize = mangaToUpdate
- .groupBy { it.manga.source }
+ .groupBy { it.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (largestSourceSize > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
@@ -392,7 +391,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val httpSource = sourceManager.get(source) as? HttpSource ?: return false
while (count < mangaToUpdateMap[source]!!.size) {
val manga = mangaToUpdateMap[source]!![count]
- val shouldDownload = manga.manga.shouldDownloadNewChapters(preferences)
+ val shouldDownload = manga.shouldDownloadNewChapters(preferences)
if (updateMangaChapters(manga, this.count.andIncrement, httpSource, shouldDownload)) {
hasDownloads = true
}
@@ -411,15 +410,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
try {
var hasDownloads = false
ensureActive()
- notifier.showProgressNotification(manga.manga, progress, mangaToUpdate.size)
- val fetchedChapters = source.getChapterList(manga.manga.copy())
+ notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
+ val fetchedChapters = source.getChapterList(manga.copy())
if (fetchedChapters.isNotEmpty()) {
- val newChapters = syncChaptersWithSource(fetchedChapters, manga.manga, source)
+ val newChapters = syncChaptersWithSource(fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (shouldDownload) {
downloadChapters(
- manga.manga,
+ manga,
newChapters.first.sortedBy { it.chapter_number },
)
hasDownloads = true
@@ -429,24 +428,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
if (deleteRemoved && newChapters.second.isNotEmpty()) {
val removedChapters = newChapters.second.filter {
- downloadManager.isChapterDownloaded(it, manga.manga) &&
+ downloadManager.isChapterDownloaded(it, manga) &&
newChapters.first.none { newChapter ->
newChapter.chapter_number == it.chapter_number && it.scanlator.isNullOrBlank()
}
}
if (removedChapters.isNotEmpty()) {
- downloadManager.deleteChapters(removedChapters, manga.manga, source)
+ downloadManager.deleteChapters(removedChapters, manga, source)
}
}
if (newChapters.first.size + newChapters.second.size > 0) {
- sendUpdate(manga.manga.id)
+ sendUpdate(manga.id)
}
}
return@coroutineScope hasDownloads
} catch (e: Exception) {
if (e !is CancellationException) {
- failedUpdates[manga.manga] = e.message
- Logger.e { "Failed updating: ${manga.manga.title}: $e" }
+ failedUpdates[manga] = e.message
+ Logger.e { "Failed updating: ${manga.title}: $e" }
}
return@coroutineScope false
}
@@ -462,17 +461,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val restrictions = preferences.libraryUpdateMangaRestriction().get()
return mangaToAdd.filter { manga ->
when {
- MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED -> {
- skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_completed)
+ MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED -> {
+ skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_completed)
}
MANGA_HAS_UNREAD in restrictions && manga.unread != 0 -> {
- skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_caught_up)
+ skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_caught_up)
}
MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead -> {
- skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_started)
+ skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_started)
}
- manga.manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> {
- skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_always_update)
+ manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> {
+ skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_always_update)
}
else -> {
return@filter true
@@ -504,10 +503,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
preferences.libraryUpdateCategories().get().map(String::toInt)
if (categoriesToUpdate.isNotEmpty()) {
categoryIds.addAll(categoriesToUpdate)
- libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.manga.id }
+ libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.id }
} else {
categoryIds.addAll(getCategories.await().mapNotNull { it.id } + 0)
- libraryManga.distinctBy { it.manga.id }
+ libraryManga.distinctBy { it.id }
}
}
@@ -565,13 +564,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
private fun addMangaToQueue(categoryId: Int, manga: List) {
- val mangas = filterMangaToUpdate(manga).sortedBy { it.manga.title }
+ val mangas = filterMangaToUpdate(manga).sortedBy { it.title }
categoryIds.add(categoryId)
addManga(mangas)
}
private fun addCategory(categoryId: Int) {
- val mangas = filterMangaToUpdate(runBlocking { getMangaToUpdate(categoryId) }).sortedBy { it.manga.title }
+ val mangas = filterMangaToUpdate(runBlocking { getMangaToUpdate(categoryId) }).sortedBy { it.title }
categoryIds.add(categoryId)
addManga(mangas)
}
@@ -580,7 +579,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val distinctManga = mangaToAdd.filter { it !in mangaToUpdate }
mangaToUpdate.addAll(distinctManga)
checkIfMassiveUpdate()
- distinctManga.groupBy { it.manga.source }.forEach {
+ distinctManga.groupBy { it.source }.forEach {
// if added queue items is a new source not in the async list or an async list has
// finished running
if (mangaToUpdateMap[it.key].isNullOrEmpty()) {
@@ -728,9 +727,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (mangaToUse != null) {
builder.putLongArray(
KEY_MANGAS,
- mangaToUse.firstOrNull()?.manga?.id?.let { longArrayOf(it) } ?: longArrayOf(),
+ mangaToUse.firstOrNull()?.id?.let { longArrayOf(it) } ?: longArrayOf(),
)
- extraManga = mangaToUse.subList(1, mangaToUse.size).mapNotNull { it.manga.id }
+ extraManga = mangaToUse.subList(1, mangaToUse.size).mapNotNull { it.id }
}
}
val inputData = builder.build()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt
index 323361883f..fd878892e7 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt
@@ -185,14 +185,14 @@ class LibraryUpdateNotifier(private val context: Context) {
val manga = it.key
val chapters = it.value
val chapterNames = chapters.map { chapter ->
- chapter.preferredChapterName(context, manga.manga, preferences)
+ chapter.preferredChapterName(context, manga, preferences)
}
notifications.add(
Pair(
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_yokai)
try {
- val request = ImageRequest.Builder(context).data(manga.manga.cover())
+ val request = ImageRequest.Builder(context).data(manga.cover())
.networkCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.transformations(CircleCropTransformation())
@@ -205,7 +205,7 @@ class LibraryUpdateNotifier(private val context: Context) {
} catch (_: Exception) {
}
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
- setContentTitle(manga.manga.title)
+ setContentTitle(manga.title)
color = ContextCompat.getColor(context, R.color.secondaryTachiyomi)
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
"${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
@@ -224,7 +224,7 @@ class LibraryUpdateNotifier(private val context: Context) {
setContentIntent(
NotificationReceiver.openChapterPendingActivity(
context,
- manga.manga,
+ manga,
chapters.first(),
),
)
@@ -233,7 +233,7 @@ class LibraryUpdateNotifier(private val context: Context) {
context.getString(MR.strings.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context,
- manga.manga,
+ manga,
chapters,
Notifications.ID_NEW_CHAPTERS,
),
@@ -243,13 +243,13 @@ class LibraryUpdateNotifier(private val context: Context) {
context.getString(MR.strings.view_chapters),
NotificationReceiver.openChapterPendingActivity(
context,
- manga.manga,
+ manga,
Notifications.ID_NEW_CHAPTERS,
),
)
setAutoCancel(true)
},
- manga.manga.id.hashCode(),
+ manga.id.hashCode(),
),
)
}
@@ -281,13 +281,13 @@ class LibraryUpdateNotifier(private val context: Context) {
NotificationCompat.BigTextStyle()
.bigText(
updates.keys.joinToString("\n") {
- it.manga.title.chop(45)
+ it.title.chop(45)
},
),
)
}
} else if (!preferences.hideNotificationContent().get()) {
- setContentText(updates.keys.first().manga.title.chop(45))
+ setContentText(updates.keys.first().title.chop(45))
}
priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
index 57af3da28a..649ead87a9 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
@@ -487,7 +487,7 @@ class NotificationReceiver : BroadcastReceiver() {
*/
internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int): PendingIntent {
val newIntent =
- Intent(context, MainActivity::class.java).setAction(Constants.SHORTCUT_MANGA)
+ Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(Constants.MANGA_EXTRA, manga.id)
.putExtra("notificationId", manga.id.hashCode())
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 5d63f59b83..ec29bc9262 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
@@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.core.preference.Preference
import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.core.preference.getEnum
-import eu.kanade.tachiyomi.core.storage.preference.asDateFormat
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
@@ -20,12 +19,13 @@ import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
import eu.kanade.tachiyomi.util.system.Themes
-import java.text.DateFormat
-import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
@@ -187,10 +187,10 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", "POINT_10")
- fun dateFormatRaw() = preferenceStore.getString(Keys.dateFormat, "")
-
- @Deprecated("Use dateFormatRaw().get().asDateFormat() instead")
- fun dateFormat(format: String = dateFormatRaw().get()): DateFormat = format.asDateFormat()
+ fun dateFormat(format: String = preferenceStore.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
+ "" -> DateFormat.getDateInstance(DateFormat.SHORT)
+ else -> SimpleDateFormat(format, Locale.getDefault())
+ }
fun appLanguage() = preferenceStore.getString("app_language", "")
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt
index 69e554f1aa..d86b58ac72 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt
@@ -49,13 +49,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date))
}
}
- authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
- .awaitSuccess()
- .parseAs()
- .let {
- track.library_id = it.data.entry.id
- track
- }
+ with(json) {
+ authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
+ .awaitSuccess()
+ .parseAs()
+ .let {
+ track.library_id = it.data.entry.id
+ track
+ }
+ }
}
}
@@ -72,9 +74,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date))
}
}
- authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
- .awaitSuccess()
- track
+ with(json) {
+ authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
+ .awaitSuccess()
+ track
+ }
}
}
@@ -86,11 +90,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", search)
}
}
- authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
- .awaitSuccess()
- .parseAs()
- .data.page.media
- .map { it.toALManga().toTrack() }
+ with(json) {
+ authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
+ .awaitSuccess()
+ .parseAs()
+ .data.page.media
+ .map { it.toALManga().toTrack() }
+ }
}
}
@@ -103,13 +109,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("manga_id", track.media_id)
}
}
- authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
- .awaitSuccess()
- .parseAs()
- .data.page.mediaList
- .map { it.toALUserManga() }
- .firstOrNull()
- ?.toTrack()
+ with(json) {
+ authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
+ .awaitSuccess()
+ .parseAs()
+ .data.page.mediaList
+ .map { it.toALUserManga() }
+ .firstOrNull()
+ ?.toTrack()
+ }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt
index 44fc5c9d5d..77c6694a20 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt
@@ -75,25 +75,29 @@ class BangumiApi(
.appendQueryParameter("responseGroup", "large")
.appendQueryParameter("max_results", "20")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs()
- .let { result ->
- if (result.code == 404) emptyList()
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .let { result ->
+ if (result.code == 404) emptyList()
- result.list
- ?.map { it.toTrackSearch(trackId) }
- .orEmpty()
- }
+ result.list
+ ?.map { it.toTrackSearch(trackId) }
+ .orEmpty()
+ }
+ }
}
}
suspend fun findLibManga(track: Track): Track? {
return withIOContext {
- authClient.newCall(GET("$API_URL/subject/${track.media_id}"))
- .awaitSuccess()
- .parseAs()
- .toTrackSearch(trackId)
+ with(json) {
+ authClient.newCall(GET("$API_URL/subject/${track.media_id}"))
+ .awaitSuccess()
+ .parseAs()
+ .toTrackSearch(trackId)
+ }
}
}
@@ -107,25 +111,29 @@ class BangumiApi(
.build()
// TODO: get user readed chapter here
- authClient.newCall(requestUserRead)
- .awaitSuccess()
- .parseAs()
- .let {
- if (it.code == 400) return@let null
+ with(json) {
+ authClient.newCall(requestUserRead)
+ .awaitSuccess()
+ .parseAs()
+ .let {
+ if (it.code == 400) return@let null
- track.status = it.status?.id?.toInt() ?: Bangumi.DEFAULT_STATUS
- track.last_chapter_read = it.epStatus!!.toFloat()
- track.score = it.rating!!.toFloat()
- track
- }
+ track.status = it.status?.id?.toInt() ?: Bangumi.DEFAULT_STATUS
+ track.last_chapter_read = it.epStatus!!.toFloat()
+ track.score = it.rating!!.toFloat()
+ track
+ }
+ }
}
}
suspend fun accessToken(code: String): BGMOAuth {
return withIOContext {
- client.newCall(accessTokenRequest(code))
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ client.newCall(accessTokenRequest(code))
+ .awaitSuccess()
+ .parseAs()
+ }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt
index f737edc924..2157c1b69d 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt
@@ -44,7 +44,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
try {
client.newCall(request).execute().use {
when (it.code) {
- 200 -> return it.parseAs().token
+ 200 -> return with(json) { it.parseAs().token }
401 -> {
Logger.w { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
throw IOException("Unauthorized / api key not valid")
@@ -89,10 +89,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
private fun getTotalChapters(url: String): Long {
val requestUrl = getApiVolumesUrl(url)
try {
- val listVolumeDto =
+ val listVolumeDto = with(json) {
authClient.newCall(GET(requestUrl))
.execute()
.parseAs>()
+ }
var volumeNumber = 0L
var maxChapterNumber = 0L
for (volume in listVolumeDto) {
@@ -116,7 +117,9 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
try {
authClient.newCall(GET(requestUrl)).execute().use {
if (it.code == 200) {
- return it.parseAs().number!!.replace(",", ".").toFloat()
+ return with(json) {
+ it.parseAs().number!!.replace(",", ".").toFloat()
+ }
}
if (it.code == 204) {
return 0F
@@ -131,10 +134,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
try {
- val serieDto: SeriesDto =
+ val serieDto: SeriesDto = with(json) {
authClient.newCall(GET(url))
.awaitSuccess()
.parseAs()
+ }
val track = serieDto.toTrack()
track.apply {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt
index b01a0a3a7c..288d9e5c18 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt
@@ -131,12 +131,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun search(query: String): List {
return withIOContext {
- authClient.newCall(GET(ALGOLIA_KEY_URL))
- .awaitSuccess()
- .parseAs()
- .let {
- algoliaSearch(it.media.key, query)
- }
+ with(json) {
+ authClient.newCall(GET(ALGOLIA_KEY_URL))
+ .awaitSuccess()
+ .parseAs()
+ .let {
+ algoliaSearch(it.media.key, query)
+ }
+ }
}
}
@@ -145,23 +147,25 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
val jsonObject = buildJsonObject {
put("params", "query=$query$ALGOLIA_FILTER")
}
- client.newCall(
- POST(
- ALGOLIA_URL,
- headers = headersOf(
- "X-Algolia-Application-Id",
- ALGOLIA_APP_ID,
- "X-Algolia-API-Key",
- key,
+ with(json) {
+ client.newCall(
+ POST(
+ ALGOLIA_URL,
+ headers = headersOf(
+ "X-Algolia-Application-Id",
+ ALGOLIA_APP_ID,
+ "X-Algolia-API-Key",
+ key,
+ ),
+ body = jsonObject.toString().toRequestBody(jsonMime),
),
- body = jsonObject.toString().toRequestBody(jsonMime),
- ),
- )
- .awaitSuccess()
- .parseAs()
- .hits
- .filter { it.subtype != "novel" }
- .map { it.toTrack() }
+ )
+ .awaitSuccess()
+ .parseAs()
+ .hits
+ .filter { it.subtype != "novel" }
+ .map { it.toTrack() }
+ }
}
}
@@ -171,16 +175,18 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
.appendQueryParameter("include", "manga")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs()
- .let {
- if (it.data.isNotEmpty() && it.included.isNotEmpty()) {
- it.firstToTrack()
- } else {
- null
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .let {
+ if (it.data.isNotEmpty() && it.included.isNotEmpty()) {
+ it.firstToTrack()
+ } else {
+ null
+ }
}
- }
+ }
}
}
@@ -190,16 +196,18 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.encodedQuery("filter[id]=${track.media_id}")
.appendQueryParameter("include", "manga")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs()
- .let {
- if (it.data.isNotEmpty() && it.included.isNotEmpty()) {
- it.firstToTrack()
- } else {
- throw Exception("Could not find manga")
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .let {
+ if (it.data.isNotEmpty() && it.included.isNotEmpty()) {
+ it.firstToTrack()
+ } else {
+ throw Exception("Could not find manga")
+ }
}
- }
+ }
}
}
@@ -212,9 +220,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.add("client_id", CLIENT_ID)
.add("client_secret", CLIENT_SECRET)
.build()
- client.newCall(POST(LOGIN_URL, body = formBody))
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ client.newCall(POST(LOGIN_URL, body = formBody))
+ .awaitSuccess()
+ .parseAs()
+ }
}
}
@@ -223,11 +233,13 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
val url = "${BASE_URL}users".toUri().buildUpon()
.encodedQuery("filter[self]=true")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs()
- .data[0]
- .id
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .data[0]
+ .id
+ }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt
index ab2ffd327c..68f3bed857 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt
@@ -26,19 +26,21 @@ class KomgaApi(private val client: OkHttpClient) {
withIOContext {
try {
val track =
- if (url.contains(READLIST_API)) {
- client.newCall(GET(url))
- .awaitSuccess()
- .parseAs()
- .toTrack()
- } else {
- client.newCall(GET(url))
- .awaitSuccess()
- .parseAs()
- .toTrack()
+ with(json) {
+ if (url.contains(READLIST_API)) {
+ client.newCall(GET(url))
+ .awaitSuccess()
+ .parseAs()
+ .toTrack()
+ } else {
+ client.newCall(GET(url))
+ .awaitSuccess()
+ .parseAs()
+ .toTrack()
+ }
}
- val progress =
+ val progress = with(json) {
client
.newCall(
GET(
@@ -57,6 +59,7 @@ class KomgaApi(private val client: OkHttpClient) {
it.parseAs().toV2()
}
}
+ }
track.apply {
cover_url = "$url/thumbnail"
tracking_url = url
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt
index a4b8887c83..ab664dd504 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt
@@ -37,13 +37,15 @@ class MangaUpdatesApi(
suspend fun getSeriesListItem(track: Track): Pair {
val listItem =
- authClient.newCall(
- GET(
- url = "$BASE_URL/v1/lists/series/${track.media_id}",
- ),
- )
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ authClient.newCall(
+ GET(
+ url = "$BASE_URL/v1/lists/series/${track.media_id}",
+ ),
+ )
+ .awaitSuccess()
+ .parseAs()
+ }
val rating = getSeriesRating(track)
@@ -102,13 +104,15 @@ class MangaUpdatesApi(
private suspend fun getSeriesRating(track: Track): MURating? {
return try {
- authClient.newCall(
- GET(
- url = "$BASE_URL/v1/series/${track.media_id}/rating",
- ),
- )
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ authClient.newCall(
+ GET(
+ url = "$BASE_URL/v1/series/${track.media_id}/rating",
+ ),
+ )
+ .awaitSuccess()
+ .parseAs()
+ }
} catch (e: Exception) {
null
}
@@ -147,16 +151,18 @@ class MangaUpdatesApi(
},
)
}
- return client.newCall(
- POST(
- url = "$BASE_URL/v1/series/search",
- body = body.toString().toRequestBody(CONTENT_TYPE),
- ),
- )
- .awaitSuccess()
- .parseAs()
- .results
- .map { it.record }
+ return with(json) {
+ client.newCall(
+ POST(
+ url = "$BASE_URL/v1/series/search",
+ body = body.toString().toRequestBody(CONTENT_TYPE),
+ ),
+ )
+ .awaitSuccess()
+ .parseAs()
+ .results
+ .map { it.record }
+ }
}
suspend fun authenticate(username: String, password: String): MUContext? {
@@ -164,15 +170,17 @@ class MangaUpdatesApi(
put("username", username)
put("password", password)
}
- return client.newCall(
- PUT(
- url = "$BASE_URL/v1/account/login",
- body = body.toString().toRequestBody(CONTENT_TYPE),
- ),
- )
- .awaitSuccess()
- .parseAs()
- .context
+ return with(json) {
+ client.newCall(
+ PUT(
+ url = "$BASE_URL/v1/account/login",
+ body = body.toString().toRequestBody(CONTENT_TYPE),
+ ),
+ )
+ .awaitSuccess()
+ .parseAs()
+ .context
+ }
}
companion object {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
index b3411cd2d7..4a5bc58d0a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
@@ -45,9 +45,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.add("code_verifier", codeVerifier)
.add("grant_type", "authorization_code")
.build()
- client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
+ .awaitSuccess()
+ .parseAs()
+ }
}
}
@@ -57,10 +59,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url("$BASE_API_URL/users/@me")
.get()
.build()
- authClient.newCall(request)
- .awaitSuccess()
- .parseAs()
- .name
+ with(json) {
+ authClient.newCall(request)
+ .awaitSuccess()
+ .parseAs()
+ .name
+ }
}
}
@@ -71,13 +75,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendQueryParameter("q", query.take(64))
.appendQueryParameter("nsfw", "true")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs()
- .data
- .map { async { getMangaDetails(it.node.id) } }
- .awaitAll()
- .filter { !it.publishing_type.contains("novel") }
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .data
+ .map { async { getMangaDetails(it.node.id) } }
+ .awaitAll()
+ .filter { !it.publishing_type.contains("novel") }
+ }
}
}
@@ -87,22 +93,24 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendPath(id.toString())
.appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs()
- .let {
- TrackSearch.create(TrackManager.MYANIMELIST).apply {
- media_id = it.id
- title = it.title
- summary = it.synopsis
- total_chapters = it.numChapters
- cover_url = (it.covers?.large ?: it.covers?.medium).orEmpty()
- tracking_url = "https://myanimelist.net/manga/$media_id"
- publishing_status = it.status.replace("_", " ")
- publishing_type = it.mediaType.replace("_", " ")
- start_date = it.startDate ?: ""
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .let {
+ TrackSearch.create(TrackManager.MYANIMELIST).apply {
+ media_id = it.id
+ title = it.title
+ summary = it.synopsis
+ total_chapters = it.numChapters
+ cover_url = it.covers.large
+ tracking_url = "https://myanimelist.net/manga/$media_id"
+ publishing_status = it.status.replace("_", " ")
+ publishing_type = it.mediaType.replace("_", " ")
+ start_date = it.startDate ?: ""
+ }
}
- }
+ }
}
}
@@ -124,10 +132,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString())
.put(formBodyBuilder.build())
.build()
- authClient.newCall(request)
- .awaitSuccess()
- .parseAs()
- .let { parseMangaItem(it, track) }
+ with(json) {
+ authClient.newCall(request)
+ .awaitSuccess()
+ .parseAs()
+ .let { parseMangaItem(it, track) }
+ }
}
}
@@ -137,13 +147,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendPath(track.media_id.toString())
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
.build()
- authClient.newCall(GET(uri.toString()))
- .awaitSuccess()
- .parseAs()
- .let { item ->
- track.total_chapters = item.numChapters
- item.myListStatus?.let { parseMangaItem(it, track) }
- }
+ with(json) {
+ authClient.newCall(GET(uri.toString()))
+ .awaitSuccess()
+ .parseAs()
+ .let { item ->
+ track.total_chapters = item.numChapters
+ item.myListStatus?.let { parseMangaItem(it, track) }
+ }
+ }
}
}
@@ -178,9 +190,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(urlBuilder.build().toString())
.get()
.build()
- authClient.newCall(request)
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ authClient.newCall(request)
+ .awaitSuccess()
+ .parseAs()
+ }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
index 412e4a49ed..9b7de62837 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
@@ -34,7 +34,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
// Add the authorization header to the original request
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.accessToken}")
- // .header("User-Agent", "null2264/yokai/${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
+ .header("User-Agent", "null2264/yokai/${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
.build()
return chain.proceed(authRequest)
@@ -66,7 +66,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
return runCatching {
if (response.isSuccessful) {
- response.parseAs()
+ with(json) { response.parseAs() }
} else {
response.close()
null
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt
index 6950c54960..c4ab92ee92 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt
@@ -12,7 +12,7 @@ data class MALManga(
val numChapters: Long,
val mean: Double = -1.0,
@SerialName("main_picture")
- val covers: MALMangaCovers?,
+ val covers: MALMangaCovers,
val status: String,
@SerialName("media_type")
val mediaType: String,
@@ -22,6 +22,5 @@ data class MALManga(
@Serializable
data class MALMangaCovers(
- val large: String?,
- val medium: String,
+ val large: String = "",
)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt
index 61286976fa..456acbdfdd 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt
@@ -74,10 +74,12 @@ class ShikimoriApi(
.appendQueryParameter("search", search)
.appendQueryParameter("limit", "20")
.build()
- authClient.newCall(GET(url.toString()))
- .awaitSuccess()
- .parseAs>()
- .map { it.toTrack(trackId) }
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .awaitSuccess()
+ .parseAs>()
+ .map { it.toTrack(trackId) }
+ }
}
}
@@ -100,44 +102,51 @@ class ShikimoriApi(
val urlMangas = "$API_URL/mangas".toUri().buildUpon()
.appendPath(track.media_id.toString())
.build()
- val manga =
+ val manga = with(json) {
authClient.newCall(GET(urlMangas.toString()))
.awaitSuccess()
.parseAs()
+ }
val url = "$API_URL/v2/user_rates".toUri().buildUpon()
.appendQueryParameter("user_id", user_id)
.appendQueryParameter("target_id", track.media_id.toString())
.appendQueryParameter("target_type", "Manga")
.build()
- authClient.newCall(GET(url.toString()))
- .execute()
- .parseAs>()
- .let { entries ->
- if (entries.size > 1) {
- throw Exception("Too manga manga in response")
+ with(json) {
+ authClient.newCall(GET(url.toString()))
+ .execute()
+ .parseAs>()
+ .let { entries ->
+ if (entries.size > 1) {
+ throw Exception("Too manga manga in response")
+ }
+ entries
+ .map { it.toTrack(trackId, manga) }
+ .firstOrNull()
}
- entries
- .map { it.toTrack(trackId, manga) }
- .firstOrNull()
- }
+ }
}
}
suspend fun getCurrentUser(): Int {
return withIOContext {
- authClient.newCall(GET("$API_URL/users/whoami"))
- .awaitSuccess()
- .parseAs()
- .id
+ with(json) {
+ authClient.newCall(GET("$API_URL/users/whoami"))
+ .awaitSuccess()
+ .parseAs()
+ .id
+ }
}
}
suspend fun accessToken(code: String): SMOAuth {
return withIOContext {
- client.newCall(accessTokenRequest(code))
- .awaitSuccess()
- .parseAs()
+ with(json) {
+ client.newCall(accessTokenRequest(code))
+ .awaitSuccess()
+ .parseAs()
+ }
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt
index 7d67193034..7c8f70f519 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt
@@ -52,7 +52,9 @@ class TachideskApi {
trackUrl
}
- val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs()
+ val manga = with(json) {
+ client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs()
+ }
TrackSearch.create(TrackManager.SUWAYOMI).apply {
title = manga.title
@@ -72,7 +74,9 @@ class TachideskApi {
suspend fun updateProgress(track: Track): Track {
val url = track.tracking_url
- val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs>()
+ val chapters = with(json) {
+ client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs>()
+ }
val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index
client.newCall(
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt
index 05d7755975..1ee4855567 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt
@@ -11,12 +11,12 @@ import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.withIOContext
-import java.util.Date
-import java.util.concurrent.TimeUnit
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.domain.base.models.Version
+import java.util.*
+import java.util.concurrent.*
class AppUpdateChecker(
private val json: Json = Injekt.get(),
@@ -31,46 +31,48 @@ class AppUpdateChecker(
}
return withIOContext {
- val result = if (preferences.checkForBetas().get()) {
- networkService.client
- .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases"))
- .await()
- .parseAs>()
- .let { githubReleases ->
- val releases =
- githubReleases.take(10).filter { isNewVersion(it.version) }
- // Check if any of the latest versions are newer than the current version
- val release = releases
- .maxWithOrNull { r1, r2 ->
- when {
- r1.version == r2.version -> 0
- isNewVersion(r2.version, r1.version) -> -1
- else -> 1
+ val result = with(json) {
+ if (preferences.checkForBetas().get()) {
+ networkService.client
+ .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases"))
+ .await()
+ .parseAs>()
+ .let { githubReleases ->
+ val releases =
+ githubReleases.take(10).filter { isNewVersion(it.version) }
+ // Check if any of the latest versions are newer than the current version
+ val release = releases
+ .maxWithOrNull { r1, r2 ->
+ when {
+ r1.version == r2.version -> 0
+ isNewVersion(r2.version, r1.version) -> -1
+ else -> 1
+ }
}
+ preferences.lastAppCheck().set(Date().time)
+
+ if (release != null) {
+ AppUpdateResult.NewUpdate(release)
+ } else {
+ AppUpdateResult.NoNewUpdate
}
- preferences.lastAppCheck().set(Date().time)
-
- if (release != null) {
- AppUpdateResult.NewUpdate(release)
- } else {
- AppUpdateResult.NoNewUpdate
}
- }
- } else {
- networkService.client
- .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
- .await()
- .parseAs()
- .let {
- preferences.lastAppCheck().set(Date().time)
+ } else {
+ networkService.client
+ .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
+ .await()
+ .parseAs()
+ .let {
+ preferences.lastAppCheck().set(Date().time)
- // Check if latest version is newer than the current version
- if (isNewVersion(it.version)) {
- AppUpdateResult.NewUpdate(it)
- } else {
- AppUpdateResult.NoNewUpdate
+ // Check if latest version is newer than the current version
+ if (isNewVersion(it.version)) {
+ AppUpdateResult.NewUpdate(it)
+ } else {
+ AppUpdateResult.NoNewUpdate
+ }
}
- }
+ }
}
if (doExtrasAfterNewUpdate && result is AppUpdateResult.NewUpdate) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
index d128020810..3492704094 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
@@ -7,7 +7,6 @@ import android.os.Parcelable
import co.touchlab.kermit.Logger
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.api.ExtensionApi
-import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.extension.model.LoadResult
@@ -18,9 +17,6 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
import eu.kanade.tachiyomi.util.system.launchNow
import eu.kanade.tachiyomi.util.system.withIOContext
-import java.util.Date
-import java.util.Locale
-import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
@@ -31,6 +27,8 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.domain.base.BasePreferences
import yokai.domain.extension.interactor.TrustExtension
+import java.util.*
+import java.util.concurrent.*
/**
* The manager of extensions installed as another apk which extend the available sources. It handles
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt
similarity index 55%
rename from app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt
rename to app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt
index ae0307b6a2..de12d213bb 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt
@@ -1,16 +1,21 @@
-package eu.kanade.tachiyomi.extension.installer
+package eu.kanade.tachiyomi.extension
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
import android.content.pm.PackageManager
+import android.net.Uri
import android.os.Build
import android.os.Process
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
import co.touchlab.kermit.Logger
+import eu.kanade.tachiyomi.R
+import yokai.i18n.MR
+import yokai.util.lang.getString
+import dev.icerock.moko.resources.compose.stringResource
+import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
import eu.kanade.tachiyomi.util.system.getUriSize
-import eu.kanade.tachiyomi.util.system.isShizukuInstalled
-import java.io.BufferedReader
-import java.io.InputStream
-import java.lang.reflect.Method
-import java.util.*
+import eu.kanade.tachiyomi.util.system.isPackageInstalled
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -18,21 +23,38 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuRemoteProcess
-import yokai.i18n.MR
-import yokai.util.lang.getString
+import rikka.sui.Sui
+import uy.kohesive.injekt.injectLazy
+import java.io.BufferedReader
+import java.io.InputStream
+import java.lang.reflect.Method
+import java.util.*
+import java.util.concurrent.atomic.AtomicReference
-class ShizukuInstaller(
- context: Context,
- finishedQueue: (Installer) -> Unit,
-) : Installer(context, finishedQueue) {
+class ShizukuInstaller(private val context: Context, val finishedQueue: (ShizukuInstaller) -> Unit) {
+ private val extensionManager: ExtensionManager by injectLazy()
+
+ private var waitingInstall = AtomicReference(null)
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+ private val cancelReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return
+ cancelQueue(downloadId)
+ }
+ }
+
+ data class Entry(val downloadId: Long, val pkgName: String, val uri: Uri)
+ private val queue = Collections.synchronizedList(mutableListOf())
+
private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
Logger.d { "Shizuku was killed prematurely" }
finishedQueue(this)
}
+ fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName }
+
private val shizukuPermissionListener = object : Shizuku.OnRequestPermissionResultListener {
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
@@ -47,13 +69,13 @@ class ShizukuInstaller(
}
}
- override var ready = false
+ var ready = false
private val newProcess: Method
init {
Shizuku.addBinderDeadListener(shizukuDeadListener)
- require(Shizuku.pingBinder() && context.isShizukuInstalled) {
+ require(Shizuku.pingBinder() && (context.isPackageInstalled(shizukuPkgName) || Sui.isSui())) {
finishedQueue(this)
context.getString(MR.strings.ext_installer_shizuku_stopped)
}
@@ -69,8 +91,9 @@ class ShizukuInstaller(
newProcess.isAccessible = true
}
- override fun processEntry(entry: Entry) {
- super.processEntry(entry)
+ @Suppress("BlockingMethodInNonBlockingContext")
+ fun processEntry(entry: Entry) {
+ extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode())
ioScope.launch {
var sessionId: String? = null
try {
@@ -108,14 +131,85 @@ class ShizukuInstaller(
}
}
- // Don't cancel if entry is already started installing
- override fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry
+ /**
+ * Checks the queue. The provided service will be stopped if the queue is empty.
+ * Will not be run when not ready.
+ *
+ * @see ready
+ */
+ fun checkQueue() {
+ if (!ready) {
+ return
+ }
+ if (queue.isEmpty()) {
+ finishedQueue(this)
+ return
+ }
+ val nextEntry = queue.first()
+ if (waitingInstall.compareAndSet(null, nextEntry)) {
+ queue.removeAt(0)
+ processEntry(nextEntry)
+ }
+ }
- override fun onDestroy() {
+ /**
+ * Tells the queue to continue processing the next entry and updates the install step
+ * of the completed entry ([waitingInstall]) to [ExtensionManager].
+ *
+ * @param resultStep new install step for the processed entry.
+ * @see waitingInstall
+ */
+ fun continueQueue(succeeded: Boolean) {
+ val completedEntry = waitingInstall.getAndSet(null)
+ if (completedEntry != null) {
+ extensionManager.setInstallationResult(completedEntry.downloadId, succeeded)
+ checkQueue()
+ }
+ }
+
+ /**
+ * Add an item to install queue.
+ *
+ * @param downloadId Download ID as known by [ExtensionManager]
+ * @param uri Uri of APK to install
+ */
+ fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) {
+ queue.add(Entry(downloadId, pkgName, uri))
+ checkQueue()
+ }
+
+ /**
+ * Cancels queue for the provided download ID if exists.
+ *
+ * @param downloadId Download ID as known by [ExtensionManager]
+ */
+ private fun cancelQueue(downloadId: Long) {
+ val waitingInstall = this.waitingInstall.get()
+ val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
+ if (cancelEntry(toCancel)) {
+ queue.remove(toCancel)
+ if (waitingInstall == toCancel) {
+ // Currently processing removed entry, continue queue
+ this.waitingInstall.set(null)
+ checkQueue()
+ }
+ queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) }
+// extensionManager.up(downloadId, InstallStep.Idle)
+ }
+ }
+
+ // Don't cancel if entry is already started installing
+ fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry
+ fun getActiveEntry(): Entry? = waitingInstall.get()
+
+ fun onDestroy() {
Shizuku.removeBinderDeadListener(shizukuDeadListener)
Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener)
ioScope.cancel()
- super.onDestroy()
+ LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver)
+ queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) }
+ queue.clear()
+ waitingInstall.set(null)
}
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt
index 0eaf7c2f80..a33e307fb6 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt
@@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -23,6 +24,7 @@ import yokai.domain.extension.repo.model.ExtensionRepo
internal class ExtensionApi {
+ private val json: Json by injectLazy()
private val networkService: NetworkHelper by injectLazy()
private val getExtensionRepo: GetExtensionRepo by injectLazy()
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
@@ -45,9 +47,11 @@ internal class ExtensionApi {
.newCall(GET("$repoBaseUrl/index.min.json"))
.awaitSuccess()
- response
- .parseAs>()
- .toExtensions(repoBaseUrl)
+ with(json) {
+ response
+ .parseAs>()
+ .toExtensions(repoBaseUrl)
+ }
} catch (e: Throwable) {
Logger.e(e) { "Failed to get extensions from $repoBaseUrl" }
emptyList()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt
deleted file mode 100644
index f759a572ee..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-package eu.kanade.tachiyomi.extension.installer
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import androidx.annotation.CallSuper
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import eu.kanade.tachiyomi.extension.ExtensionManager
-import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
-import java.util.Collections
-import java.util.concurrent.atomic.AtomicReference
-import uy.kohesive.injekt.injectLazy
-
-abstract class Installer(
- internal val context: Context,
- // TODO: Remove finishedQueue
- internal val finishedQueue: (Installer) -> Unit,
-) {
-
- private val extensionManager: ExtensionManager by injectLazy()
-
- private var waitingInstall = AtomicReference(null)
- private val queue = Collections.synchronizedList(mutableListOf())
-
- private val cancelReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return
- cancelQueue(downloadId)
- }
- }
-
- abstract var ready: Boolean
-
- fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName }
-
- /**
- * Add an item to install queue.
- *
- * @param downloadId Download ID as known by [ExtensionManager]
- * @param uri Uri of APK to install
- */
- fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) {
- queue.add(Entry(downloadId, pkgName, uri))
- checkQueue()
- }
-
- @CallSuper
- open fun processEntry(entry: Entry) {
- extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode())
- }
-
- open fun cancelEntry(entry: Entry): Boolean {
- return true
- }
-
- /**
- * Tells the queue to continue processing the next entry and updates the install step
- * of the completed entry ([waitingInstall]) to [ExtensionManager].
- *
- * @param resultStep new install step for the processed entry.
- * @see waitingInstall
- */
- fun continueQueue(succeeded: Boolean) {
- val completedEntry = waitingInstall.getAndSet(null)
- if (completedEntry != null) {
- extensionManager.setInstallationResult(completedEntry.downloadId, succeeded)
- checkQueue()
- }
- }
-
- fun checkQueue() {
- if (!ready) {
- return
- }
- if (queue.isEmpty()) {
- finishedQueue(this)
- return
- }
- val nextEntry = queue.first()
- if (waitingInstall.compareAndSet(null, nextEntry)) {
- queue.removeAt(0)
- processEntry(nextEntry)
- }
- }
-
- @CallSuper
- open fun onDestroy() {
- LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver)
- queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) }
- queue.clear()
- waitingInstall.set(null)
- }
-
- protected fun getActiveEntry(): Entry? = waitingInstall.get()
-
- /**
- * Cancels queue for the provided download ID if exists.
- *
- * @param downloadId Download ID as known by [ExtensionManager]
- */
- fun cancelQueue(downloadId: Long) {
- val waitingInstall = this.waitingInstall.get()
- val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
- if (cancelEntry(toCancel)) {
- queue.remove(toCancel)
- if (waitingInstall == toCancel) {
- // Currently processing removed entry, continue queue
- this.waitingInstall.set(null)
- checkQueue()
- }
- queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) }
-// extensionManager.up(downloadId, InstallStep.Idle)
- }
- }
-
- data class Entry(
- val downloadId: Long,
- val pkgName: String,
- val uri: Uri,
- )
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
index d97ad8de5b..981d973c41 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
@@ -14,7 +14,7 @@ import androidx.core.net.toUri
import co.touchlab.kermit.Logger
import eu.kanade.tachiyomi.extension.ExtensionInstallerJob
import eu.kanade.tachiyomi.extension.ExtensionManager
-import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
+import eu.kanade.tachiyomi.extension.ShizukuInstaller
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
import eu.kanade.tachiyomi.util.storage.getUriCompat
@@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.system.e
import eu.kanade.tachiyomi.util.system.isPackageInstalled
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
-import java.io.File
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -48,6 +47,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.domain.base.BasePreferences
+import java.io.File
/**
* The installer which installs, updates and uninstalls the extensions.
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
index e8e9510f61..f7040a5c66 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
@@ -12,6 +12,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil
+import eu.kanade.tachiyomi.util.storage.EpubFile
+import eu.kanade.tachiyomi.util.storage.fillMetadata
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.extension
import eu.kanade.tachiyomi.util.system.nameWithoutExtension
@@ -31,8 +33,7 @@ import nl.adaptivity.xmlutil.serialization.XML
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
-import yokai.core.archive.util.archiveReader
-import yokai.core.archive.util.epubReader
+import yokai.core.archive.archiveReader
import yokai.core.metadata.COMIC_INFO_FILE
import yokai.core.metadata.ComicInfo
import yokai.core.metadata.copyFromComicInfo
@@ -41,7 +42,6 @@ import yokai.domain.chapter.services.ChapterRecognition
import yokai.domain.source.SourcePreferences
import yokai.domain.storage.StorageManager
import yokai.i18n.MR
-import yokai.util.fillMetadata
import yokai.util.lang.getString
class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSource {
@@ -410,7 +410,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour
}
}
is Format.Epub -> {
- format.file.epubReader(context).use { epub ->
+ EpubFile(format.file.archiveReader(context)).use { epub ->
val entry = epub.getImagesFromPages().firstOrNull()
entry?.let { updateCover(manga, epub.getInputStream(it)!!, context) }
@@ -433,7 +433,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour
true
}
is Format.Epub -> {
- format.file.epubReader(context).use { epub ->
+ EpubFile(format.file.archiveReader(context)).use { epub ->
epub.fillMetadata(chapter, manga)
}
true
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt
index b503491fc7..407aa4a491 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt
@@ -1,13 +1,20 @@
package eu.kanade.tachiyomi.source
import android.content.Context
+import eu.kanade.tachiyomi.R
+import yokai.i18n.MR
+import yokai.util.lang.getString
+import dev.icerock.moko.resources.compose.stringResource
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.DelegatedHttpSource
import eu.kanade.tachiyomi.source.online.HttpSource
-import java.util.concurrent.ConcurrentHashMap
+import eu.kanade.tachiyomi.source.online.all.Cubari
+import eu.kanade.tachiyomi.source.online.all.MangaDex
+import eu.kanade.tachiyomi.source.online.english.KireiCake
+import eu.kanade.tachiyomi.source.online.english.MangaPlus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -17,8 +24,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import yokai.i18n.MR
-import yokai.util.lang.getString
+import java.util.concurrent.ConcurrentHashMap
class SourceManager(
private val context: Context,
@@ -34,8 +40,28 @@ class SourceManager(
val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() }
val onlineSources: Flow> = catalogueSources.map { it.filterIsInstance() }
- // FIXME: Delegated source, unused at the moment, J2K only delegate deep links
- private val delegatedSources = emptyList().associateBy { it.sourceId }
+ private val delegatedSources = listOf(
+ DelegatedSource(
+ "reader.kireicake.com",
+ 5509224355268673176,
+ KireiCake(),
+ ),
+ DelegatedSource(
+ "mangadex.org",
+ 2499283573021220255,
+ MangaDex(),
+ ),
+ DelegatedSource(
+ "mangaplus.shueisha.co.jp",
+ 1998944621602463790,
+ MangaPlus(),
+ ),
+ DelegatedSource(
+ "cubari.moe",
+ 6338219619148105941,
+ Cubari(),
+ ),
+ ).associateBy { it.sourceId }
init {
scope.launch {
@@ -45,8 +71,8 @@ class SourceManager(
extensions.forEach { extension ->
extension.sources.forEach {
mutableMap[it.id] = it
- //delegatedSources[it.id]?.delegatedHttpSource?.delegate = it as? HttpSource
- //registerStubSource(it)
+ delegatedSources[it.id]?.delegatedHttpSource?.delegate = it as? HttpSource
+// registerStubSource(it)
}
}
sourcesMapFlow.value = mutableMap
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt
new file mode 100644
index 0000000000..1680498ce0
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt
@@ -0,0 +1,47 @@
+package eu.kanade.tachiyomi.source.online
+
+import android.net.Uri
+import eu.kanade.tachiyomi.data.database.models.Chapter
+import eu.kanade.tachiyomi.data.database.models.MangaImpl
+import eu.kanade.tachiyomi.data.database.models.create
+import eu.kanade.tachiyomi.domain.manga.models.Manga
+import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.source.model.SChapter
+import uy.kohesive.injekt.injectLazy
+import yokai.domain.chapter.interactor.GetChapter
+import yokai.domain.manga.interactor.GetManga
+
+abstract class DelegatedHttpSource {
+
+ var delegate: HttpSource? = null
+ abstract val domainName: String
+
+ protected val getChapter: GetChapter by injectLazy()
+ protected val getManga: GetManga by injectLazy()
+
+ protected val network: NetworkHelper by injectLazy()
+
+ abstract fun canOpenUrl(uri: Uri): Boolean
+ abstract fun chapterUrl(uri: Uri): String?
+ open fun pageNumber(uri: Uri): Int? = uri.pathSegments.lastOrNull()?.toIntOrNull()
+ abstract suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>?
+
+ protected open suspend fun getMangaInfo(url: String): Manga? {
+ val id = delegate?.id ?: return null
+ val manga = Manga.create(url, "", id)
+ val networkManga = delegate?.getMangaDetails(manga.copy()) ?: return null
+ val newManga = MangaImpl().apply {
+ this.url = url
+ title = try { networkManga.title } catch (e: Exception) { "" }
+ source = id
+ }
+ newManga.copyFrom(networkManga)
+ return newManga
+ }
+
+ suspend fun getChapters(url: String): List? {
+ val id = delegate?.id ?: return null
+ val manga = Manga.create(url, "", id)
+ return delegate?.getChapterList(manga)
+ }
+}
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 a0d0db6da4..78816561fe 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
@@ -1,31 +1,22 @@
package eu.kanade.tachiyomi.source.online.all
import android.net.Uri
+import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.toChapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
-import eu.kanade.tachiyomi.source.online.HttpSource
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
-import yokai.domain.chapter.interactor.GetChapter
-import yokai.domain.manga.interactor.GetManga
import yokai.i18n.MR
import yokai.util.lang.getString
-class Cubari(delegate: HttpSource) :
- DelegatedHttpSource(delegate) {
-
- private val getManga: GetManga = Injekt.get()
- private val getChapter: GetChapter = Injekt.get()
-
- override val lang = "all"
-
+class Cubari : DelegatedHttpSource() {
override val domainName: String = "cubari"
override fun canOpenUrl(uri: Uri): Boolean = true
@@ -33,24 +24,24 @@ class Cubari(delegate: HttpSource) :
override fun pageNumber(uri: Uri): Int? = uri.pathSegments.getOrNull(4)?.toIntOrNull()
- override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
+ override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
val cubariType = uri.pathSegments.getOrNull(1)?.lowercase(Locale.ROOT) ?: return null
val cubariPath = uri.pathSegments.getOrNull(2) ?: return null
val chapterNumber = uri.pathSegments.getOrNull(3)?.replace("-", ".")?.toFloatOrNull() ?: return null
val mangaUrl = "/read/$cubariType/$cubariPath"
return withContext(Dispatchers.IO) {
val deferredManga = async {
- getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl)
+ getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl)
}
val deferredChapters = async {
- getManga.awaitByUrlAndSource(mangaUrl, delegate.id)?.let { manga ->
+ getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!)?.let { manga ->
val chapters = getChapter.awaitAll(manga, false)
val chapter = findChapter(chapters, cubariType, chapterNumber)
if (chapter != null) {
return@async chapters
}
}
- getChapterListByUrl(mangaUrl)
+ getChapters(mangaUrl)
}
val manga = deferredManga.await()
val chapters = deferredChapters.await()
@@ -59,7 +50,11 @@ class Cubari(delegate: HttpSource) :
?: error(
context.getString(MR.strings.chapter_not_found),
)
- Triple(trueChapter, manga, chapters)
+ if (manga != null) {
+ Triple(trueChapter, manga, chapters.orEmpty())
+ } else {
+ null
+ }
}
}
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 f87714d5d0..15f011c18e 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
@@ -1,15 +1,15 @@
package eu.kanade.tachiyomi.source.online.all
import android.net.Uri
+import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.toChapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
-import eu.kanade.tachiyomi.source.online.HttpSource
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -20,15 +20,10 @@ import okhttp3.CacheControl
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
-import yokai.domain.manga.interactor.GetManga
import yokai.i18n.MR
import yokai.util.lang.getString
-class MangaDex(delegate: HttpSource) : DelegatedHttpSource(delegate) {
-
- private val getManga: GetManga = Injekt.get()
-
- override val lang: String = "all"
+class MangaDex : DelegatedHttpSource() {
override val domainName: String = "mangadex"
@@ -47,13 +42,13 @@ class MangaDex(delegate: HttpSource) : DelegatedHttpSource(delegate) {
return uri.pathSegments.getOrNull(2)?.toIntOrNull()
}
- override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
+ override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
val url = chapterUrl(uri) ?: return null
val request =
GET("https:///api.mangadex.org/v2$url", delegate!!.headers, CacheControl.FORCE_NETWORK)
val response = network.client.newCall(request).await()
if (response.code != 200) throw Exception("HTTP error ${response.code}")
- val body = response.body.string()
+ val body = response.body.string().orEmpty()
if (body.isEmpty()) {
throw Exception("Null Response")
}
@@ -61,19 +56,28 @@ class MangaDex(delegate: HttpSource) : DelegatedHttpSource(delegate) {
val jsonObject = Json.decodeFromString(body)
val dataObject = jsonObject.data ?: throw Exception("Chapter not found")
val mangaId = dataObject.mangaId ?: throw Exception("No manga associated with chapter")
+ val langCode = getRealLangCode(dataObject.language ?: "en").uppercase(Locale.getDefault())
+ // Use the correct MangaDex source based on the language code, or the api will not return
+ // the correct chapter list
+ delegate = sourceManager.getOnlineSources().find { it.toString() == "MangaDex ($langCode)" }
+ ?: error("Source not found")
val mangaUrl = "/manga/$mangaId/"
return withContext(Dispatchers.IO) {
val deferredManga = async {
- getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl)
+ getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl)
}
- val deferredChapters = async { getChapterListByUrl(mangaUrl) }
+ val deferredChapters = async { getChapters(mangaUrl) }
val manga = deferredManga.await()
val chapters = deferredChapters.await()
val context = Injekt.get().context
- val trueChapter = chapters.find { it.url == "/api$url" }?.toChapter() ?: error(
+ val trueChapter = chapters?.find { it.url == "/api$url" }?.toChapter() ?: error(
context.getString(MR.strings.chapter_not_found),
)
- Triple(trueChapter, manga, chapters)
+ if (manga != null) {
+ Triple(trueChapter, manga, chapters.orEmpty())
+ } else {
+ null
+ }
}
}
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
new file mode 100644
index 0000000000..adeec2e93c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt
@@ -0,0 +1,110 @@
+package eu.kanade.tachiyomi.source.online.english
+
+import android.net.Uri
+import eu.kanade.tachiyomi.data.database.models.Chapter
+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.domain.manga.models.Manga
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.network.await
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.withContext
+import okhttp3.FormBody
+import okhttp3.Request
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import yokai.i18n.MR
+import yokai.util.lang.getString
+
+open class FoolSlide(override val domainName: String, private val urlModifier: String = "") :
+ DelegatedHttpSource
+ () {
+
+ override fun canOpenUrl(uri: Uri): Boolean = true
+
+ override fun chapterUrl(uri: Uri): String? {
+ val offset = if (urlModifier.isEmpty()) 0 else 1
+ val mangaName = uri.pathSegments.getOrNull(1 + offset) ?: return null
+ val lang = uri.pathSegments.getOrNull(2 + offset) ?: return null
+ val volume = uri.pathSegments.getOrNull(3 + offset) ?: return null
+ val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
+ val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString()
+ return "$urlModifier/read/" + listOfNotNull(
+ mangaName,
+ lang,
+ volume,
+ chapterNumber,
+ subChapterNumber,
+ ).joinToString("/") + "/"
+ }
+
+ override fun pageNumber(uri: Uri): Int? {
+ val count = uri.pathSegments.count()
+ if (count > 2 && uri.pathSegments[count - 2] == "page") {
+ return super.pageNumber(uri)
+ }
+ return null
+ }
+
+ override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
+ val offset = if (urlModifier.isEmpty()) 0 else 1
+ val mangaName = uri.pathSegments.getOrNull(1 + offset) ?: return null
+ var chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
+ val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()
+ if (subChapterNumber != null) {
+ chapterNumber += ".$subChapterNumber"
+ }
+ return withContext(Dispatchers.IO) {
+ val mangaUrl = "$urlModifier/series/$mangaName/"
+ val sourceId = delegate?.id ?: return@withContext null
+ val deferredManga = async {
+ getManga.awaitByUrlAndSource(mangaUrl, sourceId) ?: getManga(mangaUrl)
+ }
+ val chapterUrl = chapterUrl(uri)
+ val deferredChapters = async { getChapters(mangaUrl) }
+ val manga = deferredManga.await()
+ val chapters = deferredChapters.await()
+ val context = Injekt.get().context
+ val trueChapter = chapters?.find { it.url == chapterUrl }?.toChapter() ?: error(
+ context.getString(MR.strings.chapter_not_found),
+ )
+ if (manga != null) Triple(trueChapter, manga, chapters) else null
+ }
+ }
+
+ open suspend fun getManga(url: String): Manga? {
+ val request = GET("${delegate!!.baseUrl}$url")
+ val document = network.client.newCall(allowAdult(request)).await().asJsoup()
+ val mangaDetailsInfoSelector = "div.info"
+ val infoElement = document.select(mangaDetailsInfoSelector).first()?.text() ?: return null
+ return MangaImpl().apply {
+ this.url = url
+ source = delegate?.id ?: -1
+ title = infoElement.substringAfter("Title:").substringBefore("Author:").trim()
+ author = infoElement.substringAfter("Author:").substringBefore("Artist:").trim()
+ artist = infoElement.substringAfter("Artist:").substringBefore("Synopsis:").trim()
+ description = infoElement.substringAfter("Synopsis:").trim()
+ thumbnail_url = document.select("div.thumbnail img").firstOrNull()?.attr("abs:src")?.trim()
+ }
+ }
+
+ /**
+ * Transform a GET request into a POST request that automatically authorizes all adult content
+ */
+ private fun allowAdult(request: Request) = allowAdult(request.url.toString())
+
+ private fun allowAdult(url: String): Request {
+ return POST(
+ url,
+ body = FormBody.Builder()
+ .add("adult", "true")
+ .build(),
+ )
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt
new file mode 100644
index 0000000000..ecad868775
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt
@@ -0,0 +1,28 @@
+package eu.kanade.tachiyomi.source.online.english
+
+import eu.kanade.tachiyomi.data.database.models.MangaImpl
+import eu.kanade.tachiyomi.domain.manga.models.Manga
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.await
+import eu.kanade.tachiyomi.util.asJsoup
+import eu.kanade.tachiyomi.util.lang.capitalizeWords
+
+class KireiCake : FoolSlide("kireicake") {
+
+ override suspend fun getManga(url: String): Manga? {
+ val request = GET("${delegate!!.baseUrl}$url")
+ val document = network.client.newCall(request).await().asJsoup()
+ val mangaDetailsInfoSelector = "div.info"
+ return MangaImpl().apply {
+ this.url = url
+ source = delegate?.id ?: -1
+ title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first()
+ ?.ownText()?.substringAfter(":")?.trim()
+ ?: url.split("/").last().replace("_", " " + "").capitalizeWords()
+ description =
+ document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first()
+ ?.ownText()?.substringAfter(":")
+ thumbnail_url = document.select("div.thumbnail img").firstOrNull()?.attr("abs:src")
+ }
+ }
+}
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 ebcbc527ce..710b6e4793 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
@@ -1,31 +1,24 @@
package eu.kanade.tachiyomi.source.online.english
import android.net.Uri
+import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.toChapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
-import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import okhttp3.CacheControl
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
-import yokai.domain.manga.interactor.GetManga
import yokai.i18n.MR
import yokai.util.lang.getString
-class MangaPlus(delegate: HttpSource) :
- DelegatedHttpSource(delegate) {
-
- private val getManga: GetManga = Injekt.get()
-
- override val lang: String get() = delegate.lang
-
+class MangaPlus : DelegatedHttpSource() {
override val domainName: String = "jumpg-webapi.tokyo-cdn"
private val titleIdRegex =
@@ -41,11 +34,11 @@ class MangaPlus(delegate: HttpSource) :
override fun pageNumber(uri: Uri): Int? = null
- override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
+ override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? {
val url = chapterUrl(uri) ?: return null
val request = GET(
chapterUrlTemplate.replace("##", uri.pathSegments[1]),
- delegate.headers,
+ delegate!!.headers,
CacheControl.FORCE_NETWORK,
)
return withContext(Dispatchers.IO) {
@@ -60,22 +53,26 @@ class MangaPlus(delegate: HttpSource) :
val trimmedTitle = title.substring(0, title.length - 1)
val mangaUrl = "#/titles/$titleId"
val deferredManga = async {
- getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl)
+ getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl)
}
- val deferredChapters = async { getChapterListByUrl(mangaUrl) }
+ val deferredChapters = async { getChapters(mangaUrl) }
val manga = deferredManga.await()
val chapters = deferredChapters.await()
val context = Injekt.get().context
- val trueChapter = chapters.find { it.url == url }?.toChapter() ?: error(
+ val trueChapter = chapters?.find { it.url == url }?.toChapter() ?: error(
context.getString(MR.strings.chapter_not_found),
)
- Triple(
- trueChapter,
- manga.apply {
- this.title = trimmedTitle
- },
- chapters,
- )
+ if (manga != null) {
+ Triple(
+ trueChapter,
+ manga.apply {
+ this.title = trimmedTitle
+ },
+ chapters,
+ )
+ } else {
+ null
+ }
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt
index 4ecd065463..8f78c042ec 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt
@@ -5,26 +5,19 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
-import eu.kanade.tachiyomi.util.compose.LocalBackPress
-import eu.kanade.tachiyomi.util.compose.LocalDialogHostState
-import yokai.domain.DialogHostState
import yokai.presentation.theme.YokaiTheme
abstract class BaseComposeController(bundle: Bundle? = null) :
BaseController(bundle) {
- override val shouldHideLegacyAppBar = true
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
- setAppBarVisibility()
+ hideLegacyAppBar()
return ComposeView(container.context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -32,14 +25,8 @@ abstract class BaseComposeController(bundle: Bundle? = null) :
)
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
- val dialogHostState = remember { DialogHostState() }
YokaiTheme {
- CompositionLocalProvider(
- LocalDialogHostState provides dialogHostState,
- LocalBackPress provides router::handleBack,
- ) {
- ScreenContent()
- }
+ ScreenContent()
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt
index b1fcea81e2..f378bed539 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt
@@ -25,8 +25,6 @@ import kotlinx.coroutines.cancel
abstract class BaseController(bundle: Bundle? = null) :
Controller(bundle), BackHandlerControllerInterface, BaseControllerPreferenceControllerCommonInterface {
- abstract val shouldHideLegacyAppBar: Boolean
-
lateinit var viewScope: CoroutineScope
var isDragging = false
@@ -60,10 +58,6 @@ abstract class BaseController(bundle: Bundle? = null) :
open fun onViewCreated(view: View) { }
- internal fun setAppBarVisibility() {
- if (shouldHideLegacyAppBar) hideLegacyAppBar() else showLegacyAppBar()
- }
-
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
if (type.isEnter && !isControllerVisible) {
view?.alpha = 0f
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt
index e710deaf8d..996074df6f 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt
@@ -20,13 +20,11 @@ import eu.kanade.tachiyomi.util.view.isControllerVisible
abstract class BaseLegacyController(bundle: Bundle? = null) :
BaseController(bundle) {
- override val shouldHideLegacyAppBar = false
-
lateinit var binding: VB
val isBindingInitialized get() = this::binding.isInitialized
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
- setAppBarVisibility()
+ showLegacyAppBar()
binding = createBinding(inflater)
binding.root.backgroundColor = binding.root.context.getResourceColor(R.attr.background)
return binding.root
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt
deleted file mode 100644
index e89ed20283..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package eu.kanade.tachiyomi.ui.base.presenter
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/**
- * Presenter that mimic [cafe.adriel.voyager.core.model.StateScreenModel] for easier migration.
- * Temporary class while we're migrating to Compose.
- */
-abstract class StateCoroutinePresenter(initialState: S) : BaseCoroutinePresenter() {
-
- protected val mutableState: MutableStateFlow = MutableStateFlow(initialState)
- val state: StateFlow = mutableState.asStateFlow()
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt
index 0afa1b2af0..ef9a074b29 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt
@@ -5,8 +5,6 @@ import android.content.Context
import android.util.AttributeSet
import android.view.MenuItem
import android.widget.LinearLayout
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.FileDownloadOff
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isInvisible
import androidx.core.view.updateLayoutParams
@@ -214,7 +212,7 @@ class DownloadBottomSheet @JvmOverloads constructor(
setBottomSheet()
if (presenter.downloadQueueState.value.isEmpty()) {
binding.emptyView.show(
- Icons.Filled.FileDownloadOff,
+ R.drawable.ic_download_off_24dp,
MR.strings.nothing_is_downloading,
)
} else {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
index dc63b2db5e..271d9d1755 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
@@ -13,15 +13,15 @@ import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.isLTR
import eu.kanade.tachiyomi.util.system.timeSpanFromNow
import eu.kanade.tachiyomi.util.system.withDefContext
-import java.util.*
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.injectLazy
-import yokai.domain.category.interactor.GetCategories
-import yokai.domain.chapter.interactor.GetChapter
-import yokai.domain.history.interactor.GetHistory
import yokai.domain.ui.UiPreferences
import yokai.i18n.MR
import yokai.util.lang.getString
+import java.util.*
+import yokai.domain.category.interactor.GetCategories
+import yokai.domain.chapter.interactor.GetChapter
+import yokai.domain.history.interactor.GetHistory
/**
* Adapter storing a list of manga in a certain category.
@@ -117,8 +117,8 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
*/
fun indexOf(manga: Manga): Int {
return currentItems.indexOfFirst {
- if (it is LibraryMangaItem) {
- it.manga.manga.id == manga.id
+ if (it is LibraryItem) {
+ it.manga.id == manga.id
} else {
false
}
@@ -142,7 +142,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
*/
fun allIndexOf(manga: Manga): List {
return currentItems.mapIndexedNotNull { index, it ->
- if (it is LibraryMangaItem && it.manga.manga.id == manga.id) {
+ if (it is LibraryItem && it.manga.id == manga.id) {
index
} else {
null
@@ -164,7 +164,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
} else {
val filteredManga = withDefContext { mangas.filter { it.filter(s) } }
if (filteredManga.isEmpty() && controller?.presenter?.showAllCategories == false) {
- val catId = (mangas.firstOrNull() as? LibraryMangaItem)?.let { it.header?.catId ?: it.manga.category }
+ val catId = mangas.firstOrNull()?.let { it.header?.catId ?: it.manga.category }
val blankItem = catId?.let { controller.presenter.blankItem(it) }
updateDataSet(blankItem ?: emptyList())
} else {
@@ -173,7 +173,6 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
}
isLongPressDragEnabled = libraryListener?.canDrag() == true && s.isNullOrBlank()
setItemsPerCategoryMap()
- notifyDataSetChanged()
}
private fun getFirstLetter(name: String): String {
@@ -203,19 +202,18 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
vibrateOnCategoryChange(item.category.name)
item.category.name
}
- is LibraryPlaceholderItem -> {
- item.header?.category?.name.orEmpty()
- }
- is LibraryMangaItem -> {
- val text =
+ is LibraryItem -> {
+ val text = if (item.manga.isBlank()) {
+ return item.header?.category?.name.orEmpty()
+ } else {
when (getSort(position)) {
LibrarySort.DragAndDrop -> {
- if (item.header.category.isDynamic && item.manga.manga.id != null) {
+ if (item.header.category.isDynamic && item.manga.id != null) {
// FIXME: Don't do blocking
- val category = runBlocking { getCategories.awaitByMangaId(item.manga.manga.id!!) }.firstOrNull()?.name
+ val category = runBlocking { getCategories.awaitByMangaId(item.manga.id!!) }.firstOrNull()?.name
category ?: context.getString(MR.strings.default_value)
} else {
- val title = item.manga.manga.title
+ val title = item.manga.title
if (preferences.removeArticles().get()) {
title.removeArticles().chop(15)
} else {
@@ -224,14 +222,14 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
}
}
LibrarySort.DateFetched -> {
- val id = item.manga.manga.id ?: return ""
+ val id = item.manga.id ?: return ""
// FIXME: Don't do blocking
val history = runBlocking { getChapter.awaitAll(id, false) }
val last = history.maxOfOrNull { it.date_fetch }
context.timeSpanFromNow(MR.strings.fetched_, last ?: 0)
}
LibrarySort.LastRead -> {
- val id = item.manga.manga.id ?: return ""
+ val id = item.manga.id ?: return ""
// FIXME: Don't do blocking
val history = runBlocking { getHistory.awaitAllByMangaId(id) }
val last = history.maxOfOrNull { it.last_read }
@@ -258,23 +256,21 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
}
}
LibrarySort.LatestChapter -> {
- context.timeSpanFromNow(MR.strings.updated_, item.manga.manga.last_update)
+ context.timeSpanFromNow(MR.strings.updated_, item.manga.last_update)
}
LibrarySort.DateAdded -> {
- context.timeSpanFromNow(MR.strings.added_, item.manga.manga.date_added)
+ context.timeSpanFromNow(MR.strings.added_, item.manga.date_added)
}
LibrarySort.Title -> {
val title = if (preferences.removeArticles().get()) {
- item.manga.manga.title.removeArticles()
+ item.manga.title.removeArticles()
} else {
- item.manga.manga.title
+ item.manga.title
}
getFirstLetter(title)
}
- LibrarySort.Random -> {
- context.getString(MR.strings.random)
- }
}
+ }
if (!isSingleCategory) {
vibrateOnCategoryChange(item.header?.category?.name.orEmpty())
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
index 00d07100e6..2493614196 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
@@ -28,8 +28,6 @@ import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.HeartBroken
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.animation.doOnEnd
import androidx.core.view.ViewCompat
@@ -451,7 +449,7 @@ open class LibraryController(
private fun setActiveCategory() {
val currentCategory = presenter.categories.indexOfFirst {
- if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategoryId == it.id
+ if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategory == it.id
}
if (currentCategory > -1) {
binding.categoryRecycler.setCategories(currentCategory)
@@ -521,13 +519,14 @@ open class LibraryController(
}
private fun openRandomManga(global: Boolean) {
- val items =
- if (global) { presenter.currentLibraryItems } else { adapter.currentItems }
- .filterIsInstance()
- .filter { !it.manga.manga.initialized || it.manga.unread > 0 }
+ val items = if (global) {
+ presenter.allLibraryItems
+ } else {
+ adapter.currentItems
+ }.filter { (it is LibraryItem && !it.manga.isBlank() && !it.manga.isHidden() && (!it.manga.initialized || it.manga.unread > 0)) }
if (items.isNotEmpty()) {
- val item = items.random() as LibraryMangaItem
- openManga(item.manga.manga)
+ val item = items.random() as LibraryItem
+ openManga(item.manga)
}
}
@@ -558,7 +557,7 @@ open class LibraryController(
}
presenter.groupType = item
shouldScrollToTop = true
- presenter.updateLibrary()
+ presenter.getLibrary()
true
}.show()
}
@@ -661,7 +660,7 @@ open class LibraryController(
createActionModeIfNeeded()
}
- if (presenter.libraryItemsToDisplay.isNotEmpty() && !isSubClass) {
+ if (presenter.libraryItems.isNotEmpty() && !isSubClass) {
presenter.restoreLibrary()
if (justStarted) {
val activityBinding = activityBinding ?: return
@@ -705,7 +704,7 @@ open class LibraryController(
if (!LibraryUpdateJob.isRunning(context)) {
when {
!presenter.showAllCategories && presenter.groupType == BY_DEFAULT -> {
- presenter.currentCategory?.let {
+ presenter.findCurrentCategory()?.let {
updateLibrary(it)
}
}
@@ -903,7 +902,7 @@ open class LibraryController(
}
} else {
val newOffset =
- presenter.categories.indexOfFirst { presenter.currentCategoryId == it.id } +
+ presenter.categories.indexOfFirst { presenter.currentCategory == it.id } +
(if (next) 1 else -1)
if (if (!next) {
newOffset > -1
@@ -1012,7 +1011,7 @@ open class LibraryController(
override fun getSpanSize(position: Int): Int {
if (libraryLayout == LibraryItem.LAYOUT_LIST) return managerSpanCount
val item = this@LibraryController.mAdapter?.getItem(position)
- return if (item is LibraryHeaderItem || item is SearchGlobalItem || item is LibraryPlaceholderItem) {
+ return if (item is LibraryHeaderItem || item is SearchGlobalItem || (item is LibraryItem && item.manga.isBlank())) {
managerSpanCount
} else {
1
@@ -1057,7 +1056,7 @@ open class LibraryController(
if (type.isEnter) {
binding.filterBottomSheet.filterBottomSheet.isVisible = true
if (type == ControllerChangeType.POP_ENTER) {
- presenter.updateLibrary()
+ presenter.getLibrary()
isPoppingIn = true
}
binding.recyclerCover.isClickable = false
@@ -1096,7 +1095,7 @@ open class LibraryController(
if (!isBindingInitialized) return
updateFilterSheetY()
if (observeLater) {
- presenter.updateLibrary()
+ presenter.getLibrary()
}
}
@@ -1136,7 +1135,7 @@ open class LibraryController(
binding.emptyView.hide()
} else {
binding.emptyView.show(
- Icons.Filled.HeartBroken,
+ R.drawable.ic_heart_off_24dp,
if (hasActiveFilters) {
MR.strings.no_matches_for_filters
} else {
@@ -1375,7 +1374,7 @@ open class LibraryController(
setActiveCategory()
return
}
- val headerPosition = mAdapter?.indexOf(pos) ?: return
+ val headerPosition = adapter.indexOf(pos)
if (headerPosition > -1) {
val activityBinding = activityBinding ?: return
val index = adapter.headerItems.indexOf(adapter.getItem(headerPosition))
@@ -1409,7 +1408,7 @@ open class LibraryController(
private fun onRefresh() {
showCategories(false)
- presenter.updateLibrary()
+ presenter.getLibrary()
destroyActionModeIfNeeded()
}
@@ -1433,14 +1432,14 @@ open class LibraryController(
val isShowAllCategoriesSet = preferences.showAllCategories().get()
if (!query.isNullOrBlank() && this.query.isBlank() && !isShowAllCategoriesSet) {
presenter.forceShowAllCategories = preferences.showAllCategoriesWhenSearchingSingleCategory().get()
- presenter.updateLibrary()
+ presenter.getLibrary()
} else if (query.isNullOrBlank() && this.query.isNotBlank() && !isShowAllCategoriesSet) {
if (!isSubClass) {
preferences.showAllCategoriesWhenSearchingSingleCategory()
.set(presenter.forceShowAllCategories)
}
presenter.forceShowAllCategories = false
- presenter.updateLibrary()
+ presenter.getLibrary()
}
if (query != this.query && !query.isNullOrBlank()) {
@@ -1458,7 +1457,7 @@ open class LibraryController(
adapter.removeAllScrollableHeaders()
}
adapter.setFilter(query)
- if (presenter.currentLibraryItems.isEmpty()) return true
+ if (presenter.allLibraryItems.isEmpty()) return true
viewScope.launchUI {
adapter.performFilterAsync()
}
@@ -1477,6 +1476,7 @@ open class LibraryController(
}
private fun setSelection(manga: Manga, selected: Boolean) {
+ if (manga.isBlank()) return
val currentMode = adapter.mode
if (selected) {
if (selectedMangas.add(manga)) {
@@ -1526,7 +1526,7 @@ open class LibraryController(
toggleSelection(position)
return
}
- val manga = (adapter.getItem(position) as? LibraryMangaItem)?.manga?.manga ?: return
+ val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
val activity = activity ?: return
val chapter = presenter.getFirstUnread(manga) ?: return
activity.apply {
@@ -1542,8 +1542,9 @@ open class LibraryController(
}
private fun toggleSelection(position: Int) {
- val item = adapter.getItem(position) as? LibraryMangaItem ?: return
- setSelection(item.manga.manga, !adapter.isSelected(position))
+ val item = adapter.getItem(position) as? LibraryItem ?: return
+ if (item.manga.isBlank()) return
+ setSelection(item.manga, !adapter.isSelected(position))
invalidateActionMode()
}
@@ -1559,14 +1560,14 @@ open class LibraryController(
* @return true if the item should be selected, false otherwise.
*/
override fun onItemClick(view: View?, position: Int): Boolean {
- val item = adapter.getItem(position) as? LibraryMangaItem ?: return false
+ val item = adapter.getItem(position) as? LibraryItem ?: return false
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
snack?.dismiss()
lastClickPosition = position
toggleSelection(position)
false
} else {
- openManga(item.manga.manga)
+ openManga(item.manga)
false
}
}
@@ -1588,10 +1589,10 @@ open class LibraryController(
*/
override fun onItemLongClick(position: Int) {
val item = adapter.getItem(position)
- if (item !is LibraryMangaItem) return
+ if (item !is LibraryItem) return
snack?.dismiss()
if (libraryLayout == LibraryItem.LAYOUT_COVER_ONLY_GRID && actionMode == null) {
- snack = view?.snack(item.manga.manga.title) {
+ snack = view?.snack(item.manga.title) {
anchorView = activityBinding?.bottomNav
view.elevation = 15f.dpToPx
}
@@ -1640,14 +1641,14 @@ open class LibraryController(
if (mangaId == null) {
adapter.getHeaderPositions().forEach { adapter.notifyItemChanged(it) }
} else {
- presenter.updateLibrary()
+ presenter.updateManga()
}
}
private fun setSelection(position: Int, selected: Boolean = true) {
- val item = adapter.getItem(position) as? LibraryMangaItem ?: return
+ val item = adapter.getItem(position) as? LibraryItem ?: return
- setSelection(item.manga.manga, selected)
+ setSelection(item.manga, selected)
invalidateActionMode()
}
@@ -1671,7 +1672,7 @@ open class LibraryController(
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
if (adapter.isSelected(fromPosition)) toggleSelection(fromPosition)
- val item = adapter.getItem(fromPosition) as? LibraryMangaItem ?: return false
+ val item = adapter.getItem(fromPosition) as? LibraryItem ?: return false
val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem
if (toPosition < 1) return false
return (adapter.getItem(toPosition) !is LibraryHeaderItem) && (
@@ -1686,18 +1687,18 @@ open class LibraryController(
lastItem = null
isDragging = false
binding.swipeRefresh.isEnabled = true
- if (mAdapter == null || adapter.selectedItemCount > 0) {
+ if (adapter.selectedItemCount > 0) {
lastItemPosition = null
return
}
destroyActionModeIfNeeded()
// if nothing moved
if (lastItemPosition == null) return
- val item = adapter.getItem(position) as? LibraryMangaItem ?: return
+ val item = adapter.getItem(position) as? LibraryItem ?: return
val newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem
val libraryItems = getSectionItems(adapter.getSectionHeader(position), item)
- .filterIsInstance()
- val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id }
+ .filterIsInstance()
+ val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
if (newHeader?.category?.id == item.manga.category) {
presenter.rearrangeCategory(item.manga.category, mangaIds)
} else {
@@ -1819,7 +1820,7 @@ open class LibraryController(
val category = (adapter.getItem(position) as? LibraryHeaderItem)?.category ?: return
if (!category.isDynamic) {
ManageCategoryDialog(category) {
- presenter.updateLibrary()
+ presenter.getLibrary()
}.showDialog(router)
}
}
@@ -1829,8 +1830,8 @@ open class LibraryController(
if (category?.isDynamic == false && sortBy == LibrarySort.DragAndDrop.categoryValue) {
val item = adapter.findCategoryHeader(catId) ?: return
val libraryItems = adapter.getSectionItems(item)
- .filterIsInstance()
- val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id }
+ .filterIsInstance()
+ val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
presenter.rearrangeCategory(catId, mangaIds)
} else {
presenter.sortCategory(catId, sortBy)
@@ -1912,7 +1913,7 @@ open class LibraryController(
isGone = true
setOnClickListener {
presenter.forceShowAllCategories = !presenter.forceShowAllCategories
- presenter.updateLibrary()
+ presenter.getLibrary()
isSelected = presenter.forceShowAllCategories
}
val pad = 12.dpToPx
@@ -2192,7 +2193,7 @@ open class LibraryController(
val activity = activity ?: return
viewScope.launchIO {
selectedMangas.toList().moveCategories(activity) {
- presenter.updateLibrary()
+ presenter.getLibrary()
destroyActionModeIfNeeded()
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt
index 86287493a5..091642038b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt
@@ -23,7 +23,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.setCards
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
@@ -64,24 +64,23 @@ class LibraryGridHolder(
* @param item the manga item to bind.
*/
override fun onSetValues(item: LibraryItem) {
- if (item !is LibraryMangaItem) throw IllegalStateException("Only LibraryMangaItem can use grid holder")
// Update the title and subtitle of the manga.
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
binding.playButton.transitionName = "library chapter $bindingAdapterPosition transition"
- binding.constraintLayout.isVisible = item.manga.manga.id != Long.MIN_VALUE
- binding.title.text = item.manga.manga.title.highlightText(item.filter, color)
- binding.behindTitle.text = item.manga.manga.title
- val mangaColor = item.manga.manga.dominantCoverColors
+ binding.constraintLayout.isVisible = !item.manga.isBlank()
+ binding.title.text = item.manga.title.highlightText(item.filter, color)
+ binding.behindTitle.text = item.manga.title
+ val mangaColor = item.manga.dominantCoverColors
binding.coverConstraint.backgroundColor = mangaColor?.first ?: itemView.context.getResourceColor(R.attr.background)
binding.behindTitle.setTextColor(
mangaColor?.second ?: itemView.context.getResourceColor(R.attr.colorOnBackground),
)
- val authorArtist = if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) {
- item.manga.manga.author?.trim() ?: ""
+ val authorArtist = if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) {
+ item.manga.author?.trim() ?: ""
} else {
listOfNotNull(
- item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() },
- item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() },
+ item.manga.author?.trim()?.takeIf { it.isNotBlank() },
+ item.manga.artist?.trim()?.takeIf { it.isNotBlank() },
).joinToString(", ")
}
binding.subtitle.text = authorArtist.highlightText(item.filter, color)
@@ -102,7 +101,7 @@ class LibraryGridHolder(
// Update the cover.
binding.coverThumbnail.dispose()
- setCover(item.manga.manga)
+ setCover(item.manga)
}
override fun toggleActivation() {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt
index 85be9b426a..9ef0bd1db5 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt
@@ -32,17 +32,14 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.compatToolTipText
import eu.kanade.tachiyomi.util.view.setText
import eu.kanade.tachiyomi.util.view.text
-import kotlin.random.Random
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
-import yokai.domain.library.LibraryPreferences
import yokai.i18n.MR
import yokai.util.lang.getString
class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) :
BaseFlexibleViewHolder(view, adapter, true) {
- private val libraryPreferences: LibraryPreferences = Injekt.get()
private val binding = LibraryCategoryHeaderItemBinding.bind(view)
val progressDrawableStart = CircularProgressDrawable(itemView.context)
val progressDrawableEnd = CircularProgressDrawable(itemView.context)
@@ -186,17 +183,7 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) :
binding.categoryTitle.text = categoryName +
if (adapter.showNumber) {
- val filteredCount = adapter.currentItems.count {
- it is LibraryMangaItem && it.header?.catId == item.catId
- }
- val totalCount = adapter.itemsPerCategory[item.catId] ?: 0
- val searchText = adapter.getFilter(String::class.java)
- var countText = if (searchText.isNullOrBlank()) {
- " ($totalCount)"
- } else {
- " ($filteredCount/$totalCount)"
- }
- countText
+ " (${adapter.itemsPerCategory[item.catId]})"
} else { "" }
if (category.sourceId != null) {
val icon = adapter.sourceManager.get(category.sourceId!!)?.icon()
@@ -211,7 +198,7 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) :
val isAscending = category.isAscending()
val sortingMode = category.sortingMode()
- val sortDrawable = getSortRes(sortingMode, isAscending, category.isDynamic, false)
+ val sortDrawable = getSortRes(sortingMode, isAscending, R.drawable.ic_sort_24dp)
binding.categorySort.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, sortDrawable, 0)
binding.categorySort.setText(category.sortRes())
@@ -276,7 +263,7 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) :
val cat = category ?: return
adapter.controller?.activity?.let { activity ->
val items = LibrarySort.entries.map { it.menuSheetItem(cat.isDynamic) }
- val sortingMode = cat.sortingMode() ?: if (!cat.isDynamic) LibrarySort.DragAndDrop else null
+ val sortingMode = cat.sortingMode(true)
val sheet = MaterialMenuSheet(
activity,
items,
@@ -285,68 +272,69 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) :
) { sheet, item ->
onCatSortClicked(cat, item)
val nCategory = (adapter.getItem(flexibleAdapterPosition) as? LibraryHeaderItem)?.category
- sheet.updateSortIcon(nCategory, LibrarySort.valueOf(item))
+ val isAscending = nCategory?.isAscending() ?: false
+ val drawableRes = getSortRes(item, isAscending)
+ sheet.setDrawable(item, drawableRes)
false
}
- sheet.updateSortIcon(cat, sortingMode)
+ val isAscending = cat.isAscending()
+ val drawableRes = getSortRes(sortingMode, isAscending)
+ sheet.setDrawable(sortingMode?.mainValue ?: -1, drawableRes)
sheet.show()
}
}
- private fun MaterialMenuSheet.updateSortIcon(category: Category?, sortingMode: LibrarySort?) {
- val isAscending = category?.isAscending() ?: false
- val drawableRes = getSortRes(sortingMode, isAscending, category?.isDynamic ?: false, true)
- this.setDrawable(sortingMode?.mainValue ?: -1, drawableRes)
- }
-
private fun getSortRes(
sortMode: LibrarySort?,
isAscending: Boolean,
- isDynamic: Boolean,
- onSelection: Boolean,
- @DrawableRes defaultDrawableRes: Int = R.drawable.ic_sort_24dp,
- @DrawableRes defaultSelectedDrawableRes: Int = R.drawable.ic_check_24dp,
+ @DrawableRes defaultDrawableRes: Int = R.drawable.ic_check_24dp,
): Int {
- sortMode ?: return if (onSelection) defaultSelectedDrawableRes else defaultDrawableRes
-
- if (sortMode.isDirectional) {
- return if (if (sortMode.hasInvertedSort) !isAscending else isAscending) {
- R.drawable.ic_arrow_downward_24dp
- } else {
- R.drawable.ic_arrow_upward_24dp
+ sortMode ?: return defaultDrawableRes
+ return when (sortMode) {
+ LibrarySort.DragAndDrop -> defaultDrawableRes
+ else -> {
+ if (if (sortMode.hasInvertedSort) !isAscending else isAscending) {
+ R.drawable.ic_arrow_downward_24dp
+ } else {
+ R.drawable.ic_arrow_upward_24dp
+ }
}
}
+ }
- if (onSelection) {
- return when(sortMode) {
- LibrarySort.DragAndDrop -> R.drawable.ic_check_24dp
- LibrarySort.Random -> R.drawable.ic_refresh_24dp
- else -> defaultSelectedDrawableRes
+ private fun getSortRes(
+ sortingMode: Int?,
+ isAscending: Boolean,
+ @DrawableRes defaultDrawableRes: Int = R.drawable.ic_check_24dp,
+ ): Int {
+ sortingMode ?: return defaultDrawableRes
+ return when (val sortMode = LibrarySort.valueOf(sortingMode)) {
+ LibrarySort.DragAndDrop -> defaultDrawableRes
+ else -> {
+ if (if (sortMode?.hasInvertedSort == true) !isAscending else isAscending) {
+ R.drawable.ic_arrow_downward_24dp
+ } else {
+ R.drawable.ic_arrow_upward_24dp
+ }
}
}
-
- return sortMode.iconRes(isDynamic)
}
private fun onCatSortClicked(category: Category, menuId: Int?) {
- val (mode, modType) = if (menuId == null) {
+ val modType = if (menuId == null) {
val sortingMode = category.sortingMode() ?: LibrarySort.Title
- sortingMode to
- if (sortingMode != LibrarySort.Random && category.isAscending()) {
- sortingMode.categoryValueDescending
- } else {
- sortingMode.categoryValue
- }
+ if (category.isAscending()) {
+ sortingMode.categoryValueDescending
+ } else {
+ sortingMode.categoryValue
+ }
} else {
val sortingMode = LibrarySort.valueOf(menuId) ?: LibrarySort.Title
if (sortingMode != LibrarySort.DragAndDrop && sortingMode == category.sortingMode()) {
onCatSortClicked(category, null)
return
}
- sortingMode to sortingMode.categoryValue
- }
- if (mode == LibrarySort.Random) {
- libraryPreferences.randomSortSeed().set(Random.nextInt())
+ sortingMode.categoryValue
}
adapter.libraryListener?.sortCategory(category.id!!, modType)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
index 5760a4808f..73e10f7fd9 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
@@ -5,6 +5,9 @@ import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import com.google.android.material.card.MaterialCardView
import eu.kanade.tachiyomi.R
+import yokai.i18n.MR
+import yokai.util.lang.getString
+import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.system.getResourceColor
@@ -14,7 +17,9 @@ import eu.kanade.tachiyomi.util.view.setCards
* Generic class used to hold the displayed data of a manga in the library.
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
+ * @param listener a listener to react to the single tap and long tap events.
*/
+
abstract class LibraryHolder(
view: View,
val adapter: LibraryCategoryAdapter,
@@ -38,7 +43,7 @@ abstract class LibraryHolder(
*/
abstract fun onSetValues(item: LibraryItem)
- fun setUnreadBadge(badge: LibraryBadge, item: LibraryMangaItem) {
+ fun setUnreadBadge(badge: LibraryBadge, item: LibraryItem) {
val showTotal = item.header.category.sortingMode() == LibrarySort.TotalChapters
badge.setUnreadDownload(
when {
@@ -49,7 +54,7 @@ abstract class LibraryHolder(
},
when {
item.downloadCount == -1 -> -1
- item.manga.manga.isLocal() -> -2
+ item.manga.isLocal() -> -2
else -> item.downloadCount
},
showTotal,
@@ -58,7 +63,7 @@ abstract class LibraryHolder(
)
}
- fun setReadingButton(item: LibraryMangaItem) {
+ fun setReadingButton(item: LibraryItem) {
itemView.findViewById(R.id.play_layout)?.isVisible =
item.manga.unread > 0 && !item.hideReadingButton
}
@@ -75,8 +80,8 @@ abstract class LibraryHolder(
override fun onLongClick(view: View?): Boolean {
return if (adapter.isLongPressDragEnabled) {
- val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryMangaItem)?.manga
- if (manga != null && !isDraggable) {
+ val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryItem)?.manga
+ if (manga != null && !isDraggable && !manga.isBlank() && !manga.isHidden()) {
adapter.mItemLongClickListener.onItemLongClick(flexibleAdapterPosition)
toggleActivation()
true
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt
index 134e8241ae..956bca1be7 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt
@@ -1,48 +1,205 @@
package eu.kanade.tachiyomi.ui.library
import android.content.Context
-import androidx.annotation.CallSuper
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFilterable
import eu.davidea.flexibleadapter.items.IFlexible
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.LibraryManga
+import eu.kanade.tachiyomi.data.database.models.seriesType
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.util.system.dpToPx
+import eu.kanade.tachiyomi.util.view.compatToolTipText
+import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import uy.kohesive.injekt.injectLazy
import yokai.domain.ui.UiPreferences
-abstract class LibraryItem(
+class LibraryItem(
+ val manga: LibraryManga,
header: LibraryHeaderItem,
- internal val context: Context?,
+ private val context: Context?,
) : AbstractSectionableItem(header), IFilterable {
+ var downloadCount = -1
+ var unreadType = 2
+ var sourceLanguage: String? = null
var filter = ""
- internal val sourceManager: SourceManager by injectLazy()
+ private val sourceManager: SourceManager by injectLazy()
private val uiPreferences: UiPreferences by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
- internal val uniformSize: Boolean
+ private val uniformSize: Boolean
get() = uiPreferences.uniformGrid().get()
- internal val libraryLayout: Int
+ private val libraryLayout: Int
get() = preferences.libraryLayout().get()
val hideReadingButton: Boolean
get() = preferences.hideStartReadingButton().get()
- @CallSuper
+ override fun getLayoutRes(): Int {
+ return if (libraryLayout == LAYOUT_LIST || manga.isBlank()) {
+ R.layout.manga_list_item
+ } else {
+ R.layout.manga_grid_item
+ }
+ }
+
+ override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder {
+ val parent = adapter.recyclerView
+ return if (parent is AutofitRecyclerView) {
+ val libraryLayout = libraryLayout
+ val isFixedSize = uniformSize
+ if (libraryLayout == LAYOUT_LIST || manga.isBlank()) {
+ LibraryListHolder(view, adapter as LibraryCategoryAdapter)
+ } else {
+ view.apply {
+ val isStaggered = parent.layoutManager is StaggeredGridLayoutManager
+ val binding = MangaGridItemBinding.bind(this)
+ binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID
+ if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) {
+ binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID
+ binding.card.setCardForegroundColor(
+ ContextCompat.getColorStateList(
+ context,
+ R.color.library_comfortable_grid_foreground,
+ ),
+ )
+ }
+ if (isFixedSize) {
+ binding.constraintLayout.layoutParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+ binding.coverThumbnail.maxHeight = Int.MAX_VALUE
+ binding.coverThumbnail.minimumHeight = 0
+ binding.constraintLayout.minHeight = 0
+ binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP
+ binding.coverThumbnail.adjustViewBounds = false
+ binding.coverThumbnail.updateLayoutParams {
+ height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
+ dimensionRatio = "15:22"
+ }
+ }
+ if (libraryLayout != LAYOUT_COMFORTABLE_GRID) {
+ binding.card.updateLayoutParams {
+ bottomMargin = (if (isStaggered) 2 else 6).dpToPx
+ }
+ }
+ binding.setBGAndFG(libraryLayout)
+ }
+ val gridHolder = LibraryGridHolder(
+ view,
+ adapter as LibraryCategoryAdapter,
+ libraryLayout == LAYOUT_COMPACT_GRID,
+ isFixedSize,
+ )
+ if (!isFixedSize) {
+ gridHolder.setFreeformCoverRatio(manga, parent)
+ }
+ gridHolder
+ }
+ } else {
+ LibraryListHolder(view, adapter as LibraryCategoryAdapter)
+ }
+ }
+
override fun bindViewHolder(
adapter: FlexibleAdapter>,
holder: LibraryHolder,
position: Int,
payloads: MutableList?,
) {
+ if (holder is LibraryGridHolder && !holder.fixedSize) {
+ holder.setFreeformCoverRatio(manga, adapter.recyclerView as? AutofitRecyclerView)
+ }
holder.onSetValues(this)
(holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position))
- (holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.isFullSpan = this is LibraryPlaceholderItem
+ val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams
+ layoutParams?.isFullSpan = manga.isBlank()
+ if (libraryLayout == LAYOUT_COVER_ONLY_GRID) {
+ holder.itemView.compatToolTipText = manga.title
+ }
+ }
+
+ /**
+ * Returns true if this item is draggable.
+ */
+ override fun isDraggable(): Boolean {
+ return !manga.isBlank() && header.category.isDragAndDrop
+ }
+
+ override fun isEnabled(): Boolean {
+ return !manga.isBlank()
+ }
+
+ override fun isSelectable(): Boolean {
+ return !manga.isBlank()
+ }
+
+ /**
+ * Filters a manga depending on a query.
+ *
+ * @param constraint the query to apply.
+ * @return true if the manga should be included, false otherwise.
+ */
+ override fun filter(constraint: String): Boolean {
+ filter = constraint
+ if (manga.isBlank() && manga.title.isBlank()) {
+ return constraint.isEmpty()
+ }
+ val sourceName by lazy { sourceManager.getOrStub(manga.source).name }
+ return manga.title.contains(constraint, true) ||
+ (manga.author?.contains(constraint, true) ?: false) ||
+ (manga.artist?.contains(constraint, true) ?: false) ||
+ sourceName.contains(constraint, true) ||
+ if (constraint.contains(",")) {
+ val genres = manga.genre?.split(", ")
+ constraint.split(",").all { containsGenre(it.trim(), genres) }
+ } else {
+ containsGenre(constraint, manga.genre?.split(", "))
+ }
+ }
+
+ private fun containsGenre(tag: String, genres: List?): Boolean {
+ if (tag.trim().isEmpty()) return true
+ context ?: return false
+ val seriesType by lazy { manga.seriesType(context, sourceManager) }
+ return if (tag.startsWith("-")) {
+ val realTag = tag.substringAfter("-")
+ genres?.find {
+ it.trim().equals(realTag, ignoreCase = true) || seriesType.equals(realTag, true)
+ } == null
+ } else {
+ genres?.find {
+ it.trim().equals(tag, ignoreCase = true) || seriesType.equals(tag, true)
+ } != null
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is LibraryItem) {
+ return manga.id == other.manga.id && manga.category == other.manga.category
+ }
+ return false
+ }
+
+ override fun hashCode(): Int {
+ return 31 * manga.id!!.hashCode() + header!!.hashCode()
}
companion object {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt
index e3696c68e7..568068a324 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt
@@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.util.lang.highlightText
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.setCards
import yokai.i18n.MR
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
import yokai.util.lang.getString
/**
@@ -39,25 +39,22 @@ class LibraryListHolder(
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
binding.title.isVisible = true
binding.constraintLayout.minHeight = 56.dpToPx
- if (item is LibraryPlaceholderItem) {
+ if (item.manga.isBlank()) {
binding.constraintLayout.minHeight = 0
binding.constraintLayout.updateLayoutParams {
height = ViewGroup.MarginLayoutParams.WRAP_CONTENT
}
- when (item.type) {
- is LibraryPlaceholderItem.Type.Blank -> {
- binding.title.text = itemView.context.getString(
- if (adapter.hasActiveFilters && item.type.mangaCount >= 1) {
- MR.strings.no_matches_for_filters_short
- } else {
- MR.strings.category_is_empty
- },
- )
- }
- is LibraryPlaceholderItem.Type.Hidden -> {
- binding.title.text = null
- binding.title.isVisible = false
- }
+ if (item.manga.status == -1) {
+ binding.title.text = null
+ binding.title.isVisible = false
+ } else {
+ binding.title.text = itemView.context.getString(
+ if (adapter.hasActiveFilters && item.manga.realMangaCount >= 1) {
+ MR.strings.no_matches_for_filters_short
+ } else {
+ MR.strings.category_is_empty
+ },
+ )
}
binding.title.textAlignment = View.TEXT_ALIGNMENT_CENTER
binding.card.isVisible = false
@@ -66,9 +63,6 @@ class LibraryListHolder(
binding.subtitle.isVisible = false
return
}
-
- if (item !is LibraryMangaItem) error("${item::class.qualifiedName} is not a valid item")
-
binding.constraintLayout.updateLayoutParams {
height = 52.dpToPx
}
@@ -77,16 +71,16 @@ class LibraryListHolder(
binding.title.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
// Update the binding.title of the manga.
- binding.title.text = item.manga.manga.title.highlightText(item.filter, color)
+ binding.title.text = item.manga.title.highlightText(item.filter, color)
setUnreadBadge(binding.unreadDownloadBadge.badgeView, item)
val authorArtist =
- if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) {
- item.manga.manga.author?.trim() ?: ""
+ if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) {
+ item.manga.author?.trim() ?: ""
} else {
listOfNotNull(
- item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() },
- item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() },
+ item.manga.author?.trim()?.takeIf { it.isNotBlank() },
+ item.manga.artist?.trim()?.takeIf { it.isNotBlank() },
).joinToString(", ")
}
@@ -101,7 +95,7 @@ class LibraryListHolder(
// Update the cover.
binding.coverThumbnail.dispose()
- binding.coverThumbnail.loadManga(item.manga.manga)
+ binding.coverThumbnail.loadManga(item.manga)
}
override fun onActionStateChanged(position: Int, actionState: Int) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt
deleted file mode 100644
index 28de0771f0..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-package eu.kanade.tachiyomi.ui.library
-
-import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.ImageView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.ContextCompat
-import androidx.core.view.isVisible
-import androidx.core.view.updateLayoutParams
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.StaggeredGridLayoutManager
-import eu.davidea.flexibleadapter.FlexibleAdapter
-import eu.davidea.flexibleadapter.items.IFlexible
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.models.LibraryManga
-import eu.kanade.tachiyomi.data.database.models.seriesType
-import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
-import eu.kanade.tachiyomi.util.system.dpToPx
-import eu.kanade.tachiyomi.util.view.compatToolTipText
-import eu.kanade.tachiyomi.widget.AutofitRecyclerView
-
-class LibraryMangaItem(
- val manga: LibraryManga,
- header: LibraryHeaderItem,
- context: Context?,
-) : LibraryItem(header, context) {
-
- var downloadCount = -1
- var unreadType = 2
- var sourceLanguage: String? = null
-
- override fun getLayoutRes(): Int {
- return if (libraryLayout == LAYOUT_LIST) {
- R.layout.manga_list_item
- } else {
- R.layout.manga_grid_item
- }
- }
-
- override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder {
- val listHolder by lazy { LibraryListHolder(view, adapter as LibraryCategoryAdapter) }
- val parent = adapter.recyclerView
- if (parent !is AutofitRecyclerView) return listHolder
-
- val libraryLayout = libraryLayout
- val isFixedSize = uniformSize
-
- if (libraryLayout == LAYOUT_LIST) { return listHolder }
-
- view.apply {
- val isStaggered = parent.layoutManager is StaggeredGridLayoutManager
- val binding = MangaGridItemBinding.bind(this)
- binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID
- if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) {
- binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID
- binding.card.setCardForegroundColor(
- ContextCompat.getColorStateList(
- context,
- R.color.library_comfortable_grid_foreground,
- ),
- )
- }
- if (isFixedSize) {
- binding.constraintLayout.layoutParams = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- )
- binding.coverThumbnail.maxHeight = Int.MAX_VALUE
- binding.coverThumbnail.minimumHeight = 0
- binding.constraintLayout.minHeight = 0
- binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP
- binding.coverThumbnail.adjustViewBounds = false
- binding.coverThumbnail.updateLayoutParams {
- height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
- dimensionRatio = "2:3"
- }
- }
- if (libraryLayout != LAYOUT_COMFORTABLE_GRID) {
- binding.card.updateLayoutParams {
- bottomMargin = (if (isStaggered) 2 else 6).dpToPx
- }
- }
- binding.setBGAndFG(libraryLayout)
- }
- val gridHolder = LibraryGridHolder(
- view,
- adapter as LibraryCategoryAdapter,
- libraryLayout == LAYOUT_COMPACT_GRID,
- isFixedSize,
- )
- if (!isFixedSize) {
- gridHolder.setFreeformCoverRatio(manga.manga, parent)
- }
- return gridHolder
- }
-
- override fun bindViewHolder(
- adapter: FlexibleAdapter>,
- holder: LibraryHolder,
- position: Int,
- payloads: MutableList?,
- ) {
- if (holder is LibraryGridHolder && !holder.fixedSize) {
- holder.setFreeformCoverRatio(manga.manga, adapter.recyclerView as? AutofitRecyclerView)
- }
- super.bindViewHolder(adapter, holder, position, payloads)
- if (libraryLayout == LAYOUT_COVER_ONLY_GRID) {
- holder.itemView.compatToolTipText = manga.manga.title
- }
- }
-
- /**
- * Returns true if this item is draggable.
- */
- override fun isDraggable(): Boolean {
- return header.category.isDragAndDrop
- }
-
- override fun isEnabled(): Boolean {
- return true
- }
-
- override fun isSelectable(): Boolean {
- return true
- }
-
- /**
- * Filters a manga depending on a query.
- *
- * @param constraint the query to apply.
- * @return true if the manga should be included, false otherwise.
- */
- override fun filter(constraint: String): Boolean {
- filter = constraint
- if (manga.manga.title.isBlank()) {
- return constraint.isEmpty()
- }
- val sourceName by lazy { sourceManager.getOrStub(manga.manga.source).name }
- return manga.manga.title.contains(constraint, true) ||
- (manga.manga.author?.contains(constraint, true) ?: false) ||
- (manga.manga.artist?.contains(constraint, true) ?: false) ||
- sourceName.contains(constraint, true) ||
- if (constraint.contains(",")) {
- val genres = manga.manga.genre?.split(", ")
- constraint.split(",").all { containsGenre(it.trim(), genres) }
- } else {
- containsGenre(constraint, manga.manga.genre?.split(", "))
- }
- }
-
- private fun containsGenre(tag: String, genres: List?): Boolean {
- if (tag.trim().isEmpty()) return true
- context ?: return false
-
- val seriesType by lazy { manga.manga.seriesType(context, sourceManager) }
- return if (tag.startsWith("-")) {
- val realTag = tag.substringAfter("-")
- genres?.find {
- it.trim().equals(realTag, ignoreCase = true) || seriesType.equals(realTag, true)
- } == null
- } else {
- genres?.find {
- it.trim().equals(tag, ignoreCase = true) || seriesType.equals(tag, true)
- } != null
- }
- }
-
- override fun equals(other: Any?): Boolean {
- if (other is LibraryMangaItem) {
- return manga.manga.id == other.manga.manga.id && manga.category == other.manga.category
- }
- return false
- }
-
- override fun hashCode(): Int {
- return 31 * manga.manga.id.hashCode() + header!!.hashCode()
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt
deleted file mode 100644
index d0b810a4a6..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package eu.kanade.tachiyomi.ui.library
-
-import android.content.Context
-import android.view.View
-import androidx.recyclerview.widget.RecyclerView
-import eu.davidea.flexibleadapter.FlexibleAdapter
-import eu.davidea.flexibleadapter.items.IFlexible
-import eu.kanade.tachiyomi.R
-
-/**
- * Placeholder item to indicate if the category is hidden or empty/filtered out.
- */
-class LibraryPlaceholderItem (
- val category: Int,
- val type: Type,
- header: LibraryHeaderItem,
- context: Context?,
-) : LibraryItem(header, context) {
-
- override fun getLayoutRes(): Int = R.layout.manga_list_item
-
- override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder {
- return LibraryListHolder(view, adapter as LibraryCategoryAdapter)
- }
-
- override fun filter(constraint: String): Boolean {
- filter = constraint
-
- if (type !is Type.Hidden || type.title.isBlank()) return constraint.isEmpty()
-
- return type.title.contains(constraint, true)
- }
-
- override fun equals(other: Any?): Boolean {
- if (other is LibraryPlaceholderItem) {
- return category == other.category
- }
- return false
- }
-
- override fun hashCode(): Int {
- return 31 * Long.MIN_VALUE.hashCode() + header!!.hashCode()
- }
-
- sealed class Type {
- data class Hidden(val title: String, val hiddenItems: List) : Type()
- data class Blank(val mangaCount: Int) : Type()
- }
-
- companion object {
- fun hidden(category: Int, header: LibraryHeaderItem, context: Context?, title: String, hiddenItems: List) =
- LibraryPlaceholderItem(category, Type.Hidden(title, hiddenItems), header, context)
-
- fun blank(category: Int, header: LibraryHeaderItem, context: Context?, mangaCount: Int = 0) =
- LibraryPlaceholderItem(category, Type.Blank(mangaCount), header, context)
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
index e82f372dcc..77ed4ca035 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
@@ -4,15 +4,12 @@ import eu.kanade.tachiyomi.core.preference.minusAssign
import eu.kanade.tachiyomi.core.preference.plusAssign
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Category
-import eu.kanade.tachiyomi.data.database.models.Category.Companion.langSplitter
-import eu.kanade.tachiyomi.data.database.models.Category.Companion.sourceSplitter
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Chapter.Companion.copy
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.removeCover
import eu.kanade.tachiyomi.data.database.models.seriesType
-import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.DelayedLibrarySuggestionsJob
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -54,11 +51,14 @@ import kotlin.random.Random
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.retry
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -73,7 +73,6 @@ import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.chapter.models.ChapterUpdate
import yokai.domain.history.interactor.GetHistory
-import yokai.domain.library.LibraryPreferences
import yokai.domain.manga.interactor.GetLibraryManga
import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.UpdateManga
@@ -83,18 +82,13 @@ import yokai.i18n.MR
import yokai.util.isLewd
import yokai.util.lang.getString
-typealias LibraryMap = Map>
-typealias LibraryMutableMap = MutableMap>
-
/**
* Presenter of [LibraryController].
*/
class LibraryPresenter(
private val preferences: PreferencesHelper = Injekt.get(),
- private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
val sourceManager: SourceManager = Injekt.get(),
- private val downloadCache: DownloadCache = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val chapterFilter: ChapterFilter = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(),
@@ -109,7 +103,10 @@ class LibraryPresenter(
private val getTrack: GetTrack by injectLazy()
private val getHistory: GetHistory by injectLazy()
- private val forceUpdateEvent: Channel = Channel(Channel.UNLIMITED)
+ private val _fetchLibrary: Channel = Channel(Channel.UNLIMITED)
+ val fetchLibrary = _fetchLibrary.receiveAsFlow()
+ .onStart { emit(Unit) }
+ .shareIn(presenterScope, SharingStarted.Lazily, 1)
private val context = preferences.context
private val viewContext
@@ -126,27 +123,18 @@ class LibraryPresenter(
var categories: List = emptyList()
private set
+ private var removeArticles: Boolean = preferences.removeArticles().get()
+
/** All categories of the library, in case they are hidden because of hide categories is on */
private var allCategories: List = emptyList()
- private var removeArticles: Boolean = preferences.removeArticles().get()
-
- /** List of all manga */
- var currentLibrary: LibraryMap = mapOf()
+ /** List of all manga to update the */
+ var libraryItems: List = emptyList()
+ private var sectionedLibraryItems: MutableMap> = mutableMapOf()
+ var currentCategory = -1
private set
- val currentLibraryItems: List
- get() = currentLibrary.values.flatten()
- /** List of all manga to be displayed */
- private var libraryToDisplay: LibraryMutableMap = mutableMapOf()
- val libraryItemsToDisplay: List
- get() = libraryToDisplay.values.flatten()
-
- var currentCategoryId = -1
+ var allLibraryItems: List = emptyList()
private set
- var currentCategory: Category?
- get() = allCategories.find { it.id == currentCategoryId }
- set(value) { currentCategoryId = value?.id ?: 0 }
-
private var hiddenLibraryItems: List = emptyList()
var forceShowAllCategories = false
val showAllCategories
@@ -183,14 +171,16 @@ class LibraryPresenter(
fun isCategoryMoreThanOne(): Boolean = allCategories.size > 1
+ fun findCurrentCategory() = allCategories.find { it.id == currentCategory }
+
/** Save the current list to speed up loading later */
override fun onDestroy() {
val isSubController = controllerIsSubClass
super.onDestroy()
if (!isSubController) {
- lastDisplayedLibrary = libraryToDisplay
+ lastLibraryItems = libraryItems
lastCategories = categories
- lastLibrary = currentLibrary
+ lastAllLibraryItems = allLibraryItems
}
}
@@ -198,16 +188,15 @@ class LibraryPresenter(
super.onCreate()
if (!controllerIsSubClass) {
- lastDisplayedLibrary?.let { libraryToDisplay = it }
+ lastLibraryItems?.let { libraryItems = it }
lastCategories?.let { categories = it }
- lastLibrary?.let { currentLibrary = it }
+ lastAllLibraryItems?.let { allLibraryItems = it }
lastCategories = null
- lastDisplayedLibrary = null
- lastLibrary = null
+ lastLibraryItems = null
+ lastAllLibraryItems = null
}
subscribeLibrary()
- updateLibrary()
if (!preferences.showLibrarySearchSuggestions().isSet()) {
DelayedLibrarySuggestionsJob.setupTask(context, true)
@@ -223,16 +212,12 @@ class LibraryPresenter(
}
fun getItemCountInCategories(categoryId: Int): Int {
- val category = categories.find { it.id == categoryId }
- val items = libraryToDisplay[category]
- val firstItem = items?.firstOrNull() as? LibraryPlaceholderItem?
- if (firstItem != null) {
- if (firstItem.type !is LibraryPlaceholderItem.Type.Hidden) {
- return 0
- }
- return firstItem.type.hiddenItems.size
+ val items = sectionedLibraryItems[categoryId]
+ return if (items?.firstOrNull()?.manga?.isHidden() == true || items?.firstOrNull()?.manga?.isBlank() == true) {
+ items.firstOrNull()?.manga?.read ?: 0
+ } else {
+ sectionedLibraryItems[categoryId]?.size ?: 0
}
- return items?.size ?: 0
}
private fun subscribeLibrary() {
@@ -248,31 +233,38 @@ class LibraryPresenter(
combine(
getLibraryFlow(),
- downloadCache.changes,
- ) { data, _ -> data }.collectLatest { data ->
+ fetchLibrary,
+ ) { data, _ ->
categories = data.categories
allCategories = data.allCategories
- val library = data.items
- val hiddenItems = data.hiddenItems
-
- library.forEach { (_, items) ->
- setDownloadCount(items)
- setUnreadBadge(items)
- setSourceLanguage(items)
- }
- setDownloadCount(hiddenItems)
- setUnreadBadge(hiddenItems)
- setSourceLanguage(hiddenItems)
-
- currentLibrary = library
- hiddenLibraryItems = hiddenItems
- val mangaMap = library
- .applyFilters()
- .applySort()
- val freshStart = libraryToDisplay.isEmpty()
- sectionLibrary(mangaMap, freshStart)
+ data.items
}
+ .collectLatest { library ->
+ val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten()
+
+ setDownloadCount(library)
+ setUnreadBadge(library)
+ setSourceLanguage(library)
+ setDownloadCount(hiddenItems)
+ setUnreadBadge(hiddenItems)
+ setSourceLanguage(hiddenItems)
+
+ allLibraryItems = library
+ hiddenLibraryItems = hiddenItems
+ val mangaMap = library
+ .applyFilters()
+ .applySort()
+ val freshStart = libraryItems.isEmpty()
+ sectionLibrary(mangaMap, freshStart)
+ }
+ }
+ }
+
+ /** Get favorited manga for library and sort and filter it */
+ fun getLibrary() {
+ presenterScope.launch {
+ _fetchLibrary.send(Unit)
}
}
@@ -286,16 +278,16 @@ class LibraryPresenter(
fun switchSection(order: Int) {
preferences.lastUsedCategory().set(order)
- val category = categories.find { it.order == order } ?: return
+ val category = categories.find { it.order == order }?.id ?: return
currentCategory = category
- view?.onNextLibraryUpdate(libraryToDisplay[category] ?: blankItem())
+ view?.onNextLibraryUpdate(sectionedLibraryItems[currentCategory] ?: blankItem())
}
- fun blankItem(id: Int = currentCategoryId, categories: List? = null): List {
+ fun blankItem(id: Int = currentCategory, categories: List? = null): List {
val actualCategories = categories ?: this.categories
return listOf(
- LibraryPlaceholderItem.blank(
- id,
+ LibraryItem(
+ LibraryManga.createBlank(id),
LibraryHeaderItem({ actualCategories.getOrDefault(id) }, id),
viewContext,
),
@@ -303,17 +295,20 @@ class LibraryPresenter(
}
fun restoreLibrary() {
+ val items = libraryItems
val show = showAllCategories || !libraryIsGrouped || categories.size == 1
- if (!show && currentCategoryId == -1) {
- currentCategory = categories.find { it.order == preferences.lastUsedCategory().get() }
+ sectionedLibraryItems = items.groupBy { it.header.category.id!! }.toMutableMap()
+ if (!show && currentCategory == -1) {
+ currentCategory = categories.find {
+ it.order == preferences.lastUsedCategory().get()
+ }?.id ?: 0
}
view?.onNextLibraryUpdate(
if (!show) {
- libraryToDisplay[currentCategory]
- ?: libraryToDisplay[categories.first()]
- ?: blankItem()
+ sectionedLibraryItems[currentCategory]
+ ?: sectionedLibraryItems[categories.first().id] ?: blankItem()
} else {
- libraryItemsToDisplay
+ libraryItems
},
true,
)
@@ -321,29 +316,26 @@ class LibraryPresenter(
fun getMangaInCategories(catId: Int?): List? {
catId ?: return null
- return currentLibraryItems
- .filterIsInstance()
- .filter { it.header.category.id == catId }
- .map { it.manga }
+ return allLibraryItems.filter { it.header.category.id == catId }.map { it.manga }
}
- private suspend fun sectionLibrary(items: LibraryMap, freshStart: Boolean = false) {
- val showAll = showAllCategories || !libraryIsGrouped || categories.size <= 1
-
- libraryToDisplay = items.toMutableMap()
-
- if (!showAll && currentCategoryId == -1) {
- currentCategory = categories.find { it.order == preferences.lastUsedCategory().get() }
+ private suspend fun sectionLibrary(items: List, freshStart: Boolean = false) {
+ libraryItems = items
+ val showAll = showAllCategories || !libraryIsGrouped ||
+ categories.size <= 1
+ sectionedLibraryItems = items.groupBy { it.header.category.id ?: 0 }.toMutableMap()
+ if (!showAll && currentCategory == -1) {
+ currentCategory = categories.find {
+ it.order == preferences.lastUsedCategory().get()
+ }?.id ?: 0
}
-
withUIContext {
view?.onNextLibraryUpdate(
if (!showAll) {
- libraryToDisplay[currentCategory]
- ?: libraryToDisplay[categories.first()]
- ?: blankItem()
+ sectionedLibraryItems[currentCategory]
+ ?: sectionedLibraryItems[categories.first().id] ?: blankItem()
} else {
- libraryItemsToDisplay
+ libraryItems
},
freshStart,
)
@@ -355,7 +347,7 @@ class LibraryPresenter(
*
* @param items the items to filter.
*/
- private suspend fun LibraryMap.applyFilters(): LibraryMap {
+ private suspend fun List.applyFilters(): List {
val filterPrefs = getPreferencesFlow().first()
val showEmptyCategoriesWhileFiltering = preferences.showEmptyCategoriesWhileFiltering().get()
@@ -371,68 +363,57 @@ class LibraryPresenter(
filterPrefs.filterContentType == 0
)
hasActiveFilters = !filtersOff
+ val missingCategorySet = categories.mapNotNull { it.id }.toMutableSet()
val realCount = mutableMapOf()
- val filteredItems = this.mapValues { (key, items) ->
+ val filteredItems = this.filter f@{ item ->
if (showEmptyCategoriesWhileFiltering) {
- realCount[key.id ?: 0] = libraryToDisplay[key]?.size ?: 0
+ realCount[item.manga.category] = sectionedLibraryItems[item.manga.category]?.size ?: 0
}
- items.filter f@{ item ->
- if (item is LibraryMangaItem) {
- return@f matchesFilters(
- item,
- filterPrefs,
- filterTrackers,
- )
- }
-
- if (
- !showEmptyCategoriesWhileFiltering
- && item is LibraryPlaceholderItem
- && item.type is LibraryPlaceholderItem.Type.Hidden
- ) {
- val subItems = (libraryToDisplay[key] ?: hiddenLibraryItems)
- .filterIsInstance()
- .filter { it.manga.category == item.category }
- if (subItems.isEmpty()) {
- return@f filtersOff
- } else {
- return@f subItems.any {
- matchesFilters(
- it,
- filterPrefs,
- filterTrackers,
- )
- }
+ if (!showEmptyCategoriesWhileFiltering && item.manga.isHidden()) {
+ val subItems = sectionedLibraryItems[item.manga.category]?.takeUnless { it.size <= 1 }
+ ?: hiddenLibraryItems.filter { it.manga.category == item.manga.category }
+ if (subItems.isEmpty()) {
+ return@f filtersOff
+ } else {
+ return@f subItems.any {
+ matchesFilters(
+ it,
+ filterPrefs,
+ filterTrackers,
+ )
}
}
-
- if (showAllCategories) {
+ } else if (item.manga.isBlank() || item.manga.isHidden()) {
+ missingCategorySet.remove(item.manga.category)
+ return@f if (showAllCategories) {
filtersOff || showEmptyCategoriesWhileFiltering
} else {
true
}
- }.ifEmpty {
- if (showEmptyCategoriesWhileFiltering) {
- val catId = key.id!!
- listOf(
- LibraryPlaceholderItem.blank(
- catId,
- LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(catId) }, catId),
- viewContext,
- realCount[catId] ?: 0,
- ),
- )
- } else {
- emptyList()
- }
}
- }.toMutableMap()
+ val matches = matchesFilters(
+ item,
+ filterPrefs,
+ filterTrackers,
+ )
+ if (matches) {
+ missingCategorySet.remove(item.manga.category)
+ }
+ matches
+ }.toMutableList()
+ if (showEmptyCategoriesWhileFiltering) {
+ missingCategorySet.forEach {
+ filteredItems.add(
+ blankItem(it).first().apply { manga.realMangaCount = realCount[it] ?: 0 }
+ )
+ }
+ }
return filteredItems
}
private suspend fun matchesFilters(
- item: LibraryMangaItem,
+ item: LibraryItem,
filterPrefs: ItemPreferences,
filterTrackers: String,
): Boolean {
@@ -452,9 +433,9 @@ class LibraryPresenter(
if (filterPrefs.filterMangaType > 0) {
if (if (filterPrefs.filterMangaType == Manga.TYPE_MANHWA) {
- item.manga.manga.seriesType(sourceManager = sourceManager) !in arrayOf(filterPrefs.filterMangaType, Manga.TYPE_WEBTOON)
+ item.manga.seriesType(sourceManager = sourceManager) !in arrayOf(filterPrefs.filterMangaType, Manga.TYPE_WEBTOON)
} else {
- filterPrefs.filterMangaType != item.manga.manga.seriesType(sourceManager = sourceManager)
+ filterPrefs.filterMangaType != item.manga.seriesType(sourceManager = sourceManager)
}
) {
return false
@@ -462,51 +443,51 @@ class LibraryPresenter(
}
// Filter for completed status of manga
- if (filterPrefs.filterCompleted == STATE_INCLUDE && item.manga.manga.status != SManga.COMPLETED) return false
- if (filterPrefs.filterCompleted == STATE_EXCLUDE && item.manga.manga.status == SManga.COMPLETED) return false
+ if (filterPrefs.filterCompleted == STATE_INCLUDE && item.manga.status != SManga.COMPLETED) return false
+ if (filterPrefs.filterCompleted == STATE_EXCLUDE && item.manga.status == SManga.COMPLETED) return false
if (!matchesFilterTracking(item, filterPrefs.filterTracked, filterTrackers)) return false
// Filter for downloaded manga
if (filterPrefs.filterDownloaded != STATE_IGNORE) {
val isDownloaded = when {
- item.manga.manga.isLocal() -> true
+ item.manga.isLocal() -> true
item.downloadCount != -1 -> item.downloadCount > 0
- else -> downloadManager.getDownloadCount(item.manga.manga) > 0
+ else -> downloadManager.getDownloadCount(item.manga) > 0
}
return if (filterPrefs.filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded
}
// Filter for NSFW/SFW contents
- if (filterPrefs.filterContentType == STATE_INCLUDE) return !item.manga.manga.isLewd()
- if (filterPrefs.filterContentType == STATE_EXCLUDE) return item.manga.manga.isLewd()
+ if (filterPrefs.filterContentType == STATE_INCLUDE) return !item.manga.isLewd()
+ if (filterPrefs.filterContentType == STATE_EXCLUDE) return item.manga.isLewd()
return true
}
private suspend fun matchesCustomFilters(
- item: LibraryMangaItem,
+ item: LibraryItem,
customFilters: FilteredLibraryController,
filterTrackers: String,
): Boolean {
val statuses = customFilters.filterStatus
if (statuses.isNotEmpty()) {
- if (item.manga.manga.status !in statuses) return false
+ if (item.manga.status !in statuses) return false
}
val seriesTypes = customFilters.filterMangaType
if (seriesTypes.isNotEmpty()) {
- if (item.manga.manga.seriesType(sourceManager = sourceManager) !in seriesTypes) return false
+ if (item.manga.seriesType(sourceManager = sourceManager) !in seriesTypes) return false
}
val languages = customFilters.filterLanguages
if (languages.isNotEmpty()) {
- if (getLanguage(item.manga.manga) !in languages) return false
+ if (getLanguage(item.manga) !in languages) return false
}
val sources = customFilters.filterSources
if (sources.isNotEmpty()) {
- if (item.manga.manga.source !in sources) return false
+ if (item.manga.source !in sources) return false
}
val trackingScore = customFilters.filterTrackingScore
if (trackingScore > 0 || trackingScore == -1) {
- val tracks = getTrack.awaitAllByMangaId(item.manga.manga.id!!)
+ val tracks = getTrack.awaitAllByMangaId(item.manga.id!!)
val hasTrack = loggedServices.any { service ->
tracks.any { it.sync_id == service.id }
@@ -531,7 +512,7 @@ class LibraryPresenter(
}
val tags = customFilters.filterTags
if (tags.isNotEmpty()) {
- val genres = item.manga.manga.getGenres() ?: return false
+ val genres = item.manga.getGenres() ?: return false
if (tags.none { tag -> genres.any { it.equals(tag, true) } }) return false
}
return true
@@ -547,8 +528,8 @@ class LibraryPresenter(
}
private suspend fun LibraryManga.getStartYear(): Int {
- if (getChapter.awaitAll(manga.id!!, false).any { it.read }) {
- val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 }
+ if (getChapter.awaitAll(id!!, false).any { it.read }) {
+ val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 }
val date = chapters.minOfOrNull { it.last_read } ?: return -1
val cal = Calendar.getInstance().apply { timeInMillis = date }
return if (date <= 0L) -1 else cal.get(Calendar.YEAR)
@@ -565,13 +546,13 @@ class LibraryPresenter(
}
private suspend fun matchesFilterTracking(
- item: LibraryMangaItem,
+ item: LibraryItem,
filterTracked: Int,
filterTrackers: String,
): Boolean {
// Filter for tracked (or per tracked service)
if (filterTracked != STATE_IGNORE) {
- val tracks = getTrack.awaitAllByMangaId(item.manga.manga.id!!)
+ val tracks = getTrack.awaitAllByMangaId(item.manga.id!!)
val hasTrack = loggedServices.any { service ->
tracks.any { it.sync_id == service.id }
@@ -614,22 +595,19 @@ class LibraryPresenter(
if (!preferences.downloadBadge().get()) {
// Unset download count if the preference is not enabled.
for (item in itemList) {
- if (item !is LibraryMangaItem) continue
item.downloadCount = -1
}
return
}
for (item in itemList) {
- if (item !is LibraryMangaItem) continue
- item.downloadCount = downloadManager.getDownloadCount(item.manga.manga)
+ item.downloadCount = downloadManager.getDownloadCount(item.manga)
}
}
private fun setUnreadBadge(itemList: List) {
val unreadType = preferences.unreadBadgeType().get()
for (item in itemList) {
- if (item !is LibraryMangaItem) continue
item.unreadType = unreadType
}
}
@@ -637,8 +615,7 @@ class LibraryPresenter(
private fun setSourceLanguage(itemList: List) {
val showLanguageBadges = preferences.languageBadge().get()
for (item in itemList) {
- if (item !is LibraryMangaItem) continue
- item.sourceLanguage = if (showLanguageBadges) getLanguage(item.manga.manga) else null
+ item.sourceLanguage = if (showLanguageBadges) getLanguage(item.manga) else null
}
}
@@ -659,113 +636,88 @@ class LibraryPresenter(
*
* @param itemList the map to sort.
*/
- private fun LibraryMap.applySort(): LibraryMap {
- // Making sure `allCategories` is stable for `.sort()`
- val categoryOrderMap = allCategories.associate { it.id to it.order }
-
+ private fun List.applySort(): List {
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
- val category = i1.header.category
- val compare = when {
- i1 is LibraryPlaceholderItem -> -1
- i2 is LibraryPlaceholderItem -> 1
- i1 !is LibraryMangaItem || i2 !is LibraryMangaItem -> 0
- category.mangaSort != null -> {
- var sort = when (category.sortingMode() ?: LibrarySort.Title) {
- LibrarySort.Title -> sortAlphabetical(i1, i2)
- LibrarySort.LatestChapter -> i2.manga.latestUpdate.compareTo(i1.manga.latestUpdate)
- LibrarySort.Unread -> when {
- i1.manga.unread == i2.manga.unread -> 0
- i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1
- i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1
- else -> i1.manga.unread.compareTo(i2.manga.unread)
- }
- LibrarySort.LastRead -> {
- i1.manga.lastRead.compareTo(i2.manga.lastRead)
- }
- LibrarySort.TotalChapters -> {
- i1.manga.totalChapters.compareTo(i2.manga.totalChapters)
- }
- LibrarySort.DateFetched -> {
- i1.manga.lastFetch.compareTo(i2.manga.lastFetch)
- }
- LibrarySort.DateAdded -> i2.manga.manga.date_added.compareTo(i1.manga.manga.date_added)
- LibrarySort.DragAndDrop -> {
- if (category.isDynamic) {
- val category1 = categoryOrderMap[i1.manga.category] ?: 0
- val category2 = categoryOrderMap[i2.manga.category] ?: 0
- category1.compareTo(category2)
- } else {
- sortAlphabetical(i1, i2)
+ if (i1.header.category.id == i2.header.category.id) {
+ val category = i1.header.category
+ if (category.mangaOrder.isEmpty() && category.mangaSort == null) {
+ category.changeSortTo(preferences.librarySortingMode().get())
+ if (category.id == 0) {
+ preferences.defaultMangaOrder()
+ .set(category.mangaSort.toString())
+ } else if (!category.isDynamic) {
+ onCategoryUpdate(
+ CategoryUpdate(
+ id = category.id!!.toLong(),
+ mangaOrder = category.mangaOrderToString(),
+ )
+ )
+ }
+ }
+ val compare = when {
+ category.mangaSort != null -> {
+ var sort = when (category.sortingMode() ?: LibrarySort.Title) {
+ LibrarySort.Title -> sortAlphabetical(i1, i2)
+ LibrarySort.LatestChapter -> i2.manga.latestUpdate.compareTo(i1.manga.latestUpdate)
+ LibrarySort.Unread -> when {
+ i1.manga.unread == i2.manga.unread -> 0
+ i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1
+ i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1
+ else -> i1.manga.unread.compareTo(i2.manga.unread)
+ }
+ LibrarySort.LastRead -> {
+ i1.manga.lastRead.compareTo(i2.manga.lastRead)
+ }
+ LibrarySort.TotalChapters -> {
+ i1.manga.totalChapters.compareTo(i2.manga.totalChapters)
+ }
+ LibrarySort.DateFetched -> {
+ i1.manga.lastFetch.compareTo(i2.manga.lastFetch)
+ }
+ LibrarySort.DateAdded -> i2.manga.date_added.compareTo(i1.manga.date_added)
+ LibrarySort.DragAndDrop -> {
+ if (category.isDynamic) {
+ val category1 =
+ allCategories.find { i1.manga.category == it.id }?.order
+ ?: 0
+ val category2 =
+ allCategories.find { i2.manga.category == it.id }?.order
+ ?: 0
+ category1.compareTo(category2)
+ } else {
+ sortAlphabetical(i1, i2)
+ }
}
}
- LibrarySort.Random -> {
- error("You're not supposed to be here...")
- }
+ if (!category.isAscending()) sort *= -1
+ sort
}
- if (!category.isAscending()) sort *= -1
- sort
- }
- category.mangaOrder.isNotEmpty() -> {
- val order = category.mangaOrder
- val index1 = order.indexOf(i1.manga.manga.id!!)
- val index2 = order.indexOf(i2.manga.manga.id!!)
- when {
- index1 == index2 -> 0
- index1 == -1 -> -1
- index2 == -1 -> 1
- else -> index1.compareTo(index2)
- }
- }
- else -> 0
- }
- if (compare == 0 && i1 is LibraryMangaItem && i2 is LibraryMangaItem) {
- sortAlphabetical(i1, i2)
- } else {
- compare
- }
- }
-
- return this.mapValues { (category, values) ->
- // Making sure category has valid sort before doing the actual sorting
- if (category.mangaOrder.isEmpty() && category.mangaSort == null) {
- category.changeSortTo(preferences.librarySortingMode().get())
- if (category.id == 0) {
- preferences.defaultMangaOrder()
- .set(category.mangaSort.toString())
- } else if (!category.isDynamic) {
- onCategoryUpdate(
- CategoryUpdate(
- id = category.id!!.toLong(),
- mangaOrder = category.mangaOrderToString(),
- )
- )
- }
- }
-
- if (LibrarySort.valueOf(category.mangaSort) == LibrarySort.Random) {
- return@mapValues values
- .asSequence()
- .shuffled(Random(libraryPreferences.randomSortSeed().get()))
- .sortedWith { i1, i2 ->
+ category.mangaOrder.isNotEmpty() -> {
+ val order = category.mangaOrder
+ val index1 = order.indexOf(i1.manga.id!!)
+ val index2 = order.indexOf(i2.manga.id!!)
when {
- i1 is LibraryPlaceholderItem -> -1
- i2 is LibraryPlaceholderItem -> 1
- else -> 0
+ index1 == index2 -> 0
+ index1 == -1 -> -1
+ index2 == -1 -> 1
+ else -> index1.compareTo(index2)
}
}
- .toList()
- }
-
- values.sortedWith(Comparator(sortFn))
- }.toSortedMap { category, category2 ->
- when {
- // Force default category to always be at the top. This also for some reason fixed a bug where Default
- // category would disappear whenever a new category is added.
- category.id == 0 -> -1
- category2.id == 0 -> 1
- else -> category.order.compareTo(category2.order)
+ else -> 0
+ }
+ if (compare == 0) {
+ sortAlphabetical(i1, i2)
+ } else {
+ compare
+ }
+ } else {
+ val category = i1.header.category.order
+ val category2 = i2.header.category.order
+ category.compareTo(category2)
}
}
+
+ return this.sortedWith(Comparator(sortFn))
}
/** Gets the category by id
@@ -784,11 +736,11 @@ class LibraryPresenter(
* @param i1 the first manga
* @param i2 the second manga to compare
*/
- private fun sortAlphabetical(i1: LibraryMangaItem, i2: LibraryMangaItem): Int {
+ private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int {
return if (removeArticles) {
- i1.manga.manga.title.removeArticles().compareTo(i2.manga.manga.title.removeArticles(), true)
+ i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true)
} else {
- i1.manga.manga.title.compareTo(i2.manga.manga.title, true)
+ i1.manga.title.compareTo(i2.manga.title, true)
}
}
@@ -803,12 +755,9 @@ class LibraryPresenter(
preferences.groupLibraryBy().changes(),
preferences.showAllCategories().changes(),
-
+
preferences.librarySortingMode().changes(),
preferences.librarySortingAscending().changes(),
-
- preferences.collapsedCategories().changes(),
- preferences.collapsedDynamicCategories().changes(),
) {
ItemPreferences(
filterDownloaded = it[0] as Int,
@@ -822,38 +771,36 @@ class LibraryPresenter(
showAllCategories = it[8] as Boolean,
sortingMode = it[9] as Int,
sortAscending = it[10] as Boolean,
- collapsedCategories = it[11] as Set,
- collapsedDynamicCategories = it[12] as Set,
)
}
-// private fun MutableList.addRemovedManga(
-// removedManga: Map>,
-// ): MutableList {
-// removedManga.keys.forEach { key ->
-// val manga = removedManga[key] ?: return@forEach
-// val headerItem = try {
-// manga.first().header
-// } catch (e: NoSuchElementException) {
-// return@forEach // No hidden manga to be handled
-// }
-// val mergedTitle = manga.joinToString("-") {
-// it.manga.title + "-" + it.manga.manga.author
-// }
-// this.add(
-// LibraryItem(
-// LibraryManga.createHide(
-// headerItem.catId,
-// mergedTitle,
-// manga,
-// ),
-// headerItem,
-// viewContext,
-// ),
-// )
-// }
-// return this
-// }
+ private fun MutableList.addRemovedManga(
+ removedManga: Map>,
+ ): MutableList {
+ removedManga.keys.forEach { key ->
+ val manga = removedManga[key] ?: return@forEach
+ val headerItem = try {
+ manga.first().header
+ } catch (e: NoSuchElementException) {
+ return@forEach // No hidden manga to be handled
+ }
+ val mergedTitle = manga.joinToString("-") {
+ it.manga.title + "-" + it.manga.author
+ }
+ this.add(
+ LibraryItem(
+ LibraryManga.createHide(
+ headerItem.catId,
+ mergedTitle,
+ manga,
+ ),
+ headerItem,
+ viewContext,
+ ),
+ )
+ }
+ return this
+ }
/**
* Library's flow.
@@ -861,26 +808,26 @@ class LibraryPresenter(
* If category id '-1' is not empty, it means the library not grouped by categories
*/
private fun getLibraryFlow(): Flow {
- val libraryFlow = combine(
+ return combine(
getCategories.subscribe(),
// FIXME: Remove retry once a real solution is found
getLibraryManga.subscribe().retry(1) { e -> e is NullPointerException },
getPreferencesFlow(),
- forceUpdateEvent.receiveAsFlow(),
- ) { dbCategories, libraryMangaList, prefs, _ ->
+ preferences.removeArticles().changes(),
+ fetchLibrary
+ ) { dbCategories, libraryMangaList, prefs, removeArticles, _ ->
groupType = prefs.groupType
val defaultCategory = createDefaultCategory()
+ val allCategories = listOf(defaultCategory) + dbCategories
- // FIXME: Should return Map where Int is category id
- if (groupType <= BY_DEFAULT || !libraryIsGrouped) {
+ val (items, categories, hiddenItems) = if (groupType <= BY_DEFAULT || !libraryIsGrouped) {
getLibraryItems(
dbCategories,
libraryMangaList,
prefs.sortingMode,
prefs.sortAscending,
prefs.showAllCategories,
- prefs.collapsedCategories,
defaultCategory,
)
} else {
@@ -889,17 +836,8 @@ class LibraryPresenter(
prefs.sortingMode,
prefs.sortAscending,
groupType,
- prefs.collapsedDynamicCategories,
)
- } to listOf(defaultCategory) + dbCategories
- }
-
- return combine(
- libraryFlow,
- preferences.removeArticles().changes(),
- ) { library, removeArticles ->
- val (libraryItems, allCategories) = library
- val (items, categories, hiddenItems) = libraryItems
+ }
LibraryData(
categories = categories,
@@ -912,15 +850,14 @@ class LibraryPresenter(
}
private fun getLibraryItems(
- dbCategories: List,
+ allCategories: List,
libraryManga: List,
sortingMode: Int,
isAscending: Boolean,
showAll: Boolean,
- collapsedCategories: Set,
defaultCategory: Category,
- ): Triple, List> {
- val categories = dbCategories.mapNotNull { if (it.id == null) null else it }.toMutableList()
+ ): Triple, List, List> {
+ val categories = allCategories.toMutableList()
val hiddenItems = mutableListOf()
val categoryAll = Category.createAll(
@@ -929,81 +866,85 @@ class LibraryPresenter(
isAscending,
)
val catItemAll = LibraryHeaderItem({ categoryAll }, -1)
-
- // NOTE: Don't call header.category, only header.catId
+ val categorySet = mutableSetOf()
val headerItems = (
- categories.map { category ->
- val id = category.id!!
- id to LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(id) }, id)
- } + (0 to LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(0) }, 0))
+ categories.mapNotNull { category ->
+ val id = category.id
+ if (id == null) {
+ null
+ } else {
+ id to LibraryHeaderItem({ categories.getOrDefault(id) }, id)
+ }
+ } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ categories.getOrDefault(0) }, 0))
).toMap()
+ val items = if (libraryIsGrouped) {
+ libraryManga
+ } else {
+ libraryManga.distinctBy { it.id }
+ }.mapNotNull {
+ val headerItem = (
+ if (!libraryIsGrouped) {
+ catItemAll
+ } else {
+ headerItems[it.category]
+ }
+ ) ?: return@mapNotNull null
+ categorySet.add(it.category)
+ LibraryItem(it, headerItem, viewContext)
+ }.toMutableList()
+
val categoriesHidden = if (forceShowAllCategories || controllerIsSubClass) {
emptySet()
} else {
- collapsedCategories.mapNotNull { it.toIntOrNull() }.toSet()
+ preferences.collapsedCategories().get().mapNotNull { it.toIntOrNull() }.toSet()
}
- val map = if (!libraryIsGrouped)
- libraryManga
- .asSequence()
- .distinctBy { it.manga.id }
- .map { LibraryMangaItem(it, catItemAll, viewContext) }
- .groupBy { categoryAll }
- else {
- val rt = libraryManga
- .asSequence()
- .mapNotNull {
- val headerItem = headerItems[it.category] ?: return@mapNotNull null
- LibraryMangaItem(it, headerItem, viewContext)
- }
- .groupBy { it.header.catId }
-
- // Only show default category when needed
- if (rt.containsKey(0)) categories.add(0, defaultCategory)
-
- // NOTE: Empty list means hide the category entirely
- categories
- .associateWith { rt[it.id].orEmpty() }
- .mapValues { (key, values) ->
- val catId = key.id!! // null check already handled by mapNotNull
- val headerItem = headerItems[catId]!! // null check already handled by mapNotNull
-
- // Hide category if "Show all categories" is enabled and there's more than 1 category
- if (catId in categoriesHidden && showAll && categories.size > 1) {
- val mergedTitle = values.joinToString("-") {
- it.manga.manga.title + "-" + it.manga.manga.author
- }
- libraryToDisplay[key] = values
- hiddenItems.addAll(values)
- return@mapValues listOf(
- LibraryPlaceholderItem.hidden(
- catId,
- headerItem,
- viewContext,
- mergedTitle,
- values,
- ),
+ if (categorySet.contains(0)) categories.add(0, defaultCategory)
+ if (libraryIsGrouped) {
+ categories.forEach { category ->
+ val catId = category.id ?: return@forEach
+ if (catId > 0 && !categorySet.contains(catId) &&
+ (catId !in categoriesHidden || !showAll)
+ ) {
+ val headerItem = headerItems[catId]
+ if (headerItem != null) {
+ items.add(
+ LibraryItem(LibraryManga.createBlank(catId), headerItem, viewContext),
)
}
-
- // Making sure empty category is shown properly
- values.ifEmpty {
- listOf(
- LibraryPlaceholderItem.blank(
- catId,
+ } else if (catId in categoriesHidden && showAll && categories.size > 1) {
+ val mangaToRemove = items.filter { it.manga.category == catId }
+ val mergedTitle = mangaToRemove.joinToString("-") {
+ it.manga.title + "-" + it.manga.author
+ }
+ sectionedLibraryItems[catId] = mangaToRemove
+ hiddenItems.addAll(mangaToRemove)
+ items.removeAll(mangaToRemove)
+ val headerItem = headerItems[catId]
+ if (headerItem != null) {
+ items.add(
+ LibraryItem(
+ LibraryManga.createHide(
+ catId,
+ mergedTitle,
+ mangaToRemove,
+ ),
headerItem,
viewContext,
),
)
}
}
- }.toMutableMap()
+ }
+ }
- categories.forEach { it.isHidden = it.id in categoriesHidden && showAll && categories.size > 1 }
+ categories.forEach {
+ it.isHidden = it.id in categoriesHidden && showAll && categories.size > 1
+ }
return Triple(
- map,
+ items,
if (!libraryIsGrouped) {
arrayListOf(categoryAll)
} else {
@@ -1018,14 +959,12 @@ class LibraryPresenter(
sortingMode: Int,
isAscending: Boolean,
groupType: Int,
- collapsedDynamicCategories: Set,
- ): Triple, List> {
+ ): Triple, List, List> {
val tagItems: MutableMap = mutableMapOf()
- val hiddenItems = mutableListOf()
// internal function to make headers
fun makeOrGetHeader(name: String, checkNameSwap: Boolean = false): LibraryHeaderItem {
- tagItems[name]?.let { return it }
+ tagItems.get(name)?.let { return it }
if (checkNameSwap && name.contains(" ")) {
val swappedName = name.split(" ").reversed().joinToString(" ")
if (tagItems.containsKey(swappedName)) {
@@ -1037,32 +976,26 @@ class LibraryPresenter(
return headerItem
}
- val hiddenDynamics = if (controllerIsSubClass) {
- emptySet()
- } else {
- collapsedDynamicCategories
- }
-
val unknown = context.getString(MR.strings.unknown)
- val items = libraryManga.distinctBy { it.manga.id }.map { manga ->
+ val items = libraryManga.distinctBy { it.id }.map { manga ->
when (groupType) {
BY_TAG -> {
- val tags = if (manga.manga.genre.isNullOrBlank()) {
+ val tags = if (manga.genre.isNullOrBlank()) {
listOf(unknown)
} else {
- manga.manga.genre?.split(",")?.mapNotNull {
+ manga.genre?.split(",")?.mapNotNull {
val tag = it.trim().capitalizeWords()
tag.ifBlank { null }
} ?: listOf(unknown)
}
tags.map {
- LibraryMangaItem(manga, makeOrGetHeader(it), viewContext)
+ LibraryItem(manga, makeOrGetHeader(it), viewContext)
}
}
BY_TRACK_STATUS -> {
- val tracks = getTrack.awaitAllByMangaId(manga.manga.id!!)
+ val tracks = getTrack.awaitAllByMangaId(manga.id!!)
val track = tracks.find { track ->
- loggedServices.any { it.id == track.sync_id }
+ loggedServices.any { it.id == track?.sync_id }
}
val service = loggedServices.find { it.id == track?.sync_id }
val status: String = if (track != null && service != null) {
@@ -1074,12 +1007,12 @@ class LibraryPresenter(
} else {
view?.view?.context?.getString(MR.strings.not_tracked) ?: ""
}
- listOf(LibraryMangaItem(manga, makeOrGetHeader(status), viewContext))
+ listOf(LibraryItem(manga, makeOrGetHeader(status), viewContext))
}
BY_SOURCE -> {
- val source = sourceManager.getOrStub(manga.manga.source)
+ val source = sourceManager.getOrStub(manga.source)
listOf(
- LibraryMangaItem(
+ LibraryItem(
manga,
makeOrGetHeader("${source.name}$sourceSplitter${source.id}"),
viewContext,
@@ -1087,26 +1020,26 @@ class LibraryPresenter(
)
}
BY_AUTHOR -> {
- if (manga.manga.artist.isNullOrBlank() && manga.manga.author.isNullOrBlank()) {
- listOf(LibraryMangaItem(manga, makeOrGetHeader(unknown), viewContext))
+ if (manga.artist.isNullOrBlank() && manga.author.isNullOrBlank()) {
+ listOf(LibraryItem(manga, makeOrGetHeader(unknown), viewContext))
} else {
listOfNotNull(
- manga.manga.author.takeUnless { it.isNullOrBlank() },
- manga.manga.artist.takeUnless { it.isNullOrBlank() },
+ manga.author.takeUnless { it.isNullOrBlank() },
+ manga.artist.takeUnless { it.isNullOrBlank() },
).map {
it.split(",", "/", " x ", " - ", ignoreCase = true).mapNotNull { name ->
val author = name.trim()
author.ifBlank { null }
}
}.flatten().distinct().map {
- LibraryMangaItem(manga, makeOrGetHeader(it, true), viewContext)
+ LibraryItem(manga, makeOrGetHeader(it, true), viewContext)
}
}
}
BY_LANGUAGE -> {
- val lang = getLanguage(manga.manga)
+ val lang = getLanguage(manga)
listOf(
- LibraryMangaItem(
+ LibraryItem(
manga,
makeOrGetHeader(
lang?.plus(langSplitter)?.plus(
@@ -1121,11 +1054,15 @@ class LibraryPresenter(
),
)
}
- // BY_STATUS
- else -> listOf(LibraryMangaItem(manga, makeOrGetHeader(context.mapStatus(manga.manga.status)), viewContext))
+ else -> listOf(LibraryItem(manga, makeOrGetHeader(context.mapStatus(manga.status)), viewContext)) // BY_STATUS
}
- }.flatten().groupBy { it.header.catId }
+ }.flatten().toMutableList()
+ val hiddenDynamics = if (controllerIsSubClass) {
+ emptySet()
+ } else {
+ preferences.collapsedDynamicCategories().get()
+ }
val headers = tagItems.map { item ->
Category.createCustom(
item.key,
@@ -1156,35 +1093,37 @@ class LibraryPresenter(
if (!preferences.collapsedDynamicAtBottom().get()) return@let headers
headers.filterNot { it.isHidden } + headers.filter { it.isHidden }
}
-
- val map = headers
- .associateWith { items[it.id].orEmpty() }
- .mapValues { (key, values) ->
- val catId = key.id!! // null check already handled by mapNotNull
- val headerItem = tagItems[key.dynamicHeaderKey()]
- if (key.isHidden) {
- val mergedTitle = values.joinToString("-") {
- it.manga.manga.title + "-" + it.manga.manga.author
- }
- libraryToDisplay[key] = values
- hiddenItems.addAll(values)
- if (headerItem != null) {
- return@mapValues listOf(
- LibraryPlaceholderItem.hidden(
- catId,
- headerItem,
- viewContext,
- mergedTitle,
- values,
- ),
- )
- }
+ headers.forEach { category ->
+ val catId = category.id ?: return@forEach
+ val headerItem =
+ tagItems[
+ when {
+ category.sourceId != null -> "${category.name}$sourceSplitter${category.sourceId}"
+ category.langId != null -> "${category.langId}$langSplitter${category.name}"
+ else -> category.name
+ },
+ ]
+ if (category.isHidden) {
+ val mangaToRemove = items.filter { it.header.catId == catId }
+ val mergedTitle = mangaToRemove.joinToString("-") {
+ it.manga.title + "-" + it.manga.author
+ }
+ sectionedLibraryItems[catId] = mangaToRemove
+ items.removeAll { it.header.catId == catId }
+ if (headerItem != null) {
+ items.add(
+ LibraryItem(
+ LibraryManga.createHide(catId, mergedTitle, mangaToRemove),
+ headerItem,
+ viewContext,
+ ),
+ )
}
- values
}
+ }
headers.forEachIndexed { index, category -> category.order = index }
- return Triple(map, headers, hiddenItems)
+ return Triple(items, headers, listOf())
}
private fun mapTrackingOrder(status: String): String {
@@ -1217,7 +1156,7 @@ class LibraryPresenter(
/** Requests the library to be filtered. */
fun requestFilterUpdate() {
presenterScope.launch {
- val mangaMap = currentLibrary
+ val mangaMap = allLibraryItems
.applyFilters()
.applySort()
sectionLibrary(mangaMap)
@@ -1226,11 +1165,11 @@ class LibraryPresenter(
private fun requestBadgeUpdate(badgeUpdate: (List) -> Unit) {
presenterScope.launch {
- val mangaMap = currentLibrary
- mangaMap.forEach { (_, items) -> badgeUpdate(items) }
- currentLibrary = mangaMap
- val current = libraryToDisplay
- current.forEach { (_, items) -> badgeUpdate(items) }
+ val mangaMap = allLibraryItems
+ badgeUpdate(mangaMap)
+ allLibraryItems = mangaMap
+ val current = libraryItems
+ badgeUpdate(current)
sectionLibrary(current)
}
}
@@ -1253,7 +1192,7 @@ class LibraryPresenter(
/** Requests the library to be sorted. */
private fun requestSortUpdate() {
presenterScope.launch {
- val mangaMap = libraryToDisplay
+ val mangaMap = libraryItems
.applySort()
sectionLibrary(mangaMap)
}
@@ -1278,6 +1217,7 @@ class LibraryPresenter(
.mapNotNull { if (it.id != null) MangaUpdate(it.id!!, favorite = false) else null }
withIOContext { updateManga.awaitAll(mangaToDelete) }
+ getLibrary()
}
}
@@ -1300,11 +1240,8 @@ class LibraryPresenter(
}
}
- /** Force update the library */
- fun updateLibrary() = presenterScope.launch {
- forceUpdateEvent.send(Unit)
- }
-
+ /** Called when Library Service updates a manga, update the item as well */
+ fun updateManga() = getLibrary()
/** Undo the removal of the manga once in library */
fun reAddMangas(mangas: List) {
@@ -1314,12 +1251,12 @@ class LibraryPresenter(
withIOContext { updateManga.awaitAll(mangaToAdd) }
(view as? FilteredLibraryController)?.updateStatsPage()
+ getLibrary()
}
}
/** Returns first unread chapter of a manga */
fun getFirstUnread(manga: Manga): Chapter? {
- // FIXME: Don't do blocking
val chapters = runBlocking { getChapter.awaitAll(manga) }
return ChapterSort(manga, chapterFilter, preferences).getNextUnreadChapter(chapters, false)
}
@@ -1371,6 +1308,7 @@ class LibraryPresenter(
}
}
+ // TODO: Use SQLDelight
/** Shift a manga's category via drag & drop */
fun moveMangaToCategory(
manga: LibraryManga,
@@ -1390,7 +1328,7 @@ class LibraryPresenter(
if (catId == 0) {
emptyList()
} else {
- getCategories.awaitByMangaId(manga.manga.id!!)
+ getCategories.awaitByMangaId(manga.id!!)
.filter { it.id != oldCatId } + listOf(category)
}
@@ -1398,11 +1336,11 @@ class LibraryPresenter(
mc.add(cat.id!!.toLong())
}
- setMangaCategories.await(manga.manga.id!!, mc)
+ setMangaCategories.await(manga.id!!, mc)
if (category.mangaSort == null) {
val ids = mangaIds.toMutableList()
- if (!ids.contains(manga.manga.id!!)) ids.add(manga.manga.id!!)
+ if (!ids.contains(manga.id!!)) ids.add(manga.id!!)
category.mangaOrder = ids
if (category.id == 0) {
preferences.defaultMangaOrder()
@@ -1416,14 +1354,14 @@ class LibraryPresenter(
)
}
}
- updateLibrary()
+ getLibrary()
}
}
/** Returns if manga is in a category by id */
fun mangaIsInCategory(manga: LibraryManga, catId: Int?): Boolean {
// FIXME: Don't do blocking
- val categories = runBlocking { getCategories.awaitByMangaId(manga.manga.id!!) }.map { it.id }
+ val categories = runBlocking { getCategories.awaitByMangaId(manga.id!!) }.map { it.id }
return catId in categories
}
@@ -1451,6 +1389,7 @@ class LibraryPresenter(
}
preferences.collapsedDynamicCategories().set(categoriesHidden)
}
+ getLibrary()
}
private fun getDynamicCategoryName(category: Category): String =
@@ -1481,6 +1420,7 @@ class LibraryPresenter(
}
}
}
+ getLibrary()
}
fun allCategoriesExpanded(): Boolean {
@@ -1522,7 +1462,7 @@ class LibraryPresenter(
mapMangaChapters[manga] = chapters
}
- updateLibrary()
+ getLibrary()
}
return mapMangaChapters
}
@@ -1538,7 +1478,7 @@ class LibraryPresenter(
}
}.flatten()
updateChapter.awaitAll(updates)
- updateLibrary()
+ getLibrary()
}
}
@@ -1563,9 +1503,11 @@ class LibraryPresenter(
}
companion object {
- private var lastDisplayedLibrary: LibraryMutableMap? = null
+ private var lastLibraryItems: List? = null
private var lastCategories: List? = null
- private var lastLibrary: LibraryMap? = null
+ private var lastAllLibraryItems: List? = null
+ private const val sourceSplitter = "◘•◘"
+ private const val langSplitter = "⨼⨦⨠"
private const val dynamicCategorySplitter = "▄╪\t▄╪\t▄"
private val randomTags = arrayOf(0, 1, 2)
@@ -1581,9 +1523,9 @@ class LibraryPresenter(
private const val randomGroupOfTagsNegate = 2
fun onLowMemory() {
- lastDisplayedLibrary = null
+ lastLibraryItems = null
lastCategories = null
- lastLibrary = null
+ lastAllLibraryItems = null
}
suspend fun setSearchSuggestion(
@@ -1603,15 +1545,15 @@ class LibraryPresenter(
preferences.librarySearchSuggestion().set(
when (val value = random.nextInt(0, 5)) {
randomSource -> {
- val distinctSources = getLibraryManga.await().distinctBy { it.manga.source }
+ val distinctSources = getLibraryManga.await().distinctBy { it.source }
val randomSource =
sourceManager.get(
- distinctSources.randomOrNull(random)?.manga?.source ?: 0L,
+ distinctSources.randomOrNull(random)?.source ?: 0L,
)?.name
randomSource?.chopByWords(30)
}
randomTitle -> {
- getLibraryManga.await().randomOrNull(random)?.manga?.title?.chopByWords(30)
+ getLibraryManga.await().randomOrNull(random)?.title?.chopByWords(30)
}
in randomTags -> {
val tags = RecentsPresenter.getRecentManga(true)
@@ -1655,11 +1597,11 @@ class LibraryPresenter(
) {
val libraryManga = getLibraryManga.await()
libraryManga.forEach { manga ->
- if (manga.manga.id == null) return@forEach
- if (manga.manga.date_added == 0L) {
- val chapters = getChapter.awaitAll(manga.manga.id!!, manga.manga.filtered_scanlators?.isNotBlank() == true)
- manga.manga.date_added = chapters.minByOrNull { it.date_fetch }?.date_fetch ?: 0L
- updateManga.await(MangaUpdate(manga.manga.id!!, dateAdded = manga.manga.date_added))
+ if (manga.id == null) return@forEach
+ if (manga.date_added == 0L) {
+ val chapters = getChapter.awaitAll(manga)
+ manga.date_added = chapters.minByOrNull { it.date_fetch }?.date_fetch ?: 0L
+ updateManga.await(MangaUpdate(manga.id!!, dateAdded = manga.date_added))
}
}
}
@@ -1681,15 +1623,15 @@ class LibraryPresenter(
val getLibraryManga: GetLibraryManga by injectLazy()
val libraryManga = getLibraryManga.await()
libraryManga.forEach { manga ->
- if (manga.manga.id == null) return@forEach
- if (manga.manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) {
- val file = cc.getCoverFile(manga.manga.thumbnail_url, !manga.manga.favorite)
+ if (manga.id == null) return@forEach
+ if (manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) {
+ val file = cc.getCoverFile(manga.thumbnail_url, !manga.favorite)
if (file != null && file.exists()) {
- file.renameTo(cc.getCustomCoverFile(manga.manga))
+ file.renameTo(cc.getCustomCoverFile(manga))
}
- manga.manga.thumbnail_url =
- manga.manga.thumbnail_url!!.lowercase(Locale.ROOT).substringAfter("custom-")
- updateManga.await(MangaUpdate(manga.manga.id!!, thumbnailUrl = manga.manga.thumbnail_url))
+ manga.thumbnail_url =
+ manga.thumbnail_url!!.lowercase(Locale.ROOT).substringAfter("custom-")
+ updateManga.await(MangaUpdate(manga.id!!, thumbnailUrl = manga.thumbnail_url))
}
}
}
@@ -1709,15 +1651,12 @@ class LibraryPresenter(
val sortingMode: Int,
val sortAscending: Boolean,
-
- val collapsedCategories: Set,
- val collapsedDynamicCategories: Set,
)
data class LibraryData(
val categories: List,
val allCategories: List,
- val items: LibraryMap,
+ val items: List,
val hiddenItems: List,
val removeArticles: Boolean,
)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
index 460906a65b..0d5ae1dd7d 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
@@ -1,10 +1,13 @@
package eu.kanade.tachiyomi.ui.library
import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
import yokai.i18n.MR
+import yokai.util.lang.getString
+import dev.icerock.moko.resources.compose.stringResource
+import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
enum class LibrarySort(
val mainValue: Int,
@@ -30,11 +33,7 @@ enum class LibrarySort(
MR.strings.category,
R.drawable.ic_label_outline_24dp,
),
- Random(
- 8,
- MR.strings.random,
- R.drawable.ic_shuffle_24dp,
- ),
+
;
val categoryValue: Char
@@ -51,7 +50,6 @@ enum class LibrarySort(
LatestChapter -> "LATEST_CHAPTER"
DateFetched -> "CHAPTER_FETCH_DATE"
DateAdded -> "DATE_ADDED"
- Random -> "RANDOM"
else -> "ALPHABETICAL"
}
return "$type,ASCENDING"
@@ -65,9 +63,6 @@ enum class LibrarySort(
val hasInvertedSort: Boolean
get() = this in listOf(LastRead, DateAdded, LatestChapter, DateFetched)
- val isDirectional: Boolean
- get() = this !in listOf(DragAndDrop, Random)
-
fun menuSheetItem(isDynamic: Boolean): MaterialMenuSheet.MenuSheetItem {
return MaterialMenuSheet.MenuSheetItem(
mainValue,
@@ -90,7 +85,6 @@ enum class LibrarySort(
"LATEST_CHAPTER" -> LatestChapter
"CHAPTER_FETCH_DATE" -> DateFetched
"DATE_ADDED" -> DateAdded
- "RANDOM" -> Random
else -> Title
}
} catch (e: Exception) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt
deleted file mode 100644
index ac75e11f02..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.compose
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
-import androidx.core.view.isGone
-import androidx.core.view.isVisible
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.databinding.LibraryControllerBinding
-import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController
-import eu.kanade.tachiyomi.ui.library.models.LibraryItem
-import eu.kanade.tachiyomi.ui.main.BottomSheetController
-import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
-import eu.kanade.tachiyomi.ui.main.RootSearchInterface
-import java.util.Locale
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import yokai.domain.ui.UiPreferences
-import yokai.i18n.MR
-import yokai.presentation.library.LibraryContent
-import yokai.presentation.theme.YokaiTheme
-import yokai.util.lang.getString
-
-class LibraryComposeController(
- bundle: Bundle? = null,
- val uiPreferences: UiPreferences = Injekt.get(),
- val preferences: PreferencesHelper = Injekt.get(),
-) : BaseCoroutineController(bundle) ,
- BottomSheetController,
- RootSearchInterface,
- FloatingSearchInterface {
-
- override fun getTitle(): String? {
- return view?.context?.getString(MR.strings.library)
- }
-
- override fun getSearchTitle(): String? {
- val searchSuggestion by lazy { preferences.librarySearchSuggestion().get() }
-
- return searchTitle(
- if (preferences.showLibrarySearchSuggestions().get() && searchSuggestion.isNotBlank()) {
- "\"$searchSuggestion\""
- } else {
- view?.context?.getString(MR.strings.your_library)?.lowercase(Locale.ROOT)
- },
- )
- }
-
- override val presenter = LibraryComposePresenter()
-
- override fun createBinding(inflater: LayoutInflater) = LibraryControllerBinding.inflate(inflater)
-
- override fun onViewCreated(view: View) {
- super.onViewCreated(view)
-
- binding.composeView.isVisible = true
- binding.swipeRefresh.isGone = true
- binding.fastScroller.isGone = true
-
- binding.composeView.setContent {
- YokaiTheme {
- ScreenContent()
- }
- }
- }
-
- @Composable
- fun ScreenContent() {
- val nestedScrollInterop = rememberNestedScrollInteropConnection()
-
- val state by presenter.state.collectAsState()
- LibraryContent(
- modifier = Modifier.nestedScroll(nestedScrollInterop),
- items = (0..50).map { LibraryItem.Blank(it) },
- columns = 3,
- )
- }
-
- override fun showSheet() {
- }
-
- override fun hideSheet() {
- }
-
- override fun toggleSheet() {
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt
deleted file mode 100644
index 03461eeee5..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.compose
-
-import eu.kanade.tachiyomi.data.database.models.Category
-import eu.kanade.tachiyomi.ui.base.presenter.StateCoroutinePresenter
-import eu.kanade.tachiyomi.ui.library.models.LibraryItem
-
-typealias LibraryMap = Map>
-
-class LibraryComposePresenter :
- StateCoroutinePresenter(State()) {
-
- data class State(
- var isLoading: Boolean = true,
- var library: LibraryMap = emptyMap()
- )
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt
index 0dc927b9e0..b409856977 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt
@@ -2,14 +2,16 @@ package eu.kanade.tachiyomi.ui.library.display
import android.content.Context
import android.util.AttributeSet
+import eu.kanade.tachiyomi.R
+import yokai.i18n.MR
+import yokai.util.lang.getString
+import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.databinding.LibraryCategoryLayoutBinding
import eu.kanade.tachiyomi.util.bindToPreference
import eu.kanade.tachiyomi.util.lang.withSubtitle
import eu.kanade.tachiyomi.util.system.toInt
import eu.kanade.tachiyomi.widget.BaseLibraryDisplayView
import kotlin.math.min
-import yokai.i18n.MR
-import yokai.util.lang.getString
class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
BaseLibraryDisplayView(context, attrs) {
@@ -18,7 +20,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
override fun initGeneralPreferences() {
with(binding) {
showAll.bindToPreference(preferences.showAllCategories()) {
- controller?.presenter?.updateLibrary()
+ controller?.presenter?.getLibrary()
binding.categoryShow.isEnabled = it
}
categoryShow.isEnabled = showAll.isChecked
@@ -28,7 +30,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
dynamicToBottom.text = context.getString(MR.strings.move_dynamic_to_bottom)
.withSubtitle(context, MR.strings.when_grouping_by_sources_tags)
dynamicToBottom.bindToPreference(preferences.collapsedDynamicAtBottom()) {
- controller?.presenter?.updateLibrary()
+ controller?.presenter?.getLibrary()
}
showEmptyCatsFiltering.bindToPreference(preferences.showEmptyCategoriesWhileFiltering()) {
controller?.presenter?.requestFilterUpdate()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt
index 94d55f515c..dbf17c3c49 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt
@@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.library.LibraryGroup
-import eu.kanade.tachiyomi.ui.library.LibraryMangaItem
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.launchUI
@@ -37,8 +36,6 @@ import eu.kanade.tachiyomi.util.view.isCollapsed
import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.isHidden
import eu.kanade.tachiyomi.util.view.setText
-import kotlin.math.max
-import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.drop
@@ -51,6 +48,8 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.i18n.MR
import yokai.util.lang.getString
+import kotlin.math.max
+import kotlin.math.roundToInt
class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs),
@@ -369,12 +368,11 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
suspend fun checkForManhwa(sourceManager: SourceManager) {
if (checked) return
withIOContext {
- val libraryManga = controller?.presenter?.currentLibraryItems ?: return@withIOContext
+ val libraryManga = controller?.presenter?.allLibraryItems ?: return@withIOContext
checked = true
var types = mutableSetOf()
libraryManga.forEach {
- if (it !is LibraryMangaItem) return@forEach
- when (it.manga.manga.seriesType(sourceManager = sourceManager)) {
+ when (it.manga.seriesType(sourceManager = sourceManager)) {
Manga.TYPE_MANHWA, Manga.TYPE_WEBTOON -> types.add(MR.strings.manhwa)
Manga.TYPE_MANHUA -> types.add(MR.strings.manhua)
Manga.TYPE_COMIC -> types.add(MR.strings.comic)
@@ -639,7 +637,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
SeriesType('m', MR.strings.series_type),
Bookmarked('b', MR.strings.bookmarked),
Tracked('t', MR.strings.tracking),
- ContentType('s', MR.strings.content_type)
+ ContentType('s', MR.strings.content_type);
;
companion object {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt
deleted file mode 100644
index a952dae416..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package eu.kanade.tachiyomi.ui.library.models
-
-import eu.kanade.tachiyomi.data.database.models.LibraryManga
-
-sealed interface LibraryItem {
- data class Blank(val mangaCount: Int = 0) : LibraryItem
- data class Hidden(val title: String, val hiddenItems: List) : LibraryItem
- data class Manga(
- val libraryManga: LibraryManga,
- val isLocal: Boolean = false,
- val downloadCount: Long = -1,
- val unreadCount: Long = -1,
- val language: String = "",
- ) : LibraryItem
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
index c68776b631..570db5e7ac 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
@@ -86,7 +86,6 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.controller.BaseLegacyController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.library.LibraryController
-import eu.kanade.tachiyomi.ui.library.compose.LibraryComposeController
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.more.AboutController
import eu.kanade.tachiyomi.ui.more.OverflowDialog
@@ -118,7 +117,6 @@ import eu.kanade.tachiyomi.util.system.prepareSideNavContext
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.tryTakePersistableUriPermission
-import eu.kanade.tachiyomi.util.system.withUIContext
import eu.kanade.tachiyomi.util.view.BackHandlerControllerInterface
import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.blurBehindWindow
@@ -138,10 +136,12 @@ import kotlin.collections.set
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.roundToLong
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy
import yokai.core.migration.Migrator
import yokai.domain.base.BasePreferences
@@ -198,7 +198,6 @@ open class MainActivity : BaseActivity() {
dimenW to dimenH
}
- @Deprecated("Create contract directly from Composable")
private val requestNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (!isGranted) {
@@ -539,7 +538,7 @@ open class MainActivity : BaseActivity() {
if (currentRoot?.tag()?.toIntOrNull() != id) {
setRoot(
when (id) {
- R.id.nav_library -> if (basePreferences.composeLibrary().get()) LibraryComposeController() else LibraryController()
+ R.id.nav_library -> LibraryController()
R.id.nav_recents -> RecentsController()
else -> BrowseController()
},
@@ -1002,7 +1001,7 @@ open class MainActivity : BaseActivity() {
}
private fun checkForAppUpdates() {
- if (isUpdaterEnabled && router.backstack.lastOrNull()?.controller !is AboutController) {
+ if (isUpdaterEnabled) {
lifecycleScope.launchIO {
try {
val result = updateChecker.checkForUpdate(this@MainActivity)
@@ -1012,7 +1011,7 @@ open class MainActivity : BaseActivity() {
val isBeta = result.release.preRelease == true
// Create confirmation window
- withUIContext {
+ withContext(Dispatchers.Main) {
showNotificationPermissionPrompt()
AppUpdateNotifier.releasePageUrl = result.release.releaseLink
AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router)
@@ -1038,7 +1037,6 @@ open class MainActivity : BaseActivity() {
@SuppressLint("MissingSuperCall")
override fun onNewIntent(intent: Intent) {
- splashState.ready = true
if (!handleIntentAction(intent)) {
super.onNewIntent(intent)
}
@@ -1055,13 +1053,13 @@ open class MainActivity : BaseActivity() {
}
when (intent.action) {
SHORTCUT_LIBRARY -> nav.selectedItemId = R.id.nav_library
- SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ, Constants.SHORTCUT_RECENTS -> {
+ SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ, SHORTCUT_RECENTS -> {
if (nav.selectedItemId != R.id.nav_recents) {
nav.selectedItemId = R.id.nav_recents
} else {
router.popToRoot()
}
- if (intent.action == Constants.SHORTCUT_RECENTS) return true
+ if (intent.action == SHORTCUT_RECENTS) return true
nav.post {
val controller =
router.backstack.firstOrNull()?.controller as? RecentsController
@@ -1094,11 +1092,7 @@ open class MainActivity : BaseActivity() {
SHORTCUT_UPDATE_NOTES -> {
val extras = intent.extras ?: return false
if (router.backstack.isEmpty()) nav.selectedItemId = R.id.nav_library
- if (
- router.backstack.lastOrNull()?.controller !is AboutController.NewUpdateDialogController &&
- // FIXME: Show Compose version of NewUpdateDialog for AboutController
- router.backstack.lastOrNull()?.controller !is AboutController
- ) {
+ if (router.backstack.lastOrNull()?.controller !is AboutController.NewUpdateDialogController) {
AboutController.NewUpdateDialogController(extras).showDialog(router)
}
}
@@ -1128,6 +1122,7 @@ open class MainActivity : BaseActivity() {
else -> return false
}
+ splashState.ready = true
return true
}
@@ -1614,12 +1609,20 @@ open class MainActivity : BaseActivity() {
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
+ const val MAIN_ACTIVITY = Constants.MAIN_ACTIVITY
+
// Shortcut actions
const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"
+ @Deprecated("Use the one from Constants object instead")
+ const val SHORTCUT_RECENTS = Constants.SHORTCUT_RECENTS
const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
const val SHORTCUT_RECENTLY_READ = "eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
const val SHORTCUT_BROWSE = "eu.kanade.tachiyomi.SHOW_BROWSE"
const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS"
+ @Deprecated("Use the one from Constants object instead")
+ const val SHORTCUT_MANGA = Constants.SHORTCUT_MANGA
+ @Deprecated("Use the one from Constants object instead")
+ const val SHORTCUT_MANGA_BACK = Constants.SHORTCUT_MANGA_BACK
const val SHORTCUT_UPDATE_NOTES = "eu.kanade.tachiyomi.SHOW_UPDATE_NOTES"
const val SHORTCUT_SOURCE = "eu.kanade.tachiyomi.SHOW_SOURCE"
const val SHORTCUT_READER_SETTINGS = "eu.kanade.tachiyomi.READER_SETTINGS"
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt
index 9a01c3873c..894a5468e7 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt
@@ -97,7 +97,7 @@ class SearchActivity : MainActivity() {
}
private fun intentShouldGoBack() =
- intent.action in listOf(Constants.SHORTCUT_MANGA, SHORTCUT_READER_SETTINGS, SHORTCUT_BROWSE)
+ intent.action in listOf(SHORTCUT_MANGA, SHORTCUT_READER_SETTINGS, SHORTCUT_BROWSE)
override fun syncActivityViewWithController(
to: Controller?,
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt
index f4dda11b1b..be1f17a67e 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt
@@ -43,8 +43,8 @@ import uy.kohesive.injekt.injectLazy
import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.models.cover
import yokai.i18n.MR
-import yokai.util.coil.asTarget
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.asTarget
+import yokai.presentation.core.util.coil.loadManga
import yokai.util.lang.getString
import android.R as AR
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt
index 1337881ee6..9b288c450c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt
@@ -806,8 +806,6 @@ class MangaDetailsController :
}
private fun getHeader(): MangaHeaderHolder? {
- if (!isBindingInitialized) return null
-
return if (isTablet) {
binding.tabletRecycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder
} else {
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 9e21f6c4b3..85e74a2599 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
@@ -47,7 +47,7 @@ import eu.kanade.tachiyomi.util.system.isInNightMode
import eu.kanade.tachiyomi.util.system.isLTR
import eu.kanade.tachiyomi.util.view.resetStrokeColor
import yokai.i18n.MR
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
import yokai.util.lang.getString
import android.R as AR
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt
index b8b6e8479e..5fcdb7a29b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt
@@ -18,8 +18,6 @@ import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.SearchOff
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.net.toUri
import androidx.core.view.WindowInsetsCompat.Type.systemBars
@@ -33,6 +31,7 @@ import com.google.android.material.datepicker.MaterialDatePicker
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import com.mikepenz.fastadapter.listeners.addClickListener
+import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
@@ -318,7 +317,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) :
if (results.isEmpty()) {
setMiddleTrackView(binding.searchEmptyView.id)
binding.searchEmptyView.show(
- Icons.Filled.SearchOff,
+ R.drawable.ic_search_off_24dp,
MR.strings.no_results_found,
)
} else {
@@ -339,7 +338,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) :
binding.trackSearchRecycler.isVisible = false
searchItemAdapter.clear()
binding.searchEmptyView.show(
- Icons.Filled.SearchOff,
+ R.drawable.ic_search_off_24dp,
error.message ?: "",
)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt
index d43c1dfc74..6732e191a5 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt
@@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.MangaListItemBinding
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.setCards
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
class MangaHolder(
view: View,
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt
index 392641714a..bb57bc541c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt
@@ -27,7 +27,7 @@ import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.models.cover
import yokai.i18n.MR
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
import yokai.util.lang.getString
class MigrationProcessHolder(
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
index f830d0ff81..5fd462890a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
@@ -1,41 +1,175 @@
package eu.kanade.tachiyomi.ui.more
import android.app.Dialog
-import android.content.Context
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Intent
import android.os.Build
import android.os.Bundle
-import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.view.View
import android.widget.TextView
-import androidx.compose.runtime.Composable
-import cafe.adriel.voyager.navigator.Navigator
-import cafe.adriel.voyager.transitions.CrossfadeTransition
+import androidx.core.content.getSystemService
+import androidx.core.net.toUri
+import androidx.preference.PreferenceScreen
+import co.touchlab.kermit.Logger
+import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob
-import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
+import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
+import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier
+import eu.kanade.tachiyomi.data.updater.AppUpdateResult
+import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
+import eu.kanade.tachiyomi.ui.setting.add
+import eu.kanade.tachiyomi.ui.setting.onClick
+import eu.kanade.tachiyomi.ui.setting.preference
+import eu.kanade.tachiyomi.ui.setting.preferenceCategory
+import eu.kanade.tachiyomi.ui.setting.titleMRes
+import eu.kanade.tachiyomi.util.CrashLogUtil
+import eu.kanade.tachiyomi.util.lang.toTimestampString
+import eu.kanade.tachiyomi.util.system.isOnline
+import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.materialAlertDialog
+import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setNegativeButton
import eu.kanade.tachiyomi.util.view.setPositiveButton
import eu.kanade.tachiyomi.util.view.setTitle
+import eu.kanade.tachiyomi.util.view.snack
+import eu.kanade.tachiyomi.util.view.withFadeTransaction
import io.noties.markwon.Markwon
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import yokai.i18n.MR
-import yokai.presentation.settings.screen.about.AboutScreen
+import yokai.util.lang.getString
+import java.text.DateFormat
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
import android.R as AR
-class AboutController : BaseComposeController() {
+class AboutController : SettingsLegacyController() {
- @Composable
- override fun ScreenContent() {
- Navigator(
- screen = AboutScreen(),
- content = {
- CrossfadeTransition(navigator = it)
- },
- )
+ /**
+ * Checks for new releases
+ */
+ private val updateChecker by lazy { AppUpdateChecker() }
+
+ private val dateFormat: DateFormat by lazy {
+ preferences.dateFormat()
+ }
+
+ private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER
+
+ override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
+ titleMRes = MR.strings.about
+
+ preference {
+ key = "pref_whats_new"
+ titleMRes = MR.strings.whats_new_this_release
+ onClick {
+ val intent = Intent(
+ Intent.ACTION_VIEW,
+ if (BuildConfig.DEBUG) {
+ "https://github.com/null2264/yokai/commits/master"
+ } else {
+ RELEASE_URL
+ }.toUri(),
+ )
+ startActivity(intent)
+ }
+ }
+ if (isUpdaterEnabled) {
+ preference {
+ key = "pref_check_for_updates"
+ titleMRes = MR.strings.check_for_updates
+ onClick {
+ if (activity!!.isOnline()) {
+ checkVersion()
+ } else {
+ activity!!.toast(MR.strings.no_network_connection)
+ }
+ }
+ }
+ }
+ preference {
+ key = "pref_version"
+ titleMRes = MR.strings.version
+ summary = if (BuildConfig.DEBUG || BuildConfig.NIGHTLY) {
+ "r" + BuildConfig.COMMIT_COUNT
+ } else {
+ BuildConfig.VERSION_NAME
+ }
+
+ onClick {
+ activity?.let {
+ val deviceInfo = CrashLogUtil(it.localeContext).getDebugInfo()
+ val clipboard = it.getSystemService()!!
+ val appInfo = it.getString(MR.strings.app_info)
+ clipboard.setPrimaryClip(ClipData.newPlainText(appInfo, deviceInfo))
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ view?.snack(context.getString(MR.strings._copied_to_clipboard, appInfo))
+ }
+ }
+ }
+ }
+ preference {
+ key = "pref_build_time"
+ titleMRes = MR.strings.build_time
+ summary = getFormattedBuildTime(dateFormat)
+ }
+
+ preferenceCategory {
+ preference {
+ key = "pref_oss"
+ titleMRes = MR.strings.open_source_licenses
+
+ onClick {
+ router.pushController(AboutLicenseController().withFadeTransaction())
+ }
+ }
+ }
+ add(AboutLinksPreference(context))
+ }
+
+ /**
+ * Checks version and shows a user prompt if an update is available.
+ */
+ private fun checkVersion() {
+ val activity = activity ?: return
+
+ activity.toast(MR.strings.searching_for_updates)
+ viewScope.launch {
+ val result = try {
+ updateChecker.checkForUpdate(activity, true)
+ } catch (error: Exception) {
+ withContext(Dispatchers.Main) {
+ activity.toast(error.message)
+ Logger.e(error) { "Couldn't check new update" }
+ }
+ }
+ when (result) {
+ is AppUpdateResult.NewUpdate -> {
+ val body = result.release.info
+ val url = result.release.downloadLink
+ val isBeta = result.release.preRelease == true
+
+ // Create confirmation window
+ withContext(Dispatchers.Main) {
+ AppUpdateNotifier.releasePageUrl = result.release.releaseLink
+ NewUpdateDialogController(body, url, isBeta).showDialog(router)
+ }
+ }
+ is AppUpdateResult.NoNewUpdate -> {
+ withContext(Dispatchers.Main) {
+ activity.toast(MR.strings.no_new_updates_available)
+ }
+ }
+ }
+ }
}
- @Deprecated("Use [DialogHostState.showNewUpdateDialog] instead", ReplaceWith("DialogHostState.showNewUpdateDialog()"))
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
constructor(body: String, url: String, isBeta: Boolean?) : this(
@@ -47,7 +181,9 @@ class AboutController : BaseComposeController() {
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
- val info = activity!!.parseReleaseNotes(args.getString(BODY_KEY) ?: "")
+ val releaseBody = (args.getString(BODY_KEY) ?: "")
+ .replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "")
+ val info = Markwon.create(activity!!).toMarkdown(releaseBody)
val isOnA12 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val isBeta = args.getBoolean(IS_BETA, false)
@@ -84,9 +220,19 @@ class AboutController : BaseComposeController() {
const val IS_BETA = "NewUpdateDialogController.is_beta"
}
}
-}
-fun Context.parseReleaseNotes(releaseNotes: String): Spanned {
- val releaseBody = releaseNotes.replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "")
- return Markwon.create(this).toMarkdown(releaseBody)
+ companion object {
+ fun getFormattedBuildTime(dateFormat: DateFormat): String {
+ try {
+ val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault())
+ inputDf.timeZone = TimeZone.getTimeZone("UTC")
+ val buildTime =
+ inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME
+
+ return buildTime.toTimestampString(dateFormat)
+ } catch (e: ParseException) {
+ return BuildConfig.BUILD_TIME
+ }
+ }
+ }
}
diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutLibraryLicenseScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLibraryLicenseScreen.kt
similarity index 98%
rename from app/src/main/java/yokai/presentation/settings/screen/about/AboutLibraryLicenseScreen.kt
rename to app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLibraryLicenseScreen.kt
index 7ed11d10d8..11b2e7c66d 100644
--- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutLibraryLicenseScreen.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLibraryLicenseScreen.kt
@@ -1,4 +1,4 @@
-package yokai.presentation.settings.screen.about
+package eu.kanade.tachiyomi.ui.more
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt
new file mode 100644
index 0000000000..6a2ea78a8e
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt
@@ -0,0 +1,27 @@
+package eu.kanade.tachiyomi.ui.more
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import cafe.adriel.voyager.core.stack.StackEvent
+import cafe.adriel.voyager.navigator.Navigator
+import cafe.adriel.voyager.transitions.ScreenTransition
+import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
+import eu.kanade.tachiyomi.util.compose.LocalBackPress
+import soup.compose.material.motion.animation.materialSharedAxisZ
+
+class AboutLicenseController : BaseComposeController() {
+ @Composable
+ override fun ScreenContent() {
+ Navigator(
+ screen = AboutLicenseScreen(),
+ content = {
+ CompositionLocalProvider(LocalBackPress provides router::handleBack) {
+ ScreenTransition(
+ navigator = it,
+ transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) },
+ )
+ }
+ },
+ )
+ }
+}
diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutLicenseScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseScreen.kt
similarity index 97%
rename from app/src/main/java/yokai/presentation/settings/screen/about/AboutLicenseScreen.kt
rename to app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseScreen.kt
index 6d4ca8be2a..c98bc48cfa 100644
--- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutLicenseScreen.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseScreen.kt
@@ -1,4 +1,4 @@
-package yokai.presentation.settings.screen.about
+package eu.kanade.tachiyomi.ui.more
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.TopAppBarDefaults
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt
new file mode 100644
index 0000000000..df08c5852d
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt
@@ -0,0 +1,46 @@
+package eu.kanade.tachiyomi.ui.more
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.util.system.openInBrowser
+import eu.kanade.tachiyomi.util.view.compatToolTipText
+
+class AboutLinksPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ Preference(context, attrs) {
+
+ init {
+ layoutResource = R.layout.pref_about_links
+ isSelectable = false
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ /*
+ (holder.itemView as LinearLayout).apply {
+ checkHeightThen {
+ val childCount = (this.getChildAt(0) as ViewGroup).childCount
+ val childCount2 = (this.getChildAt(1) as ViewGroup).childCount
+ val fullCount = childCount + childCount2
+ orientation =
+ if (width >= (56 * fullCount).dpToPx) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
+ }
+ }
+ */
+ holder.findViewById(R.id.btn_website).apply {
+ compatToolTipText = (contentDescription.toString())
+ setOnClickListener { context.openInBrowser("https://mihon.app") }
+ }
+ holder.findViewById(R.id.btn_discord).apply {
+ compatToolTipText = (contentDescription.toString())
+ setOnClickListener { context.openInBrowser("https://discord.gg/mihon") }
+ }
+ holder.findViewById(R.id.btn_github).apply {
+ compatToolTipText = (contentDescription.toString())
+ setOnClickListener { context.openInBrowser("https://github.com/null2264/yokai") }
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt
index 113df828cd..054669930b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt
@@ -28,9 +28,9 @@ import eu.kanade.tachiyomi.util.system.roundToTwoDecimal
import eu.kanade.tachiyomi.util.view.compatToolTipText
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.withFadeTransaction
-import kotlin.math.roundToInt
import yokai.i18n.MR
import yokai.util.lang.getString
+import kotlin.math.roundToInt
import android.R as AR
class StatsController : BaseLegacyController() {
@@ -61,7 +61,7 @@ class StatsController : BaseLegacyController() {
}
private fun handleGeneralStats() {
- val mangaTracks = mangaDistinct.map { it to presenter.getTracks(it.manga) }
+ val mangaTracks = mangaDistinct.map { it to presenter.getTracks(it) }
scoresList = getScoresList(mangaTracks)
with(binding) {
viewDetailLayout.isVisible = mangaDistinct.isNotEmpty()
@@ -76,8 +76,8 @@ class StatsController : BaseLegacyController() {
}
statsTrackedMangaText.text = mangaTracks.count { it.second.isNotEmpty() }.toString()
statsChaptersDownloadedText.text = mangaDistinct.sumOf { presenter.getDownloadCount(it) }.toString()
- statsTotalTagsText.text = mangaDistinct.flatMap { it.manga.getTags() }.distinct().count().toString()
- statsMangaLocalText.text = mangaDistinct.count { it.manga.isLocal() }.toString()
+ statsTotalTagsText.text = mangaDistinct.flatMap { it.getTags() }.distinct().count().toString()
+ statsMangaLocalText.text = mangaDistinct.count { it.isLocal() }.toString()
statsGlobalUpdateMangaText.text = presenter.getGlobalUpdateManga().count().toString()
statsSourcesText.text = presenter.getSources().count().toString()
statsTrackersText.text = presenter.getLoggedTrackers().count().toString()
@@ -105,7 +105,7 @@ class StatsController : BaseLegacyController() {
val pieEntries = ArrayList()
val mangaStatusDistributionList = statusMap.mapNotNull { (status, color) ->
- val libraryCount = mangaDistinct.count { it.manga.status == status }
+ val libraryCount = mangaDistinct.count { it.status == status }
if (status == SManga.UNKNOWN && libraryCount == 0) return@mapNotNull null
pieEntries.add(PieEntry(libraryCount.toFloat(), activity!!.mapStatus(status)))
StatusDistributionItem(activity!!.mapStatus(status), libraryCount, color)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt
index 6d7c2f3dc2..71cb38068c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt
@@ -65,19 +65,19 @@ class StatsPresenter(
val includedCategories = prefs.libraryUpdateCategories().get().map(String::toInt)
val excludedCategories = prefs.libraryUpdateCategoriesExclude().get().map(String::toInt)
val restrictions = prefs.libraryUpdateMangaRestriction().get()
- return libraryMangas.groupBy { it.manga.id }
+ return libraryMangas.groupBy { it.id }
.filterNot { it.value.any { manga -> manga.category in excludedCategories } }
.filter { includedCategories.isEmpty() || it.value.any { manga -> manga.category in includedCategories } }
.filterNot {
val manga = it.value.first()
- (MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED) ||
+ (MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED) ||
(MANGA_HAS_UNREAD in restrictions && manga.unread != 0) ||
(MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead)
}
}
fun getDownloadCount(manga: LibraryManga): Int {
- return downloadManager.getDownloadCount(manga.manga)
+ return downloadManager.getDownloadCount(manga)
}
fun get10PointScore(track: Track): Float? {
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 c9d366fb39..7636389cee 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
@@ -12,8 +12,6 @@ import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.HeartBroken
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.graphics.ColorUtils
import androidx.core.util.Pair
@@ -460,10 +458,7 @@ class StatsDetailsController :
with(binding ?: headerBinding) {
val hasNoData = currentStats.isNullOrEmpty() || currentStats.all { it.count == 0 }
if (hasNoData) {
- this@StatsDetailsController.binding.noChartData.show(
- Icons.Filled.HeartBroken,
- MR.strings.no_data_for_filters,
- )
+ this@StatsDetailsController.binding.noChartData.show(R.drawable.ic_heart_off_24dp, MR.strings.no_data_for_filters)
presenter.currentStats?.removeAll { it.count == 0 }
handleNoChartLayout()
this?.statsPieChart?.isVisible = false
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 2dc2160d22..d47536ffb9 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
@@ -153,7 +153,7 @@ class StatsDetailsPresenter(
private suspend fun setupSeriesType() {
currentStats = ArrayList()
- val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.seriesType() }
+ val libraryFormat = mangasDistinct.filterByChip().groupBy { it.seriesType() }
libraryFormat.forEach { (seriesType, mangaList) ->
currentStats?.add(
@@ -173,7 +173,7 @@ class StatsDetailsPresenter(
private suspend fun setupStatus() {
currentStats = ArrayList()
- val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.status }
+ val libraryFormat = mangasDistinct.filterByChip().groupBy { it.status }
libraryFormat.forEach { (status, mangaList) ->
currentStats?.add(
@@ -263,7 +263,7 @@ class StatsDetailsPresenter(
private suspend fun setupTrackers() {
currentStats = ArrayList()
val libraryFormat = mangasDistinct.filterByChip()
- .map { it to getTracks(it.manga).ifEmpty { listOf(null) } }
+ .map { it to getTracks(it).ifEmpty { listOf(null) } }
.flatMap { it.second.map { track -> it.first to track } }
val loggedServices = trackManager.services.filter { it.isLogged }
@@ -292,7 +292,7 @@ class StatsDetailsPresenter(
private suspend fun setupSources() {
currentStats = ArrayList()
- val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.source }
+ val libraryFormat = mangasDistinct.filterByChip().groupBy { it.source }
libraryFormat.forEach { (sourceId, mangaList) ->
val source = sourceManager.getOrStub(sourceId)
@@ -339,10 +339,10 @@ class StatsDetailsPresenter(
private suspend fun setupTags() {
currentStats = ArrayList()
val mangaFiltered = mangasDistinct.filterByChip()
- val tags = mangaFiltered.flatMap { it.manga.getTags() }.distinctBy { it.uppercase() }
+ val tags = mangaFiltered.flatMap { it.getTags() }.distinctBy { it.uppercase() }
val libraryFormat = tags.map { tag ->
tag to mangaFiltered.filter {
- it.manga.getTags().any { mangaTag -> mangaTag.equals(tag, true) }
+ it.getTags().any { mangaTag -> mangaTag.equals(tag, true) }
}
}
@@ -433,7 +433,7 @@ class StatsDetailsPresenter(
this
} else {
filter { manga ->
- context.mapSeriesType(manga.manga.seriesType()) in selectedSeriesType
+ context.mapSeriesType(manga.seriesType()) in selectedSeriesType
}
}
}
@@ -443,7 +443,7 @@ class StatsDetailsPresenter(
this
} else {
filter { manga ->
- context.mapStatus(manga.manga.status) in selectedStatus
+ context.mapStatus(manga.status) in selectedStatus
}
}
}
@@ -463,7 +463,7 @@ class StatsDetailsPresenter(
this
} else {
filter { manga ->
- manga.manga.source in selectedSource.map { it.id }
+ manga.source in selectedSource.map { it.id }
}
}
}
@@ -504,10 +504,10 @@ class StatsDetailsPresenter(
* Get language name of a manga
*/
private fun LibraryManga.getLanguage(): String {
- val code = if (manga.isLocal()) {
- LocalSource.getMangaLang(this.manga)
+ val code = if (isLocal()) {
+ LocalSource.getMangaLang(this)
} else {
- sourceManager.get(manga.source)?.lang
+ sourceManager.get(source)?.lang
} ?: return context.getString(MR.strings.unknown)
return LocaleHelper.getLocalizedDisplayName(code)
}
@@ -516,7 +516,7 @@ class StatsDetailsPresenter(
* Get mean score rounded to two decimal of a list of manga
*/
private suspend fun List.getMeanScoreRounded(): Double? {
- val mangaTracks = this.map { it to getTracks(it.manga) }
+ val mangaTracks = this.map { it to getTracks(it) }
val scoresList = mangaTracks.filter { it.second.isNotEmpty() }
.mapNotNull { it.second.getMeanScoreByTracker() }
return if (scoresList.isEmpty()) null else scoresList.average().roundToTwoDecimal()
@@ -526,7 +526,7 @@ class StatsDetailsPresenter(
* Get mean score rounded to int of a single manga
*/
private suspend fun LibraryManga.getMeanScoreToInt(): Int? {
- val mangaTracks = getTracks(this.manga)
+ val mangaTracks = getTracks(this)
val scoresList = mangaTracks.filter { it.score > 0 }
.mapNotNull { it.get10PointScore() }
return if (scoresList.isEmpty()) null else scoresList.average().roundToInt().coerceIn(1..10)
@@ -550,8 +550,8 @@ class StatsDetailsPresenter(
}
private suspend fun LibraryManga.getStartYear(): Int? {
- if (getChapter.awaitAll(manga.id!!, false).any { it.read }) {
- val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 }
+ if (getChapter.awaitAll(id!!, false).any { it.read }) {
+ val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 }
val date = chapters.minOfOrNull { it.last_read } ?: return null
val cal = Calendar.getInstance().apply { timeInMillis = date }
return if (date <= 0L) null else cal.get(Calendar.YEAR)
@@ -564,7 +564,7 @@ class StatsDetailsPresenter(
}
private fun getEnabledSources(): List {
- return mangasDistinct.mapNotNull { sourceManager.get(it.manga.source) }
+ return mangasDistinct.mapNotNull { sourceManager.get(it.source) }
.distinct().sortedBy { it.name }
}
@@ -589,7 +589,7 @@ class StatsDetailsPresenter(
}
private suspend fun List.getReadDuration(): Long {
- return sumOf { manga -> getHistory.awaitAllByMangaId(manga.manga.id!!).sumOf { it.time_read } }
+ return sumOf { manga -> getHistory.awaitAllByMangaId(manga.id!!).sumOf { it.time_read } }
}
/**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
index eb45718063..da7f99e2b8 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
@@ -148,14 +148,6 @@ import eu.kanade.tachiyomi.util.view.setMessage
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.doOnEnd
import eu.kanade.tachiyomi.widget.doOnStart
-import java.io.ByteArrayOutputStream
-import java.text.DecimalFormat
-import java.text.DecimalFormatSymbols
-import java.util.Collections
-import java.util.Locale
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -175,6 +167,13 @@ import yokai.domain.ui.settings.ReaderPreferences
import yokai.domain.ui.settings.ReaderPreferences.LandscapeCutoutBehaviour
import yokai.i18n.MR
import yokai.util.lang.getString
+import java.io.ByteArrayOutputStream
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+import java.util.*
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.roundToInt
import android.R as AR
/**
@@ -513,6 +512,7 @@ class ReaderActivity : BaseActivity() {
}
}
}
+ viewModel.onSaveInstanceState()
super.onSaveInstanceState(outState)
}
@@ -1304,13 +1304,13 @@ class ReaderActivity : BaseActivity() {
}
override fun onPause() {
- viewModel.flushReadTimer()
+ viewModel.saveCurrentChapterReadingProgress()
super.onPause()
}
override fun onResume() {
super.onResume()
- viewModel.restartReadTimer()
+ viewModel.setReadStartTime()
}
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
@@ -1655,7 +1655,7 @@ class ReaderActivity : BaseActivity() {
}
private fun showSetCoverPrompt(page: ReaderPage) {
- if (page.status !is Page.State.Ready) return
+ if (page.status != Page.State.READY) return
materialAlertDialog()
.setMessage(MR.strings.use_image_as_cover)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
index 5097eb1809..cf0a856541 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
@@ -12,7 +12,6 @@ import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.History
-import eu.kanade.tachiyomi.data.database.models.create
import eu.kanade.tachiyomi.data.database.models.defaultReaderType
import eu.kanade.tachiyomi.data.database.models.orientationType
import eu.kanade.tachiyomi.data.database.models.readingModeType
@@ -55,7 +54,9 @@ import eu.kanade.tachiyomi.util.system.withIOContext
import eu.kanade.tachiyomi.util.system.withUIContext
import java.util.Date
import java.util.concurrent.CancellationException
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -67,7 +68,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -80,7 +80,6 @@ import yokai.domain.chapter.models.ChapterUpdate
import yokai.domain.download.DownloadPreferences
import yokai.domain.history.interactor.GetHistory
import yokai.domain.history.interactor.UpsertHistory
-import yokai.domain.library.LibraryPreferences
import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga
import yokai.domain.manga.interactor.UpdateManga
@@ -102,7 +101,6 @@ class ReaderViewModel(
private val chapterFilter: ChapterFilter = Injekt.get(),
private val storageManager: StorageManager = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
- private val libraryPreferences: LibraryPreferences = Injekt.get(),
) : ViewModel() {
private val getCategories: GetCategories by injectLazy()
private val getChapter: GetChapter by injectLazy()
@@ -157,15 +155,12 @@ class ReaderViewModel(
private var finished = false
private var chapterToDownload: Download? = null
- private val unfilteredChapterList by lazy {
- val manga = manga!!
- runBlocking { getChapter.awaitAll(manga, filterScanlators = false) }
- }
-
private lateinit var chapterList: List
private var chapterItems = emptyList()
+ private var scope = CoroutineScope(Job() + Dispatchers.Default)
+
private var hasTrackers: Boolean = false
private suspend fun checkTrackers(manga: Manga) = getTrack.awaitAllByMangaId(manga.id).isNotEmpty()
@@ -196,12 +191,24 @@ class ReaderViewModel(
val currentChapters = state.value.viewerChapters
if (currentChapters != null) {
currentChapters.unref()
+ saveReadingProgress(currentChapters.currChapter)
chapterToDownload?.let {
downloadManager.addDownloadsToStartOfQueue(listOf(it))
}
}
}
+ /**
+ * Called when the activity is saved and not changing configurations. It updates the database
+ * to persist the current progress of the active chapter.
+ */
+ fun onSaveInstanceState() {
+ val currentChapter = getCurrentChapter() ?: return
+ viewModelScope.launchNonCancellableIO {
+ saveChapterProgress(currentChapter)
+ }
+ }
+
/**
* Whether this presenter is initialized yet.
*/
@@ -300,7 +307,6 @@ class ReaderViewModel(
return delegatedSource.pageNumber(url)?.minus(1)
}
- // FIXME: Unused at the moment, handles J2K's delegated deep link, refactor or remove later
suspend fun loadChapterURL(url: Uri) {
val host = url.host ?: return
val context = Injekt.get()
@@ -308,7 +314,9 @@ class ReaderViewModel(
context.getString(MR.strings.source_not_installed),
)
val chapterUrl = delegatedSource.chapterUrl(url)
- val sourceId = delegatedSource.delegate.id
+ val sourceId = delegatedSource.delegate?.id ?: error(
+ context.getString(MR.strings.source_not_installed),
+ )
if (chapterUrl != null) {
val dbChapter = getChapter.awaitAllByUrl(chapterUrl, false).find {
val source = getManga.awaitById(it.manga_id!!)?.source ?: return@find false
@@ -326,9 +334,7 @@ class ReaderViewModel(
}
val info = delegatedSource.fetchMangaFromChapterUrl(url)
if (info != null) {
- val (sChapter, sManga, chapters) = info
- val manga = Manga.create(sManga.url, sManga.title, sourceId).apply { copyFrom(sManga) }
- val chapter = Chapter.create().apply { copyFrom(sChapter) }
+ val (chapter, manga, chapters) = info
val id = insertManga.await(manga)
manga.id = id ?: manga.id
chapter.manga_id = manga.id
@@ -367,15 +373,12 @@ class ReaderViewModel(
* Called when the user changed to the given [chapter] when changing pages from the viewer.
* It's used only to set this chapter as active.
*/
- private fun loadNewChapter(chapter: ReaderChapter) {
+ private suspend fun loadNewChapter(chapter: ReaderChapter) {
val loader = loader ?: return
- viewModelScope.launchIO {
- Logger.d { "Loading ${chapter.chapter.url}" }
-
- flushReadTimer()
- restartReadTimer()
+ Logger.d { "Loading ${chapter.chapter.url}" }
+ withIOContext {
try {
loadChapter(loader, chapter)
} catch (e: Throwable) {
@@ -506,15 +509,28 @@ class ReaderViewModel(
val selectedChapter = page.chapter
// Save last page read and mark as read if needed
- viewModelScope.launchNonCancellableIO {
- saveChapterProgress(selectedChapter, page, hasExtraPage)
+ selectedChapter.chapter.last_page_read = page.index
+ selectedChapter.chapter.pages_left =
+ (selectedChapter.pages?.size ?: page.index) - page.index
+ val shouldTrack = !preferences.incognitoMode().get() || hasTrackers
+ if (shouldTrack &&
+ // For double pages, check if the second to last page is doubled up
+ (
+ (selectedChapter.pages?.lastIndex == page.index && page.firstHalf != true) ||
+ (hasExtraPage && selectedChapter.pages?.lastIndex?.minus(1) == page.index)
+ )
+ ) {
+ selectedChapter.chapter.read = true
+ updateTrackChapterAfterReading(selectedChapter)
+ deleteChapterIfNeeded(selectedChapter)
}
if (selectedChapter != currentChapters.currChapter) {
Logger.d { "Setting ${selectedChapter.chapter.url} as active" }
- loadNewChapter(selectedChapter)
+ saveReadingProgress(currentChapters.currChapter)
+ setReadStartTime()
+ scope.launch { loadNewChapter(selectedChapter) }
}
-
val pages = page.chapter.pages ?: return
val inDownloadRange = page.number.toDouble() / pages.size > 0.2
if (inDownloadRange) {
@@ -602,28 +618,28 @@ class ReaderViewModel(
}
}
+ /**
+ * Called when reader chapter is changed in reader or when activity is paused.
+ */
+ private fun saveReadingProgress(readerChapter: ReaderChapter) {
+ viewModelScope.launchNonCancellableIO {
+ saveChapterProgress(readerChapter)
+ saveChapterHistory(readerChapter)
+ }
+ }
+
+ fun saveCurrentChapterReadingProgress() = getCurrentChapter()?.let { saveReadingProgress(it) }
+
/**
* Saves this [readerChapter]'s progress (last read page and whether it's read).
* If incognito mode isn't on or has at least 1 tracker
*/
- private suspend fun saveChapterProgress(readerChapter: ReaderChapter, page: ReaderPage, hasExtraPage: Boolean) {
+ private suspend fun saveChapterProgress(readerChapter: ReaderChapter) {
readerChapter.requestedPage = readerChapter.chapter.last_page_read
getChapter.awaitById(readerChapter.chapter.id!!)?.let { dbChapter ->
readerChapter.chapter.bookmark = dbChapter.bookmark
}
-
- val shouldTrack = !preferences.incognitoMode().get() || hasTrackers
- if (shouldTrack && page.status !is Page.State.Error) {
- readerChapter.chapter.last_page_read = page.index
- readerChapter.chapter.pages_left = (readerChapter.pages?.size ?: page.index) - page.index
- // For double pages, check if the second to last page is doubled up
- if (
- (readerChapter.pages?.lastIndex == page.index && page.firstHalf != true) ||
- (hasExtraPage && readerChapter.pages?.lastIndex?.minus(1) == page.index)
- ) {
- onChapterReadComplete(readerChapter)
- }
-
+ if (!preferences.incognitoMode().get() || hasTrackers) {
updateChapter.await(
ChapterUpdate(
id = readerChapter.chapter.id!!,
@@ -636,56 +652,24 @@ class ReaderViewModel(
}
}
- private suspend fun onChapterReadComplete(readerChapter: ReaderChapter) {
- readerChapter.chapter.read = true
- updateTrackChapterAfterReading(readerChapter)
- deleteChapterIfNeeded(readerChapter)
-
- val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
- .contains(LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING)
- if (!markDuplicateAsRead) return
-
- val duplicateUnreadChapters = unfilteredChapterList
- .mapNotNull { chapter ->
- if (
- !chapter.read &&
- chapter.isRecognizedNumber &&
- chapter.chapter_number == readerChapter.chapter.chapter_number
- ) {
- ChapterUpdate(id = chapter.id!!, read = true)
- } else {
- null
- }
- }
- updateChapter.awaitAll(duplicateUnreadChapters)
- }
-
- fun restartReadTimer() {
- chapterReadStartTime = Date().time
- }
-
- fun flushReadTimer() {
- getCurrentChapter()?.let {
- viewModelScope.launchNonCancellableIO {
- saveChapterHistory(it)
- }
- }
- }
-
/**
* Saves this [readerChapter] last read history.
*/
private suspend fun saveChapterHistory(readerChapter: ReaderChapter) {
- if (preferences.incognitoMode().get()) return
-
- val endTime = Date().time
- val sessionReadDuration = chapterReadStartTime?.let { endTime - it } ?: 0
- val history = History.create(readerChapter.chapter).apply {
- last_read = endTime
- time_read = sessionReadDuration
+ if (!preferences.incognitoMode().get()) {
+ val readAt = Date().time
+ val sessionReadDuration = chapterReadStartTime?.let { readAt - it } ?: 0
+ val history = History.create(readerChapter.chapter).apply {
+ last_read = readAt
+ time_read = sessionReadDuration
+ }
+ upsertHistory.await(history)
+ chapterReadStartTime = null
}
- upsertHistory.await(history)
- chapterReadStartTime = null
+ }
+
+ fun setReadStartTime() {
+ chapterReadStartTime = Date().time
}
/**
@@ -860,7 +844,7 @@ class ReaderViewModel(
* There's also a notification to allow sharing the image somewhere else or deleting it.
*/
fun saveImage(page: ReaderPage) {
- if (page.status !is Page.State.Ready) return
+ if (page.status != Page.State.READY) return
val manga = manga ?: return
val context = Injekt.get()
@@ -890,9 +874,9 @@ class ReaderViewModel(
}
fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
- viewModelScope.launch {
- if (firstPage.status !is Page.State.Ready) return@launch
- if (secondPage.status !is Page.State.Ready) return@launch
+ scope.launch {
+ if (firstPage.status != Page.State.READY) return@launch
+ if (secondPage.status != Page.State.READY) return@launch
val manga = manga ?: return@launch
val context = Injekt.get()
@@ -926,7 +910,7 @@ class ReaderViewModel(
* image will be kept so it won't be taking lots of internal disk space.
*/
fun shareImage(page: ReaderPage) {
- if (page.status !is Page.State.Ready) return
+ if (page.status != Page.State.READY) return
val manga = manga ?: return
val context = Injekt.get()
@@ -939,9 +923,9 @@ class ReaderViewModel(
}
fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
- viewModelScope.launch {
- if (firstPage.status !is Page.State.Ready) return@launch
- if (secondPage.status !is Page.State.Ready) return@launch
+ scope.launch {
+ if (firstPage.status != Page.State.READY) return@launch
+ if (secondPage.status != Page.State.READY) return@launch
val manga = manga ?: return@launch
val context = Injekt.get()
@@ -958,7 +942,7 @@ class ReaderViewModel(
* Sets the image of this [page] as cover and notifies the UI of the result.
*/
fun setAsCover(page: ReaderPage) {
- if (page.status !is Page.State.Ready) return
+ if (page.status != Page.State.READY) return
val manga = manga ?: return
val stream = page.stream ?: return
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt
index 16c5a371fe..ad78920502 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt
@@ -11,8 +11,6 @@ import yokai.core.archive.ArchiveReader
*/
internal class ArchivePageLoader(private val reader: ArchiveReader) : PageLoader() {
- override val isLocal: Boolean = true
-
/**
* Recycles this loader and the open archive.
*/
@@ -31,7 +29,7 @@ internal class ArchivePageLoader(private val reader: ArchiveReader) : PageLoader
.mapIndexed { i, entry ->
ReaderPage(i).apply {
stream = { reader.getInputStream(entry.name)!! }
- status = Page.State.Ready
+ status = Page.State.READY
}
}
.toList()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt
index 44f89294e9..7756cdd585 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt
@@ -10,8 +10,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.util.system.withIOContext
-import yokai.core.archive.util.archiveReader
-import yokai.core.archive.util.epubReader
+import yokai.core.archive.archiveReader
import yokai.i18n.MR
import yokai.util.lang.getString
@@ -83,7 +82,7 @@ class ChapterLoader(
when (format) {
is LocalSource.Format.Directory -> DirectoryPageLoader(format.file)
is LocalSource.Format.Archive -> ArchivePageLoader(format.file.archiveReader(context))
- is LocalSource.Format.Epub -> EpubPageLoader(format.file.epubReader(context))
+ is LocalSource.Format.Epub -> EpubPageLoader(format.file.archiveReader(context))
}
}
else -> error(context.getString(MR.strings.source_not_installed))
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt
index 825b14a9dd..e6557c5a13 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt
@@ -11,8 +11,6 @@ import eu.kanade.tachiyomi.util.system.ImageUtil
*/
class DirectoryPageLoader(val file: UniFile) : PageLoader() {
- override val isLocal: Boolean = true
-
/**
* Returns the pages found on this directory ordered with a natural comparator.
*/
@@ -24,7 +22,7 @@ class DirectoryPageLoader(val file: UniFile) : PageLoader() {
val streamFn = { file.openInputStream() }
ReaderPage(i).apply {
stream = streamFn
- status = Page.State.Ready
+ status = Page.State.READY
}
} ?: emptyList()
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt
index b6efbb1fcf..f2c2ea8a46 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt
@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import uy.kohesive.injekt.injectLazy
-import yokai.core.archive.util.archiveReader
+import yokai.core.archive.archiveReader
/**
* Loader used to load a chapter from the downloaded chapters.
@@ -24,8 +24,6 @@ class DownloadPageLoader(
private val downloadProvider: DownloadProvider,
) : PageLoader() {
- override val isLocal: Boolean = true
-
// Needed to open input streams
private val context: Application by injectLazy()
@@ -60,7 +58,7 @@ class DownloadPageLoader(
ReaderPage(page.index, page.url, page.imageUrl, stream = {
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
},).apply {
- status = Page.State.Ready
+ status = Page.State.READY
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt
index c5a713fa83..53e0f26e74 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt
@@ -2,14 +2,18 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
-import yokai.core.archive.EpubReader
+import eu.kanade.tachiyomi.util.storage.EpubFile
+import yokai.core.archive.ArchiveReader
/**
* Loader used to load a chapter from a .epub file.
*/
-class EpubPageLoader(private val epub: EpubReader) : PageLoader() {
+class EpubPageLoader(reader: ArchiveReader) : PageLoader() {
- override val isLocal: Boolean = true
+ /**
+ * The epub file.
+ */
+ private val epub = EpubFile(reader)
/**
* Recycles this loader and the open zip.
@@ -28,7 +32,7 @@ class EpubPageLoader(private val epub: EpubReader) : PageLoader() {
val streamFn = { epub.getInputStream(path)!! }
ReaderPage(i).apply {
stream = streamFn
- status = Page.State.Ready
+ status = Page.State.READY
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
index 4c91b5ea8f..130a7fee57 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
@@ -8,9 +8,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.withIOContext
-import java.util.concurrent.PriorityBlockingQueue
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.math.min
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -22,6 +19,9 @@ import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.suspendCancellableCoroutine
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
+import kotlin.math.min
/**
* Loader used to load chapters from an online source.
@@ -33,8 +33,6 @@ class HttpPageLoader(
private val preferences: PreferencesHelper = Injekt.get(),
) : PageLoader() {
- override val isLocal: Boolean = false
-
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
/**
@@ -51,7 +49,7 @@ class HttpPageLoader(
emit(runInterruptible { queue.take() }.page)
}
}
- .filter { it.status is Page.State.Queue }
+ .filter { it.status == Page.State.QUEUE }
.collect {
_loadPage(it)
}
@@ -108,17 +106,17 @@ class HttpPageLoader(
val imageUrl = page.imageUrl
// Check if the image has been deleted
- if (page.status is Page.State.Ready && imageUrl != null && !chapterCache.isImageInCache(imageUrl)) {
- page.status = Page.State.Queue
+ if (page.status == Page.State.READY && imageUrl != null && !chapterCache.isImageInCache(imageUrl)) {
+ page.status = Page.State.QUEUE
}
// Automatically retry failed pages when subscribed to this page
- if (page.status is Page.State.Error) {
- page.status = Page.State.Queue
+ if (page.status == Page.State.ERROR) {
+ page.status = Page.State.QUEUE
}
val queuedPages = mutableListOf()
- if (page.status is Page.State.Queue) {
+ if (page.status == Page.State.QUEUE) {
queuedPages += PriorityPage(page, 1).also { queue.offer(it) }
}
queuedPages += preloadNextPages(page, preloadSize)
@@ -126,7 +124,7 @@ class HttpPageLoader(
suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
queuedPages.forEach {
- if (it.page.status is Page.State.Queue) {
+ if (it.page.status == Page.State.QUEUE) {
queue.remove(it)
}
}
@@ -146,7 +144,7 @@ class HttpPageLoader(
return pages
.subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size))
.mapNotNull {
- if (it.status is Page.State.Queue) {
+ if (it.status == Page.State.QUEUE) {
PriorityPage(it, 0).apply { queue.offer(this) }
} else {
null
@@ -158,8 +156,8 @@ class HttpPageLoader(
* Retries a page. This method is only called from user interaction on the viewer.
*/
override fun retryPage(page: ReaderPage) {
- if (page.status is Page.State.Error) {
- page.status = Page.State.Queue
+ if (page.status == Page.State.ERROR) {
+ page.status = Page.State.QUEUE
}
queue.offer(PriorityPage(page, 2))
}
@@ -192,21 +190,21 @@ class HttpPageLoader(
private suspend fun _loadPage(page: ReaderPage) {
try {
if (page.imageUrl.isNullOrEmpty()) {
- page.status = Page.State.LoadPage
+ page.status = Page.State.LOAD_PAGE
page.imageUrl = source.getImageUrl(page)
}
val imageUrl = page.imageUrl!!
if (!chapterCache.isImageInCache(imageUrl)) {
- page.status = Page.State.DownloadImage
+ page.status = Page.State.DOWNLOAD_IMAGE
val imageResponse = source.getImage(page)
chapterCache.putImageToCache(imageUrl, imageResponse)
}
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
- page.status = Page.State.Ready
+ page.status = Page.State.READY
} catch (e: Throwable) {
- page.status = Page.State.Error
+ page.status = Page.State.ERROR
if (e is CancellationException) {
throw e
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt
index 3362ff3caa..720e81a43c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt
@@ -9,8 +9,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
*/
abstract class PageLoader {
- abstract val isLocal: Boolean
-
/**
* Whether this loader has been already recycled.
*/
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt
index 5bb4ec428d..254a7d6309 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt
@@ -12,6 +12,6 @@ class InsertPage(parent: ReaderPage) : ReaderPage(
fullPage = true
firstHalf = false
stream = parent.stream
- status = State.Ready
+ status = State.READY
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt
index 109d260dd3..3693274498 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt
@@ -1,100 +1,155 @@
package eu.kanade.tachiyomi.ui.reader.viewer
import android.content.Context
+import android.text.SpannableStringBuilder
+import android.text.style.ImageSpan
import android.util.AttributeSet
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.LocalTextStyle
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.AbstractComposeView
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import androidx.annotation.ColorInt
+import androidx.core.text.bold
+import androidx.core.text.buildSpannedString
+import androidx.core.text.inSpans
+import androidx.core.view.isVisible
+import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadManager
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding
import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
-import eu.kanade.tachiyomi.util.isLocal
-import eu.kanade.tachiyomi.util.system.ThemeUtil
-import yokai.presentation.reader.ChapterTransition
-import yokai.presentation.theme.YokaiTheme
+import eu.kanade.tachiyomi.util.chapter.ChapterUtil.Companion.preferredChapterName
+import eu.kanade.tachiyomi.util.system.contextCompatDrawable
+import eu.kanade.tachiyomi.util.system.dpToPx
+import uy.kohesive.injekt.injectLazy
+import yokai.i18n.MR
+import yokai.util.lang.getString
+import kotlin.math.roundToInt
class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- AbstractComposeView(context, attrs) {
+ LinearLayout(context, attrs) {
- private var data: Data? by mutableStateOf(null)
+ private val binding: ReaderTransitionViewBinding =
+ ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true)
+ private val preferences: PreferencesHelper by injectLazy()
init {
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
}
- fun bind(theme: Int, transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) {
- data = if (manga != null) {
- Data(
- theme = theme,
- manga = manga,
- transition = transition,
- currChapterDownloaded = transition.from.pageLoader?.isLocal == true,
- goingToChapterDownloaded = manga.isLocal() ||
- transition.to?.chapter?.let { goingToChapter ->
- downloadManager.isChapterDownloaded(
- chapter = goingToChapter,
- manga = manga,
- skipCache = true,
- )
- } ?: false,
- )
- } else {
- null
+ fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) {
+ manga ?: return
+ when (transition) {
+ is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga)
+ is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga)
}
+
+ missingChapterWarning(transition)
}
- @Composable
- override fun Content() {
- data?.let {
- YokaiTheme {
- CompositionLocalProvider (
- LocalTextStyle provides MaterialTheme.typography.bodySmall,
- LocalContentColor provides ThemeUtil.readerContentColor(it.theme, MaterialTheme.colorScheme.onBackground),
- ) {
- ChapterTransition(
- manga = it.manga,
- transition = it.transition,
- currChapterDownloaded = it.currChapterDownloaded,
- goingToChapterDownloaded = it.goingToChapterDownloaded,
- )
- }
+ /**
+ * Binds a previous chapter transition on this view and subscribes to the page load status.
+ */
+ private fun bindPrevChapterTransition(
+ transition: ChapterTransition,
+ downloadManager: DownloadManager,
+ manga: Manga,
+ ) {
+ val prevChapter = transition.to
+
+ binding.lowerText.isVisible = prevChapter != null
+ if (prevChapter != null) {
+ binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START
+ val isPrevDownloaded = downloadManager.isChapterDownloaded(prevChapter.chapter, manga)
+ val isCurrentDownloaded = downloadManager.isChapterDownloaded(transition.from.chapter, manga)
+ binding.upperText.text = buildSpannedString {
+ bold { append(context.getString(MR.strings.previous_title)) }
+ append("\n${prevChapter.chapter.preferredChapterName(context, manga, preferences)}")
+ if (isPrevDownloaded != isCurrentDownloaded) addDLImageSpan(isPrevDownloaded)
}
+ binding.lowerText.text = buildSpannedString {
+ bold { append(context.getString(MR.strings.current_chapter)) }
+ val name = transition.from.chapter.preferredChapterName(context, manga, preferences)
+ append("\n$name")
+ }
+ } else {
+ binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER
+ binding.upperText.text = context.getString(MR.strings.theres_no_previous_chapter)
}
}
- private data class Data(
- val theme: Int,
- val manga: Manga,
- val transition: ChapterTransition,
- val currChapterDownloaded: Boolean,
- val goingToChapterDownloaded: Boolean,
- )
-}
-
-fun missingChapterCount(transition: ChapterTransition): Int {
- if (transition.to == null) {
- return 0
- }
-
- val hasMissingChapters = when (transition) {
- is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to)
- is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from)
- }
-
- if (!hasMissingChapters) {
- return 0
- }
-
- val chapterDifference = when (transition) {
- is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to)
- is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from)
- }
-
- return chapterDifference.toInt()
+ /**
+ * Binds a next chapter transition on this view and subscribes to the load status.
+ */
+ private fun bindNextChapterTransition(
+ transition: ChapterTransition,
+ downloadManager: DownloadManager,
+ manga: Manga,
+ ) {
+ val nextChapter = transition.to
+
+ binding.lowerText.isVisible = nextChapter != null
+ if (nextChapter != null) {
+ binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START
+ val isCurrentDownloaded = downloadManager.isChapterDownloaded(transition.from.chapter, manga)
+ val isNextDownloaded = downloadManager.isChapterDownloaded(nextChapter.chapter, manga)
+ binding.upperText.text = buildSpannedString {
+ bold { append(context.getString(MR.strings.finished_chapter)) }
+ val name = transition.from.chapter.preferredChapterName(context, manga, preferences)
+ append("\n$name")
+ }
+ binding.lowerText.text = buildSpannedString {
+ bold { append(context.getString(MR.strings.next_title)) }
+ append("\n${nextChapter.chapter.preferredChapterName(context, manga, preferences)}")
+ if (isNextDownloaded != isCurrentDownloaded) addDLImageSpan(isNextDownloaded)
+ }
+ } else {
+ binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER
+ binding.upperText.text = context.getString(MR.strings.theres_no_next_chapter)
+ }
+ }
+
+ private fun SpannableStringBuilder.addDLImageSpan(isDownloaded: Boolean) {
+ val icon = context.contextCompatDrawable(
+ if (isDownloaded) R.drawable.ic_file_download_24dp else R.drawable.ic_cloud_24dp,
+ )
+ ?.mutate()
+ ?.apply {
+ val size = binding.lowerText.textSize + 4f.dpToPx
+ setTint(binding.lowerText.currentTextColor)
+ setBounds(0, 0, size.roundToInt(), size.roundToInt())
+ } ?: return
+ append(" ")
+ inSpans(ImageSpan(icon)) { append("image") }
+ }
+
+ fun setTextColors(@ColorInt color: Int) {
+ binding.upperText.setTextColor(color)
+ binding.warningText.setTextColor(color)
+ binding.lowerText.setTextColor(color)
+ }
+
+ private fun missingChapterWarning(transition: ChapterTransition) {
+ if (transition.to == null) {
+ binding.warning.isVisible = false
+ return
+ }
+
+ val hasMissingChapters = when (transition) {
+ is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to)
+ is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from)
+ }
+
+ if (!hasMissingChapters) {
+ binding.warning.isVisible = false
+ return
+ }
+
+ val chapterDifference = when (transition) {
+ is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to)
+ is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from)
+ }
+
+ binding.warningText.text = context.getString(MR.plurals.missing_chapters_warning, chapterDifference.toInt(), chapterDifference.toInt())
+ binding.warning.isVisible = true
+ }
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
index 96d657296a..8dc4dcc000 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
@@ -109,8 +109,8 @@ class PagerPageHolder(
*/
private var extraProgressJob: Job? = null
- private var status = Page.State.Ready
- private var extraStatus = Page.State.Ready
+ private var status = Page.State.READY
+ private var extraStatus = Page.State.READY
private var progress: Int = 0
private var extraProgress: Int = 0
@@ -337,19 +337,19 @@ class PagerPageHolder(
*/
private suspend fun processStatus(status: Page.State) {
when (status) {
- is Page.State.Queue -> setQueued()
- is Page.State.LoadPage -> setLoading()
- is Page.State.DownloadImage -> {
+ Page.State.QUEUE -> setQueued()
+ Page.State.LOAD_PAGE -> setLoading()
+ Page.State.DOWNLOAD_IMAGE -> {
launchProgressJob()
setDownloading()
}
- is Page.State.Ready -> {
- if (extraPage == null) {
+ Page.State.READY -> {
+ if (extraStatus == Page.State.READY || extraPage == null) {
setImage()
}
cancelProgressJob(1)
}
- is Page.State.Error -> {
+ Page.State.ERROR -> {
setError()
cancelProgressJob(1)
}
@@ -363,17 +363,19 @@ class PagerPageHolder(
*/
private suspend fun processStatus2(status: Page.State) {
when (status) {
- is Page.State.Queue -> setQueued()
- is Page.State.LoadPage -> setLoading()
- is Page.State.DownloadImage -> {
+ Page.State.QUEUE -> setQueued()
+ Page.State.LOAD_PAGE -> setLoading()
+ Page.State.DOWNLOAD_IMAGE -> {
launchProgressJob2()
setDownloading()
}
- is Page.State.Ready -> {
- setImage()
+ Page.State.READY -> {
+ if (this.status == Page.State.READY) {
+ setImage()
+ }
cancelProgressJob(2)
}
- is Page.State.Error -> {
+ Page.State.ERROR -> {
setError()
cancelProgressJob(2)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt
index 7114bbc499..c2854b2c0d 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt
@@ -14,11 +14,14 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.updatePaddingRelative
+import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderButton
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView
+import eu.kanade.tachiyomi.util.system.ThemeUtil
import eu.kanade.tachiyomi.util.system.dpToPx
+import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setText
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import kotlinx.coroutines.Job
@@ -64,11 +67,17 @@ class PagerTransitionHolder(
setPadding(sidePadding, 0, sidePadding, 0)
val transitionView = ReaderTransitionView(context)
+ transitionView.setTextColors(
+ ThemeUtil.readerContentColor(
+ viewer.config.readerTheme,
+ context.getResourceColor(R.attr.colorOnBackground),
+ )
+ )
addView(transitionView)
addView(pagesContainer)
- transitionView.bind(viewer.config.readerTheme, transition, viewer.downloadManager, viewer.activity.viewModel.manga)
+ transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga)
transition.to?.let { observeStatus(it) }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
index 52c60f7adc..e1428aa4cc 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
@@ -128,9 +128,9 @@ class WebtoonPageHolder(
launchIO { loader.loadPage(page) }
page.statusFlow.collectLatest { status ->
when (status) {
- is Page.State.Queue -> setQueued()
- is Page.State.LoadPage -> setLoading()
- is Page.State.DownloadImage -> {
+ Page.State.QUEUE -> setQueued()
+ Page.State.LOAD_PAGE -> setLoading()
+ Page.State.DOWNLOAD_IMAGE -> {
setDownloading()
scope.launch {
page.progressFlow.collectLatest { value ->
@@ -138,8 +138,8 @@ class WebtoonPageHolder(
}
}
}
- is Page.State.Ready -> setImage()
- is Page.State.Error -> setError()
+ Page.State.READY -> setImage()
+ Page.State.ERROR -> setError()
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt
index cfce248456..1fb8d78df2 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt
@@ -12,10 +12,13 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.isNotEmpty
import androidx.core.view.isVisible
+import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView
+import eu.kanade.tachiyomi.util.system.ThemeUtil
import eu.kanade.tachiyomi.util.system.dpToPx
+import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setText
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
@@ -69,7 +72,13 @@ class WebtoonTransitionHolder(
* Binds the given [transition] with this view holder, subscribing to its state.
*/
fun bind(transition: ChapterTransition) {
- transitionView.bind(viewer.config.readerTheme, transition, viewer.downloadManager, viewer.activity.viewModel.manga)
+ transitionView.setTextColors(
+ ThemeUtil.readerContentColor(
+ viewer.config.readerTheme,
+ context.getResourceColor(R.attr.colorOnBackground),
+ )
+ )
+ transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga)
transition.to?.let { observeStatus(it, transition) }
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt
index dadc03fb3a..24e003c2dc 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt
@@ -34,7 +34,7 @@ import eu.kanade.tachiyomi.util.view.setCards
import java.util.Date
import java.util.concurrent.TimeUnit
import yokai.i18n.MR
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
import yokai.util.lang.getString
import android.R as AR
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt
index b4ec025691..022d491fd4 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt
@@ -12,9 +12,6 @@ import android.view.RoundedCorner
import android.view.View
import android.view.ViewGroup
import androidx.activity.BackEventCompat
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.HistoryToggleOff
-import androidx.compose.material.icons.filled.SearchOff
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
@@ -599,9 +596,9 @@ class RecentsController(bundle: Bundle? = null) :
if (recents.isEmpty()) {
binding.recentsEmptyView.show(
if (!isSearching()) {
- Icons.Filled.HistoryToggleOff
+ R.drawable.ic_history_off_24dp
} else {
- Icons.Filled.SearchOff
+ R.drawable.ic_search_off_24dp
},
if (isSearching()) {
MR.strings.no_results_found
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt
index b29671a580..138df111a7 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt
@@ -75,8 +75,8 @@ class RecentsPresenter(
}
private val newAdditionsHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEWLY_ADDED)
private val newChaptersHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEW_CHAPTERS)
- private val continueReadingHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.CONTINUE_READING)
-
+ private val continueReadingHeader =
+ RecentMangaHeaderItem(RecentMangaHeaderItem.CONTINUE_READING)
var finished = false
private var shouldMoveToTop = false
var viewType: RecentsViewType = RecentsViewType.valueOf(uiPreferences.recentsViewType().get())
@@ -113,9 +113,9 @@ class RecentsPresenter(
}
presenterScope.launchIO {
downloadManager.queueState.collectLatest {
- if (recentItems.isNotEmpty()) setDownloadedChapters(recentItems, it)
+ setDownloadedChapters(recentItems, it)
withUIContext {
- if (recentItems.isNotEmpty()) view?.showLists(recentItems, true)
+ view?.showLists(recentItems, true)
view?.updateDownloadStatus(!downloadManager.isPaused())
}
}
@@ -379,22 +379,20 @@ class RecentsPresenter(
f2.second.date_fetch.compareTo(f1.second.date_fetch)
}
}
- .take(UPDATES_CHAPTER_LIMIT)
- .map { RecentMangaItem(it.first, it.second, newChaptersHeader) }
- .toMutableList()
+ .take(4).map {
+ RecentMangaItem(it.first, it.second, newChaptersHeader)
+ }.toMutableList()
val cReadingItems =
- pairs.filter { it.first.history.id != null }
- .take(UPDATES_READING_LIMIT_UPPER - nChaptersItems.size)
- .map { RecentMangaItem(it.first, it.second, continueReadingHeader) }
- .toMutableList()
+ pairs.filter { it.first.history.id != null }.take(9 - nChaptersItems.size).map {
+ RecentMangaItem(it.first, it.second, continueReadingHeader)
+ }.toMutableList()
if (nChaptersItems.isNotEmpty()) {
nChaptersItems.add(RecentMangaItem(header = newChaptersHeader))
}
if (cReadingItems.isNotEmpty()) {
cReadingItems.add(RecentMangaItem(header = continueReadingHeader))
}
- val nAdditionsItems = pairs.filter { it.first.chapter.id == null }
- .take(UPDATES_CHAPTER_LIMIT)
+ val nAdditionsItems = pairs.filter { it.first.chapter.id == null }.take(4)
.map { RecentMangaItem(it.first, it.second, newAdditionsHeader) }
listOf(nChaptersItems, cReadingItems, nAdditionsItems).sortedByDescending {
it.firstOrNull()?.mch?.history?.last_read ?: 0L
@@ -485,6 +483,20 @@ class RecentsPresenter(
return Triple(sortedChapters, firstChapter, extraCount)
}
+ private suspend fun getNextChapter(manga: Manga): Chapter? {
+ val mangaId = manga.id ?: return null
+ val chapters = getChapter.awaitUnread(mangaId, true)
+ return ChapterSort(manga, chapterFilter, preferences).getNextChapter(chapters, false)
+ }
+
+ private suspend fun getFirstUpdatedChapter(manga: Manga, chapter: Chapter): Chapter? {
+ val mangaId = manga.id ?: return null
+ val chapters = getChapter.awaitUnread(mangaId, true)
+ return chapters.sortedWith(ChapterSort(manga, chapterFilter, preferences).sortComparator(true)).find {
+ abs(it.date_fetch - chapter.date_fetch) <= TimeUnit.HOURS.toMillis(12)
+ }
+ }
+
override fun onDestroy() {
super.onDestroy()
lastRecents = recentItems
@@ -715,31 +727,6 @@ class RecentsPresenter(
var SHORT_LIMIT = 25
private set
- suspend fun getNextChapter(
- manga: Manga,
- getChapter: GetChapter = Injekt.get(),
- chapterFilter: ChapterFilter = Injekt.get(),
- preferences: PreferencesHelper = Injekt.get(),
- ): Chapter? {
- val mangaId = manga.id ?: return null
- val chapters = getChapter.awaitUnread(mangaId, true)
- return ChapterSort(manga, chapterFilter, preferences).getNextChapter(chapters, false)
- }
-
- suspend fun getFirstUpdatedChapter(
- manga: Manga,
- chapter: Chapter,
- getChapter: GetChapter = Injekt.get(),
- chapterFilter: ChapterFilter = Injekt.get(),
- preferences: PreferencesHelper = Injekt.get(),
- ): Chapter? {
- val mangaId = manga.id ?: return null
- val chapters = getChapter.awaitUnread(mangaId, true)
- return chapters.sortedWith(ChapterSort(manga, chapterFilter, preferences).sortComparator(true)).find {
- abs(it.date_fetch - chapter.date_fetch) <= TimeUnit.HOURS.toMillis(12)
- }
- }
-
suspend fun getRecentManga(includeRead: Boolean = false, customAmount: Int = 0): List> {
val presenter = RecentsPresenter()
presenter.viewType = RecentsViewType.UngroupedAll
@@ -754,9 +741,5 @@ class RecentsPresenter(
.filter { it.mch.manga.id != null }
.map { it.mch.manga to it.mch.history.last_read }
}
-
- const val UPDATES_CHAPTER_LIMIT = 4
- const val UPDATES_READING_LIMIT_UPPER = 9
- const val UPDATES_READING_LIMIT_LOWER = 5
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt
index 0aeed45248..9912f988fe 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt
@@ -1,7 +1,11 @@
package eu.kanade.tachiyomi.ui.setting
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
+import eu.kanade.tachiyomi.util.compose.LocalAlertDialog
+import eu.kanade.tachiyomi.util.compose.LocalBackPress
+import yokai.domain.ComposableAlertDialog
import yokai.presentation.settings.ComposableSettings
abstract class SettingsComposeController: BaseComposeController(), SettingsControllerInterface {
@@ -14,6 +18,11 @@ abstract class SettingsComposeController: BaseComposeController(), SettingsContr
@Composable
override fun ScreenContent() {
- getComposableSettings().Content()
+ CompositionLocalProvider(
+ LocalAlertDialog provides ComposableAlertDialog(null),
+ LocalBackPress provides router::handleBack,
+ ) {
+ getComposableSettings().Content()
+ }
}
}
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 8724eea63c..6f2616dd61 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
@@ -24,7 +24,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob.Target
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.changesIn
-import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
+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
@@ -394,12 +394,6 @@ class SettingsAdvancedController : SettingsLegacyController() {
onClick { LibraryUpdateJob.startNow(context, target = Target.TRACKING) }
}
- if (BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG) {
- switchPreference {
- bindTo(basePreferences.composeLibrary())
- title = context.getString(MR.strings.pref_use_compose_library).addBetaTag(context)
- }
- }
}
preferenceCategory {
@@ -459,37 +453,36 @@ class SettingsAdvancedController : SettingsLegacyController() {
}
}
- if (BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY) {
- preferenceCategory {
- title = "Danger zone!"
+ preferenceCategory {
+ title = "Danger zone!"
+ isVisible = BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY
- preference {
- title = "Crash the app!"
- summary = "To test crashes"
- onClick {
- activity!!.materialAlertDialog()
- .setTitle(MR.strings.warning)
- .setMessage("I told you this would crash the app, why would you want that?")
- .setPositiveButton("Crash it anyway") { _, _ -> throw RuntimeException("Fell into the void") }
- .setNegativeButton("Nevermind", null)
- .show()
- }
+ preference {
+ title = "Crash the app!"
+ summary = "To test crashes"
+ onClick {
+ activity!!.materialAlertDialog()
+ .setTitle(MR.strings.warning)
+ .setMessage("I told you this would crash the app, why would you want that?")
+ .setPositiveButton("Crash it anyway") { _, _ -> throw RuntimeException("Fell into the void") }
+ .setNegativeButton("Nevermind", null)
+ .show()
}
+ }
- preference {
- title = "Prune finished workers"
- summary = "In case worker stuck in FAILED state and you're too impatient to wait"
- onClick {
- activity!!.materialAlertDialog()
- .setTitle("Are you sure?")
- .setMessage(
- "Failed workers should clear out by itself eventually, " +
- "this option should only be used if you're being impatient and you know what you're doing."
- )
- .setPositiveButton("Prune") { _, _ -> context.workManager.pruneWork() }
- .setNegativeButton("Cancel", null)
- .show()
- }
+ preference {
+ title = "Prune finished workers"
+ summary = "In case worker stuck in FAILED state and you're too impatient to wait"
+ onClick {
+ activity!!.materialAlertDialog()
+ .setTitle("Are you sure?")
+ .setMessage(
+ "Failed workers should clear out by itself eventually, " +
+ "this option should only be used if you're being impatient and you know what you're doing."
+ )
+ .setPositiveButton("Prune") { _, _ -> context.workManager.pruneWork() }
+ .setNegativeButton("Cancel", null)
+ .show()
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt
index 7abbb51ba3..56f4d08b87 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt
@@ -8,7 +8,6 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
-import yokai.domain.ui.UiPreferences
import yokai.i18n.MR
import yokai.util.lang.getString
import dev.icerock.moko.resources.compose.stringResource
@@ -46,8 +45,6 @@ class SettingsBrowseController : SettingsLegacyController() {
val sourceManager: SourceManager by injectLazy()
var updatedExtNotifPref: SwitchPreferenceCompat? = null
- private val uiPreferences: UiPreferences by injectLazy()
-
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = MR.strings.browse
@@ -201,15 +198,6 @@ class SettingsBrowseController : SettingsLegacyController() {
infoPreference(MR.strings.you_can_migrate_in_library)
}
-
- preferenceCategory {
- titleRes = MR.strings.sources
-
- switchPreference {
- bindTo(uiPreferences.enableSourceSwipeAction())
- titleRes = MR.strings.enable_source_swipe_action
- }
- }
preferenceCategory {
titleRes = MR.strings.nsfw_sources
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt
index 77ad3199ba..d7d0977e84 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt
@@ -34,7 +34,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.domain.category.interactor.GetCategories
-import yokai.domain.library.LibraryPreferences
import yokai.domain.manga.interactor.GetLibraryManga
import yokai.domain.ui.UiPreferences
import yokai.i18n.MR
@@ -49,7 +48,6 @@ class SettingsLibraryController : SettingsLegacyController() {
private val getCategories: GetCategories by injectLazy()
private val uiPreferences: UiPreferences by injectLazy()
- private val libraryPreferences: LibraryPreferences by injectLazy()
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = MR.strings.library
@@ -214,25 +212,12 @@ class SettingsLibraryController : SettingsLegacyController() {
}
preferenceCategory {
- titleRes = MR.strings.pref_behavior
+ titleRes = MR.strings.chapters
switchPreference {
bindTo(uiPreferences.enableChapterSwipeAction())
titleRes = MR.strings.enable_chapter_swipe_action
}
- multiSelectListPreferenceMat(activity) {
- bindTo(libraryPreferences.markDuplicateReadChapterAsRead())
- titleRes = MR.strings.pref_mark_as_read_duplicate_read_chapter
- val entries = mapOf(
- MR.strings.pref_mark_as_read_duplicate_read_chapter_existing to
- LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING,
- MR.strings.pref_mark_as_read_duplicate_read_chapter_new to
- LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_NEW,
- )
- entriesRes = entries.keys.toTypedArray()
- entryValues = entries.values.toList()
- noSelectionRes = MR.strings.none
- }
}
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt
index 316589f6cd..5c74237139 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt
@@ -7,8 +7,6 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Book
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.forEach
import androidx.core.view.isInvisible
@@ -195,7 +193,7 @@ class ClearDatabaseController :
binding.emptyView.hide()
} else {
binding.emptyView.show(
- Icons.Filled.Book,
+ R.drawable.ic_book_24dp,
MR.strings.database_clean,
)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt
index 503b715a41..ea69af4080 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt
@@ -4,15 +4,15 @@ import android.os.Build
import androidx.preference.PreferenceScreen
import androidx.webkit.WebViewCompat
import eu.kanade.tachiyomi.BuildConfig
+import eu.kanade.tachiyomi.ui.more.AboutController
import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController
import eu.kanade.tachiyomi.ui.setting.onClick
import eu.kanade.tachiyomi.ui.setting.preference
import eu.kanade.tachiyomi.ui.setting.preferenceCategory
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.view.withFadeTransaction
-import java.text.DateFormat
import yokai.i18n.MR
-import yokai.presentation.settings.screen.about.getFormattedBuildTime
+import java.text.DateFormat
class DebugController : SettingsLegacyController() {
@@ -49,7 +49,7 @@ class DebugController : SettingsLegacyController() {
preference {
key = "pref_build_time"
title = "Build Time"
- summary = getFormattedBuildTime(dateFormat)
+ summary = AboutController.getFormattedBuildTime(dateFormat)
}
preference {
key = "pref_webview_version"
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt
index fbf6cd8ca3..14d1d58722 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt
@@ -11,9 +11,6 @@ import yokai.util.lang.getString
import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import yokai.domain.ui.UiPreferences
/**
* Item that contains source information.
@@ -32,7 +29,7 @@ class SourceItem(val source: CatalogueSource, header: LangItem? = null, val isPi
}
override fun isSwipeable(): Boolean {
- return Injekt.get().enableSourceSwipeAction().get() && source.id != LocalSource.ID && header != null && header.code != SourcePresenter.LAST_USED_KEY
+ return source.id != LocalSource.ID && header != null && header.code != SourcePresenter.LAST_USED_KEY
}
/**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt
index 76aa137449..8eb367d84b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt
@@ -8,11 +8,6 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ExploreOff
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
import androidx.core.view.WindowInsetsCompat.Type.ime
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isVisible
@@ -49,9 +44,7 @@ import eu.kanade.tachiyomi.util.system.connectivityManager
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.e
import eu.kanade.tachiyomi.util.system.launchIO
-import eu.kanade.tachiyomi.util.system.materialAlertDialog
import eu.kanade.tachiyomi.util.system.openInBrowser
-import eu.kanade.tachiyomi.util.system.setTextInput
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.withUIContext
import eu.kanade.tachiyomi.util.view.activityBinding
@@ -61,11 +54,7 @@ import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setAction
-import eu.kanade.tachiyomi.util.view.setMessage
-import eu.kanade.tachiyomi.util.view.setNegativeButton
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
-import eu.kanade.tachiyomi.util.view.setPositiveButton
-import eu.kanade.tachiyomi.util.view.setTitle
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
@@ -73,14 +62,12 @@ import eu.kanade.tachiyomi.widget.EmptyView
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import kotlin.math.roundToInt
import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import uy.kohesive.injekt.injectLazy
import yokai.domain.manga.interactor.GetManga
-import yokai.domain.source.browse.filter.models.SavedSearch
import yokai.i18n.MR
-import yokai.presentation.core.icons.CustomIcons
-import yokai.presentation.core.icons.LocalSource
import yokai.util.lang.getString
/**
@@ -150,9 +137,6 @@ open class BrowseSourceController(bundle: Bundle) :
private var filterSheet: SourceFilterSheet? = null
private var lastPosition: Int = -1
- // Basically a cache just so the filter sheet is shown faster
- var savedSearches by mutableStateOf(emptyList())
-
private val isBehindGlobalSearch: Boolean
get() = router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller is GlobalSearchController
@@ -191,12 +175,11 @@ open class BrowseSourceController(bundle: Bundle) :
super.onViewCreated(view)
// Initialize adapter, scroll listener and recycler views
- adapter = FlexibleAdapter(null, this, false)
+ adapter = FlexibleAdapter(null, this, true)
setupRecycler(view)
binding.fab.isVisible = presenter.sourceFilters.isNotEmpty()
binding.fab.setOnClickListener { showFilters() }
-
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(recycler)
activityBinding?.appBar?.lockYPos = true
@@ -390,16 +373,12 @@ open class BrowseSourceController(bundle: Bundle) :
return true
}
- private fun applyFilters() {
- val allDefault = presenter.filtersMatchDefault()
- showProgressBar()
- adapter?.clear()
- presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters)
- updatePopLatestIcons()
- }
-
private fun showFilters() {
if (filterSheet != null) return
+ val sheet = SourceFilterSheet(activity!!)
+ filterSheet = sheet
+ sheet.setFilters(presenter.filterItems)
+ presenter.filtersChanged = false
val oldFilters = mutableListOf()
for (i in presenter.sourceFilters) {
if (i is Filter.Group<*>) {
@@ -412,94 +391,50 @@ open class BrowseSourceController(bundle: Bundle) :
oldFilters.add(i.state)
}
}
-
- filterSheet = SourceFilterSheet(
- activity = activity!!,
- searches = { savedSearches },
- onSearchClicked = {
- var matches = true
- for (i in presenter.sourceFilters.indices) {
- val filter = oldFilters.getOrNull(i)
- if (filter is List<*>) {
- for (j in filter.indices) {
- if (filter[j] !=
- (
- (presenter.sourceFilters[i] as Filter.Group<*>).state[j] as
- Filter<*>
- ).state
- ) {
- matches = false
- break
- }
+ sheet.onSearchClicked = {
+ var matches = true
+ for (i in presenter.sourceFilters.indices) {
+ val filter = oldFilters.getOrNull(i)
+ if (filter is List<*>) {
+ for (j in filter.indices) {
+ if (filter[j] !=
+ (
+ (presenter.sourceFilters[i] as Filter.Group<*>).state[j] as
+ Filter<*>
+ ).state
+ ) {
+ matches = false
+ break
}
- } else if (filter != presenter.sourceFilters[i].state) {
- matches = false
- break
}
- if (!matches) break
+ } else if (filter != presenter.sourceFilters[i].state) {
+ matches = false
+ break
}
- if (!matches) {
- applyFilters()
- }
- },
- onResetClicked = {
- presenter.appliedFilters = FilterList()
- val newFilters = presenter.source.getFilterList()
- presenter.sourceFilters = newFilters
- filterSheet?.setFilters(presenter.filterItems)
- },
- onSaveClicked = {
- viewScope.launchIO {
- val names = presenter.loadSearches().map { it.name }
- var searchName = ""
- withUIContext {
- activity!!.materialAlertDialog()
- .setTitle(activity!!.getString(MR.strings.save_search))
- .setTextInput(hint = activity!!.getString(MR.strings.save_search_hint)) { input ->
- searchName = input
- }
- .setPositiveButton(MR.strings.save) { _, _ ->
- if (searchName.isNotBlank() && searchName !in names) {
- presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters)
- filterSheet?.scrollToTop()
- } else {
- activity!!.toast(MR.strings.save_search_invalid_name)
- }
- }
- .setNegativeButton(MR.strings.cancel, null)
- .show()
- }
- }
- },
- onSavedSearchClicked = ss@{ searchId ->
- viewScope.launchIO {
- val search = presenter.loadSearch(searchId) // Grab the latest data from database
- if (search?.filters == null) return@launchIO
-
- withUIContext {
- presenter.sourceFilters = search.filters
- filterSheet?.setFilters(presenter.filterItems)
- // This will call onSaveClicked()
- filterSheet?.dismiss()
- }
- }
- },
- onDeleteSavedSearchClicked = { searchId ->
- activity!!.materialAlertDialog()
- .setTitle(MR.strings.save_search_delete)
- .setMessage(MR.strings.save_search_delete)
- .setPositiveButton(MR.strings.cancel, null)
- .setNegativeButton(android.R.string.ok) { _, _ -> presenter.deleteSearch(searchId) }
- .show()
+ if (!matches) break
}
- )
- filterSheet?.setFilters(presenter.filterItems)
- presenter.filtersChanged = false
+ if (!matches) {
+ val allDefault = presenter.filtersMatchDefault()
+ showProgressBar()
+ adapter?.clear()
+ presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters)
+ updatePopLatestIcons()
+ }
+ }
- filterSheet?.setOnCancelListener { filterSheet = null }
- filterSheet?.setOnDismissListener { filterSheet = null }
-
- filterSheet?.show()
+ sheet.onResetClicked = {
+ presenter.appliedFilters = FilterList()
+ val newFilters = presenter.source.getFilterList()
+ presenter.sourceFilters = newFilters
+ sheet.setFilters(presenter.filterItems)
+ }
+ sheet.setOnDismissListener {
+ filterSheet = null
+ }
+ sheet.setOnCancelListener {
+ filterSheet = null
+ }
+ sheet.show()
}
/**
@@ -642,7 +577,7 @@ open class BrowseSourceController(bundle: Bundle) :
snack?.dismiss()
val message = getErrorMessage(error)
- val retryAction = {
+ val retryAction = View.OnClickListener {
// If not the first page, show bottom binding.progress bar.
if (adapter.mainItemCount > 0 && progressItem != null) {
adapter.addScrollableFooterWithDelay(progressItem!!, 0, true)
@@ -671,16 +606,16 @@ open class BrowseSourceController(bundle: Bundle) :
binding.emptyView.show(
if (presenter.source is HttpSource) {
- Icons.Filled.ExploreOff
+ R.drawable.ic_browse_off_24dp
} else {
- CustomIcons.LocalSource
+ R.drawable.ic_local_library_24dp
},
message,
actions,
)
} else {
snack = binding.sourceLayout.snack(message, Snackbar.LENGTH_INDEFINITE) {
- setAction(MR.strings.retry) { retryAction() }
+ setAction(MR.strings.retry, retryAction)
}
}
if (isControllerVisible) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt
index 92c31cb860..50ed40ec23 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt
@@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
import eu.kanade.tachiyomi.util.view.setCards
import yokai.domain.manga.models.cover
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
/**
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt
index d5e4818672..20b4a8ebe0 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt
@@ -66,7 +66,7 @@ class BrowseSourceItem(
binding.coverThumbnail.adjustViewBounds = false
binding.coverThumbnail.updateLayoutParams {
height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
- dimensionRatio = "2:3"
+ dimensionRatio = "15:22"
}
}
BrowseSourceGridHolder(view, adapter, listType == LibraryItem.LAYOUT_COMPACT_GRID, outlineOnCovers.get())
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt
index fcffcab813..b76cfe7e8a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt
@@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.databinding.MangaListItemBinding
import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.util.view.setCards
import yokai.domain.manga.models.cover
-import yokai.util.coil.loadManga
+import yokai.presentation.core.util.coil.loadManga
/**
* Class used to hold the displayed data of a manga in the catalogue, like the cover or the title.
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt
index 8ed61311f0..693e04c267 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt
@@ -28,7 +28,6 @@ import eu.kanade.tachiyomi.ui.source.filter.TextSectionItem
import eu.kanade.tachiyomi.ui.source.filter.TriStateItem
import eu.kanade.tachiyomi.ui.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.system.launchIO
-import eu.kanade.tachiyomi.util.system.launchNonCancellableIO
import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.asFlow
@@ -36,12 +35,8 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -49,11 +44,6 @@ import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga
import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.MangaUpdate
-import yokai.domain.source.browse.filter.FilterSerializer
-import yokai.domain.source.browse.filter.interactor.DeleteSavedSearch
-import yokai.domain.source.browse.filter.interactor.GetSavedSearch
-import yokai.domain.source.browse.filter.interactor.InsertSavedSearch
-import yokai.domain.source.browse.filter.models.SavedSearch
import yokai.domain.ui.UiPreferences
// FIXME: Migrate to Compose
@@ -73,11 +63,6 @@ open class BrowseSourcePresenter(
private val insertManga: InsertManga by injectLazy()
private val updateManga: UpdateManga by injectLazy()
- private val deleteSavedSearch: DeleteSavedSearch by injectLazy()
- private val getSavedSearch: GetSavedSearch by injectLazy()
- private val insertSavedSearch: InsertSavedSearch by injectLazy()
- private val filterSerializer: FilterSerializer by injectLazy()
-
/**
* Selected source.
*/
@@ -144,15 +129,6 @@ open class BrowseSourcePresenter(
}
}
filtersChanged = false
-
- runBlocking { view?.savedSearches = loadSearches() }
-
- getSavedSearch.subscribeAllBySourceId(sourceId)
- .map { it.applyAllSave(source.getFilterList()) }
- .onEach {
- withUIContext { view?.savedSearches = it }
- }
- .launchIn(presenterScope)
}
}
@@ -384,33 +360,4 @@ open class BrowseSourcePresenter(
}
}
}
-
- fun saveSearch(name: String, query: String, filters: FilterList) {
- presenterScope.launchNonCancellableIO {
- insertSavedSearch.await(
- sourceId,
- name,
- query,
- try {
- Json.encodeToString(filterSerializer.serialize(filters))
- } catch (e: Exception) {
- "[]"
- },
- )
- }
- }
-
- fun deleteSearch(searchId: Long) {
- presenterScope.launchNonCancellableIO {
- deleteSavedSearch.await(searchId)
- }
- }
-
- suspend fun loadSearch(id: Long): SavedSearch? {
- return getSavedSearch.awaitById(id)?.applySave(source.getFilterList())
- }
-
- suspend fun loadSearches(): List {
- return getSavedSearch.awaitAllBySourceId(sourceId).applyAllSave(source.getFilterList())
- }
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt
deleted file mode 100644
index d07c2f9430..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package eu.kanade.tachiyomi.ui.source.browse
-
-import eu.kanade.tachiyomi.source.model.FilterList
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonArray
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import yokai.domain.source.browse.filter.FilterSerializer
-import yokai.domain.source.browse.filter.models.RawSavedSearch
-import yokai.domain.source.browse.filter.models.SavedSearch
-
-fun RawSavedSearch.applySave(
- originalFilters: FilterList,
- json: Json = Injekt.get(),
- filterSerializer: FilterSerializer = Injekt.get(),
-): SavedSearch {
- val rt = SavedSearch(
- id = this.id,
- name = this.name,
- query = this.query.orEmpty(),
- filters = null,
- )
- if (filtersJson == null) {
- return rt
- }
-
- val filters = try {
- json.decodeFromString(filtersJson!!)
- } catch (e: Exception) {
- null
- } ?: return rt
-
- try {
- filterSerializer.deserialize(originalFilters, filters)
- return rt.copy(filters = originalFilters)
- } catch (e: Exception) {
- return rt
- }
-}
-
-fun List.applyAllSave(
- originalFilters: FilterList,
- json: Json = Injekt.get(),
- filterSerializer: FilterSerializer = Injekt.get(),
-) = this.map { it.applySave(originalFilters, json, filterSerializer) }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt
deleted file mode 100644
index 5dfca7f047..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package eu.kanade.tachiyomi.ui.source.browse
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.ElevatedSuggestionChip
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SuggestionChipDefaults
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.compose.ui.unit.dp
-import androidx.core.view.isVisible
-import androidx.recyclerview.widget.RecyclerView
-import eu.kanade.tachiyomi.databinding.SourceFilterSheetSavedSearchBinding
-import yokai.domain.source.browse.filter.models.SavedSearch
-import yokai.presentation.theme.YokaiTheme
-
-class SavedSearchesAdapter(
- val searches: () -> List,
- val onSavedSearchClicked: (Long) -> Unit,
- val onDeleteSavedSearchClicked: (Long) -> Unit,
-) :
- RecyclerView.Adapter() {
-
- private lateinit var binding: SourceFilterSheetSavedSearchBinding
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SavedSearchesViewHolder {
- binding = SourceFilterSheetSavedSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return SavedSearchesViewHolder(binding.root)
- }
-
- override fun getItemCount(): Int = 1
-
- override fun onBindViewHolder(holder: SavedSearchesViewHolder, position: Int) {
- holder.bind()
- }
-
- inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- fun bind() {
- binding.savedSearches.setContent {
- YokaiTheme {
- Content()
- }
- }
- binding.savedSearches.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool)
- }
-
- @Composable
- fun Content() {
- binding.savedSearchesTitle.isVisible = searches().isNotEmpty()
-
- FlowRow(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(6.dp),
- ) {
- searches().forEach { search ->
- val inputChipInteractionSource = remember { MutableInteractionSource() }
- Box {
- ElevatedSuggestionChip(
- label = { Text(search.name) },
- onClick = { },
- interactionSource = inputChipInteractionSource,
- colors = SuggestionChipDefaults.elevatedSuggestionChipColors().copy(
- containerColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.4f),
- labelColor = MaterialTheme.colorScheme.onSurface,
- ),
- )
- // Workaround to add long click to chips
- Box(
- modifier = Modifier
- .matchParentSize()
- .combinedClickable(
- onLongClick = { onDeleteSavedSearchClicked(search.id) },
- onClick = { onSavedSearchClicked(search.id) },
- interactionSource = inputChipInteractionSource,
- indication = null,
- )
- )
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt
index 2eb00a48df..14be0b9ae3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt
@@ -6,12 +6,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.withStyledAttributes
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePaddingRelative
-import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.davidea.flexibleadapter.FlexibleAdapter
@@ -23,37 +21,26 @@ import eu.kanade.tachiyomi.util.view.checkHeightThen
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat
import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog
-import yokai.domain.source.browse.filter.models.SavedSearch
-import yokai.presentation.component.recyclerview.VertPaddingDecoration
import android.R as AR
-class SourceFilterSheet(
- val activity: Activity,
- searches: () -> List