mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Merge branch 'main' into features/komga_book_specific_tracker
This commit is contained in:
commit
b8014bee6b
295 changed files with 2398 additions and 1625 deletions
10
.github/mergify.yml
vendored
10
.github/mergify.yml
vendored
|
@ -1,10 +0,0 @@
|
|||
#pull_request_rules:
|
||||
# - name: Automatically merge translations
|
||||
# conditions:
|
||||
# - "author = weblate"
|
||||
# - "-conflict"
|
||||
# - "current-day-of-week = Sat"
|
||||
# - "created-at < 1 day ago"
|
||||
# actions:
|
||||
# merge:
|
||||
# method: squash
|
14
.github/renovate.json5
vendored
14
.github/renovate.json5
vendored
|
@ -1,18 +1,14 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": ["every friday"],
|
||||
"extends": ["config:base"],
|
||||
"labels": ["Dependencies"],
|
||||
"packageRules": [
|
||||
{
|
||||
// Compiler plugins are tightly coupled to Kotlin version
|
||||
"groupName": "Kotlin",
|
||||
"matchPackagePrefixes": [
|
||||
"androidx.compose.compiler",
|
||||
"org.jetbrains.kotlin",
|
||||
"groupName": "Compose BOM (Alpha)",
|
||||
"matchPackageNames": [
|
||||
"dev.chrisbanes.compose:compose-bom"
|
||||
],
|
||||
"ignoreUnstable": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
26
.github/workflows/build_pull_request.yml
vendored
26
.github/workflows/build_pull_request.yml
vendored
|
@ -3,8 +3,8 @@ on:
|
|||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'i18n/src/commonMain/resources/**/strings.xml'
|
||||
- 'i18n/src/commonMain/resources/**/plurals.xml'
|
||||
- 'i18n/src/commonMain/moko-resources/**/strings.xml'
|
||||
- 'i18n/src/commonMain/moko-resources/**/plurals.xml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
|
@ -20,13 +20,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 # v2.1.2
|
||||
uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@5bbc3ba658137598168acb2ab73b21c432dd411b # v4.2.5
|
||||
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
|
@ -35,7 +35,19 @@ jobs:
|
|||
distribution: adopt
|
||||
|
||||
- name: Set up gradle
|
||||
uses: gradle/actions/setup-gradle@e24011a3b5db78bd5ab798036042d9312002f252 # v3.2.0
|
||||
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
|
||||
- name: Build app and run unit tests
|
||||
run: ./gradlew detekt assembleStandardRelease testReleaseUnitTest
|
||||
run: ./gradlew detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
|
||||
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/standardRelease
|
||||
|
|
22
.github/workflows/build_push.yml
vendored
22
.github/workflows/build_push.yml
vendored
|
@ -17,10 +17,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 # v2.1.2
|
||||
uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
|
||||
- name: Setup Android SDK
|
||||
run: |
|
||||
|
@ -33,10 +33,22 @@ jobs:
|
|||
distribution: adopt
|
||||
|
||||
- name: Set up gradle
|
||||
uses: gradle/actions/setup-gradle@e24011a3b5db78bd5ab798036042d9312002f252 # v3.2.0
|
||||
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
|
||||
- name: Build app and run unit tests
|
||||
run: ./gradlew detekt assembleStandardRelease testReleaseUnitTest
|
||||
run: ./gradlew detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
|
||||
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/standardRelease
|
||||
|
||||
# Sign APK and create release for tags
|
||||
|
||||
|
@ -83,7 +95,7 @@ jobs:
|
|||
|
||||
- name: Create Release
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4
|
||||
uses: softprops/action-gh-release@a74c6b72af54cfa997e81df42d94703d6313a2d0 # v2.0.6
|
||||
with:
|
||||
tag_name: ${{ env.VERSION_TAG }}
|
||||
name: Mihon ${{ env.VERSION_TAG }}
|
||||
|
|
35
.github/workflows/issue_moderator.yml
vendored
35
.github/workflows/issue_moderator.yml
vendored
|
@ -1,35 +0,0 @@
|
|||
name: Issue moderator
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
moderate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Moderate issues
|
||||
uses: keiyoushi/issue-moderator-action@a017be83547db6e107431ce7575f53c1dfa3296a
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
duplicate-label: Duplicate
|
||||
|
||||
auto-close-rules: |
|
||||
[
|
||||
{
|
||||
"type": "both",
|
||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
||||
"ignoreCase": true,
|
||||
"message": "Mihon does not support anime, and has no plans to support anime. In addition Mihon is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
|
||||
},
|
||||
{
|
||||
"type": "both",
|
||||
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
|
||||
"ignoreCase": true,
|
||||
"labels": ["Cloudflare protected"],
|
||||
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
||||
}
|
||||
]
|
||||
auto-close-ignore-label: do-not-autoclose
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
.gradle
|
||||
.kotlin
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
|
|
BIN
.idea/icon.png
BIN
.idea/icon.png
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 62 KiB |
|
@ -49,8 +49,8 @@ Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/fa
|
|||
|
||||
### Repositories
|
||||
|
||||
[![mihonapp/website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=website&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true)](https://github.com/mihonapp/website/)
|
||||
[![mihonapp/bitmap.kt - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=bitmap.kt&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true)](https://github.com/mihonapp/bitmap.kt/)
|
||||
[![mihonapp/website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=website&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true&description_lines_count=2)](https://github.com/mihonapp/website/)
|
||||
[![mihonapp/bitmap.kt - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=bitmap.kt&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true&description_lines_count=2)](https://github.com/mihonapp/bitmap.kt/)
|
||||
|
||||
### Credits
|
||||
|
||||
|
|
|
@ -151,7 +151,6 @@ dependencies {
|
|||
implementation(compose.activity)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3.core)
|
||||
implementation(compose.material.core)
|
||||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.animation.graphics)
|
||||
|
@ -159,6 +158,8 @@ dependencies {
|
|||
implementation(compose.ui.tooling.preview)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
|
||||
implementation(androidx.interpolator)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
|
@ -204,7 +205,6 @@ dependencies {
|
|||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.unifile)
|
||||
implementation(libs.bundles.archive)
|
||||
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
|
@ -275,7 +275,7 @@ androidComponents {
|
|||
tasks {
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-Xcontext-receivers",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
|
@ -286,7 +286,6 @@ tasks {
|
|||
"-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",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
|
|
7
app/proguard-rules.pro
vendored
7
app/proguard-rules.pro
vendored
|
@ -44,6 +44,10 @@
|
|||
-dontnote rx.internal.util.PlatformDependent
|
||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
||||
|
||||
##---------------Begin: proguard configuration for okhttp ----------
|
||||
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
|
||||
##---------------End: proguard configuration for okhttp ----------
|
||||
|
||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.** # core serialization annotations
|
||||
|
@ -73,9 +77,6 @@
|
|||
# XmlUtil
|
||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||
|
||||
# Apache Commons Compress
|
||||
-keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { <init>(); }
|
||||
|
||||
# Firebase
|
||||
-keep class com.google.firebase.installations.** { *; }
|
||||
-keep interface com.google.firebase.installations.** { *; }
|
||||
|
|
|
@ -179,7 +179,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { ToggleLanguage(get()) }
|
||||
addFactory { ToggleSource(get()) }
|
||||
addFactory { ToggleSourcePin(get()) }
|
||||
addFactory { TrustExtension(get()) }
|
||||
addFactory { TrustExtension(get(), get()) }
|
||||
|
||||
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
||||
addFactory { ExtensionRepoService(get(), get()) }
|
||||
|
|
|
@ -20,7 +20,7 @@ class GetExtensionsByType(
|
|||
extensionManager.installedExtensionsFlow,
|
||||
extensionManager.untrustedExtensionsFlow,
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
||||
) { enabledLanguages, _installed, _untrusted, _available ->
|
||||
val (updates, installed) = _installed
|
||||
.filter { (showNsfwSources || !it.isNsfw) }
|
||||
.sortedWith(
|
||||
|
@ -40,9 +40,9 @@ class GetExtensionsByType(
|
|||
}
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
|
||||
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
|
||||
}
|
||||
ext.sources.filter { it.lang in _activeLanguages }
|
||||
ext.sources.filter { it.lang in enabledLanguages }
|
||||
.map {
|
||||
ext.copy(
|
||||
name = it.name,
|
||||
|
|
|
@ -3,15 +3,18 @@ package eu.kanade.domain.extension.interactor
|
|||
import android.content.pm.PackageInfo
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
|
||||
class TrustExtension(
|
||||
private val extensionRepoRepository: ExtensionRepoRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean {
|
||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash"
|
||||
return key in preferences.trustedExtensions().get()
|
||||
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
|
||||
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
|
||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
|
||||
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
|
||||
}
|
||||
|
||||
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
||||
|
@ -19,9 +22,7 @@ class TrustExtension(
|
|||
// Remove previously trusted versions
|
||||
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
||||
|
||||
removed.also {
|
||||
it += "$pkgName:$versionCode:$signatureHash"
|
||||
}
|
||||
removed.also { it += "$pkgName:$versionCode:$signatureHash" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,4 +48,9 @@ class SourcePreferences(
|
|||
Preference.appStateKey("trusted_extensions"),
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
fun globalSearchFilterState() = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("has_filters_toggle_state"),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.net.Uri
|
|||
import android.provider.Settings
|
||||
import android.util.DisplayMetrics
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
@ -191,7 +190,7 @@ private fun ExtensionDetails(
|
|||
key = { it.source.id },
|
||||
) { source ->
|
||||
SourceSwitchPreference(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = source,
|
||||
onClickSourcePreferences = onClickSourcePreferences,
|
||||
onClickSource = onClickSource,
|
||||
|
@ -354,10 +353,8 @@ private fun InfoText(
|
|||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
val clickableModifier = if (onClick != null) {
|
||||
Modifier.clickable(interactionSource, indication = null) { onClick() }
|
||||
Modifier.clickable(interactionSource = null, indication = null, onClick = onClick)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ private fun ExtensionFilterContent(
|
|||
) {
|
||||
items(state.languages) { language ->
|
||||
SwitchPreferenceWidget(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||
checked = language in state.enabledLanguages,
|
||||
onCheckedChanged = { onClickLang(language) },
|
||||
|
|
|
@ -90,7 +90,7 @@ fun ExtensionScreen(
|
|||
PullRefresh(
|
||||
refreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
enabled = { !state.isLoading },
|
||||
enabled = !state.isLoading,
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
|
@ -187,14 +187,14 @@ private fun ExtensionContent(
|
|||
}
|
||||
ExtensionHeader(
|
||||
textRes = header.textRes,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
action = action,
|
||||
)
|
||||
}
|
||||
is ExtensionUiModel.Header.Text -> {
|
||||
ExtensionHeader(
|
||||
text = header.text,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ private fun ExtensionContent(
|
|||
},
|
||||
) { item ->
|
||||
ExtensionItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
item = item,
|
||||
onClickItem = {
|
||||
when (it) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||
|
@ -79,6 +80,7 @@ internal fun GlobalSearchContent(
|
|||
} ?: source.name,
|
||||
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||
onClick = { onClickSource(source) },
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
when (result) {
|
||||
SearchItemResult.Loading -> {
|
||||
|
|
|
@ -133,7 +133,7 @@ private fun MigrateSourceList(
|
|||
key = { (source, _) -> "migrate-${source.id}" },
|
||||
) { (source, count) ->
|
||||
MigrateSourceItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = source,
|
||||
count = count,
|
||||
onClickItem = { onClickItem(source) },
|
||||
|
|
|
@ -68,7 +68,7 @@ private fun SourcesFilterContent(
|
|||
contentType = "source-filter-header",
|
||||
) {
|
||||
SourcesFilterHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
language = language,
|
||||
enabled = enabled,
|
||||
onClickItem = onClickLanguage,
|
||||
|
@ -81,7 +81,7 @@ private fun SourcesFilterContent(
|
|||
contentType = { "source-filter-item" },
|
||||
) { source ->
|
||||
SourcesFilterItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = source,
|
||||
enabled = "${source.id}" !in state.disabledSources,
|
||||
onClickItem = onClickSource,
|
||||
|
|
|
@ -74,12 +74,12 @@ fun SourcesScreen(
|
|||
when (model) {
|
||||
is SourceUiModel.Header -> {
|
||||
SourceHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
language = model.language,
|
||||
)
|
||||
}
|
||||
is SourceUiModel.Item -> SourceItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
source = model.source,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = onLongClickItem,
|
||||
|
|
|
@ -32,9 +32,10 @@ fun GlobalSearchResultItem(
|
|||
title: String,
|
||||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
|
|
|
@ -107,7 +107,7 @@ private fun CategoryContent(
|
|||
key = { _, category -> "category-${category.id}" },
|
||||
) { index, category ->
|
||||
CategoryListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
category = category,
|
||||
canMoveUp = index != 0,
|
||||
canMoveDown = index != categories.lastIndex,
|
||||
|
|
|
@ -10,8 +10,7 @@ import androidx.compose.ui.Modifier
|
|||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrollingUp
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
|
||||
@Composable
|
||||
fun CategoryFloatingActionButton(
|
||||
|
@ -23,7 +22,7 @@ fun CategoryFloatingActionButton(
|
|||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||
onClick = onCreate,
|
||||
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
||||
expanded = lazyListState.shouldExpandFAB(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import androidx.compose.animation.fadeOut
|
|||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
||||
|
@ -23,7 +21,6 @@ import tachiyomi.presentation.core.components.AdaptiveSheet as AdaptiveSheetImpl
|
|||
@Composable
|
||||
fun NavigatorAdaptiveSheet(
|
||||
screen: Screen,
|
||||
tonalElevation: Dp = 1.dp,
|
||||
enableSwipeDismiss: (Navigator) -> Boolean = { true },
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
|
@ -31,7 +28,6 @@ fun NavigatorAdaptiveSheet(
|
|||
screen = screen,
|
||||
content = { sheetNavigator ->
|
||||
AdaptiveSheet(
|
||||
tonalElevation = tonalElevation,
|
||||
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
|
@ -73,7 +69,6 @@ fun NavigatorAdaptiveSheet(
|
|||
fun AdaptiveSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
tonalElevation: Dp = 1.dp,
|
||||
enableSwipeDismiss: Boolean = true,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
|
@ -86,7 +81,6 @@ fun AdaptiveSheet(
|
|||
AdaptiveSheetImpl(
|
||||
modifier = modifier,
|
||||
isTabletUi = isTabletUi,
|
||||
tonalElevation = tonalElevation,
|
||||
enableSwipeDismiss = enableSwipeDismiss,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
|
@ -21,6 +20,7 @@ import androidx.compose.material3.LocalContentColor
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
|
@ -179,7 +179,7 @@ fun AppBarTitle(
|
|||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.basicMarquee(
|
||||
delayMillis = 2_000,
|
||||
repeatDelayMillis = 2_000,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ fun SearchToolbar(
|
|||
visualTransformation = visualTransformation,
|
||||
interactionSource = interactionSource,
|
||||
decorationBox = { innerTextField ->
|
||||
TextFieldDefaults.TextFieldDecorationBox(
|
||||
TextFieldDefaults.DecorationBox(
|
||||
value = searchQuery,
|
||||
innerTextField = innerTextField,
|
||||
enabled = true,
|
||||
|
@ -331,6 +331,7 @@ fun SearchToolbar(
|
|||
),
|
||||
)
|
||||
},
|
||||
container = {},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -58,6 +58,7 @@ fun TabbedDialog(
|
|||
PrimaryTabRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
divider = {},
|
||||
) {
|
||||
tabTitles.fastForEachIndexed { index, tab ->
|
||||
|
|
|
@ -37,7 +37,7 @@ fun CrashScreen(
|
|||
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
||||
onAcceptClick = {
|
||||
scope.launch {
|
||||
CrashLogUtil(context).dumpLogs()
|
||||
CrashLogUtil(context).dumpLogs(exception)
|
||||
}
|
||||
},
|
||||
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
||||
|
|
|
@ -113,14 +113,14 @@ private fun HistoryScreenContent(
|
|||
when (item) {
|
||||
is HistoryUiModel.Header -> {
|
||||
ListGroupHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
text = relativeDateText(item.date),
|
||||
)
|
||||
}
|
||||
is HistoryUiModel.Item -> {
|
||||
val value = item.item
|
||||
HistoryItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
history = value,
|
||||
onClickCover = { onClickCover(value) },
|
||||
onClickResume = { onClickResume(value) },
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll
|
|||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -125,7 +126,7 @@ private fun ColumnScope.FilterPage(
|
|||
)
|
||||
}
|
||||
|
||||
val trackers = remember { screenModel.trackers }
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
when (trackers.size) {
|
||||
0 -> {
|
||||
// No trackers
|
||||
|
@ -158,15 +159,15 @@ private fun ColumnScope.SortPage(
|
|||
category: Category?,
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
val trackers by screenModel.trackersFlow.collectAsState()
|
||||
val sortingMode = category.sort.type
|
||||
val sortDescending = !category.sort.isAscending
|
||||
|
||||
val trackerSortOption =
|
||||
if (screenModel.trackers.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||
}
|
||||
val trackerSortOption = if (trackers.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||
}
|
||||
|
||||
listOf(
|
||||
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
||||
|
|
|
@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
|
@ -42,15 +43,22 @@ import tachiyomi.i18n.MR
|
|||
import tachiyomi.presentation.core.components.BadgeGroup
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.selectedBackground
|
||||
import tachiyomi.domain.manga.model.MangaCover as MangaCoverModel
|
||||
|
||||
object CommonMangaItemDefaults {
|
||||
val GridHorizontalSpacer = 4.dp
|
||||
val GridVerticalSpacer = 4.dp
|
||||
|
||||
@Suppress("ConstPropertyName")
|
||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||
}
|
||||
|
||||
private val ContinueReadingButtonSize = 28.dp
|
||||
private val ContinueReadingButtonSizeSmall = 28.dp
|
||||
private val ContinueReadingButtonSizeLarge = 32.dp
|
||||
|
||||
private val ContinueReadingButtonIconSizeSmall = 16.dp
|
||||
private val ContinueReadingButtonIconSizeLarge = 20.dp
|
||||
|
||||
private val ContinueReadingButtonGridPadding = 6.dp
|
||||
private val ContinueReadingButtonListSpacing = 8.dp
|
||||
|
||||
|
@ -62,7 +70,7 @@ private const val GridSelectedCoverAlpha = 0.76f
|
|||
*/
|
||||
@Composable
|
||||
fun MangaCompactGridItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverData: MangaCoverModel,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
isSelected: Boolean = false,
|
||||
|
@ -96,10 +104,12 @@ fun MangaCompactGridItem(
|
|||
)
|
||||
} else if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeLarge,
|
||||
iconSize = ContinueReadingButtonIconSizeLarge,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier
|
||||
.padding(ContinueReadingButtonGridPadding)
|
||||
.align(Alignment.BottomEnd),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -148,11 +158,13 @@ private fun BoxScope.CoverTextOverlay(
|
|||
)
|
||||
if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeSmall,
|
||||
iconSize = ContinueReadingButtonIconSizeSmall,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier.padding(
|
||||
end = ContinueReadingButtonGridPadding,
|
||||
bottom = ContinueReadingButtonGridPadding,
|
||||
),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +175,7 @@ private fun BoxScope.CoverTextOverlay(
|
|||
*/
|
||||
@Composable
|
||||
fun MangaComfortableGridItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverData: MangaCoverModel,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
|
@ -194,10 +206,12 @@ fun MangaComfortableGridItem(
|
|||
content = {
|
||||
if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
size = ContinueReadingButtonSizeLarge,
|
||||
iconSize = ContinueReadingButtonIconSizeLarge,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier
|
||||
.padding(ContinueReadingButtonGridPadding)
|
||||
.align(Alignment.BottomEnd),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -309,14 +323,14 @@ private fun GridItemSelectable(
|
|||
private fun Modifier.selectedOutline(
|
||||
isSelected: Boolean,
|
||||
color: Color,
|
||||
) = this then drawBehind { if (isSelected) drawRect(color = color) }
|
||||
) = drawBehind { if (isSelected) drawRect(color = color) }
|
||||
|
||||
/**
|
||||
* Layout of list item.
|
||||
*/
|
||||
@Composable
|
||||
fun MangaListItem(
|
||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||
coverData: MangaCoverModel,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
|
@ -354,8 +368,10 @@ fun MangaListItem(
|
|||
BadgeGroup(content = badge)
|
||||
if (onClickContinueReading != null) {
|
||||
ContinueReadingButton(
|
||||
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
size = ContinueReadingButtonSizeSmall,
|
||||
iconSize = ContinueReadingButtonIconSizeSmall,
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -363,23 +379,25 @@ fun MangaListItem(
|
|||
|
||||
@Composable
|
||||
private fun ContinueReadingButton(
|
||||
size: Dp,
|
||||
iconSize: Dp,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onClickContinueReading: () -> Unit,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
FilledIconButton(
|
||||
onClick = onClickContinueReading,
|
||||
modifier = Modifier.size(ContinueReadingButtonSize),
|
||||
onClick = onClick,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
colors = IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
||||
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
|
||||
),
|
||||
modifier = Modifier.size(size)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = stringResource(MR.strings.action_resume),
|
||||
modifier = Modifier.size(16.dp),
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ fun LibraryContent(
|
|||
isRefreshing = false
|
||||
}
|
||||
},
|
||||
enabled = { notSelectionMode },
|
||||
enabled = notSelectionMode,
|
||||
) {
|
||||
LibraryPager(
|
||||
state = pagerState,
|
||||
|
|
|
@ -75,8 +75,7 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut
|
|||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrollingUp
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.time.Instant
|
||||
|
||||
|
@ -346,7 +345,7 @@ private fun MangaScreenSmallImpl(
|
|||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -356,7 +355,7 @@ private fun MangaScreenSmallImpl(
|
|||
PullRefresh(
|
||||
refreshing = state.isRefreshingData,
|
||||
onRefresh = onRefresh,
|
||||
enabled = { !isAnySelected },
|
||||
enabled = !isAnySelected,
|
||||
indicatorPadding = PaddingValues(top = topPadding),
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
|
@ -594,7 +593,7 @@ fun MangaScreenLargeImpl(
|
|||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -602,7 +601,7 @@ fun MangaScreenLargeImpl(
|
|||
PullRefresh(
|
||||
refreshing = state.isRefreshingData,
|
||||
onRefresh = onRefresh,
|
||||
enabled = { !isAnySelected },
|
||||
enabled = !isAnySelected,
|
||||
indicatorPadding = PaddingValues(
|
||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||
top = with(density) { topBarHeight.toDp() },
|
||||
|
|
|
@ -9,13 +9,13 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.outlined.ArrowDownward
|
||||
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||
import androidx.compose.material.ripple
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.compose.animation.fadeIn
|
|||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
@ -30,11 +29,11 @@ import androidx.compose.material.icons.outlined.Delete
|
|||
import androidx.compose.material.icons.outlined.DoneAll
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.RemoveDone
|
||||
import androidx.compose.material.ripple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
|
@ -82,7 +81,7 @@ fun MangaBottomActionMenu(
|
|||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
||||
tonalElevation = 3.dp,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
||||
|
@ -191,7 +190,7 @@ private fun RowScope.Button(
|
|||
.size(48.dp)
|
||||
.weight(animatedWeight)
|
||||
.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
interactionSource = null,
|
||||
indication = ripple(bounded = false),
|
||||
onLongClick = onLongClick,
|
||||
onClick = onClick,
|
||||
|
@ -238,7 +237,7 @@ fun LibraryBottomActionMenu(
|
|||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
||||
tonalElevation = 3.dp,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
|
||||
|
|
|
@ -30,7 +30,6 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
@ -66,9 +65,6 @@ fun MangaChapterListItem(
|
|||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val textAlpha = if (read) ReadItemAlpha else 1f
|
||||
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
|
||||
|
||||
val start = getSwipeAction(
|
||||
action = chapterSwipeStartAction,
|
||||
read = read,
|
||||
|
@ -133,15 +129,20 @@ fun MangaChapterListItem(
|
|||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = LocalContentColor.current.copy(alpha = textAlpha),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
onTextLayout = { textHeight = it.size.height },
|
||||
color = LocalContentColor.current.copy(alpha = if (read) ReadItemAlpha else 1f),
|
||||
)
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.alpha(textSubtitleAlpha)) {
|
||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
Row {
|
||||
val subtitleStyle = MaterialTheme.typography.bodySmall
|
||||
.merge(
|
||||
color = LocalContentColor.current
|
||||
.copy(alpha = if (read) ReadItemAlpha else SecondaryItemAlpha)
|
||||
)
|
||||
ProvideTextStyle(value = subtitleStyle) {
|
||||
if (date != null) {
|
||||
Text(
|
||||
text = date,
|
||||
|
|
|
@ -37,6 +37,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.view.updatePadding
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
|
|
|
@ -102,9 +102,12 @@ fun SetIntervalDialog(
|
|||
),
|
||||
),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(MaterialTheme.padding.small))
|
||||
} else {
|
||||
Text(
|
||||
stringResource(MR.strings.manga_interval_expected_update_null),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(MaterialTheme.padding.small))
|
||||
|
||||
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
|
||||
Text(stringResource(MR.strings.manga_interval_custom_amount))
|
||||
|
|
|
@ -42,7 +42,7 @@ import androidx.compose.material.icons.outlined.Sync
|
|||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
|
@ -649,7 +649,7 @@ private fun TagsChip(
|
|||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||
SuggestionChip(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
|
|
|
@ -31,8 +31,6 @@ import tachiyomi.i18n.MR
|
|||
import tachiyomi.presentation.core.components.material.TextButton
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
@Composable
|
||||
fun ScanlatorFilterDialog(
|
||||
|
@ -96,8 +94,8 @@ fun ScanlatorFilterDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
},
|
||||
properties = DialogProperties(
|
||||
|
|
|
@ -28,11 +28,11 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||
import tachiyomi.i18n.MR
|
||||
|
|
|
@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut
|
|||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.structuralEqualityPolicy
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
||||
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
||||
|
@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
|||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
||||
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
|
||||
|
@ -156,16 +154,14 @@ internal fun PreferenceItem(
|
|||
)
|
||||
}
|
||||
is Preference.PreferenceItem.TrackerPreference -> {
|
||||
val uName by Injekt.get<TrackPreferences>()
|
||||
.trackUsername(item.tracker)
|
||||
.collectAsState()
|
||||
item.tracker.run {
|
||||
TrackingPreferenceWidget(
|
||||
tracker = this,
|
||||
checked = uName.isNotEmpty(),
|
||||
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||
)
|
||||
val isLoggedIn by item.tracker.let { tracker ->
|
||||
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
|
||||
}
|
||||
TrackingPreferenceWidget(
|
||||
tracker = item.tracker,
|
||||
checked = isLoggedIn,
|
||||
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||
)
|
||||
}
|
||||
is Preference.PreferenceItem.InfoPreference -> {
|
||||
InfoWidget(text = item.title)
|
||||
|
|
|
@ -111,7 +111,17 @@ object SettingsDataScreen : SearchableSettings {
|
|||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
// For some reason InkBook devices do not implement the SAF properly. Persistable URI grants do not
|
||||
// work. However, simply retrieving the URI and using it works fine for these devices. Access is not
|
||||
// revoked after the app is closed or the device is restarted.
|
||||
// This also holds for some Samsung devices. Thus, we simply execute inside of a try-catch block and
|
||||
// ignore the exception if it is thrown.
|
||||
try {
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
} catch (e: SecurityException) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
context.toast(MR.strings.file_picker_uri_permission_unsupported)
|
||||
}
|
||||
|
||||
UniFile.fromUri(context, uri)?.let {
|
||||
storageDirPref.set(it.uri.toString())
|
||||
|
|
|
@ -14,6 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -61,12 +62,8 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
pref = readerPref.pageTransitions(),
|
||||
title = stringResource(MR.strings.pref_page_transitions),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPref.flashOnPageChange(),
|
||||
title = stringResource(MR.strings.pref_flash_page),
|
||||
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
||||
),
|
||||
getDisplayGroup(readerPreferences = readerPref),
|
||||
getEInkGroup(readerPreferences = readerPref),
|
||||
getReadingGroup(readerPreferences = readerPref),
|
||||
getPagedGroup(readerPreferences = readerPref),
|
||||
getWebtoonGroup(readerPreferences = readerPref),
|
||||
|
@ -122,6 +119,65 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
|
||||
|
||||
val flashMillisPref = readerPreferences.flashDurationMillis()
|
||||
val flashMillis by flashMillisPref.collectAsState()
|
||||
|
||||
val flashIntervalPref = readerPreferences.flashPageInterval()
|
||||
val flashInterval by flashIntervalPref.collectAsState()
|
||||
|
||||
val flashColorPref = readerPreferences.flashColor()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = "E-Ink",
|
||||
preferenceItems = persistentListOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.flashOnPageChange(),
|
||||
title = stringResource(MR.strings.pref_flash_page),
|
||||
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||
min = 1,
|
||||
max = 15,
|
||||
title = stringResource(MR.strings.pref_flash_duration),
|
||||
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||
onValueChanged = {
|
||||
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
|
||||
true
|
||||
},
|
||||
enabled = flashPageState,
|
||||
),
|
||||
Preference.PreferenceItem.SliderPreference(
|
||||
value = flashInterval,
|
||||
min = 1,
|
||||
max = 10,
|
||||
title = stringResource(MR.strings.pref_flash_page_interval),
|
||||
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||
onValueChanged = {
|
||||
flashIntervalPref.set(it)
|
||||
true
|
||||
},
|
||||
enabled = flashPageState,
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = flashColorPref,
|
||||
title = stringResource(MR.strings.pref_flash_with),
|
||||
entries = persistentMapOf(
|
||||
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
|
||||
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
|
||||
ReaderPreferences.FlashColor.WHITE_BLACK
|
||||
to stringResource(MR.strings.pref_flash_style_white_black),
|
||||
),
|
||||
enabled = flashPageState,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||
return Preference.PreferenceGroup(
|
||||
|
|
|
@ -46,7 +46,7 @@ fun ExtensionReposContent(
|
|||
repos.forEach {
|
||||
item {
|
||||
ExtensionRepoListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
repo = it,
|
||||
onOpenWebsite = { onOpenWebsite(it) },
|
||||
onDelete = { onClickDelete(it.baseUrl) },
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -14,6 +15,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.coroutines.delay
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
|
@ -74,6 +76,7 @@ fun ExtensionRepoCreateDialog(
|
|||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -223,13 +223,12 @@ fun AppThemePreviewItem(
|
|||
contentAlignment = Alignment.BottomCenter,
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 3.dp,
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(32.dp)
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
|
@ -26,8 +26,6 @@ import androidx.compose.ui.unit.dp
|
|||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
@Composable
|
||||
fun <T> ListPreferenceWidget(
|
||||
|
@ -69,8 +67,8 @@ fun <T> ListPreferenceWidget(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
|
|
|
@ -30,8 +30,6 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
private enum class State {
|
||||
CHECKED, INVERSED, UNCHECKED
|
||||
|
@ -115,16 +113,8 @@ fun <T> TriStateListDialog(
|
|||
}
|
||||
}
|
||||
|
||||
if (!listState.isScrolledToStart()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
)
|
||||
}
|
||||
if (!listState.isScrolledToEnd()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
}
|
||||
if (listState.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (listState.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,19 +7,42 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Stable
|
||||
class DisplayRefreshHost {
|
||||
|
||||
internal var currentDisplayRefresh by mutableStateOf(false)
|
||||
private val readerPreferences = Injekt.get<ReaderPreferences>()
|
||||
|
||||
internal val flashMillis = readerPreferences.flashDurationMillis()
|
||||
internal val flashMode = readerPreferences.flashColor()
|
||||
|
||||
internal val flashIntervalPref = readerPreferences.flashPageInterval()
|
||||
|
||||
// Internal State for Flash
|
||||
private var flashInterval = flashIntervalPref.get()
|
||||
private var timesCalled = 0
|
||||
|
||||
fun flash() {
|
||||
currentDisplayRefresh = true
|
||||
if (timesCalled % flashInterval == 0) {
|
||||
currentDisplayRefresh = true
|
||||
}
|
||||
timesCalled += 1
|
||||
}
|
||||
|
||||
fun setInterval(interval: Int) {
|
||||
flashInterval = interval
|
||||
timesCalled = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,18 +52,39 @@ fun DisplayRefreshHost(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val currentDisplayRefresh = hostState.currentDisplayRefresh
|
||||
val refreshDuration by hostState.flashMillis.collectAsState()
|
||||
val flashMode by hostState.flashMode.collectAsState()
|
||||
val flashInterval by hostState.flashIntervalPref.collectAsState()
|
||||
|
||||
var currentColor by remember { mutableStateOf<Color?>(null) }
|
||||
|
||||
LaunchedEffect(currentDisplayRefresh) {
|
||||
if (currentDisplayRefresh) {
|
||||
delay(1.5.seconds)
|
||||
hostState.currentDisplayRefresh = false
|
||||
if (!currentDisplayRefresh) {
|
||||
currentColor = null
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val refreshDurationHalf = refreshDuration.milliseconds / 2
|
||||
currentColor = if (flashMode == ReaderPreferences.FlashColor.BLACK) {
|
||||
Color.Black
|
||||
} else {
|
||||
Color.White
|
||||
}
|
||||
delay(refreshDurationHalf)
|
||||
if (flashMode == ReaderPreferences.FlashColor.WHITE_BLACK) {
|
||||
currentColor = Color.Black
|
||||
}
|
||||
delay(refreshDurationHalf)
|
||||
hostState.currentDisplayRefresh = false
|
||||
}
|
||||
|
||||
LaunchedEffect(flashInterval) {
|
||||
hostState.setInterval(flashInterval)
|
||||
}
|
||||
|
||||
Canvas(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
if (currentDisplayRefresh) {
|
||||
drawRect(Color.Black)
|
||||
}
|
||||
currentColor?.let { drawRect(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,7 @@ fun ReaderPageActionsDialog(
|
|||
) {
|
||||
var showSetCoverDialog by remember { mutableStateOf(false) }
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
|
|
|
@ -5,10 +5,13 @@ import androidx.compose.material3.FilterChip
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.CheckboxItem
|
||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
|
||||
|
@ -19,9 +22,27 @@ private val themes = listOf(
|
|||
MR.strings.automatic_background to 3,
|
||||
)
|
||||
|
||||
private val flashColors = listOf(
|
||||
MR.strings.pref_flash_style_black to ReaderPreferences.FlashColor.BLACK,
|
||||
MR.strings.pref_flash_style_white to ReaderPreferences.FlashColor.WHITE,
|
||||
MR.strings.pref_flash_style_white_black to ReaderPreferences.FlashColor.WHITE_BLACK,
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
||||
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
||||
|
||||
val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState()
|
||||
|
||||
val flashMillisPref = screenModel.preferences.flashDurationMillis()
|
||||
val flashMillis by flashMillisPref.collectAsState()
|
||||
|
||||
val flashIntervalPref = screenModel.preferences.flashPageInterval()
|
||||
val flashInterval by flashIntervalPref.collectAsState()
|
||||
|
||||
val flashColorPref = screenModel.preferences.flashColor()
|
||||
val flashColor by flashColorPref.collectAsState()
|
||||
|
||||
SettingsChipRow(MR.strings.pref_reader_theme) {
|
||||
themes.map { (labelRes, value) ->
|
||||
FilterChip(
|
||||
|
@ -73,4 +94,33 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||
label = stringResource(MR.strings.pref_flash_page),
|
||||
pref = screenModel.preferences.flashOnPageChange(),
|
||||
)
|
||||
if (flashPageState) {
|
||||
SliderItem(
|
||||
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||
label = stringResource(MR.strings.pref_flash_duration),
|
||||
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
|
||||
min = 1,
|
||||
max = 15,
|
||||
)
|
||||
SliderItem(
|
||||
value = flashInterval,
|
||||
label = stringResource(MR.strings.pref_flash_page_interval),
|
||||
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||
onChange = {
|
||||
flashIntervalPref.set(it)
|
||||
},
|
||||
min = 1,
|
||||
max = 10,
|
||||
)
|
||||
SettingsChipRow(MR.strings.pref_flash_with) {
|
||||
flashColors.map { (labelRes, value) ->
|
||||
FilterChip(
|
||||
selected = flashColor == value,
|
||||
onClick = { flashColorPref.set(value) },
|
||||
label = { Text(stringResource(labelRes)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,12 @@ internal abstract class BaseColorScheme {
|
|||
abstract val darkScheme: ColorScheme
|
||||
abstract val lightScheme: ColorScheme
|
||||
|
||||
// Cannot be pure black as there's content scrolling behind it
|
||||
// https://m3.material.io/components/navigation-bar/guidelines#90615a71-607e-485e-9e09-778bfc080563
|
||||
private val surfaceContainer = Color(0xFF0C0C0C)
|
||||
private val surfaceContainerHigh = Color(0xFF131313)
|
||||
private val surfaceContainerHighest = Color(0xFF1B1B1B)
|
||||
|
||||
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme {
|
||||
if (!isDark) return lightScheme
|
||||
|
||||
|
@ -18,6 +24,12 @@ internal abstract class BaseColorScheme {
|
|||
onBackground = Color.White,
|
||||
surface = Color.Black,
|
||||
onSurface = Color.White,
|
||||
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
|
||||
surfaceContainerLowest = surfaceContainer,
|
||||
surfaceContainerLow = surfaceContainer,
|
||||
surfaceContainer = surfaceContainer, // Navigation bar background
|
||||
surfaceContainerHigh = surfaceContainerHigh,
|
||||
surfaceContainerHighest = surfaceContainerHighest,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,53 +19,77 @@ internal object GreenAppleColorScheme : BaseColorScheme() {
|
|||
|
||||
override val darkScheme = darkColorScheme(
|
||||
primary = Color(0xFF7ADB8F),
|
||||
onPrimary = Color(0xFF003915),
|
||||
primaryContainer = Color(0xFF005322),
|
||||
onPrimaryContainer = Color(0xFF96F8A9),
|
||||
inversePrimary = Color(0xFF006D2F),
|
||||
secondary = Color(0xFF7ADB8F),
|
||||
onSecondary = Color(0xFF003915),
|
||||
secondaryContainer = Color(0xFF005322),
|
||||
onSecondaryContainer = Color(0xFF96F8A9),
|
||||
tertiary = Color(0xFFFFB3AA),
|
||||
onTertiary = Color(0xFF680006),
|
||||
tertiaryContainer = Color(0xFF93000D),
|
||||
onTertiaryContainer = Color(0xFFFFDAD5),
|
||||
background = Color(0xFF1A1C19),
|
||||
onBackground = Color(0xFFE1E3DD),
|
||||
surface = Color(0xFF1A1C19),
|
||||
onSurface = Color(0xFFE1E3DD),
|
||||
surfaceVariant = Color(0xFF414941),
|
||||
onSurfaceVariant = Color(0xFFC1C8BE),
|
||||
surfaceTint = Color(0xFF7ADB8F),
|
||||
inverseSurface = Color(0xFFE1E3DD),
|
||||
inverseOnSurface = Color(0xFF1A1C19),
|
||||
outline = Color(0xFF8B9389),
|
||||
onPrimary = Color(0xFF003917),
|
||||
primaryContainer = Color(0xFF017737),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFF7ADB8F), // Unread badge
|
||||
onSecondary = Color(0xFF003917), // Unread badge text
|
||||
secondaryContainer = Color(0xFF017737), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selected icon
|
||||
tertiary = Color(0xFFFFB3AC), // Downloaded badge
|
||||
onTertiary = Color(0xFF680008), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFC7282A),
|
||||
onTertiaryContainer = Color(0xFFFFFFFF),
|
||||
error = Color(0xFFFFB4AB),
|
||||
onError = Color(0xFF690005),
|
||||
errorContainer = Color(0xFF93000A),
|
||||
onErrorContainer = Color(0xFFFFDAD6),
|
||||
background = Color(0xFF0F1510),
|
||||
onBackground = Color(0xFFDFE4DB),
|
||||
surface = Color(0xFF0F1510),
|
||||
onSurface = Color(0xFFDFE4DB),
|
||||
surfaceVariant = Color(0xFF3F493F), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFBECABC),
|
||||
outline = Color(0xFF889487),
|
||||
outlineVariant = Color(0xFF3F493F),
|
||||
scrim = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFFDFE4DB),
|
||||
inverseOnSurface = Color(0xFF2C322C),
|
||||
inversePrimary = Color(0xFF006D32),
|
||||
surfaceDim = Color(0xFF0F1510),
|
||||
surfaceBright = Color(0xFF353B35),
|
||||
surfaceContainerLowest = Color(0xFF0A0F0B),
|
||||
surfaceContainerLow = Color(0xFF181D18),
|
||||
surfaceContainer = Color(0xFF1C211C), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF262B26),
|
||||
surfaceContainerHighest = Color(0xFF313630),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
primary = Color(0xFF006D2F),
|
||||
primary = Color(0xFF005927),
|
||||
onPrimary = Color(0xFFFFFFFF),
|
||||
primaryContainer = Color(0xFF96F8A9),
|
||||
onPrimaryContainer = Color(0xFF002109),
|
||||
primaryContainer = Color(0xFF188140),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFF005927), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFF97f7a9), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF000000), // Navigation bar selected icon
|
||||
tertiary = Color(0xFF9D0012), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFD33131),
|
||||
onTertiaryContainer = Color(0xFFFFFFFF),
|
||||
error = Color(0xFFBA1A1A),
|
||||
onError = Color(0xFFFFFFFF),
|
||||
errorContainer = Color(0xFFFFDAD6),
|
||||
onErrorContainer = Color(0xFF410002),
|
||||
background = Color(0xFFF6FBF2),
|
||||
onBackground = Color(0xFF181D18),
|
||||
surface = Color(0xFFF6FBF2),
|
||||
onSurface = Color(0xFF181D18),
|
||||
surfaceVariant = Color(0xFFDAE6D7), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF3F493F),
|
||||
outline = Color(0xFF6F7A6E),
|
||||
outlineVariant = Color(0xFFBECABC),
|
||||
scrim = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFF2C322C),
|
||||
inverseOnSurface = Color(0xFFEDF2E9),
|
||||
inversePrimary = Color(0xFF7ADB8F),
|
||||
secondary = Color(0xFF006D2F),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFF96F8A9),
|
||||
onSecondaryContainer = Color(0xFF002109),
|
||||
tertiary = Color(0xFFB91D22),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
tertiaryContainer = Color(0xFFFFDAD5),
|
||||
onTertiaryContainer = Color(0xFF410003),
|
||||
background = Color(0xFFFBFDF7),
|
||||
onBackground = Color(0xFF1A1C19),
|
||||
surface = Color(0xFFFBFDF7),
|
||||
onSurface = Color(0xFF1A1C19),
|
||||
surfaceVariant = Color(0xFFDDE5DA),
|
||||
onSurfaceVariant = Color(0xFF414941),
|
||||
surfaceTint = Color(0xFF006D2F),
|
||||
inverseSurface = Color(0xFF2F312E),
|
||||
inverseOnSurface = Color(0xFFF0F2EC),
|
||||
outline = Color(0xFF717970),
|
||||
surfaceDim = Color(0xFFD6DCD3),
|
||||
surfaceBright = Color(0xFFF6FBF2),
|
||||
surfaceContainerLowest = Color(0xFFFFFFFF),
|
||||
surfaceContainerLow = Color(0xFFF0F5EC),
|
||||
surfaceContainer = Color(0xFFEAEFE6), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFE4EAE1),
|
||||
surfaceContainerHighest = Color(0xFFDFE4DB),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,53 +18,77 @@ internal object LavenderColorScheme : BaseColorScheme() {
|
|||
|
||||
override val darkScheme = darkColorScheme(
|
||||
primary = Color(0xFFA177FF),
|
||||
onPrimary = Color(0xFF111129),
|
||||
onPrimary = Color(0xFF3D0090),
|
||||
primaryContainer = Color(0xFFA177FF),
|
||||
onPrimaryContainer = Color(0xFF111129),
|
||||
inversePrimary = Color(0xFF006D2F),
|
||||
secondary = Color(0xFFA177FF),
|
||||
onSecondary = Color(0xFF111129),
|
||||
secondaryContainer = Color(0xFFA177FF),
|
||||
onSecondaryContainer = Color(0xFF111129),
|
||||
tertiary = Color(0xFF5E25E1),
|
||||
onTertiary = Color(0xFFE8E8E8),
|
||||
tertiaryContainer = Color(0xFF111129),
|
||||
onTertiaryContainer = Color(0xFFDEE8FF),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFA177FF), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFF423271), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFA177FF), // Navigation bar selected icon
|
||||
tertiary = Color(0xFFCDBDFF), // Downloaded badge
|
||||
onTertiary = Color(0xFF360096), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF5512D8),
|
||||
onTertiaryContainer = Color(0xFFEFE6FF),
|
||||
error = Color(0xFFFFB4AB),
|
||||
onError = Color(0xFF690005),
|
||||
errorContainer = Color(0xFF93000A),
|
||||
onErrorContainer = Color(0xFFFFDAD6),
|
||||
background = Color(0xFF111129),
|
||||
onBackground = Color(0xFFDEE8FF),
|
||||
onBackground = Color(0xFFE7E0EC),
|
||||
surface = Color(0xFF111129),
|
||||
onSurface = Color(0xFFDEE8FF),
|
||||
surfaceVariant = Color(0x2CB6B6B6),
|
||||
onSurfaceVariant = Color(0xFFE8E8E8),
|
||||
surfaceTint = Color(0xFFA177FF),
|
||||
inverseSurface = Color(0xFF221247),
|
||||
inverseOnSurface = Color(0xFFDEE8FF),
|
||||
outline = Color(0xA8905FFF),
|
||||
onSurface = Color(0xFFE7E0EC),
|
||||
surfaceVariant = Color(0xFF3D2F6B), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFCBC3D6),
|
||||
outline = Color(0xFF958E9F),
|
||||
outlineVariant = Color(0xFF4A4453),
|
||||
scrim = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFFE7E0EC),
|
||||
inverseOnSurface = Color(0xFF322F38),
|
||||
inversePrimary = Color(0xFF6D41C8),
|
||||
surfaceDim = Color(0xFF111129),
|
||||
surfaceBright = Color(0xFF3B3841),
|
||||
surfaceContainerLowest = Color(0xFF15132d),
|
||||
surfaceContainerLow = Color(0xFF171531),
|
||||
surfaceContainer = Color(0xFF1D193B), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF241f41),
|
||||
surfaceContainerHighest = Color(0xFF282446),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
primary = Color(0xFF7B46AF),
|
||||
onPrimary = Color(0xFFEDE2FF),
|
||||
primary = Color(0xFF6D41C8),
|
||||
onPrimary = Color(0xFFFFFFFF),
|
||||
primaryContainer = Color(0xFF7B46AF),
|
||||
onPrimaryContainer = Color(0xFFEDE2FF),
|
||||
inversePrimary = Color(0xFFD6BAFF),
|
||||
secondary = Color(0xFF7B46AF),
|
||||
onSecondary = Color(0xFFEDE2FF),
|
||||
secondaryContainer = Color(0xFF7B46AF),
|
||||
onSecondaryContainer = Color(0xFFEDE2FF),
|
||||
tertiary = Color(0xFFEDE2FF),
|
||||
onTertiary = Color(0xFF7B46AF),
|
||||
tertiaryContainer = Color(0xFFEDE2FF),
|
||||
onTertiaryContainer = Color(0xFF7B46AF),
|
||||
onPrimaryContainer = Color(0xFF130038),
|
||||
secondary = Color(0xFF7B46AF), // Unread badge
|
||||
onSecondary = Color(0xFFEDE2FF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFC9B0E6), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF7B46AF), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFEDE2FF), // Downloaded badge
|
||||
onTertiary = Color(0xFF7B46AF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF6D3BF0),
|
||||
onTertiaryContainer = Color(0xFFFFFFFF),
|
||||
error = Color(0xFFBA1A1A),
|
||||
onError = Color(0xFFFFFFFF),
|
||||
errorContainer = Color(0xFFFFDAD6),
|
||||
onErrorContainer = Color(0xFF410002),
|
||||
background = Color(0xFFEDE2FF),
|
||||
onBackground = Color(0xFF1B1B22),
|
||||
onBackground = Color(0xFF1D1A22),
|
||||
surface = Color(0xFFEDE2FF),
|
||||
onSurface = Color(0xFF1B1B22),
|
||||
surfaceVariant = Color(0xFFB9B0CC),
|
||||
onSurfaceVariant = Color(0xD849454E),
|
||||
surfaceTint = Color(0xFF7B46AF),
|
||||
inverseSurface = Color(0xFF313033),
|
||||
inverseOnSurface = Color(0xFFF3EFF4),
|
||||
outline = Color(0xFF7B46AF),
|
||||
onSurface = Color(0xFF1D1A22),
|
||||
surfaceVariant = Color(0xFFE4D5F8), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF4A4453),
|
||||
outline = Color(0xFF7B7485),
|
||||
outlineVariant = Color(0xFFCBC3D6),
|
||||
scrim = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFF322F38),
|
||||
inverseOnSurface = Color(0xFFF5EEFA),
|
||||
inversePrimary = Color(0xFFA177FF),
|
||||
surfaceDim = Color(0xFFDED7E3),
|
||||
surfaceBright = Color(0xFFEDE2FF),
|
||||
surfaceContainerLowest = Color(0xFFDACCEC),
|
||||
surfaceContainerLow = Color(0xFFDED0F1),
|
||||
surfaceContainer = Color(0xFFE4D5F8), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFEADCFD),
|
||||
surfaceContainerHighest = Color(0xFFEEE2FF),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,24 +23,29 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFBD1C5C),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
inversePrimary = Color(0xFFF02475),
|
||||
secondary = Color(0xFFF02475),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFF02475),
|
||||
onSecondaryContainer = Color(0xFFFFFFFF),
|
||||
tertiary = Color(0xFF55971C),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFF02475), // Unread badge
|
||||
onSecondary = Color(0xFF16151D), // Unread badge text
|
||||
secondaryContainer = Color(0xFF66183C), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFF02475), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF55971C), // Downloaded badge
|
||||
onTertiary = Color(0xFF16151D), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF386412),
|
||||
onTertiaryContainer = Color(0xFFE5E1E5),
|
||||
background = Color(0xFF16151D),
|
||||
onBackground = Color(0xFFE5E1E5),
|
||||
surface = Color(0xFF16151D),
|
||||
onSurface = Color(0xFFE5E1E5),
|
||||
surfaceVariant = Color(0xFF524346),
|
||||
surfaceVariant = Color(0xFF281624), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFD6C1C4),
|
||||
surfaceTint = Color(0xFFF02475),
|
||||
inverseSurface = Color(0xFF333043),
|
||||
inverseOnSurface = Color(0xFFFFFFFF),
|
||||
outline = Color(0xFF9F8C8F),
|
||||
surfaceContainerLowest = Color(0xFF221320),
|
||||
surfaceContainerLow = Color(0xFF251522),
|
||||
surfaceContainer = Color(0xFF281624), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF2D1C2A),
|
||||
surfaceContainerHighest = Color(0xFF2F1F2C),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -49,23 +54,28 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFFFD9E1),
|
||||
onPrimaryContainer = Color(0xFF3F0017),
|
||||
inversePrimary = Color(0xFFFFB1C4),
|
||||
secondary = Color(0xFFBB0054),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFFFD9E1),
|
||||
onSecondaryContainer = Color(0xFF3F0017),
|
||||
tertiary = Color(0xFF006638),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFBB0054), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFEFBAD4), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFD1377C), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF006638), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF00894b),
|
||||
onTertiaryContainer = Color(0xFF2D1600),
|
||||
background = Color(0xFFFFFBFF),
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
surface = Color(0xFFFFFBFF),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
surfaceVariant = Color(0xFFF3DDE0),
|
||||
surfaceVariant = Color(0xFFF9E6F1), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF524346),
|
||||
surfaceTint = Color(0xFFBB0054),
|
||||
inverseSurface = Color(0xFF313033),
|
||||
inverseOnSurface = Color(0xFFF4F0F4),
|
||||
outline = Color(0xFF847376),
|
||||
surfaceContainerLowest = Color(0xFFDAC0CD),
|
||||
surfaceContainerLow = Color(0xFFE8D1DD),
|
||||
surfaceContainer = Color(0xFFF9E6F1), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFFCF3F8),
|
||||
surfaceContainerHighest = Color(0xFFFEF9FC),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,19 +17,19 @@ internal object NordColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF88C0D0),
|
||||
onPrimaryContainer = Color(0xFF2E3440),
|
||||
inversePrimary = Color(0xFF397E91),
|
||||
secondary = Color(0xFF81A1C1),
|
||||
onSecondary = Color(0xFF2E3440),
|
||||
secondaryContainer = Color(0xFF81A1C1),
|
||||
onSecondaryContainer = Color(0xFF2E3440),
|
||||
tertiary = Color(0xFF5E81AC),
|
||||
onTertiary = Color(0xFF000000),
|
||||
secondary = Color(0xFF81A1C1), // Unread badge
|
||||
onSecondary = Color(0xFF2E3440), // Unread badge text
|
||||
secondaryContainer = Color(0xFF506275), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF88C0D0), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF5E81AC), // Downloaded badge
|
||||
onTertiary = Color(0xFF000000), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF5E81AC),
|
||||
onTertiaryContainer = Color(0xFF000000),
|
||||
background = Color(0xFF2E3440),
|
||||
onBackground = Color(0xFFECEFF4),
|
||||
surface = Color(0xFF3B4252),
|
||||
surface = Color(0xFF2E3440),
|
||||
onSurface = Color(0xFFECEFF4),
|
||||
surfaceVariant = Color(0xFF2E3440),
|
||||
surfaceVariant = Color(0xFF414C5C), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFECEFF4),
|
||||
surfaceTint = Color(0xFF88C0D0),
|
||||
inverseSurface = Color(0xFFD8DEE9),
|
||||
|
@ -39,6 +39,11 @@ internal object NordColorScheme : BaseColorScheme() {
|
|||
onError = Color(0xFF2E3440),
|
||||
errorContainer = Color(0xFFBF616A),
|
||||
onErrorContainer = Color(0xFF000000),
|
||||
surfaceContainerLowest = Color(0xFF373F4D),
|
||||
surfaceContainerLow = Color(0xFF3E4756),
|
||||
surfaceContainer = Color(0xFF414C5C),
|
||||
surfaceContainerHigh = Color(0xFF4E5766),
|
||||
surfaceContainerHighest = Color(0xFF505968), // Navigation bar background
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -47,19 +52,19 @@ internal object NordColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF5E81AC),
|
||||
onPrimaryContainer = Color(0xFF000000),
|
||||
inversePrimary = Color(0xFF8CA8CD),
|
||||
secondary = Color(0xFF81A1C1),
|
||||
onSecondary = Color(0xFF2E3440),
|
||||
secondaryContainer = Color(0xFF81A1C1),
|
||||
onSecondaryContainer = Color(0xFF2E3440),
|
||||
tertiary = Color(0xFF88C0D0),
|
||||
onTertiary = Color(0xFF2E3440),
|
||||
secondary = Color(0xFF81A1C1), // Unread badge
|
||||
onSecondary = Color(0xFF2E3440), // Unread badge text
|
||||
secondaryContainer = Color(0xFF91B4D7), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF88C0D0), // Downloaded badge
|
||||
onTertiary = Color(0xFF2E3440), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF88C0D0),
|
||||
onTertiaryContainer = Color(0xFF2E3440),
|
||||
background = Color(0xFFECEFF4),
|
||||
onBackground = Color(0xFF2E3440),
|
||||
surface = Color(0xFFE5E9F0),
|
||||
onSurface = Color(0xFF2E3440),
|
||||
surfaceVariant = Color(0xFFffffff),
|
||||
surfaceVariant = Color(0xFFDAE0EA), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF2E3440),
|
||||
surfaceTint = Color(0xFF5E81AC),
|
||||
inverseSurface = Color(0xFF3B4252),
|
||||
|
@ -68,5 +73,10 @@ internal object NordColorScheme : BaseColorScheme() {
|
|||
onError = Color(0xFFECEFF4),
|
||||
errorContainer = Color(0xFFBF616A),
|
||||
onErrorContainer = Color(0xFF000000),
|
||||
surfaceContainerLowest = Color(0xFFD1D7E0),
|
||||
surfaceContainerLow = Color(0xFFD6DCE6),
|
||||
surfaceContainer = Color(0xFFDAE0EA), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFE9EDF3),
|
||||
surfaceContainerHighest = Color(0xFFF2F4F8),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,54 +18,78 @@ import androidx.compose.ui.graphics.Color
|
|||
internal object StrawberryColorScheme : BaseColorScheme() {
|
||||
|
||||
override val darkScheme = darkColorScheme(
|
||||
primary = Color(0xFFFFB2B9),
|
||||
onPrimary = Color(0xFF67001B),
|
||||
primaryContainer = Color(0xFF91002A),
|
||||
onPrimaryContainer = Color(0xFFFFDADD),
|
||||
inversePrimary = Color(0xFFB61E40),
|
||||
secondary = Color(0xFFFFB2B9),
|
||||
onSecondary = Color(0xFF67001B),
|
||||
secondaryContainer = Color(0xFF91002A),
|
||||
onSecondaryContainer = Color(0xFFFFDADD),
|
||||
tertiary = Color(0xFFE8C08E),
|
||||
onTertiary = Color(0xFF432C06),
|
||||
tertiaryContainer = Color(0xFF5D421B),
|
||||
onTertiaryContainer = Color(0xFFFFDDB1),
|
||||
primary = Color(0xFFFFB2B8),
|
||||
onPrimary = Color(0xFF67001D),
|
||||
primaryContainer = Color(0xFFD53855),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFED4A65), // Unread badge
|
||||
onSecondary = Color(0xFF201A1A), // Unread badge text
|
||||
secondaryContainer = Color(0xFF91002A), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFE8C08E), // Downloaded badge
|
||||
onTertiary = Color(0xFF201A1A), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF775930),
|
||||
onTertiaryContainer = Color(0xFFFFF7F1),
|
||||
error = Color(0xFFFFB4AB),
|
||||
onError = Color(0xFF690005),
|
||||
errorContainer = Color(0xFF93000A),
|
||||
onErrorContainer = Color(0xFFFFDAD6),
|
||||
background = Color(0xFF201A1A),
|
||||
onBackground = Color(0xFFECDFDF),
|
||||
onBackground = Color(0xFFF7DCDD),
|
||||
surface = Color(0xFF201A1A),
|
||||
onSurface = Color(0xFFECDFDF),
|
||||
surfaceVariant = Color(0xFF534344),
|
||||
onSurfaceVariant = Color(0xFFD7C1C2),
|
||||
surfaceTint = Color(0xFFFFB2B9),
|
||||
inverseSurface = Color(0xFFECDFDF),
|
||||
inverseOnSurface = Color(0xFF201A1A),
|
||||
outline = Color(0xFFA08C8D),
|
||||
onSurface = Color(0xFFF7DCDD),
|
||||
surfaceVariant = Color(0xFF322727), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFE1BEC0),
|
||||
outline = Color(0xFFA9898B),
|
||||
outlineVariant = Color(0xFF594042),
|
||||
scrim = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFFF7DCDD),
|
||||
inverseOnSurface = Color(0xFF3D2C2D),
|
||||
inversePrimary = Color(0xFFB61F40),
|
||||
surfaceDim = Color(0xFF1D1011),
|
||||
surfaceBright = Color(0xFF463536),
|
||||
surfaceContainerLowest = Color(0xFF2C2222),
|
||||
surfaceContainerLow = Color(0xFF302525),
|
||||
surfaceContainer = Color(0xFF322727), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF3C2F2F),
|
||||
surfaceContainerHighest = Color(0xFF463737),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
primary = Color(0xFFB61E40),
|
||||
primary = Color(0xFFA10833),
|
||||
onPrimary = Color(0xFFFFFFFF),
|
||||
primaryContainer = Color(0xFFFFDADD),
|
||||
onPrimaryContainer = Color(0xFF40000D),
|
||||
inversePrimary = Color(0xFFFFB2B9),
|
||||
secondary = Color(0xFFB61E40),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFFFDADD),
|
||||
onSecondaryContainer = Color(0xFF40000D),
|
||||
tertiary = Color(0xFF775930),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
tertiaryContainer = Color(0xFFFFDDB1),
|
||||
onTertiaryContainer = Color(0xFF2A1800),
|
||||
background = Color(0xFFFCFCFC),
|
||||
onBackground = Color(0xFF201A1A),
|
||||
surface = Color(0xFFFCFCFC),
|
||||
onSurface = Color(0xFF201A1A),
|
||||
surfaceVariant = Color(0xFFF4DDDD),
|
||||
onSurfaceVariant = Color(0xFF534344),
|
||||
surfaceTint = Color(0xFFB61E40),
|
||||
inverseSurface = Color(0xFF362F2F),
|
||||
inverseOnSurface = Color(0xFFFBEDED),
|
||||
outline = Color(0xFF857374),
|
||||
primaryContainer = Color(0xFFD53855),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFA10833), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFD53855), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFF6EAED), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF5F441D), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF87683D),
|
||||
onTertiaryContainer = Color(0xFFFFFFFF),
|
||||
error = Color(0xFFBA1A1A),
|
||||
onError = Color(0xFFFFFFFF),
|
||||
errorContainer = Color(0xFFFFDAD6),
|
||||
onErrorContainer = Color(0xFF410002),
|
||||
background = Color(0xFFFAFAFA),
|
||||
onBackground = Color(0xFF261819),
|
||||
surface = Color(0xFFFAFAFA),
|
||||
onSurface = Color(0xFF261819),
|
||||
surfaceVariant = Color(0xFFF6EAED), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF594042),
|
||||
outline = Color(0xFF8D7071),
|
||||
outlineVariant = Color(0xFFE1BEC0),
|
||||
scrim = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFF3D2C2D),
|
||||
inverseOnSurface = Color(0xFFFFECED),
|
||||
inversePrimary = Color(0xFFFFB2B8),
|
||||
surfaceDim = Color(0xFFEED4D5),
|
||||
surfaceBright = Color(0xFFFFF8F7),
|
||||
surfaceContainerLowest = Color(0xFFF7DCDD),
|
||||
surfaceContainerLow = Color(0xFFFDE2E3),
|
||||
surfaceContainer = Color(0xFFF6EAED), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFFFF0F0),
|
||||
surfaceContainerHighest = Color(0xFFFFFFFF),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,19 +22,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF00429B),
|
||||
onPrimaryContainer = Color(0xFFD9E2FF),
|
||||
inversePrimary = Color(0xFF0058CA),
|
||||
secondary = Color(0xFFB0C6FF),
|
||||
onSecondary = Color(0xFF002D6E),
|
||||
secondaryContainer = Color(0xFF00429B),
|
||||
onSecondaryContainer = Color(0xFFD9E2FF),
|
||||
tertiary = Color(0xFF7ADC77),
|
||||
onTertiary = Color(0xFF003909),
|
||||
secondary = Color(0xFFB0C6FF), // Unread badge
|
||||
onSecondary = Color(0xFF002D6E), // Unread badge text
|
||||
secondaryContainer = Color(0xFF00429B), // Navigation bar selector pill & pro
|
||||
onSecondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF7ADC77), // Downloaded badge
|
||||
onTertiary = Color(0xFF003909), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF005312),
|
||||
onTertiaryContainer = Color(0xFF95F990),
|
||||
background = Color(0xFF1B1B1F),
|
||||
onBackground = Color(0xFFE3E2E6),
|
||||
surface = Color(0xFF1B1B1F),
|
||||
onSurface = Color(0xFFE3E2E6),
|
||||
surfaceVariant = Color(0xFF44464F),
|
||||
surfaceVariant = Color(0xFF211F26), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFC5C6D0),
|
||||
surfaceTint = Color(0xFFB0C6FF),
|
||||
inverseSurface = Color(0xFFE3E2E6),
|
||||
|
@ -45,6 +45,11 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
|
|||
onErrorContainer = Color(0xFFFFDAD6),
|
||||
outline = Color(0xFF8F9099),
|
||||
outlineVariant = Color(0xFF44464F),
|
||||
surfaceContainerLowest = Color(0xFF1A181D),
|
||||
surfaceContainerLow = Color(0xFF1E1C22),
|
||||
surfaceContainer = Color(0xFF211F26), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF292730),
|
||||
surfaceContainerHighest = Color(0xFF302E38),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -53,19 +58,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFD9E2FF),
|
||||
onPrimaryContainer = Color(0xFF001945),
|
||||
inversePrimary = Color(0xFFB0C6FF),
|
||||
secondary = Color(0xFF0058CA),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFD9E2FF),
|
||||
onSecondaryContainer = Color(0xFF001945),
|
||||
tertiary = Color(0xFF006E1B),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFF0058CA), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF001945), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF006E1B), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF95F990),
|
||||
onTertiaryContainer = Color(0xFF002203),
|
||||
background = Color(0xFFFEFBFF),
|
||||
onBackground = Color(0xFF1B1B1F),
|
||||
surface = Color(0xFFFEFBFF),
|
||||
onSurface = Color(0xFF1B1B1F),
|
||||
surfaceVariant = Color(0xFFE1E2EC),
|
||||
surfaceVariant = Color(0xFFF3EDF7), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF44464F),
|
||||
surfaceTint = Color(0xFF0058CA),
|
||||
inverseSurface = Color(0xFF303034),
|
||||
|
@ -76,5 +81,10 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
|
|||
onErrorContainer = Color(0xFF410002),
|
||||
outline = Color(0xFF757780),
|
||||
outlineVariant = Color(0xFFC5C6D0),
|
||||
surfaceContainerLowest = Color(0xFFF5F1F8),
|
||||
surfaceContainerLow = Color(0xFFF7F2FA),
|
||||
surfaceContainer = Color(0xFFF3EDF7), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFFCF7FF),
|
||||
surfaceContainerHighest = Color(0xFFFCF7FF),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,24 +23,29 @@ internal object TakoColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFF3B375),
|
||||
onPrimaryContainer = Color(0xFF38294E),
|
||||
inversePrimary = Color(0xFF84531E),
|
||||
secondary = Color(0xFFF3B375),
|
||||
onSecondary = Color(0xFF38294E),
|
||||
secondaryContainer = Color(0xFFF3B375),
|
||||
onSecondaryContainer = Color(0xFF38294E),
|
||||
tertiary = Color(0xFF66577E),
|
||||
onTertiary = Color(0xFFF3B375),
|
||||
secondary = Color(0xFFF3B375), // Unread badge
|
||||
onSecondary = Color(0xFF38294E), // Unread badge text
|
||||
secondaryContainer = Color(0xFF5C4D4B), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFF3B375), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF66577E), // Downloaded badge
|
||||
onTertiary = Color(0xFFF3B375), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF4E4065),
|
||||
onTertiaryContainer = Color(0xFFEDDCFF),
|
||||
background = Color(0xFF21212E),
|
||||
onBackground = Color(0xFFE3E0F2),
|
||||
surface = Color(0xFF21212E),
|
||||
onSurface = Color(0xFFE3E0F2),
|
||||
surfaceVariant = Color(0xFF49454E),
|
||||
surfaceVariant = Color(0xFF2A2A3C), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFCBC4CE),
|
||||
surfaceTint = Color(0xFF66577E),
|
||||
inverseSurface = Color(0xFFE5E1E6),
|
||||
inverseOnSurface = Color(0xFF1B1B1E),
|
||||
outline = Color(0xFF958F99),
|
||||
surfaceContainerLowest = Color(0xFF20202E),
|
||||
surfaceContainerLow = Color(0xFF262636),
|
||||
surfaceContainer = Color(0xFF2A2A3C), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF303044),
|
||||
surfaceContainerHighest = Color(0xFF36364D),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -49,23 +54,28 @@ internal object TakoColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF66577E),
|
||||
onPrimaryContainer = Color(0xFFF3B375),
|
||||
inversePrimary = Color(0xFFD6BAFF),
|
||||
secondary = Color(0xFF66577E),
|
||||
onSecondary = Color(0xFFF3B375),
|
||||
secondaryContainer = Color(0xFF66577E),
|
||||
onSecondaryContainer = Color(0xFFF3B375),
|
||||
tertiary = Color(0xFFF3B375),
|
||||
onTertiary = Color(0xFF574360),
|
||||
secondary = Color(0xFF66577E), // Unread badge
|
||||
onSecondary = Color(0xFFF3B375), // Unread badge text
|
||||
secondaryContainer = Color(0xFFC8BED0), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF66577E), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFF3B375), // Downloaded badge
|
||||
onTertiary = Color(0xFF574360), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFFDD6B0),
|
||||
onTertiaryContainer = Color(0xFF221437),
|
||||
background = Color(0xFFF7F5FF),
|
||||
onBackground = Color(0xFF1B1B22),
|
||||
surface = Color(0xFFF7F5FF),
|
||||
onSurface = Color(0xFF1B1B22),
|
||||
surfaceVariant = Color(0xFFE8E0EB),
|
||||
surfaceVariant = Color(0xFFE8E0EB), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF49454E),
|
||||
surfaceTint = Color(0xFF66577E),
|
||||
inverseSurface = Color(0xFF313033),
|
||||
inverseOnSurface = Color(0xFFF3EFF4),
|
||||
outline = Color(0xFF7A757E),
|
||||
surfaceContainerLowest = Color(0xFFD7D0DA),
|
||||
surfaceContainerLow = Color(0xFFDFD8E2),
|
||||
surfaceContainer = Color(0xFFE8E0EB), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFEEE6F1),
|
||||
surfaceContainerHighest = Color(0xFFF7EEFA),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,24 +15,29 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF40E0D0),
|
||||
onPrimaryContainer = Color(0xFF000000),
|
||||
inversePrimary = Color(0xFF008080),
|
||||
secondary = Color(0xFF40E0D0),
|
||||
onSecondary = Color(0xFF000000),
|
||||
secondaryContainer = Color(0xFF18544E),
|
||||
onSecondaryContainer = Color(0xFF40E0D0),
|
||||
tertiary = Color(0xFFBF1F2F),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFF40E0D0), // Unread badge
|
||||
onSecondary = Color(0xFF000000), // Unread badge text
|
||||
secondaryContainer = Color(0xFF18544E), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF40E0D0), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFBF1F2F), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF200508),
|
||||
onTertiaryContainer = Color(0xFFBF1F2F),
|
||||
background = Color(0xFF202125),
|
||||
onBackground = Color(0xFFDFDEDA),
|
||||
surface = Color(0xFF202125),
|
||||
onSurface = Color(0xFFDFDEDA),
|
||||
surfaceVariant = Color(0xFF3F4947),
|
||||
surfaceVariant = Color(0xFF233133), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFDFDEDA),
|
||||
surfaceTint = Color(0xFF40E0D0),
|
||||
inverseSurface = Color(0xFFDFDEDA),
|
||||
inverseOnSurface = Color(0xFF202125),
|
||||
outline = Color(0xFF899391),
|
||||
surfaceContainerLowest = Color(0xFF202C2E),
|
||||
surfaceContainerLow = Color(0xFF222F31),
|
||||
surfaceContainer = Color(0xFF233133), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF28383A),
|
||||
surfaceContainerHighest = Color(0xFF2F4244),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -41,23 +46,28 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF008080),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
inversePrimary = Color(0xFF40E0D0),
|
||||
secondary = Color(0xFF008080),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFBFDFDF),
|
||||
onSecondaryContainer = Color(0xFF008080),
|
||||
tertiary = Color(0xFFFF7F7F),
|
||||
onTertiary = Color(0xFF000000),
|
||||
secondary = Color(0xFF008080), // Unread badge text
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFCFE5E4), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF008080), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFFF7F7F), // Downloaded badge
|
||||
onTertiary = Color(0xFF000000), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF2A1616),
|
||||
onTertiaryContainer = Color(0xFFFF7F7F),
|
||||
background = Color(0xFFFAFAFA),
|
||||
onBackground = Color(0xFF050505),
|
||||
surface = Color(0xFFFAFAFA),
|
||||
onSurface = Color(0xFF050505),
|
||||
surfaceVariant = Color(0xFFDAE5E2),
|
||||
surfaceVariant = Color(0xFFEBF3F1), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF050505),
|
||||
surfaceTint = Color(0xFFBFDFDF),
|
||||
inverseSurface = Color(0xFF050505),
|
||||
inverseOnSurface = Color(0xFFFAFAFA),
|
||||
outline = Color(0xFF6F7977),
|
||||
surfaceContainerLowest = Color(0xFFE1E9E7),
|
||||
surfaceContainerLow = Color(0xFFE6EEEC),
|
||||
surfaceContainer = Color(0xFFEBF3F1), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFF0F8F6),
|
||||
surfaceContainerHighest = Color(0xFFF7FFFD),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,24 +22,29 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF004d61),
|
||||
onPrimaryContainer = Color(0xFFb8eaff),
|
||||
inversePrimary = Color(0xFFa12b03),
|
||||
secondary = Color(0xFF5ed4fc),
|
||||
onSecondary = Color(0xFF003544),
|
||||
secondaryContainer = Color(0xFF004d61),
|
||||
onSecondaryContainer = Color(0xFFb8eaff),
|
||||
tertiary = Color(0xFF92f7bc),
|
||||
onTertiary = Color(0xFF001c3b),
|
||||
secondary = Color(0xFF5ed4fc), // Unread badge
|
||||
onSecondary = Color(0xFF003544), // Unread badge text
|
||||
secondaryContainer = Color(0xFF004d61), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFb8eaff), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF92f7bc), // Downloaded badge
|
||||
onTertiary = Color(0xFF001c3b), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFc3fada),
|
||||
onTertiaryContainer = Color(0xFF78ffd6),
|
||||
background = Color(0xFF001c3b),
|
||||
onBackground = Color(0xFFd5e3ff),
|
||||
surface = Color(0xFF001c3b),
|
||||
onSurface = Color(0xFFd5e3ff),
|
||||
surfaceVariant = Color(0xFF40484c),
|
||||
surfaceVariant = Color(0xFF082b4b), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFbfc8cc),
|
||||
surfaceTint = Color(0xFF5ed4fc),
|
||||
inverseSurface = Color(0xFFffe3c4),
|
||||
inverseOnSurface = Color(0xFF001c3b),
|
||||
outline = Color(0xFF8a9296),
|
||||
surfaceContainerLowest = Color(0xFF072642),
|
||||
surfaceContainerLow = Color(0xFF072947),
|
||||
surfaceContainer = Color(0xFF082b4b), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF093257),
|
||||
surfaceContainerHighest = Color(0xFF0A3861),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -48,23 +53,28 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFB4D4DF),
|
||||
onPrimaryContainer = Color(0xFF001f28),
|
||||
inversePrimary = Color(0xFFff987f),
|
||||
secondary = Color(0xFF006780),
|
||||
onSecondary = Color(0xFFffffff),
|
||||
secondaryContainer = Color(0xFFb8eaff),
|
||||
onSecondaryContainer = Color(0xFF001f28),
|
||||
tertiary = Color(0xFF92f7bc),
|
||||
onTertiary = Color(0xFF001c3b),
|
||||
secondary = Color(0xFF006780), // Unread badge
|
||||
onSecondary = Color(0xFFffffff), // Unread badge text
|
||||
secondaryContainer = Color(0xFF9AE1FF), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF001f28), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF92f7bc), // Downloaded badge
|
||||
onTertiary = Color(0xFF001c3b), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFc3fada),
|
||||
onTertiaryContainer = Color(0xFF78ffd6),
|
||||
background = Color(0xFFfdfbff),
|
||||
onBackground = Color(0xFF001c3b),
|
||||
surface = Color(0xFFfdfbff),
|
||||
onSurface = Color(0xFF001c3b),
|
||||
surfaceVariant = Color(0xFFdce4e8),
|
||||
surfaceVariant = Color(0xFFe8eff5), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF40484c),
|
||||
surfaceTint = Color(0xFF006780),
|
||||
inverseSurface = Color(0xFF020400),
|
||||
inverseOnSurface = Color(0xFFffe3c4),
|
||||
outline = Color(0xFF70787c),
|
||||
surfaceContainerLowest = Color(0xFFe2e8ec),
|
||||
surfaceContainerLow = Color(0xFFe5ecf1),
|
||||
surfaceContainer = Color(0xFFe8eff5), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFedf4fA),
|
||||
surfaceContainerHighest = Color(0xFFf5faff),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,24 +17,29 @@ internal object YinYangColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFFFFFFF),
|
||||
onPrimaryContainer = Color(0xFF000000),
|
||||
inversePrimary = Color(0xFFCECECE),
|
||||
secondary = Color(0xFFFFFFFF),
|
||||
onSecondary = Color(0xFF5A5A5A),
|
||||
secondaryContainer = Color(0xFF717171),
|
||||
onSecondaryContainer = Color(0xFFE4E4E4),
|
||||
tertiary = Color(0xFF000000),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFFFFFFF), // Unread badge
|
||||
onSecondary = Color(0xFF5A5A5A), // Unread badge text
|
||||
secondaryContainer = Color(0xFF717171), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFE4E4E4), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF000000), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF00419E),
|
||||
onTertiaryContainer = Color(0xFFD8E2FF),
|
||||
background = Color(0xFF1E1E1E),
|
||||
onBackground = Color(0xFFE6E6E6),
|
||||
surface = Color(0xFF1E1E1E),
|
||||
onSurface = Color(0xFFE6E6E6),
|
||||
surfaceVariant = Color(0xFF4E4E4E),
|
||||
surfaceVariant = Color(0xFF313131), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFD1D1D1),
|
||||
surfaceTint = Color(0xFFFFFFFF),
|
||||
inverseSurface = Color(0xFFE6E6E6),
|
||||
inverseOnSurface = Color(0xFF1E1E1E),
|
||||
outline = Color(0xFF999999),
|
||||
surfaceContainerLowest = Color(0xFF2A2A2A),
|
||||
surfaceContainerLow = Color(0xFF2D2D2D),
|
||||
surfaceContainer = Color(0xFF313131), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF383838),
|
||||
surfaceContainerHighest = Color(0xFF3F3F3F),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -43,23 +48,28 @@ internal object YinYangColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF000000),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF),
|
||||
inversePrimary = Color(0xFFA6A6A6),
|
||||
secondary = Color(0xFF000000),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFDDDDDD),
|
||||
onSecondaryContainer = Color(0xFF0C0C0C),
|
||||
tertiary = Color(0xFFFFFFFF),
|
||||
onTertiary = Color(0xFF000000),
|
||||
secondary = Color(0xFF000000), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFDDDDDD), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF0C0C0C), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFFFFFFF), // Downloaded badge
|
||||
onTertiary = Color(0xFF000000), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFD8E2FF),
|
||||
onTertiaryContainer = Color(0xFF001947),
|
||||
background = Color(0xFFFDFDFD),
|
||||
onBackground = Color(0xFF222222),
|
||||
surface = Color(0xFFFDFDFD),
|
||||
onSurface = Color(0xFF222222),
|
||||
surfaceVariant = Color(0xFFEDEDED),
|
||||
surfaceVariant = Color(0xFFE8E8E8), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF515151),
|
||||
surfaceTint = Color(0xFF000000),
|
||||
inverseSurface = Color(0xFF333333),
|
||||
inverseOnSurface = Color(0xFFF4F4F4),
|
||||
outline = Color(0xFF838383),
|
||||
surfaceContainerLowest = Color(0xFFCFCFCF),
|
||||
surfaceContainerLow = Color(0xFFDADADA),
|
||||
surfaceContainer = Color(0xFFE8E8E8), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFECECEC),
|
||||
surfaceContainerHighest = Color(0xFFEFEFEF),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,24 +23,29 @@ internal object YotsubaColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFF862200),
|
||||
onPrimaryContainer = Color(0xFFFFDBCF),
|
||||
inversePrimary = Color(0xFFAE3200),
|
||||
secondary = Color(0xFFFFB59D),
|
||||
onSecondary = Color(0xFF5F1600),
|
||||
secondaryContainer = Color(0xFF862200),
|
||||
onSecondaryContainer = Color(0xFFFFDBCF),
|
||||
tertiary = Color(0xFFD7C68D),
|
||||
onTertiary = Color(0xFF3A2F05),
|
||||
secondary = Color(0xFFFFB59D), // Unread badge
|
||||
onSecondary = Color(0xFF5F1600), // Unread badge text
|
||||
secondaryContainer = Color(0xFF862200), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFFFFDBCF), // Navigation bar selector icon
|
||||
tertiary = Color(0xFFD7C68D), // Downloaded badge
|
||||
onTertiary = Color(0xFF3A2F05), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFF524619),
|
||||
onTertiaryContainer = Color(0xFFF5E2A7),
|
||||
background = Color(0xFF211A18),
|
||||
onBackground = Color(0xFFEDE0DD),
|
||||
surface = Color(0xFF211A18),
|
||||
onSurface = Color(0xFFEDE0DD),
|
||||
surfaceVariant = Color(0xFF53433F),
|
||||
surfaceVariant = Color(0xFF332723), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFFD8C2BC),
|
||||
surfaceTint = Color(0xFFFFB59D),
|
||||
inverseSurface = Color(0xFFEDE0DD),
|
||||
inverseOnSurface = Color(0xFF211A18),
|
||||
outline = Color(0xFFA08C87),
|
||||
surfaceContainerLowest = Color(0xFF2E221F),
|
||||
surfaceContainerLow = Color(0xFF312521),
|
||||
surfaceContainer = Color(0xFF332723), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFF413531),
|
||||
surfaceContainerHighest = Color(0xFF4C403D),
|
||||
)
|
||||
|
||||
override val lightScheme = lightColorScheme(
|
||||
|
@ -49,23 +54,28 @@ internal object YotsubaColorScheme : BaseColorScheme() {
|
|||
primaryContainer = Color(0xFFFFDBCF),
|
||||
onPrimaryContainer = Color(0xFF3B0A00),
|
||||
inversePrimary = Color(0xFFFFB59D),
|
||||
secondary = Color(0xFFAE3200),
|
||||
onSecondary = Color(0xFFFFFFFF),
|
||||
secondaryContainer = Color(0xFFFFDBCF),
|
||||
onSecondaryContainer = Color(0xFF3B0A00),
|
||||
tertiary = Color(0xFF6B5E2F),
|
||||
onTertiary = Color(0xFFFFFFFF),
|
||||
secondary = Color(0xFFAE3200), // Unread badge
|
||||
onSecondary = Color(0xFFFFFFFF), // Unread badge text
|
||||
secondaryContainer = Color(0xFFEBCDC2), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF3B0A00), // Navigation bar selector icon
|
||||
tertiary = Color(0xFF6B5E2F), // Downloaded badge
|
||||
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
|
||||
tertiaryContainer = Color(0xFFF5E2A7),
|
||||
onTertiaryContainer = Color(0xFF231B00),
|
||||
background = Color(0xFFFCFCFC),
|
||||
onBackground = Color(0xFF211A18),
|
||||
surface = Color(0xFFFCFCFC),
|
||||
onSurface = Color(0xFF211A18),
|
||||
surfaceVariant = Color(0xFFF5DED8),
|
||||
surfaceVariant = Color(0xFFF6EBE7), // Navigation bar background (ThemePrefWidget)
|
||||
onSurfaceVariant = Color(0xFF53433F),
|
||||
surfaceTint = Color(0xFFAE3200),
|
||||
inverseSurface = Color(0xFF362F2D),
|
||||
inverseOnSurface = Color(0xFFFBEEEB),
|
||||
outline = Color(0xFF85736E),
|
||||
surfaceContainerLowest = Color(0xFFECE3E0),
|
||||
surfaceContainerLow = Color(0xFFF1E7E4),
|
||||
surfaceContainer = Color(0xFFF6EBE7), // Navigation bar background
|
||||
surfaceContainerHigh = Color(0xFFFAF4F2),
|
||||
surfaceContainerHighest = Color(0xFFFBF6F4),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ private fun TrackInfoItem(
|
|||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
|
||||
.padding(8.dp)
|
||||
.clip(RoundedCornerShape(6.dp)),
|
||||
) {
|
||||
|
|
|
@ -43,8 +43,6 @@ import tachiyomi.presentation.core.components.WheelTextPicker
|
|||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
@Composable
|
||||
fun TrackStatusSelector(
|
||||
|
@ -86,8 +84,8 @@ fun TrackStatusSelector(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
},
|
||||
onConfirm = onConfirm,
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
|
|
@ -104,7 +104,7 @@ fun UpdateScreen(
|
|||
isRefreshing = false
|
||||
}
|
||||
},
|
||||
enabled = { !state.selectionMode },
|
||||
enabled = !state.selectionMode,
|
||||
indicatorPadding = contentPadding,
|
||||
) {
|
||||
FastScrollLazyColumn(
|
||||
|
|
|
@ -54,7 +54,7 @@ internal fun LazyListScope.updatesLastUpdatedItem(
|
|||
item(key = "updates-lastUpdated") {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.animateItemPlacement()
|
||||
.animateItem()
|
||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||
) {
|
||||
Text(
|
||||
|
@ -91,14 +91,14 @@ internal fun LazyListScope.updatesUiItems(
|
|||
when (item) {
|
||||
is UpdatesUiModel.Header -> {
|
||||
ListGroupHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
text = relativeDateText(item.date),
|
||||
)
|
||||
}
|
||||
is UpdatesUiModel.Item -> {
|
||||
val updatesItem = item.item
|
||||
UpdatesUiItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
update = updatesItem.update,
|
||||
selected = updatesItem.selected,
|
||||
readProgress = updatesItem.update.lastPageRead
|
||||
|
|
|
@ -7,9 +7,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
|
||||
@Composable
|
||||
fun rememberRequestPackageInstallsPermissionState(initialValue: Boolean = false): Boolean {
|
||||
|
|
|
@ -27,6 +27,7 @@ import eu.kanade.domain.ui.UiPreferences
|
|||
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
||||
import eu.kanade.tachiyomi.crash.CrashActivity
|
||||
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
|
||||
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
|
||||
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
|
||||
import eu.kanade.tachiyomi.data.coil.MangaKeyer
|
||||
|
@ -152,25 +153,33 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun newImageLoader(context: Context): ImageLoader {
|
||||
return ImageLoader.Builder(this).apply {
|
||||
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
|
||||
components {
|
||||
// NetworkFetcher.Factory
|
||||
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
|
||||
// Decoder.Factory
|
||||
add(TachiyomiImageDecoder.Factory())
|
||||
add(MangaCoverFetcher.MangaFactory(callFactoryLazy))
|
||||
// Fetcher.Factory
|
||||
add(BufferedSourceFetcher.Factory())
|
||||
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
|
||||
add(MangaKeyer())
|
||||
add(MangaCoverFetcher.MangaFactory(callFactoryLazy))
|
||||
// Keyer
|
||||
add(MangaCoverKeyer())
|
||||
add(MangaKeyer())
|
||||
}
|
||||
|
||||
crossfade((300 * this@App.animatorDurationScale).toInt())
|
||||
allowRgb565(DeviceUtil.isLowRamDevice(this@App))
|
||||
if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
|
||||
|
||||
// Coil spawns a new thread for every image load by default
|
||||
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
|
||||
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
}.build()
|
||||
fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8))
|
||||
decoderCoroutineContext(Dispatchers.IO.limitedParallelism(3))
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import okio.buffer
|
||||
import okio.gzip
|
||||
|
@ -33,7 +32,7 @@ class BackupDecoder(
|
|||
source
|
||||
}.use { it.readByteArray() }
|
||||
|
||||
parser.decodeFromByteArray(BackupSerializer, backupString)
|
||||
parser.decodeFromByteArray(Backup.serializer(), backupString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.backup.models.Backup
|
|||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSource
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
|
@ -84,7 +83,7 @@ class BackupCreator(
|
|||
backupSourcePreferences = backupSourcePreferences(options),
|
||||
)
|
||||
|
||||
val byteArray = parser.encodeToByteArray(BackupSerializer, backup)
|
||||
val byteArray = parser.encodeToByteArray(Backup.serializer(), backup)
|
||||
if (byteArray.isEmpty()) {
|
||||
throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error))
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package eu.kanade.tachiyomi.data.backup.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
|
||||
@Serializer(forClass = Backup::class)
|
||||
object BackupSerializer
|
||||
|
||||
@Serializable
|
||||
data class Backup(
|
||||
@ProtoNumber(1) val backupManga: List<BackupManga>,
|
||||
|
|
|
@ -17,14 +17,20 @@ class CategoriesRestorer(
|
|||
if (backupCategories.isNotEmpty()) {
|
||||
val dbCategories = getCategories.await()
|
||||
val dbCategoriesByName = dbCategories.associateBy { it.name }
|
||||
var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
||||
|
||||
val categories = backupCategories.map {
|
||||
dbCategoriesByName[it.name]
|
||||
?: handler.awaitOneExecutable {
|
||||
categoriesQueries.insert(it.name, it.order, it.flags)
|
||||
val categories = backupCategories
|
||||
.sortedBy { it.order }
|
||||
.map {
|
||||
val dbCategory = dbCategoriesByName[it.name]
|
||||
if (dbCategory != null) return@map dbCategory
|
||||
val order = nextOrder++
|
||||
handler.awaitOneExecutable {
|
||||
categoriesQueries.insert(it.name, order, it.flags)
|
||||
categoriesQueries.selectLastInsertedRowId()
|
||||
}.let { id -> it.toCategory(id) }
|
||||
}
|
||||
}
|
||||
.let { id -> it.toCategory(id).copy(order = order) }
|
||||
}
|
||||
|
||||
libraryPreferences.categorizedDisplaySettings().set(
|
||||
(dbCategories + categories)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package eu.kanade.tachiyomi.data.coil
|
||||
|
||||
import coil3.ImageLoader
|
||||
import coil3.decode.DataSource
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.FetchResult
|
||||
import coil3.fetch.Fetcher
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.request.Options
|
||||
import okio.BufferedSource
|
||||
|
||||
class BufferedSourceFetcher(
|
||||
private val data: BufferedSource,
|
||||
private val options: Options,
|
||||
) : Fetcher {
|
||||
|
||||
override suspend fun fetch(): FetchResult {
|
||||
return SourceFetchResult(
|
||||
source = ImageSource(
|
||||
source = data,
|
||||
fileSystem = options.fileSystem,
|
||||
),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.MEMORY,
|
||||
)
|
||||
}
|
||||
|
||||
class Factory : Fetcher.Factory<BufferedSource> {
|
||||
|
||||
override fun create(
|
||||
data: BufferedSource,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader,
|
||||
): Fetcher {
|
||||
return BufferedSourceFetcher(data, options)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ import okhttp3.CacheControl
|
|||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.http.HTTP_NOT_MODIFIED
|
||||
import okio.FileSystem
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import okio.Source
|
||||
|
@ -348,5 +347,7 @@ class MangaCoverFetcher(
|
|||
|
||||
private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build()
|
||||
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
|
||||
|
||||
private const val HTTP_NOT_MODIFIED = 304
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package eu.kanade.tachiyomi.data.coil
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import coil3.ImageLoader
|
||||
import coil3.asCoilImage
|
||||
import coil3.asImage
|
||||
import coil3.decode.DecodeResult
|
||||
import coil3.decode.DecodeUtils
|
||||
import coil3.decode.Decoder
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.request.Options
|
||||
import coil3.request.bitmapConfig
|
||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||
import okio.BufferedSource
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.decoder.ImageDecoder
|
||||
|
@ -18,27 +22,55 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
|||
|
||||
override suspend fun decode(): DecodeResult {
|
||||
val decoder = resources.sourceOrNull()?.use {
|
||||
ImageDecoder.newInstance(it.inputStream())
|
||||
ImageDecoder.newInstance(it.inputStream(), options.cropBorders, displayProfile)
|
||||
}
|
||||
|
||||
check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" }
|
||||
|
||||
val bitmap = decoder.decode()
|
||||
val srcWidth = decoder.width
|
||||
val srcHeight = decoder.height
|
||||
|
||||
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
|
||||
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
|
||||
|
||||
val sampleSize = DecodeUtils.calculateInSampleSize(
|
||||
srcWidth = srcWidth,
|
||||
srcHeight = srcHeight,
|
||||
dstWidth = dstWidth,
|
||||
dstHeight = dstHeight,
|
||||
scale = options.scale,
|
||||
)
|
||||
|
||||
var bitmap = decoder.decode(sampleSize = sampleSize)
|
||||
decoder.recycle()
|
||||
|
||||
check(bitmap != null) { "Failed to decode image" }
|
||||
|
||||
if (
|
||||
options.bitmapConfig == Bitmap.Config.HARDWARE &&
|
||||
maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize
|
||||
) {
|
||||
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
|
||||
if (hwBitmap != null) {
|
||||
bitmap.recycle()
|
||||
bitmap = hwBitmap
|
||||
}
|
||||
}
|
||||
|
||||
return DecodeResult(
|
||||
image = bitmap.asCoilImage(),
|
||||
isSampled = false,
|
||||
image = bitmap.asImage(),
|
||||
isSampled = sampleSize > 1,
|
||||
)
|
||||
}
|
||||
|
||||
class Factory : Decoder.Factory {
|
||||
|
||||
override fun create(result: SourceFetchResult, options: Options, imageLoader: ImageLoader): Decoder? {
|
||||
if (!isApplicable(result.source.source())) return null
|
||||
return TachiyomiImageDecoder(result.source, options)
|
||||
return if (options.customDecoder || isApplicable(result.source.source())) {
|
||||
TachiyomiImageDecoder(result.source, options)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isApplicable(source: BufferedSource): Boolean {
|
||||
|
@ -55,4 +87,8 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
|||
|
||||
override fun hashCode() = javaClass.hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
var displayProfile: ByteArray? = null
|
||||
}
|
||||
}
|
||||
|
|
44
app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt
Normal file
44
app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt
Normal file
|
@ -0,0 +1,44 @@
|
|||
package eu.kanade.tachiyomi.data.coil
|
||||
|
||||
import coil3.Extras
|
||||
import coil3.getExtra
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.Options
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.Scale
|
||||
import coil3.size.Size
|
||||
import coil3.size.isOriginal
|
||||
import coil3.size.pxOrElse
|
||||
|
||||
internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
|
||||
return if (isOriginal) original() else width.toPx(scale)
|
||||
}
|
||||
|
||||
internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int {
|
||||
return if (isOriginal) original() else height.toPx(scale)
|
||||
}
|
||||
|
||||
internal fun Dimension.toPx(scale: Scale): Int = pxOrElse {
|
||||
when (scale) {
|
||||
Scale.FILL -> Int.MIN_VALUE
|
||||
Scale.FIT -> Int.MAX_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
fun ImageRequest.Builder.cropBorders(enable: Boolean) = apply {
|
||||
extras[cropBordersKey] = enable
|
||||
}
|
||||
|
||||
val Options.cropBorders: Boolean
|
||||
get() = getExtra(cropBordersKey)
|
||||
|
||||
private val cropBordersKey = Extras.Key(default = false)
|
||||
|
||||
fun ImageRequest.Builder.customDecoder(enable: Boolean) = apply {
|
||||
extras[customDecoderKey] = enable
|
||||
}
|
||||
|
||||
val Options.customDecoder: Boolean
|
||||
get() = getExtra(customDecoderKey)
|
||||
|
||||
private val customDecoderKey = Extras.Key(default = false)
|
|
@ -17,6 +17,7 @@ import logcat.LogPriority
|
|||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.storage.extension
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
|
@ -160,7 +161,7 @@ class DownloadManager(
|
|||
fun buildPageList(source: Source, manga: Manga, chapter: Chapter): List<Page> {
|
||||
val chapterDir = provider.findChapterDir(chapter.name, chapter.scanlator, manga.title, source)
|
||||
val files = chapterDir?.listFiles().orEmpty()
|
||||
.filter { "image" in it.type.orEmpty() }
|
||||
.filter { it.isFile && ImageUtil.isImage(it.name) { it.openInputStream() } }
|
||||
|
||||
if (files.isEmpty()) {
|
||||
throw Exception(context.stringResource(MR.strings.page_list_empty_error))
|
||||
|
|
|
@ -57,7 +57,7 @@ class DownloadProvider(
|
|||
* @param source the source to query.
|
||||
*/
|
||||
fun findSourceDir(source: Source): UniFile? {
|
||||
return downloadsDir?.findFile(getSourceDirName(source), true)
|
||||
return downloadsDir?.findFile(getSourceDirName(source))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,7 +68,7 @@ class DownloadProvider(
|
|||
*/
|
||||
fun findMangaDir(mangaTitle: String, source: Source): UniFile? {
|
||||
val sourceDir = findSourceDir(source)
|
||||
return sourceDir?.findFile(getMangaDirName(mangaTitle), true)
|
||||
return sourceDir?.findFile(getMangaDirName(mangaTitle))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +82,7 @@ class DownloadProvider(
|
|||
fun findChapterDir(chapterName: String, chapterScanlator: String?, mangaTitle: String, source: Source): UniFile? {
|
||||
val mangaDir = findMangaDir(mangaTitle, source)
|
||||
return getValidChapterDirNames(chapterName, chapterScanlator).asSequence()
|
||||
.mapNotNull { mangaDir?.findFile(it, true) }
|
||||
.mapNotNull { mangaDir?.findFile(it) }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ class DownloadProvider(
|
|||
val mangaDir = findMangaDir(manga.title, source) ?: return null to emptyList()
|
||||
return mangaDir to chapters.mapNotNull { chapter ->
|
||||
getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it, true) }
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
@ -160,21 +160,12 @@ class DownloadProvider(
|
|||
*/
|
||||
fun getValidChapterDirNames(chapterName: String, chapterScanlator: String?): List<String> {
|
||||
val chapterDirName = getChapterDirName(chapterName, chapterScanlator)
|
||||
return buildList(4) {
|
||||
return buildList(2) {
|
||||
// Folder of images
|
||||
add(chapterDirName)
|
||||
|
||||
// Archived chapters
|
||||
add("$chapterDirName.cbz")
|
||||
|
||||
if (chapterScanlator.isNullOrBlank()) {
|
||||
// Previously null scanlator fields were converted to "" due to a bug
|
||||
add("_$chapterDirName")
|
||||
add("_$chapterDirName.cbz")
|
||||
} else {
|
||||
// Legacy chapter directory name used in v0.9.2 and before
|
||||
add(DiskUtil.buildValidFilename(chapterName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import logcat.LogPriority
|
||||
import mihon.core.common.archive.ZipWriter
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import okhttp3.Response
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
|
@ -58,12 +59,8 @@ import tachiyomi.domain.track.interactor.GetTracks
|
|||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.zip.CRC32
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* This class is the one in charge of downloading chapters.
|
||||
|
@ -526,14 +523,8 @@ class Downloader(
|
|||
* @param file the file where the image is already downloaded.
|
||||
*/
|
||||
private fun getImageExtension(response: Response, file: UniFile): String {
|
||||
// Read content type if available.
|
||||
val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null }
|
||||
// Else guess from the uri.
|
||||
?: context.contentResolver.getType(file.uri)
|
||||
// Else read magic numbers.
|
||||
?: ImageUtil.findImageType { file.openInputStream() }?.mime
|
||||
|
||||
return ImageUtil.getExtensionFromMimeType(mime)
|
||||
return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() }
|
||||
}
|
||||
|
||||
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
|
||||
|
@ -594,25 +585,9 @@ class Downloader(
|
|||
tmpDir: UniFile,
|
||||
) {
|
||||
val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!!
|
||||
ZipOutputStream(BufferedOutputStream(zip.openOutputStream())).use { zipOut ->
|
||||
zipOut.setMethod(ZipEntry.STORED)
|
||||
|
||||
tmpDir.listFiles()?.forEach { img ->
|
||||
img.openInputStream().use { input ->
|
||||
val data = input.readBytes()
|
||||
val size = img.length()
|
||||
val entry = ZipEntry(img.name).apply {
|
||||
val crc = CRC32().apply {
|
||||
update(data)
|
||||
}
|
||||
setCrc(crc.value)
|
||||
|
||||
compressedSize = size
|
||||
setSize(size)
|
||||
}
|
||||
zipOut.putNextEntry(entry)
|
||||
zipOut.write(data)
|
||||
}
|
||||
ZipWriter(context, zip).use { writer ->
|
||||
tmpDir.listFiles()?.forEach { file ->
|
||||
writer.write(file)
|
||||
}
|
||||
}
|
||||
zip.renameTo("$dirname.cbz")
|
||||
|
@ -645,7 +620,7 @@ class Downloader(
|
|||
)
|
||||
|
||||
// Remove the old file
|
||||
dir.findFile(COMIC_INFO_FILE, true)?.delete()
|
||||
dir.findFile(COMIC_INFO_FILE)?.delete()
|
||||
dir.createFile(COMIC_INFO_FILE)!!.openOutputStream().use {
|
||||
val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo)
|
||||
it.write(comicInfoString.toByteArray())
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.graphics.BitmapFactory
|
|||
import android.net.Uri
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.transformations
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.contentValuesOf
|
||||
import androidx.core.net.toUri
|
||||
|
@ -65,21 +66,26 @@ class ImageSaver(
|
|||
filename: String,
|
||||
data: () -> InputStream,
|
||||
): Uri {
|
||||
val pictureDir =
|
||||
val isMimeTypeSupported = MimeTypeMap.getSingleton().hasMimeType(type.mime)
|
||||
|
||||
val pictureDir = if (isMimeTypeSupported) {
|
||||
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
} else {
|
||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
}
|
||||
|
||||
val imageLocation = (image.location as Location.Pictures).relativePath
|
||||
val relativePath = listOf(
|
||||
Environment.DIRECTORY_PICTURES,
|
||||
if (isMimeTypeSupported) Environment.DIRECTORY_PICTURES else Environment.DIRECTORY_DOCUMENTS,
|
||||
context.stringResource(MR.strings.app_name),
|
||||
imageLocation,
|
||||
).joinToString(File.separator)
|
||||
|
||||
val contentValues = contentValuesOf(
|
||||
MediaStore.Images.Media.RELATIVE_PATH to relativePath,
|
||||
MediaStore.Images.Media.DISPLAY_NAME to image.name,
|
||||
MediaStore.Images.Media.MIME_TYPE to type.mime,
|
||||
MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond,
|
||||
MediaStore.MediaColumns.RELATIVE_PATH to relativePath,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename,
|
||||
MediaStore.MediaColumns.MIME_TYPE to type.mime,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond,
|
||||
)
|
||||
|
||||
val picture = findUriOrDefault(relativePath, filename) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import eu.kanade.domain.track.service.TrackPreferences
|
|||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import logcat.LogPriority
|
||||
import okhttp3.OkHttpClient
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
|
@ -53,6 +55,15 @@ abstract class BaseTracker(
|
|||
get() = getUsername().isNotEmpty() &&
|
||||
getPassword().isNotEmpty()
|
||||
|
||||
override val isLoggedInFlow: Flow<Boolean> by lazy {
|
||||
combine(
|
||||
trackPreferences.trackUsername(this).changes(),
|
||||
trackPreferences.trackPassword(this).changes(),
|
||||
) { username, password ->
|
||||
username.isNotEmpty() && password.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getUsername() = trackPreferences.trackUsername(this).get()
|
||||
|
||||
override fun getPassword() = trackPreferences.trackPassword(this).get()
|
||||
|
|
|
@ -7,6 +7,7 @@ import dev.icerock.moko.resources.StringResource
|
|||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import okhttp3.OkHttpClient
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
|
@ -61,6 +62,8 @@ interface Tracker {
|
|||
|
||||
val isLoggedIn: Boolean
|
||||
|
||||
val isLoggedInFlow: Flow<Boolean>
|
||||
|
||||
fun getUsername(): String
|
||||
|
||||
fun getPassword(): String
|
||||
|
|
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates
|
|||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
||||
import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class TrackerManager {
|
||||
|
||||
|
@ -32,5 +33,13 @@ class TrackerManager {
|
|||
|
||||
fun loggedInTrackers() = trackers.filter { it.isLoggedIn }
|
||||
|
||||
fun loggedInTrackersFlow() = combine(trackers.map { it.isLoggedInFlow }) {
|
||||
it.mapIndexedNotNull { index, isLoggedIn ->
|
||||
if (isLoggedIn) trackers[index] else null
|
||||
}
|
||||
}
|
||||
|
||||
fun get(id: Long) = trackers.find { it.id == id }
|
||||
|
||||
fun getAll(ids: Set<Long>) = trackers.filter { it.id in ids }
|
||||
}
|
||||
|
|
|
@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
|||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.lang.launchNow
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
|
@ -42,6 +45,8 @@ class ExtensionManager(
|
|||
private val trustExtension: TrustExtension = Injekt.get(),
|
||||
) {
|
||||
|
||||
val scope = CoroutineScope(SupervisorJob())
|
||||
|
||||
private val _isInitialized = MutableStateFlow(false)
|
||||
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
|
||||
|
||||
|
@ -57,24 +62,35 @@ class ExtensionManager(
|
|||
|
||||
private val iconMap = mutableMapOf<String, Drawable>()
|
||||
|
||||
private val _installedExtensionsFlow = MutableStateFlow(emptyList<Extension.Installed>())
|
||||
val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow()
|
||||
private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
|
||||
val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope)
|
||||
|
||||
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
|
||||
val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope)
|
||||
|
||||
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
|
||||
val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope)
|
||||
|
||||
init {
|
||||
initExtensions()
|
||||
ExtensionInstallReceiver(InstallationListener()).register(context)
|
||||
}
|
||||
|
||||
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
|
||||
|
||||
fun getAppIconForSource(sourceId: Long): Drawable? {
|
||||
val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName
|
||||
if (pkgName != null) {
|
||||
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
|
||||
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
||||
.loadIcon(context.packageManager)
|
||||
val pkgName = _installedExtensionsMapFlow.value.values
|
||||
.find { ext ->
|
||||
ext.sources.any { it.id == sourceId }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
?.pkgName
|
||||
?: return null
|
||||
|
||||
private val _availableExtensionsFlow = MutableStateFlow(emptyList<Extension.Available>())
|
||||
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow()
|
||||
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
|
||||
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
||||
.loadIcon(context.packageManager)
|
||||
}
|
||||
}
|
||||
|
||||
private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap()
|
||||
|
||||
|
@ -87,33 +103,25 @@ class ExtensionManager(
|
|||
|
||||
fun getSourceData(id: Long) = availableExtensionsSourcesData[id]
|
||||
|
||||
private val _untrustedExtensionsFlow = MutableStateFlow(emptyList<Extension.Untrusted>())
|
||||
val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow()
|
||||
|
||||
init {
|
||||
initExtensions()
|
||||
ExtensionInstallReceiver(InstallationListener()).register(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and registers the installed extensions.
|
||||
*/
|
||||
private fun initExtensions() {
|
||||
val extensions = ExtensionLoader.loadExtensions(context)
|
||||
|
||||
_installedExtensionsFlow.value = extensions
|
||||
_installedExtensionsMapFlow.value = extensions
|
||||
.filterIsInstance<LoadResult.Success>()
|
||||
.map { it.extension }
|
||||
.associate { it.extension.pkgName to it.extension }
|
||||
|
||||
_untrustedExtensionsFlow.value = extensions
|
||||
_untrustedExtensionsMapFlow.value = extensions
|
||||
.filterIsInstance<LoadResult.Untrusted>()
|
||||
.map { it.extension }
|
||||
.associate { it.extension.pkgName to it.extension }
|
||||
|
||||
_isInitialized.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the available extensions in the [api] and updates [availableExtensions].
|
||||
* Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow].
|
||||
*/
|
||||
suspend fun findAvailableExtensions() {
|
||||
val extensions: List<Extension.Available> = try {
|
||||
|
@ -126,7 +134,7 @@ class ExtensionManager(
|
|||
|
||||
enableAdditionalSubLanguages(extensions)
|
||||
|
||||
_availableExtensionsFlow.value = extensions
|
||||
_availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName }
|
||||
updatedInstalledExtensionsStatuses(extensions)
|
||||
setupAvailableExtensionsSourcesDataMap(extensions)
|
||||
}
|
||||
|
@ -172,35 +180,31 @@ class ExtensionManager(
|
|||
return
|
||||
}
|
||||
|
||||
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList()
|
||||
val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap()
|
||||
var changed = false
|
||||
|
||||
for ((index, installedExt) in mutInstalledExtensions.withIndex()) {
|
||||
val pkgName = installedExt.pkgName
|
||||
for ((pkgName, extension) in installedExtensionsMap) {
|
||||
val availableExt = availableExtensions.find { it.pkgName == pkgName }
|
||||
|
||||
if (availableExt == null && !installedExt.isObsolete) {
|
||||
mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
|
||||
if (availableExt == null && !extension.isObsolete) {
|
||||
installedExtensionsMap[pkgName] = extension.copy(isObsolete = true)
|
||||
changed = true
|
||||
} else if (availableExt != null) {
|
||||
val hasUpdate = installedExt.updateExists(availableExt)
|
||||
|
||||
if (installedExt.hasUpdate != hasUpdate) {
|
||||
mutInstalledExtensions[index] = installedExt.copy(
|
||||
val hasUpdate = extension.updateExists(availableExt)
|
||||
if (extension.hasUpdate != hasUpdate) {
|
||||
installedExtensionsMap[pkgName] = extension.copy(
|
||||
hasUpdate = hasUpdate,
|
||||
repoUrl = availableExt.repoUrl,
|
||||
)
|
||||
changed = true
|
||||
} else {
|
||||
mutInstalledExtensions[index] = installedExt.copy(
|
||||
installedExtensionsMap[pkgName] = extension.copy(
|
||||
repoUrl = availableExt.repoUrl,
|
||||
)
|
||||
changed = true
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
_installedExtensionsFlow.value = mutInstalledExtensions
|
||||
_installedExtensionsMapFlow.value = installedExtensionsMap
|
||||
}
|
||||
updatePendingUpdatesCount()
|
||||
}
|
||||
|
@ -224,8 +228,7 @@ class ExtensionManager(
|
|||
* @param extension The extension to be updated.
|
||||
*/
|
||||
fun updateExtension(extension: Extension.Installed): Flow<InstallStep> {
|
||||
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName }
|
||||
?: return emptyFlow()
|
||||
val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow()
|
||||
return installExtension(availableExt)
|
||||
}
|
||||
|
||||
|
@ -261,24 +264,16 @@ class ExtensionManager(
|
|||
*
|
||||
* @param extension the extension to trust
|
||||
*/
|
||||
fun trust(extension: Extension.Untrusted) {
|
||||
val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet()
|
||||
if (extension.pkgName !in untrustedPkgNames) return
|
||||
suspend fun trust(extension: Extension.Untrusted) {
|
||||
_untrustedExtensionsMapFlow.value[extension.pkgName] ?: return
|
||||
|
||||
trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)
|
||||
|
||||
val nowTrustedExtensions = _untrustedExtensionsFlow.value
|
||||
.filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode }
|
||||
_untrustedExtensionsFlow.value -= nowTrustedExtensions
|
||||
_untrustedExtensionsMapFlow.value -= extension.pkgName
|
||||
|
||||
launchNow {
|
||||
nowTrustedExtensions
|
||||
.map { extension ->
|
||||
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
|
||||
}
|
||||
.filterIsInstance<LoadResult.Success>()
|
||||
.forEach { registerNewExtension(it.extension) }
|
||||
}
|
||||
ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName)
|
||||
.let { it as? LoadResult.Success }
|
||||
?.let { registerNewExtension(it.extension) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -287,7 +282,7 @@ class ExtensionManager(
|
|||
* @param extension The extension to be registered.
|
||||
*/
|
||||
private fun registerNewExtension(extension: Extension.Installed) {
|
||||
_installedExtensionsFlow.value += extension
|
||||
_installedExtensionsMapFlow.value += extension
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -297,13 +292,7 @@ class ExtensionManager(
|
|||
* @param extension The extension to be registered.
|
||||
*/
|
||||
private fun registerUpdatedExtension(extension: Extension.Installed) {
|
||||
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList()
|
||||
val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName }
|
||||
if (oldExtension != null) {
|
||||
mutInstalledExtensions -= oldExtension
|
||||
}
|
||||
mutInstalledExtensions += extension
|
||||
_installedExtensionsFlow.value = mutInstalledExtensions
|
||||
_installedExtensionsMapFlow.value += extension
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -313,14 +302,8 @@ class ExtensionManager(
|
|||
* @param pkgName The package name of the uninstalled application.
|
||||
*/
|
||||
private fun unregisterExtension(pkgName: String) {
|
||||
val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
if (installedExtension != null) {
|
||||
_installedExtensionsFlow.value -= installedExtension
|
||||
}
|
||||
val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
if (untrustedExtension != null) {
|
||||
_untrustedExtensionsFlow.value -= untrustedExtension
|
||||
}
|
||||
_installedExtensionsMapFlow.value -= pkgName
|
||||
_untrustedExtensionsMapFlow.value -= pkgName
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,14 +322,9 @@ class ExtensionManager(
|
|||
}
|
||||
|
||||
override fun onExtensionUntrusted(extension: Extension.Untrusted) {
|
||||
val installedExtension = _installedExtensionsFlow.value
|
||||
.find { it.pkgName == extension.pkgName }
|
||||
|
||||
if (installedExtension != null) {
|
||||
_installedExtensionsFlow.value -= installedExtension
|
||||
} else {
|
||||
_untrustedExtensionsFlow.value += extension
|
||||
}
|
||||
_installedExtensionsMapFlow.value -= extension.pkgName
|
||||
_untrustedExtensionsMapFlow.value += extension
|
||||
updatePendingUpdatesCount()
|
||||
}
|
||||
|
||||
override fun onPackageUninstalled(pkgName: String) {
|
||||
|
@ -368,17 +346,24 @@ class ExtensionManager(
|
|||
}
|
||||
|
||||
private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
|
||||
val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
val availableExt = availableExtension
|
||||
?: _availableExtensionsMapFlow.value[pkgName]
|
||||
?: return false
|
||||
|
||||
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
||||
}
|
||||
|
||||
private fun updatePendingUpdatesCount() {
|
||||
val pendingUpdateCount = _installedExtensionsFlow.value.count { it.hasUpdate }
|
||||
val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate }
|
||||
preferences.extensionUpdatesCount().set(pendingUpdateCount)
|
||||
if (pendingUpdateCount == 0) {
|
||||
ExtensionUpdateNotifier(context).dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun <T : Extension> Map<String, T>.plus(extension: T) = plus(extension.pkgName to extension)
|
||||
|
||||
private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensions(scope: CoroutineScope): StateFlow<List<T>> {
|
||||
return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,10 @@ import androidx.core.content.ContextCompat
|
|||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.lang.launchNow
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
|
||||
/**
|
||||
|
@ -23,29 +21,23 @@ import tachiyomi.core.common.util.system.logcat
|
|||
*
|
||||
* @param listener The listener that should be notified of extension installation events.
|
||||
*/
|
||||
internal class ExtensionInstallReceiver(private val listener: Listener) :
|
||||
BroadcastReceiver() {
|
||||
internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() {
|
||||
|
||||
val scope = CoroutineScope(SupervisorJob())
|
||||
|
||||
/**
|
||||
* Registers this broadcast receiver
|
||||
*/
|
||||
fun register(context: Context) {
|
||||
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intent filter this receiver should subscribe to.
|
||||
*/
|
||||
private val filter
|
||||
get() = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addAction(ACTION_EXTENSION_ADDED)
|
||||
addAction(ACTION_EXTENSION_REPLACED)
|
||||
addAction(ACTION_EXTENSION_REMOVED)
|
||||
addDataScheme("package")
|
||||
}
|
||||
private val filter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addAction(ACTION_EXTENSION_ADDED)
|
||||
addAction(ACTION_EXTENSION_REPLACED)
|
||||
addAction(ACTION_EXTENSION_REMOVED)
|
||||
addDataScheme("package")
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the events of the [filter] is received. When the package is an extension,
|
||||
|
@ -58,7 +50,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
|||
Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> {
|
||||
if (isReplacing(intent)) return
|
||||
|
||||
launchNow {
|
||||
scope.launch {
|
||||
when (val result = getExtensionFromIntent(context, intent)) {
|
||||
is LoadResult.Success -> listener.onExtensionInstalled(result.extension)
|
||||
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
|
||||
|
@ -67,7 +59,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
|||
}
|
||||
}
|
||||
Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> {
|
||||
launchNow {
|
||||
scope.launch {
|
||||
when (val result = getExtensionFromIntent(context, intent)) {
|
||||
is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
|
||||
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
|
||||
|
@ -107,9 +99,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
|||
logcat(LogPriority.WARN) { "Package name not found" }
|
||||
return LoadResult.Error
|
||||
}
|
||||
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) {
|
||||
ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
|
||||
}.await()
|
||||
return ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -172,7 +172,7 @@ internal object ExtensionLoader {
|
|||
* Attempts to load an extension from the given package name. It checks if the extension
|
||||
* contains the required feature flag before trying to load it.
|
||||
*/
|
||||
fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult {
|
||||
suspend fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult {
|
||||
val extensionPackage = getExtensionInfoFromPkgName(context, pkgName)
|
||||
if (extensionPackage == null) {
|
||||
logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" }
|
||||
|
@ -223,7 +223,8 @@ internal object ExtensionLoader {
|
|||
* @param context The application context.
|
||||
* @param extensionInfo The extension to load.
|
||||
*/
|
||||
private fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult {
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
|
||||
private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult {
|
||||
val pkgManager = context.packageManager
|
||||
val pkgInfo = extensionInfo.packageInfo
|
||||
val appInfo = pkgInfo.applicationInfo
|
||||
|
@ -252,7 +253,7 @@ internal object ExtensionLoader {
|
|||
if (signatures.isNullOrEmpty()) {
|
||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||
return LoadResult.Error
|
||||
} else if (!trustExtension.isTrusted(pkgInfo, signatures.last())) {
|
||||
} else if (!trustExtension.isTrusted(pkgInfo, signatures)) {
|
||||
val extension = Extension.Untrusted(
|
||||
extName,
|
||||
pkgName,
|
||||
|
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -196,7 +197,9 @@ class ExtensionsScreenModel(
|
|||
}
|
||||
|
||||
fun trustExtension(extension: Extension.Untrusted) {
|
||||
extensionManager.trust(extension)
|
||||
screenModelScope.launch {
|
||||
extensionManager.trust(extension)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -40,9 +40,7 @@ fun SourceFilterDialog(
|
|||
) {
|
||||
val updateFilters = { onUpdate(filters) }
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
LazyColumn {
|
||||
stickyHeader {
|
||||
Row(
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.produceState
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.manga.model.toDomainManga
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
|
@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.core.common.preference.toggle
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
@ -38,6 +40,7 @@ abstract class SearchScreenModel(
|
|||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
) : StateScreenModel<SearchScreenModel.State>(initialState) {
|
||||
|
||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||
|
@ -60,6 +63,14 @@ abstract class SearchScreenModel(
|
|||
)
|
||||
}
|
||||
|
||||
init {
|
||||
screenModelScope.launch {
|
||||
preferences.globalSearchFilterState().changes().collectLatest { state ->
|
||||
mutableState.update { it.copy(onlyShowHasResults = state) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> {
|
||||
return produceState(initialValue = initialManga) {
|
||||
|
@ -107,7 +118,7 @@ abstract class SearchScreenModel(
|
|||
}
|
||||
|
||||
fun toggleFilterResults() {
|
||||
mutableState.update { it.copy(onlyShowHasResults = !it.onlyShowHasResults) }
|
||||
preferences.globalSearchFilterState().toggle()
|
||||
}
|
||||
|
||||
fun search() {
|
||||
|
|
|
@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
@ -106,10 +107,10 @@ class LibraryScreenModel(
|
|||
getTracksPerManga.subscribe(),
|
||||
getTrackingFilterFlow(),
|
||||
downloadCache.changes,
|
||||
) { searchQuery, library, tracks, loggedInTrackers, _ ->
|
||||
) { searchQuery, library, tracks, trackingFiler, _ ->
|
||||
library
|
||||
.applyFilters(tracks, loggedInTrackers)
|
||||
.applySort(tracks)
|
||||
.applyFilters(tracks, trackingFiler)
|
||||
.applySort(tracks, trackingFiler.keys)
|
||||
.mapValues { (_, value) ->
|
||||
if (searchQuery != null) {
|
||||
// Filter query
|
||||
|
@ -173,9 +174,10 @@ class LibraryScreenModel(
|
|||
/**
|
||||
* Applies library filters to the given map of manga.
|
||||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private suspend fun LibraryMap.applyFilters(
|
||||
trackMap: Map<Long, List<Track>>,
|
||||
loggedInTrackers: Map<Long, TriState>,
|
||||
trackingFiler: Map<Long, TriState>,
|
||||
): LibraryMap {
|
||||
val prefs = getLibraryItemPreferencesFlow().first()
|
||||
val downloadedOnly = prefs.globalFilterDownloaded
|
||||
|
@ -187,10 +189,10 @@ class LibraryScreenModel(
|
|||
val filterCompleted = prefs.filterCompleted
|
||||
val filterIntervalCustom = prefs.filterIntervalCustom
|
||||
|
||||
val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty()
|
||||
val isNotLoggedInAnyTrack = trackingFiler.isEmpty()
|
||||
|
||||
val excludedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
|
||||
val includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
|
||||
val excludedTracks = trackingFiler.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
|
||||
val includedTracks = trackingFiler.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
|
||||
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
|
||||
|
||||
val filterFnDownloaded: (LibraryItem) -> Boolean = {
|
||||
|
@ -254,9 +256,11 @@ class LibraryScreenModel(
|
|||
/**
|
||||
* Applies library sorting to the given map of manga.
|
||||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun LibraryMap.applySort(
|
||||
// Map<MangaId, List<Track>>
|
||||
trackMap: Map<Long, List<Track>>,
|
||||
loggedInTrackerIds: Set<Long>
|
||||
): LibraryMap {
|
||||
val sortAlphabetically: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
||||
i1.libraryManga.manga.title.lowercase().compareToWithCollator(i2.libraryManga.manga.title.lowercase())
|
||||
|
@ -264,7 +268,7 @@ class LibraryScreenModel(
|
|||
|
||||
val defaultTrackerScoreSortValue = -1.0
|
||||
val trackerScores by lazy {
|
||||
val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id }
|
||||
val trackerMap = trackerManager.getAll(loggedInTrackerIds).associateBy { e -> e.id }
|
||||
trackMap.mapValues { entry ->
|
||||
when {
|
||||
entry.value.isEmpty() -> null
|
||||
|
@ -405,18 +409,17 @@ class LibraryScreenModel(
|
|||
* @return map of track id with the filter value
|
||||
*/
|
||||
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
|
||||
val loggedInTrackers = trackerManager.loggedInTrackers()
|
||||
return if (loggedInTrackers.isNotEmpty()) {
|
||||
val prefFlows = loggedInTrackers
|
||||
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
|
||||
.toTypedArray()
|
||||
combine(*prefFlows) {
|
||||
return trackerManager.loggedInTrackersFlow().flatMapLatest { loggedInTrackers ->
|
||||
if (loggedInTrackers.isEmpty()) return@flatMapLatest flowOf(emptyMap())
|
||||
|
||||
val prefFlows = loggedInTrackers.map { tracker ->
|
||||
libraryPreferences.filterTracking(tracker.id.toInt()).changes()
|
||||
}
|
||||
combine(prefFlows) {
|
||||
loggedInTrackers
|
||||
.mapIndexed { index, tracker -> tracker.id to it[index] }
|
||||
.toMap()
|
||||
}
|
||||
} else {
|
||||
flowOf(emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.ScreenModel
|
|||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
|
@ -16,17 +18,22 @@ import tachiyomi.domain.library.model.LibrarySort
|
|||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class LibrarySettingsScreenModel(
|
||||
val preferences: BasePreferences = Injekt.get(),
|
||||
val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val setDisplayMode: SetDisplayMode = Injekt.get(),
|
||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||
private val trackerManager: TrackerManager = Injekt.get(),
|
||||
trackerManager: TrackerManager = Injekt.get(),
|
||||
) : ScreenModel {
|
||||
|
||||
val trackers
|
||||
get() = trackerManager.trackers.filter { it.isLoggedIn }
|
||||
val trackersFlow = trackerManager.loggedInTrackersFlow()
|
||||
.stateIn(
|
||||
scope = screenModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
|
||||
initialValue = trackerManager.loggedInTrackers()
|
||||
)
|
||||
|
||||
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
|
||||
preference(libraryPreferences).getAndSet {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.net.Uri
|
|||
import androidx.compose.material3.SnackbarHostState
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.size.Size
|
||||
|
|
|
@ -138,7 +138,7 @@ class MangaScreen(
|
|||
)
|
||||
}.takeIf { isHttpSource },
|
||||
onTrackingClicked = {
|
||||
if (screenModel.loggedInTrackers.isEmpty()) {
|
||||
if (!successState.hasLoggedInTrackers) {
|
||||
navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking))
|
||||
} else {
|
||||
screenModel.showTrackDialog()
|
||||
|
|
|
@ -31,7 +31,6 @@ import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
|||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.network.HttpException
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
|
@ -45,7 +44,6 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -117,8 +115,6 @@ class MangaScreenModel(
|
|||
private val successState: State.Success?
|
||||
get() = state.value as? State.Success
|
||||
|
||||
val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn } }
|
||||
|
||||
val manga: Manga?
|
||||
get() = successState?.manga
|
||||
|
||||
|
@ -978,18 +974,24 @@ class MangaScreenModel(
|
|||
val manga = successState?.manga ?: return
|
||||
|
||||
screenModelScope.launchIO {
|
||||
getTracks.subscribe(manga.id)
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.map { tracks ->
|
||||
loggedInTrackers
|
||||
// Map to TrackItem
|
||||
.map { service -> TrackItem(tracks.find { it.trackerId == service.id }, service) }
|
||||
// Show only if the service supports this manga's source
|
||||
.filter { (it.tracker as? EnhancedTracker)?.accept(source!!) ?: true }
|
||||
}
|
||||
combine(
|
||||
getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) },
|
||||
trackerManager.loggedInTrackersFlow(),
|
||||
) { mangaTracks, loggedInTrackers ->
|
||||
// Show only if the service supports this manga's source
|
||||
val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedTracker)?.accept(source!!) ?: true }
|
||||
val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet()
|
||||
val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds }
|
||||
supportedTrackerTracks.size to supportedTrackers.isNotEmpty()
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { trackItems ->
|
||||
updateSuccessState { it.copy(trackItems = trackItems) }
|
||||
.collectLatest { (trackingCount, hasLoggedInTrackers) ->
|
||||
updateSuccessState {
|
||||
it.copy(
|
||||
trackingCount = trackingCount,
|
||||
hasLoggedInTrackers = hasLoggedInTrackers,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1053,7 +1055,8 @@ class MangaScreenModel(
|
|||
val chapters: List<ChapterList.Item>,
|
||||
val availableScanlators: Set<String>,
|
||||
val excludedScanlators: Set<String>,
|
||||
val trackItems: List<TrackItem> = emptyList(),
|
||||
val trackingCount: Int = 0,
|
||||
val hasLoggedInTrackers: Boolean = false,
|
||||
val isRefreshingData: Boolean = false,
|
||||
val dialog: Dialog? = null,
|
||||
val hasPromptedToAddBefore: Boolean = false,
|
||||
|
@ -1099,9 +1102,6 @@ class MangaScreenModel(
|
|||
val filterActive: Boolean
|
||||
get() = scanlatorFilterActive || manga.chaptersFiltered()
|
||||
|
||||
val trackingCount: Int
|
||||
get() = trackItems.count { it.track != null }
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
|
|
|
@ -239,7 +239,7 @@ data class TrackInfoDialogHomeScreen(
|
|||
}
|
||||
|
||||
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
|
||||
val loggedInTrackers = Injekt.get<TrackerManager>().trackers.filter { it.isLoggedIn }
|
||||
val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers()
|
||||
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
|
||||
return loggedInTrackers
|
||||
// Map to TrackItem
|
||||
|
|
|
@ -51,6 +51,7 @@ import eu.kanade.presentation.reader.ReadingModeSelectDialog
|
|||
import eu.kanade.presentation.reader.appbars.ReaderAppBars
|
||||
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
|
||||
|
@ -872,7 +873,9 @@ class ReaderActivity : BaseActivity() {
|
|||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
SubsamplingScaleImageView.setDisplayProfile(outputStream.toByteArray())
|
||||
val data = outputStream.toByteArray()
|
||||
SubsamplingScaleImageView.setDisplayProfile(data)
|
||||
TachiyomiImageDecoder.displayProfile = data
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.core.app.NotificationCompat
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue