Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31f967235f | |||
| 7d6e746257 | |||
| d306139047 | |||
| 7e6811692e | |||
| 8df0446020 | |||
| 8ccf8fc74d | |||
| 4deaa41c53 | |||
| c843789f66 | |||
| 78da81fa42 | |||
| 4fc96f263d | |||
| 6a12b54ecb | |||
| 8ffcd5efec | |||
| 26121efeb4 | |||
| 1ecf3a567b | |||
| 20fff5798d | |||
| 0ba580ba30 | |||
| aaf28ee4f1 | |||
| 8558c110a9 | |||
| 3865583c28 | |||
| b2cc61f6fd | |||
| 47dd58de2a | |||
| 67d42c9c2b | |||
| b97f322d6f | |||
| d8cc4f8b45 | |||
| f78752fbdf | |||
| d968d58cd6 | |||
| 54d5f9baaf | |||
| c1bf53e28a | |||
| 517fd3a8f4 | |||
| 089d1aba57 | |||
| 46bf139f01 | |||
| c3fb5c0bec | |||
| 000a4ffc3f | |||
| 7b0b879d65 | |||
| 8a622f6c7d | |||
| 253060a3bc | |||
| b6b33e8c00 | |||
| 2e4f811090 | |||
| 215a1908f7 | |||
| 082acf000c | |||
| 2d1240b274 | |||
| 1e98709cc3 | |||
| 5550ddad4e | |||
| f9148c0c5e | |||
| 089e6268e7 | |||
| 712cd1493f | |||
| bbc8adc3e8 | |||
| 077b673c0a | |||
| 49eacf5178 | |||
| 98d1dddf4a | |||
| 37a616f3db | |||
| ad18696a1a | |||
| 34bb012a1c | |||
| 08c4989aa3 | |||
| 14dae420f5 | |||
| 65ed3c5ae6 | |||
| 5ae3508665 | |||
| e32eb0e009 | |||
| e0812ab5c8 | |||
| df9f79c120 | |||
| 990eb33b98 | |||
| e1bab1172a | |||
| aeeff72bed | |||
| 5895e78b39 | |||
| b24719a3e9 | |||
| d551619d9d | |||
| 06ad6c2e16 | |||
| df7e470e08 |
@@ -40,6 +40,12 @@ jobs:
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"type": "both",
|
||||
"regex": "^.*(myanimelist|mal).*$",
|
||||
"ignoreCase": true,
|
||||
"message": "For issues with linking MyAnimeList, please follow these steps:\n1. Update Mihon to version 0.16.4 or newer\n2. Change your default User-Agent (`More → Settings → Advanced → Default user agent string`)\n3. Close and restart Mihon\n4. Attempt to link MyAnimeList again\n\nIf you had MyAnimeList linked before, try to unlink it first before trying these steps."
|
||||
}
|
||||
]
|
||||
auto-close-ignore-label: do-not-autoclose
|
||||
|
||||
@@ -88,9 +88,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
* Don't group unrelated requests into one issue
|
||||
|
||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||
|
||||
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
||||
Use the [issue forms](https://github.com/jobobby04/TachiyomiSY/issues/new/choose) to submit a bug.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi.sy"
|
||||
|
||||
versionCode = 67
|
||||
versionCode = 68
|
||||
versionName = "1.10.5"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
@@ -155,7 +155,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)
|
||||
@@ -254,7 +253,6 @@ dependencies {
|
||||
implementation(libs.logcat)
|
||||
|
||||
// Crash reports/analytics
|
||||
// implementation(libs.bundles.acra)
|
||||
// "standardImplementation"(libs.firebase.analytics)
|
||||
|
||||
// Shizuku
|
||||
@@ -315,7 +313,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",
|
||||
|
||||
Vendored
+4
@@ -47,6 +47,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
|
||||
|
||||
@@ -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()) }
|
||||
|
||||
@@ -2,8 +2,6 @@ package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -22,8 +20,6 @@ class BasePreferences(
|
||||
|
||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||
|
||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||
|
||||
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||
|
||||
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||
|
||||
@@ -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(
|
||||
@@ -41,9 +41,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" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -362,10 +361,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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),
|
||||
|
||||
+1
-1
@@ -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
|
||||
@@ -33,12 +32,12 @@ import androidx.compose.material.icons.outlined.Label
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.outlined.RemoveDone
|
||||
import androidx.compose.material.icons.outlined.SwapCalls
|
||||
import androidx.compose.material.ripple
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
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
|
||||
@@ -90,7 +89,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) }
|
||||
@@ -199,7 +198,7 @@ private fun RowScope.Button(
|
||||
.size(48.dp)
|
||||
.weight(animatedWeight)
|
||||
.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
interactionSource = null,
|
||||
indication = ripple(bounded = false),
|
||||
onLongClick = onLongClick,
|
||||
onClick = onClick,
|
||||
@@ -252,7 +251,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 =
|
||||
|
||||
+3
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-2
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -43,9 +43,7 @@ fun ReaderPageActionsDialog(
|
||||
var useExtraPage by remember { mutableStateOf(false) }
|
||||
// SY <--
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
Column(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+67
-43
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
+24
-14
@@ -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(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining)
|
||||
onSecondaryContainer = Color(0xFF2E3440), // 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(0xFF81A1C1), // 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),
|
||||
)
|
||||
}
|
||||
|
||||
+67
-43
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
+24
-14
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
+24
-14
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
+24
-14
@@ -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)),
|
||||
) {
|
||||
|
||||
@@ -38,6 +38,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
|
||||
@@ -208,6 +209,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
||||
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
|
||||
add(MangaKeyer())
|
||||
add(MangaCoverKeyer())
|
||||
add(BufferedSourceFetcher.Factory())
|
||||
// SY -->
|
||||
add(PagePreviewKeyer())
|
||||
add(PagePreviewFetcher.Factory(callFactoryLazy))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
package eu.kanade.tachiyomi.data.coil
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil3.ImageLoader
|
||||
import coil3.asCoilImage
|
||||
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.storage.CbzCrypto
|
||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.model.FileHeader
|
||||
import okio.BufferedSource
|
||||
@@ -38,29 +41,58 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
||||
}
|
||||
val decoder = resources.sourceOrNull()?.use {
|
||||
zip4j.use { zipFile ->
|
||||
ImageDecoder.newInstance(zipFile?.getInputStream(entry) ?: it.inputStream())
|
||||
ImageDecoder.newInstance(zipFile?.getInputStream(entry) ?: it.inputStream(), options.cropBorders, displayProfile)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
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 (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
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,
|
||||
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 {
|
||||
@@ -83,4 +115,8 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
|
||||
|
||||
override fun hashCode() = javaClass.hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
var displayProfile: ByteArray? = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import exh.log.xLogE
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@@ -35,6 +36,7 @@ import uy.kohesive.injekt.api.get
|
||||
* and retrieved through dependency injection. You can use this class to queue new chapters or query
|
||||
* downloaded chapters.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class DownloadManager(
|
||||
private val context: Context,
|
||||
private val provider: DownloadProvider = Injekt.get(),
|
||||
@@ -475,7 +477,7 @@ class DownloadManager(
|
||||
|
||||
fun renameMangaDir(oldTitle: String, newTitle: String, source: Long) {
|
||||
val sourceDir = provider.findSourceDir(sourceManager.getOrStub(source)) ?: return
|
||||
val mangaDir = sourceDir.findFile(DiskUtil.buildValidFilename(oldTitle), true) ?: return
|
||||
val mangaDir = sourceDir.findFile(DiskUtil.buildValidFilename(oldTitle)) ?: return
|
||||
mangaDir.renameTo(DiskUtil.buildValidFilename(newTitle))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(/* SY --> */ manga.ogTitle /* SY <-- */, 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import exh.source.isEhBasedSource
|
||||
import exh.util.DataSaver
|
||||
import exh.util.DataSaver.Companion.getImage
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -75,6 +77,7 @@ import java.util.zip.ZipOutputStream
|
||||
*
|
||||
* Its queue contains the list of chapters to download.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class Downloader(
|
||||
private val context: Context,
|
||||
private val provider: DownloadProvider,
|
||||
@@ -511,6 +514,9 @@ class Downloader(
|
||||
.retryWhen { _, attempt ->
|
||||
if (attempt < 3) {
|
||||
delay((2L shl attempt.toInt()) * 1000)
|
||||
if (source.isEhBasedSource()) {
|
||||
page.imageUrl = source.getImageUrl(page)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -709,7 +715,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())
|
||||
|
||||
@@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notify
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import tachiyomi.core.common.Constants
|
||||
import tachiyomi.core.common.i18n.pluralStringResource
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
@@ -41,6 +42,7 @@ import uy.kohesive.injekt.api.get
|
||||
import java.math.RoundingMode
|
||||
import java.text.NumberFormat
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class LibraryUpdateNotifier(
|
||||
private val context: Context,
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import tachiyomi.core.common.Constants
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
@@ -41,6 +42,7 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
* Pending Broadcasts should be made from here.
|
||||
* NOTE: Use local broadcasts if possible.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class NotificationReceiver : BroadcastReceiver() {
|
||||
|
||||
private val getManga: GetManga by injectLazy()
|
||||
|
||||
@@ -45,7 +45,7 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
notifier.showSyncError(e.message)
|
||||
Result.failure()
|
||||
Result.success() // try again next time
|
||||
} finally {
|
||||
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ class SyncManager(
|
||||
}
|
||||
|
||||
// Stop the sync early if the remote backup is null or empty
|
||||
if (remoteBackup.backupManga?.size == 0) {
|
||||
if (remoteBackup.backupManga.size == 0) {
|
||||
notifier.showSyncError("No data found on remote server.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ import exh.source.BlacklistedSources
|
||||
import exh.source.EH_SOURCE_ID
|
||||
import exh.source.EXH_SOURCE_ID
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
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
|
||||
@@ -32,7 +31,6 @@ 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
|
||||
@@ -54,10 +52,10 @@ class ExtensionManager(
|
||||
private val trustExtension: TrustExtension = Injekt.get(),
|
||||
) {
|
||||
|
||||
// SY -->
|
||||
val scope = CoroutineScope(SupervisorJob())
|
||||
|
||||
private val _isInitialized = MutableStateFlow(false)
|
||||
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* API where all the available extensions can be found.
|
||||
@@ -71,13 +69,31 @@ 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>())
|
||||
// SY -->
|
||||
val availableExtensionsFlow = _availableExtensionsMapFlow.map { it.filterNotBlacklisted().values.toList() }
|
||||
.stateIn(scope, SharingStarted.Lazily, _availableExtensionsMapFlow.value.values.toList())
|
||||
// SY <--
|
||||
|
||||
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
|
||||
val pkgName = _installedExtensionsMapFlow.value.values
|
||||
.find { ext ->
|
||||
ext.sources.any { it.id == sourceId }
|
||||
}
|
||||
?.pkgName
|
||||
if (pkgName != null) {
|
||||
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
|
||||
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
||||
@@ -95,15 +111,6 @@ class ExtensionManager(
|
||||
// SY <--
|
||||
}
|
||||
|
||||
private val _availableExtensionsFlow = MutableStateFlow(emptyList<Extension.Available>())
|
||||
|
||||
// SY -->
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
val availableExtensionsFlow = _availableExtensionsFlow
|
||||
.map { it.filterNotBlacklisted() }
|
||||
.stateIn(GlobalScope, SharingStarted.Eagerly, emptyList())
|
||||
// SY <--
|
||||
|
||||
private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap()
|
||||
|
||||
private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) {
|
||||
@@ -115,38 +122,30 @@ 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 }
|
||||
// SY -->
|
||||
.filterNotBlacklisted()
|
||||
// SY <--
|
||||
|
||||
_isInitialized.value = true
|
||||
// SY <--
|
||||
}
|
||||
|
||||
// EXH -->
|
||||
private fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> {
|
||||
private fun <T : Extension> Map<String, T>.filterNotBlacklisted(): Map<String, T> {
|
||||
val blacklistEnabled = preferences.enableSourceBlacklist().get()
|
||||
return filterNot { extension ->
|
||||
return filterNot { (_, extension) ->
|
||||
extension.isBlacklisted(blacklistEnabled)
|
||||
.also {
|
||||
if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName)
|
||||
@@ -160,7 +159,7 @@ class ExtensionManager(
|
||||
// EXH <--
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@@ -173,7 +172,7 @@ class ExtensionManager(
|
||||
|
||||
enableAdditionalSubLanguages(extensions)
|
||||
|
||||
_availableExtensionsFlow.value = extensions
|
||||
_availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName }
|
||||
updatedInstalledExtensionsStatuses(extensions)
|
||||
setupAvailableExtensionsSourcesDataMap(extensions)
|
||||
}
|
||||
@@ -219,42 +218,36 @@ class ExtensionManager(
|
||||
return
|
||||
}
|
||||
|
||||
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList()
|
||||
val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap()
|
||||
var changed = false
|
||||
for ((pkgName, extension) in installedExtensionsMap) {
|
||||
val availableExt = availableExtensions.find { it.pkgName == pkgName }
|
||||
|
||||
for ((index, installedExt) in mutInstalledExtensions.withIndex()) {
|
||||
val pkgName = installedExt.pkgName
|
||||
// SY -->
|
||||
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
// SY <--
|
||||
|
||||
if (availableExt == null && !installedExt.isObsolete) {
|
||||
mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
|
||||
if (availableExt == null && !extension.isObsolete) {
|
||||
installedExtensionsMap[pkgName] = extension.copy(isObsolete = true)
|
||||
changed = true
|
||||
// SY -->
|
||||
} else if (installedExt.isBlacklisted() && !installedExt.isRedundant) {
|
||||
mutInstalledExtensions[index] = installedExt.copy(isRedundant = true)
|
||||
} else if (extension.isBlacklisted() && !extension.isRedundant) {
|
||||
installedExtensionsMap[pkgName] = extension.copy(isRedundant = true)
|
||||
changed = true
|
||||
// SY <--
|
||||
} 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()
|
||||
}
|
||||
@@ -278,8 +271,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)
|
||||
}
|
||||
|
||||
@@ -315,24 +307,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) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,7 +332,7 @@ class ExtensionManager(
|
||||
}
|
||||
// SY <--
|
||||
|
||||
_installedExtensionsFlow.value += extension
|
||||
_installedExtensionsMapFlow.value += extension
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,13 +349,7 @@ class ExtensionManager(
|
||||
}
|
||||
// SY <--
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,14 +359,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,14 +379,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) {
|
||||
@@ -436,17 +403,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,
|
||||
|
||||
@@ -806,6 +806,11 @@ class EHentai(
|
||||
override fun pageListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Unused method was called somehow!")
|
||||
|
||||
override suspend fun getImageUrl(page: Page): String {
|
||||
val imageUrlResponse = client.newCall(imageUrlRequest(page)).awaitSuccess()
|
||||
return realImageUrlParse(imageUrlResponse, page)
|
||||
}
|
||||
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
|
||||
override fun fetchImageUrl(page: Page): Observable<String> {
|
||||
return client.newCall(imageUrlRequest(page))
|
||||
|
||||
@@ -31,6 +31,7 @@ import exh.md.handlers.FollowsHandler
|
||||
import exh.md.handlers.MangaHandler
|
||||
import exh.md.handlers.MangaHotHandler
|
||||
import exh.md.handlers.MangaPlusHandler
|
||||
import exh.md.handlers.NamicomiHandler
|
||||
import exh.md.handlers.PageHandler
|
||||
import exh.md.handlers.SimilarHandler
|
||||
import exh.md.network.MangaDexLoginHelper
|
||||
@@ -123,6 +124,9 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
||||
private val mangaHotHandler by lazy {
|
||||
MangaHotHandler(network.client, network.defaultUserAgentProvider())
|
||||
}
|
||||
private val namicomiHandler by lazy {
|
||||
NamicomiHandler(network.client, network.defaultUserAgentProvider())
|
||||
}
|
||||
private val pageHandler by lazy {
|
||||
PageHandler(
|
||||
headers,
|
||||
@@ -132,6 +136,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
||||
bilibiliHandler,
|
||||
azukHandler,
|
||||
mangaHotHandler,
|
||||
namicomiHandler,
|
||||
trackPreferences,
|
||||
mdList,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -195,7 +196,9 @@ class ExtensionsScreenModel(
|
||||
}
|
||||
|
||||
fun trustExtension(extension: Extension.Untrusted) {
|
||||
extensionManager.trust(extension)
|
||||
screenModelScope.launch {
|
||||
extensionManager.trust(extension)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
||||
@@ -56,9 +56,7 @@ fun SourceFilterDialog(
|
||||
) {
|
||||
val updateFilters = { onUpdate(filters) }
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
LazyColumn {
|
||||
stickyHeader {
|
||||
Row(
|
||||
|
||||
@@ -234,6 +234,9 @@ class LibraryScreenModel(
|
||||
prefs.filterBookmarked,
|
||||
prefs.filterCompleted,
|
||||
prefs.filterIntervalCustom,
|
||||
// SY -->
|
||||
prefs.filterLewd,
|
||||
// SY <--
|
||||
) + trackFilter.values
|
||||
).any { it != TriState.DISABLED }
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ import eu.kanade.presentation.reader.appbars.NavBarType
|
||||
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
|
||||
@@ -1280,7 +1281,9 @@ class ReaderActivity : BaseActivity() {
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
SubsamplingScaleImageView.setDisplayProfile(outputStream.toByteArray())
|
||||
val data = outputStream.toByteArray()
|
||||
SubsamplingScaleImageView.setDisplayProfile(data)
|
||||
TachiyomiImageDecoder.displayProfile = data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1141,7 +1141,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
|
||||
return imageSaver.save(
|
||||
image = Image.Page(
|
||||
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg) },
|
||||
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg).inputStream() },
|
||||
name = filename,
|
||||
location = location,
|
||||
),
|
||||
|
||||
@@ -13,6 +13,7 @@ import exh.util.DataSaver
|
||||
import exh.util.DataSaver.Companion.getImage
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
@@ -31,6 +32,7 @@ import kotlin.math.min
|
||||
/**
|
||||
* Loader used to load chapters from an online source.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
internal class HttpPageLoader(
|
||||
private val chapter: ReaderChapter,
|
||||
private val source: HttpSource,
|
||||
|
||||
@@ -43,7 +43,9 @@ internal class ZipPageLoader(file: UniFile, context: Context) : PageLoader() {
|
||||
}
|
||||
|
||||
private val apacheZip: ZipFile? = if (!file.isEncryptedZip() && Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
|
||||
ZipFile(channel)
|
||||
ZipFile.Builder()
|
||||
.setSeekableByteChannel(channel)
|
||||
.get()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -18,23 +18,27 @@ import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.core.view.isVisible
|
||||
import coil3.BitmapImage
|
||||
import coil3.dispose
|
||||
import coil3.imageLoader
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import coil3.size.Precision
|
||||
import coil3.size.ViewSizeResolver
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT_QUAD
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
import com.github.chrisbanes.photoview.PhotoView
|
||||
import eu.kanade.tachiyomi.data.coil.cropBorders
|
||||
import eu.kanade.tachiyomi.data.coil.customDecoder
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import okio.BufferedSource
|
||||
|
||||
/**
|
||||
* A wrapper view for showing page image.
|
||||
@@ -140,14 +144,14 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setImage(inputStream: InputStream, isAnimated: Boolean, config: Config) {
|
||||
fun setImage(source: BufferedSource, isAnimated: Boolean, config: Config) {
|
||||
this.config = config
|
||||
if (isAnimated) {
|
||||
prepareAnimatedImageView()
|
||||
setAnimatedImage(inputStream, config)
|
||||
setAnimatedImage(source, config)
|
||||
} else {
|
||||
prepareNonAnimatedImageView()
|
||||
setNonAnimatedImage(inputStream, config)
|
||||
setNonAnimatedImage(source, config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +260,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun setNonAnimatedImage(
|
||||
image: Any,
|
||||
data: Any,
|
||||
config: Config,
|
||||
) = (pageView as? SubsamplingScaleImageView)?.apply {
|
||||
setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration())
|
||||
@@ -277,12 +281,36 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
},
|
||||
)
|
||||
|
||||
when (image) {
|
||||
is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap))
|
||||
is InputStream -> setImage(ImageSource.inputStream(image))
|
||||
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
|
||||
if (isWebtoon) {
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(data)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.diskCachePolicy(CachePolicy.DISABLED)
|
||||
.target(
|
||||
onSuccess = { result ->
|
||||
val image = result as BitmapImage
|
||||
setImage(ImageSource.bitmap(image.bitmap))
|
||||
isVisible = true
|
||||
},
|
||||
onError = {
|
||||
this@ReaderPageImageView.onImageLoadError()
|
||||
},
|
||||
)
|
||||
.size(ViewSizeResolver(this@ReaderPageImageView))
|
||||
.precision(Precision.INEXACT)
|
||||
.cropBorders(config.cropBorders)
|
||||
.customDecoder(true)
|
||||
.crossfade(false)
|
||||
.build()
|
||||
context.imageLoader.enqueue(request)
|
||||
} else {
|
||||
when (data) {
|
||||
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
|
||||
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream()))
|
||||
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
|
||||
}
|
||||
isVisible = true
|
||||
}
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
private fun prepareAnimatedImageView() {
|
||||
@@ -325,18 +353,13 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun setAnimatedImage(
|
||||
image: Any,
|
||||
data: Any,
|
||||
config: Config,
|
||||
) = (pageView as? AppCompatImageView)?.apply {
|
||||
if (this is PhotoView) {
|
||||
setZoomTransitionDuration(config.zoomDuration.getSystemScaledDuration())
|
||||
}
|
||||
|
||||
val data = when (image) {
|
||||
is Drawable -> image
|
||||
is InputStream -> ByteBuffer.wrap(image.readBytes())
|
||||
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
|
||||
}
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(data)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
|
||||
@@ -19,15 +19,14 @@ import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import logcat.LogPriority
|
||||
import okio.Buffer
|
||||
import okio.BufferedSource
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.decoder.ImageDecoder
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
@@ -159,53 +158,45 @@ class PagerPageHolder(
|
||||
val streamFn2 = extraPage?.stream
|
||||
|
||||
try {
|
||||
val (bais, isAnimated, background) = withIOContext {
|
||||
streamFn().buffered(16).use { stream ->
|
||||
val (source, isAnimated, background) = withIOContext {
|
||||
streamFn().buffered(16).use { source ->
|
||||
// SY -->
|
||||
(
|
||||
if (extraPage != null) {
|
||||
streamFn2?.invoke()
|
||||
?.buffered(16)
|
||||
if (extraPage != null) {
|
||||
streamFn2?.invoke()
|
||||
?.buffered(16)
|
||||
} else {
|
||||
null
|
||||
}.use { source2 ->
|
||||
val itemSource = if (viewer.config.dualPageSplit) {
|
||||
process(item.first, Buffer().readFrom(source))
|
||||
} else {
|
||||
mergePages(Buffer().readFrom(source), source2?.let { Buffer().readFrom(it) })
|
||||
}
|
||||
// SY <--
|
||||
val isAnimated = ImageUtil.isAnimatedAndSupported(itemSource)
|
||||
val background = if (!isAnimated && viewer.config.automaticBackground) {
|
||||
ImageUtil.chooseBackground(context, itemSource.peek())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
).use { stream2 ->
|
||||
if (viewer.config.dualPageSplit) {
|
||||
process(item.first, stream)
|
||||
} else {
|
||||
mergePages(stream, stream2)
|
||||
}.use { itemStream ->
|
||||
// SY <--
|
||||
val bais = ByteArrayInputStream(itemStream.readBytes())
|
||||
val isAnimated = ImageUtil.isAnimatedAndSupported(bais)
|
||||
bais.reset()
|
||||
val background = if (!isAnimated && viewer.config.automaticBackground) {
|
||||
ImageUtil.chooseBackground(context, bais)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
bais.reset()
|
||||
Triple(bais, isAnimated, background)
|
||||
}
|
||||
}
|
||||
Triple(itemSource, isAnimated, background)
|
||||
}
|
||||
}
|
||||
}
|
||||
withUIContext {
|
||||
bais.use {
|
||||
setImage(
|
||||
it,
|
||||
isAnimated,
|
||||
Config(
|
||||
zoomDuration = viewer.config.doubleTapAnimDuration,
|
||||
minimumScaleType = viewer.config.imageScaleType,
|
||||
cropBorders = viewer.config.imageCropBorders,
|
||||
zoomStartPosition = viewer.config.imageZoomType,
|
||||
landscapeZoom = viewer.config.landscapeZoom,
|
||||
),
|
||||
)
|
||||
if (!isAnimated) {
|
||||
pageBackground = background
|
||||
}
|
||||
setImage(
|
||||
source,
|
||||
isAnimated,
|
||||
Config(
|
||||
zoomDuration = viewer.config.doubleTapAnimDuration,
|
||||
minimumScaleType = viewer.config.imageScaleType,
|
||||
cropBorders = viewer.config.imageCropBorders,
|
||||
zoomStartPosition = viewer.config.imageZoomType,
|
||||
landscapeZoom = viewer.config.landscapeZoom,
|
||||
),
|
||||
)
|
||||
if (!isAnimated) {
|
||||
pageBackground = background
|
||||
}
|
||||
removeErrorLayout()
|
||||
}
|
||||
@@ -217,124 +208,119 @@ class PagerPageHolder(
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
|
||||
private fun process(page: ReaderPage, imageSource: BufferedSource): BufferedSource {
|
||||
if (viewer.config.dualPageRotateToFit) {
|
||||
return rotateDualPage(imageStream)
|
||||
return rotateDualPage(imageSource)
|
||||
}
|
||||
|
||||
if (!viewer.config.dualPageSplit) {
|
||||
return imageStream
|
||||
return imageSource
|
||||
}
|
||||
|
||||
if (page is InsertPage) {
|
||||
return splitInHalf(imageStream)
|
||||
return splitInHalf(imageSource)
|
||||
}
|
||||
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
val isDoublePage = ImageUtil.isWideImage(imageSource)
|
||||
if (!isDoublePage) {
|
||||
return imageStream
|
||||
return imageSource
|
||||
}
|
||||
|
||||
onPageSplit(page)
|
||||
|
||||
return splitInHalf(imageStream)
|
||||
return splitInHalf(imageSource)
|
||||
}
|
||||
|
||||
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
private fun rotateDualPage(imageSource: BufferedSource): BufferedSource {
|
||||
val isDoublePage = ImageUtil.isWideImage(imageSource)
|
||||
return if (isDoublePage) {
|
||||
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
||||
ImageUtil.rotateImage(imageStream, rotation)
|
||||
ImageUtil.rotateImage(imageSource, rotation)
|
||||
} else {
|
||||
imageStream
|
||||
imageSource
|
||||
}
|
||||
}
|
||||
|
||||
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
|
||||
private fun mergePages(imageSource: BufferedSource, imageSource2: BufferedSource?): BufferedSource {
|
||||
// Handle adding a center margin to wide images if requested
|
||||
if (imageStream2 == null) {
|
||||
return if (imageStream is BufferedInputStream &&
|
||||
!ImageUtil.isAnimatedAndSupported(imageStream) &&
|
||||
ImageUtil.isWideImage(imageStream) &&
|
||||
if (imageSource2 == null) {
|
||||
return if (
|
||||
!ImageUtil.isAnimatedAndSupported(imageSource) &&
|
||||
ImageUtil.isWideImage(imageSource) &&
|
||||
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
|
||||
!viewer.config.imageCropBorders
|
||||
) {
|
||||
ImageUtil.addHorizontalCenterMargin(imageStream, height, context)
|
||||
ImageUtil.addHorizontalCenterMargin(imageSource, height, context)
|
||||
} else {
|
||||
imageStream
|
||||
imageSource
|
||||
}
|
||||
}
|
||||
|
||||
if (page.fullPage) return imageStream
|
||||
if (ImageUtil.isAnimatedAndSupported(imageStream)) {
|
||||
if (page.fullPage) return imageSource
|
||||
if (ImageUtil.isAnimatedAndSupported(imageSource)) {
|
||||
page.fullPage = true
|
||||
splitDoublePages()
|
||||
return imageStream
|
||||
} else if (ImageUtil.isAnimatedAndSupported(imageStream2)) {
|
||||
return imageSource
|
||||
} else if (ImageUtil.isAnimatedAndSupported(imageSource2)) {
|
||||
page.isolatedPage = true
|
||||
extraPage?.fullPage = true
|
||||
splitDoublePages()
|
||||
return imageStream
|
||||
return imageSource
|
||||
}
|
||||
val imageBytes = imageStream.readBytes()
|
||||
|
||||
val imageBitmap = try {
|
||||
ImageDecoder.newInstance(imageBytes.inputStream())?.decode()
|
||||
ImageDecoder.newInstance(imageSource.inputStream())?.decode()
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) { "Cannot combine pages" }
|
||||
null
|
||||
}
|
||||
if (imageBitmap == null) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
imageSource2.close()
|
||||
page.fullPage = true
|
||||
splitDoublePages()
|
||||
logcat(LogPriority.ERROR) { "Cannot combine pages" }
|
||||
return imageBytes.inputStream()
|
||||
return imageSource
|
||||
}
|
||||
scope.launch { progressIndicator.setProgress(96) }
|
||||
val height = imageBitmap.height
|
||||
val width = imageBitmap.width
|
||||
|
||||
if (height < width) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
imageSource2.close()
|
||||
page.fullPage = true
|
||||
splitDoublePages()
|
||||
return imageBytes.inputStream()
|
||||
return imageSource
|
||||
}
|
||||
|
||||
val imageBytes2 = imageStream2.readBytes()
|
||||
val imageBitmap2 = try {
|
||||
ImageDecoder.newInstance(imageBytes2.inputStream())?.decode()
|
||||
ImageDecoder.newInstance(imageSource2.inputStream())?.decode()
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) { "Cannot combine pages" }
|
||||
null
|
||||
}
|
||||
if (imageBitmap2 == null) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
imageSource2.close()
|
||||
extraPage?.fullPage = true
|
||||
page.isolatedPage = true
|
||||
splitDoublePages()
|
||||
logcat(LogPriority.ERROR) { "Cannot combine pages" }
|
||||
return imageBytes.inputStream()
|
||||
return imageSource
|
||||
}
|
||||
scope.launch { progressIndicator.setProgress(97) }
|
||||
val height2 = imageBitmap2.height
|
||||
val width2 = imageBitmap2.width
|
||||
|
||||
if (height2 < width2) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
imageSource2.close()
|
||||
extraPage?.fullPage = true
|
||||
page.isolatedPage = true
|
||||
splitDoublePages()
|
||||
return imageBytes.inputStream()
|
||||
return imageSource
|
||||
}
|
||||
val isLTR = (viewer !is R2LPagerViewer) xor viewer.config.invertDoublePages
|
||||
|
||||
imageStream.close()
|
||||
imageStream2.close()
|
||||
imageSource.close()
|
||||
imageSource2.close()
|
||||
|
||||
val centerMargin = if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 && !viewer.config.imageCropBorders) {
|
||||
96 / (this.height.coerceAtLeast(1) / max(height, height2).coerceAtLeast(1)).coerceAtLeast(1)
|
||||
@@ -363,7 +349,7 @@ class PagerPageHolder(
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitInHalf(imageStream: InputStream): InputStream {
|
||||
private fun splitInHalf(imageSource: BufferedSource): BufferedSource {
|
||||
var side = when {
|
||||
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
||||
viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
|
||||
@@ -387,7 +373,7 @@ class PagerPageHolder(
|
||||
0
|
||||
}
|
||||
|
||||
return ImageUtil.splitInHalf(imageStream, side, sideMargin)
|
||||
return ImageUtil.splitInHalf(imageSource, side, sideMargin)
|
||||
}
|
||||
|
||||
private fun onPageSplit(page: ReaderPage) {
|
||||
|
||||
+16
-23
@@ -22,15 +22,14 @@ import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import logcat.LogPriority
|
||||
import okio.Buffer
|
||||
import okio.BufferedSource
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Holder of the webtoon reader for a single page of a chapter.
|
||||
@@ -188,16 +187,14 @@ class WebtoonPageHolder(
|
||||
val streamFn = page?.stream ?: return
|
||||
|
||||
try {
|
||||
val (openStream, isAnimated) = withIOContext {
|
||||
val stream = streamFn().buffered(16)
|
||||
val openStream = process(stream)
|
||||
|
||||
val isAnimated = ImageUtil.isAnimatedAndSupported(stream)
|
||||
Pair(openStream, isAnimated)
|
||||
val (source, isAnimated) = withIOContext {
|
||||
val source = streamFn().use { process(Buffer().readFrom(it)) }
|
||||
val isAnimated = ImageUtil.isAnimatedAndSupported(source)
|
||||
Pair(source, isAnimated)
|
||||
}
|
||||
withUIContext {
|
||||
frame.setImage(
|
||||
openStream,
|
||||
source,
|
||||
isAnimated,
|
||||
ReaderPageImageView.Config(
|
||||
zoomDuration = viewer.config.doubleTapAnimDuration,
|
||||
@@ -207,10 +204,6 @@ class WebtoonPageHolder(
|
||||
)
|
||||
removeErrorLayout()
|
||||
}
|
||||
// Suspend the coroutine to close the input stream only when the WebtoonPageHolder is recycled
|
||||
suspendCancellableCoroutine<Nothing> { continuation ->
|
||||
continuation.invokeOnCancellation { openStream.close() }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
withUIContext {
|
||||
@@ -219,29 +212,29 @@ class WebtoonPageHolder(
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(imageStream: BufferedInputStream): InputStream {
|
||||
private fun process(imageSource: BufferedSource): BufferedSource {
|
||||
if (viewer.config.dualPageRotateToFit) {
|
||||
return rotateDualPage(imageStream)
|
||||
return rotateDualPage(imageSource)
|
||||
}
|
||||
|
||||
if (viewer.config.dualPageSplit) {
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
val isDoublePage = ImageUtil.isWideImage(imageSource)
|
||||
if (isDoublePage) {
|
||||
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
|
||||
return ImageUtil.splitAndMerge(imageStream, upperSide)
|
||||
return ImageUtil.splitAndMerge(imageSource, upperSide)
|
||||
}
|
||||
}
|
||||
|
||||
return imageStream
|
||||
return imageSource
|
||||
}
|
||||
|
||||
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
private fun rotateDualPage(imageSource: BufferedSource): BufferedSource {
|
||||
val isDoublePage = ImageUtil.isWideImage(imageSource)
|
||||
return if (isDoublePage) {
|
||||
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
||||
ImageUtil.rotateImage(imageStream, rotation)
|
||||
ImageUtil.rotateImage(imageSource, rotation)
|
||||
} else {
|
||||
imageStream
|
||||
imageSource
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -28,7 +28,8 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
private var atFirstPosition = false
|
||||
private var halfWidth = 0
|
||||
private var halfHeight = 0
|
||||
private var originalHeight = 0
|
||||
var originalHeight = 0
|
||||
private set
|
||||
private var heightSet = false
|
||||
private var firstVisibleItemPosition = 0
|
||||
private var lastVisibleItemPosition = 0
|
||||
|
||||
@@ -124,7 +124,7 @@ class WebtoonViewer(
|
||||
recycler.getLocationInWindow(viewPositionRelativeToWindow)
|
||||
val pos = PointF(
|
||||
(event.rawX - viewPosition[0] + viewPositionRelativeToWindow[0]) / recycler.width,
|
||||
(event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.height,
|
||||
(event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.originalHeight,
|
||||
)
|
||||
when (config.navigator.getAction(pos)) {
|
||||
NavigationRegion.MENU -> activity.toggleMenu()
|
||||
|
||||
@@ -20,12 +20,13 @@ class CrashLogUtil(
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend fun dumpLogs() = withNonCancellableContext {
|
||||
suspend fun dumpLogs(exception: Throwable? = null) = withNonCancellableContext {
|
||||
try {
|
||||
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
||||
|
||||
file.appendText(getDebugInfo() + "\n\n")
|
||||
getExtensionsInfo()?.let { file.appendText("$it\n\n") }
|
||||
exception?.let { file.appendText("$it\n\n") }
|
||||
|
||||
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
|
||||
|
||||
|
||||
@@ -61,14 +61,14 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
override suspend fun doWork(): Result {
|
||||
return try {
|
||||
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
|
||||
Result.failure()
|
||||
Result.success() // retry again later
|
||||
} else {
|
||||
startUpdating()
|
||||
logger.d("Update job completed!")
|
||||
Result.success()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure()
|
||||
Result.success() // retry again later
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,13 @@ import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import okio.BufferedSource
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
@@ -67,7 +70,7 @@ class MemAutoFlushingLookupTable<T>(
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook)
|
||||
}
|
||||
|
||||
private fun InputStream.requireBytes(targetArray: ByteArray, byteCount: Int): Boolean {
|
||||
private fun BufferedSource.requireBytes(targetArray: ByteArray, byteCount: Int): Boolean {
|
||||
var readIter = 0
|
||||
while (true) {
|
||||
val readThisIter = read(targetArray, readIter, byteCount - readIter)
|
||||
@@ -80,7 +83,7 @@ class MemAutoFlushingLookupTable<T>(
|
||||
private fun initialLoad() {
|
||||
launch {
|
||||
try {
|
||||
atomicFile.openRead().buffered().use { input ->
|
||||
atomicFile.openRead().source().buffer().use { input ->
|
||||
val bb = ByteBuffer.allocate(8)
|
||||
|
||||
while (true) {
|
||||
@@ -126,7 +129,7 @@ class MemAutoFlushingLookupTable<T>(
|
||||
|
||||
val fos = atomicFile.startWrite()
|
||||
try {
|
||||
val out = fos.buffered()
|
||||
val out = fos.sink().buffer()
|
||||
table.forEach { key, value ->
|
||||
val v = serializer.write(value).toByteArray(Charsets.UTF_8)
|
||||
bb.putInt(0, key)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package exh.md.handlers
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
|
||||
class NamicomiHandler(currentClient: OkHttpClient, userAgent: String) {
|
||||
private val apiUrl = "https://api.namicomi.com"
|
||||
|
||||
private val headers = Headers.Builder()
|
||||
.add("User-Agent", userAgent)
|
||||
.build()
|
||||
|
||||
val client: OkHttpClient = currentClient
|
||||
|
||||
suspend fun fetchPageList(externalUrl: String, dataSaver: Boolean): List<Page> {
|
||||
val chapterId = externalUrl.substringBefore("?").substringAfterLast("/")
|
||||
val request =
|
||||
GET(
|
||||
"$apiUrl/images/chapter/$chapterId?newQualities=true",
|
||||
headers,
|
||||
)
|
||||
return pageListParse(client.newCall(request).awaitSuccess(), chapterId, dataSaver)
|
||||
}
|
||||
|
||||
private fun pageListParse(response: Response, chapterId: String, dataSaver: Boolean): List<Page> {
|
||||
val data = Json.parseToJsonElement(response.body.string()).jsonObject["data"]!!
|
||||
val baseUrl = data.jsonObject["baseUrl"]!!.jsonPrimitive.content
|
||||
val hash = data.jsonObject["hash"]!!.jsonPrimitive.content
|
||||
|
||||
/* Available quality levels: source, high, medium, low */
|
||||
val quality = if(dataSaver) "low" else "high"
|
||||
|
||||
return data
|
||||
.jsonObject[quality]!!
|
||||
.jsonArray.mapIndexed { index, element ->
|
||||
val fileName = element.jsonObject["filename"]!!.jsonPrimitive.content
|
||||
val url = "$baseUrl/chapter/$chapterId/$hash/$quality/$fileName"
|
||||
Page(index, url, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class PageHandler(
|
||||
private val bilibiliHandler: BilibiliHandler,
|
||||
private val azukiHandler: AzukiHandler,
|
||||
private val mangaHotHandler: MangaHotHandler,
|
||||
private val namicomiHandler: NamicomiHandler,
|
||||
private val preferences: TrackPreferences,
|
||||
private val mdList: MdList,
|
||||
) {
|
||||
@@ -54,6 +55,10 @@ class PageHandler(
|
||||
chapter.scanlator.equals("mangahot", true) -> mangaHotHandler.fetchPageList(
|
||||
chapterResponse.data.attributes.externalUrl,
|
||||
)
|
||||
chapter.scanlator.equals("namicomi", true) -> namicomiHandler.fetchPageList(
|
||||
chapterResponse.data.attributes.externalUrl,
|
||||
dataSaver = dataSaver,
|
||||
)
|
||||
else -> throw Exception("${chapter.scanlator} not supported")
|
||||
}
|
||||
} else {
|
||||
@@ -122,6 +127,9 @@ class PageHandler(
|
||||
page.imageUrl?.contains("mangahot", true) == true -> {
|
||||
mangaHotHandler.client.newCachelessCallWithProgress(GET(page.imageUrl!!, mangaHotHandler.headers), page)
|
||||
}
|
||||
page.imageUrl?.contains("namicomi", true) == true -> {
|
||||
mangaHotHandler.client.newCachelessCallWithProgress(GET(page.imageUrl!!, mangaHotHandler.headers), page)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import tachiyomi.core.common.util.lang.withIOContext
|
||||
/**
|
||||
* Util for evaluating JavaScript in sources.
|
||||
*/
|
||||
@Suppress("UNUSED", "UNCHECKED_CAST")
|
||||
@Suppress("UNUSED", "UNCHECKED_CAST", "UNUSED_PARAMETER")
|
||||
class JavaScriptEngine(context: Context) {
|
||||
|
||||
/**
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
@@ -48,6 +49,7 @@ abstract class WebViewInterceptor(
|
||||
|
||||
abstract fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
|
||||
@@ -26,9 +26,9 @@ class EpubFile(file: UniFile, context: Context) : Closeable {
|
||||
* Zip file of this epub.
|
||||
*/
|
||||
private val zip = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
ZipFile(tempFileManager.createTempFile(file))
|
||||
ZipFile.Builder().setFile(tempFileManager.createTempFile(file)).get()
|
||||
} else {
|
||||
ZipFile(file.openReadOnlyChannel(context))
|
||||
ZipFile.Builder().setSeekableByteChannel(file.openReadOnlyChannel(context)).get()
|
||||
}
|
||||
// SY <--
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package tachiyomi.core.common.util.lang
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -64,7 +65,7 @@ private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutin
|
||||
internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
|
||||
invokeOnCancellation { sub.unsubscribe() }
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
|
||||
fun <T> runAsObservable(
|
||||
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
|
||||
block: suspend () -> T,
|
||||
|
||||
@@ -26,11 +26,10 @@ import androidx.core.graphics.red
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.hippo.unifile.UniFile
|
||||
import logcat.LogPriority
|
||||
import okio.Buffer
|
||||
import okio.BufferedSource
|
||||
import tachiyomi.decoder.Format
|
||||
import tachiyomi.decoder.ImageDecoder
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.URLConnection
|
||||
@@ -83,9 +82,9 @@ object ImageUtil {
|
||||
?: "jpg"
|
||||
}
|
||||
|
||||
fun isAnimatedAndSupported(stream: InputStream): Boolean {
|
||||
fun isAnimatedAndSupported(source: BufferedSource): Boolean {
|
||||
return try {
|
||||
val type = getImageType(stream) ?: return false
|
||||
val type = getImageType(source.peek().inputStream()) ?: return false
|
||||
// https://coil-kt.github.io/coil/getting_started/#supported-image-formats
|
||||
when (type.format) {
|
||||
Format.Gif -> true
|
||||
@@ -132,18 +131,16 @@ object ImageUtil {
|
||||
*
|
||||
* @return true if the width is greater than the height
|
||||
*/
|
||||
fun isWideImage(imageStream: BufferedInputStream): Boolean {
|
||||
val options = extractImageOptions(imageStream)
|
||||
fun isWideImage(imageSource: BufferedSource): Boolean {
|
||||
val options = extractImageOptions(imageSource)
|
||||
return options.outWidth > options.outHeight
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the 'side' part from imageStream and return it as InputStream.
|
||||
* Extract the 'side' part from [BufferedSource] and return it as [BufferedSource].
|
||||
*/
|
||||
fun splitInHalf(imageStream: InputStream, side: Side, sidePadding: Int): InputStream {
|
||||
val imageBytes = imageStream.readBytes()
|
||||
|
||||
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
fun splitInHalf(imageSource: BufferedSource, side: Side, sidePadding: Int): BufferedSource {
|
||||
val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
|
||||
val height = imageBitmap.height
|
||||
val width = imageBitmap.width
|
||||
|
||||
@@ -157,22 +154,20 @@ object ImageUtil {
|
||||
half.applyCanvas {
|
||||
drawBitmap(imageBitmap, part, singlePage, null)
|
||||
}
|
||||
val output = ByteArrayOutputStream()
|
||||
half.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||
val output = Buffer()
|
||||
half.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
|
||||
|
||||
return ByteArrayInputStream(output.toByteArray())
|
||||
return output
|
||||
}
|
||||
|
||||
fun rotateImage(imageStream: InputStream, degrees: Float): InputStream {
|
||||
val imageBytes = imageStream.readBytes()
|
||||
|
||||
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
fun rotateImage(imageSource: BufferedSource, degrees: Float): BufferedSource {
|
||||
val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
|
||||
val rotated = rotateBitMap(imageBitmap, degrees)
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||
val output = Buffer()
|
||||
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
|
||||
|
||||
return ByteArrayInputStream(output.toByteArray())
|
||||
return output
|
||||
}
|
||||
|
||||
private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap {
|
||||
@@ -184,10 +179,8 @@ object ImageUtil {
|
||||
* Split the image into left and right parts, then merge them into a
|
||||
* new vertically-aligned image.
|
||||
*/
|
||||
fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream {
|
||||
val imageBytes = imageStream.readBytes()
|
||||
|
||||
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
fun splitAndMerge(imageSource: BufferedSource, upperSide: Side): BufferedSource {
|
||||
val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
|
||||
val height = imageBitmap.height
|
||||
val width = imageBitmap.width
|
||||
|
||||
@@ -209,9 +202,9 @@ object ImageUtil {
|
||||
drawBitmap(imageBitmap, leftPart, bottomPart, null)
|
||||
}
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||
return ByteArrayInputStream(output.toByteArray())
|
||||
val output = Buffer()
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
|
||||
return output
|
||||
}
|
||||
|
||||
enum class Side {
|
||||
@@ -225,8 +218,8 @@ object ImageUtil {
|
||||
* to compensate for scaling.
|
||||
*/
|
||||
|
||||
fun addHorizontalCenterMargin(imageStream: InputStream, viewHeight: Int, backgroundContext: Context): InputStream {
|
||||
val imageBitmap = ImageDecoder.newInstance(imageStream)?.decode()!!
|
||||
fun addHorizontalCenterMargin(imageSource: BufferedSource, viewHeight: Int, backgroundContext: Context): BufferedSource {
|
||||
val imageBitmap = ImageDecoder.newInstance(imageSource.inputStream())?.decode()!!
|
||||
val height = imageBitmap.height
|
||||
val width = imageBitmap.width
|
||||
|
||||
@@ -237,7 +230,7 @@ object ImageUtil {
|
||||
val leftTargetPart = Rect(0, 0, width / 2, height)
|
||||
val rightTargetPart = Rect(width / 2 + centerPadding, 0, width + centerPadding, height)
|
||||
|
||||
val bgColor = chooseBackground(backgroundContext, imageStream)
|
||||
val bgColor = chooseBackground(backgroundContext, imageSource)
|
||||
bgColor.setBounds(width / 2, 0, width / 2 + centerPadding, height)
|
||||
val result = createBitmap(width + centerPadding, height)
|
||||
|
||||
@@ -247,9 +240,9 @@ object ImageUtil {
|
||||
bgColor.draw(this)
|
||||
}
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||
return ByteArrayInputStream(output.toByteArray())
|
||||
val output = Buffer()
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
|
||||
return output
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@@ -258,11 +251,8 @@ object ImageUtil {
|
||||
*
|
||||
* @return true if the height:width ratio is greater than 3.
|
||||
*/
|
||||
private fun isTallImage(imageStream: InputStream): Boolean {
|
||||
val options = extractImageOptions(
|
||||
imageStream,
|
||||
resetAfterExtraction = false,
|
||||
)
|
||||
private fun isTallImage(imageSource: BufferedSource): Boolean {
|
||||
val options = extractImageOptions(imageSource)
|
||||
|
||||
return (options.outHeight / options.outWidth) > 3
|
||||
}
|
||||
@@ -275,22 +265,18 @@ object ImageUtil {
|
||||
imageFile: UniFile,
|
||||
filenamePrefix: String,
|
||||
): Boolean {
|
||||
if (isAnimatedAndSupported(imageFile.openInputStream()) ||
|
||||
!isTallImage(imageFile.openInputStream())
|
||||
) {
|
||||
val imageSource = imageFile.openInputStream().use { Buffer().readFrom(it) }
|
||||
if (isAnimatedAndSupported(imageSource) || !isTallImage(imageSource)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val bitmapRegionDecoder = getBitmapRegionDecoder(imageFile.openInputStream())
|
||||
val bitmapRegionDecoder = getBitmapRegionDecoder(imageSource.peek().inputStream())
|
||||
if (bitmapRegionDecoder == null) {
|
||||
logcat { "Failed to create new instance of BitmapRegionDecoder" }
|
||||
return false
|
||||
}
|
||||
|
||||
val options = extractImageOptions(
|
||||
imageFile.openInputStream(),
|
||||
resetAfterExtraction = false,
|
||||
).apply {
|
||||
val options = extractImageOptions(imageSource).apply {
|
||||
inJustDecodeBounds = false
|
||||
}
|
||||
|
||||
@@ -380,8 +366,8 @@ object ImageUtil {
|
||||
/**
|
||||
* Algorithm for determining what background to accompany a comic/manga page
|
||||
*/
|
||||
fun chooseBackground(context: Context, imageStream: InputStream): Drawable {
|
||||
val decoder = ImageDecoder.newInstance(imageStream)
|
||||
fun chooseBackground(context: Context, imageSource: BufferedSource): Drawable {
|
||||
val decoder = ImageDecoder.newInstance(imageSource.inputStream())
|
||||
val image = decoder?.decode()
|
||||
decoder?.recycle()
|
||||
|
||||
@@ -603,16 +589,9 @@ object ImageUtil {
|
||||
/**
|
||||
* Used to check an image's dimensions without loading it in the memory.
|
||||
*/
|
||||
private fun extractImageOptions(
|
||||
imageStream: InputStream,
|
||||
resetAfterExtraction: Boolean = true,
|
||||
): BitmapFactory.Options {
|
||||
imageStream.mark(Int.MAX_VALUE)
|
||||
|
||||
val imageBytes = imageStream.readBytes()
|
||||
private fun extractImageOptions(imageSource: BufferedSource): BitmapFactory.Options {
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
||||
if (resetAfterExtraction) imageStream.reset()
|
||||
BitmapFactory.decodeStream(imageSource.peek().inputStream(), null, options)
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -657,7 +636,7 @@ object ImageUtil {
|
||||
centerMargin: Int,
|
||||
@ColorInt background: Int = Color.WHITE,
|
||||
progressCallback: ((Int) -> Unit)? = null,
|
||||
): ByteArrayInputStream {
|
||||
): BufferedSource {
|
||||
val height = imageBitmap.height
|
||||
val width = imageBitmap.width
|
||||
val height2 = imageBitmap2.height
|
||||
@@ -687,10 +666,10 @@ object ImageUtil {
|
||||
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
|
||||
progressCallback?.invoke(99)
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||
val output = Buffer()
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
|
||||
progressCallback?.invoke(100)
|
||||
return ByteArrayInputStream(output.toByteArray())
|
||||
return output
|
||||
}
|
||||
|
||||
private val Bitmap.rect: Rect
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
[versions]
|
||||
agp_version = "8.3.1"
|
||||
lifecycle_version = "2.7.0"
|
||||
paging_version = "3.2.1"
|
||||
agp_version = "8.4.1"
|
||||
lifecycle_version = "2.8.1"
|
||||
paging_version = "3.3.0"
|
||||
|
||||
[libraries]
|
||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
|
||||
|
||||
annotation = "androidx.annotation:annotation:1.7.1"
|
||||
appcompat = "androidx.appcompat:appcompat:1.6.1"
|
||||
annotation = "androidx.annotation:annotation:1.8.0"
|
||||
appcompat = "androidx.appcompat:appcompat:1.7.0"
|
||||
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
corektx = "androidx.core:core-ktx:1.12.0"
|
||||
corektx = "androidx.core:core-ktx:1.13.1"
|
||||
splashscreen = "androidx.core:core-splashscreen:1.0.1"
|
||||
recyclerview = "androidx.recyclerview:recyclerview:1.3.2"
|
||||
viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01"
|
||||
@@ -25,9 +25,9 @@ workmanager = "androidx.work:work-runtime:2.9.0"
|
||||
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
|
||||
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
|
||||
|
||||
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.3"
|
||||
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha03"
|
||||
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha03"
|
||||
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.4"
|
||||
test-ext = "androidx.test.ext:junit-ktx:1.2.0-rc01"
|
||||
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-rc01"
|
||||
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0"
|
||||
|
||||
[bundles]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
[versions]
|
||||
compiler = "1.5.11"
|
||||
compose-bom = "2024.02.00-alpha02"
|
||||
compiler = "1.5.14"
|
||||
# 2024.04.00-alpha01 has several bugs with the new animateItem() modifier
|
||||
compose-bom = "2024.03.00-alpha02"
|
||||
accompanist = "0.35.0-alpha"
|
||||
|
||||
[libraries]
|
||||
compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compiler" }
|
||||
|
||||
activity = "androidx.activity:activity-compose:1.8.2"
|
||||
activity = "androidx.activity:activity-compose:1.9.0"
|
||||
bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||
foundation = { module = "androidx.compose.foundation:foundation" }
|
||||
animation = { module = "androidx.compose.animation:animation" }
|
||||
@@ -18,9 +19,6 @@ ui-util = { module = "androidx.compose.ui:ui-util" }
|
||||
material3-core = { module = "androidx.compose.material3:material3" }
|
||||
material-icons = { module = "androidx.compose.material:material-icons-extended" }
|
||||
|
||||
# Some components aren't available in Material3
|
||||
material-core = { module = "androidx.compose.material:material" }
|
||||
|
||||
glance = "androidx.glance:glance-appwidget:1.0.0"
|
||||
|
||||
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
kotlin_version = "1.9.23"
|
||||
kotlin_version = "1.9.24"
|
||||
serialization_version = "1.6.3"
|
||||
xml_serialization_version = "0.86.3"
|
||||
|
||||
|
||||
+10
-14
@@ -1,7 +1,6 @@
|
||||
[versions]
|
||||
aboutlib_version = "11.1.1"
|
||||
acra = "5.11.3"
|
||||
leakcanary = "2.13"
|
||||
aboutlib_version = "11.2.1"
|
||||
leakcanary = "2.14"
|
||||
moko = "0.23.0"
|
||||
okhttp_version = "5.0.0-alpha.12"
|
||||
richtext = "0.20.0"
|
||||
@@ -15,7 +14,7 @@ detektCompose = "0.3.12"
|
||||
[libraries]
|
||||
desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
|
||||
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"
|
||||
google-services-gradle = "com.google.gms:google-services:4.4.1"
|
||||
google-services-gradle = "com.google.gms:google-services:4.4.2"
|
||||
|
||||
rxjava = "io.reactivex:rxjava:1.3.8"
|
||||
|
||||
@@ -32,8 +31,8 @@ quickjs-android = "app.cash.quickjs:quickjs-android:0.9.2"
|
||||
jsoup = "org.jsoup:jsoup:1.17.2"
|
||||
|
||||
disklrucache = "com.jakewharton:disklrucache:2.0.2"
|
||||
unifile = "com.github.tachiyomiorg:unifile:7c257e1c64"
|
||||
common-compress = "org.apache.commons:commons-compress:1.26.1"
|
||||
unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc"
|
||||
common-compress = "org.apache.commons:commons-compress:1.26.2"
|
||||
junrar = "com.github.junrar:junrar:7.5.5"
|
||||
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
|
||||
|
||||
@@ -52,7 +51,7 @@ coil-gif = { module = "io.coil-kt.coil3:coil-gif" }
|
||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose" }
|
||||
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" }
|
||||
|
||||
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:aeaa170036"
|
||||
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:b8e1b0ed2b"
|
||||
image-decoder = "com.github.tachiyomiorg:image-decoder:e08e9be535"
|
||||
exifinterface = "androidx.exifinterface:exifinterface:1.3.7"
|
||||
|
||||
@@ -61,7 +60,7 @@ natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
||||
richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" }
|
||||
richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" }
|
||||
|
||||
material = "com.google.android.material:material:1.11.0"
|
||||
material = "com.google.android.material:material:1.12.0"
|
||||
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
||||
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
||||
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
||||
@@ -77,9 +76,7 @@ moko-gradle = { module = "dev.icerock.moko:resources-generator", version.ref = "
|
||||
|
||||
logcat = "com.squareup.logcat:logcat:0.1"
|
||||
|
||||
acra-http = { module = "ch.acra:acra-http", version.ref = "acra" }
|
||||
acra-scheduler = { module = "ch.acra:acra-advanced-scheduler", version.ref = "acra" }
|
||||
firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.6.1"
|
||||
firebase-analytics = "com.google.firebase:firebase-analytics:22.0.1"
|
||||
|
||||
aboutLibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlib_version" }
|
||||
aboutLibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlib_version" }
|
||||
@@ -97,8 +94,8 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect",
|
||||
sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" }
|
||||
|
||||
junit = "org.junit.jupiter:junit-jupiter:5.10.2"
|
||||
kotest-assertions = "io.kotest:kotest-assertions-core:5.8.1"
|
||||
mockk = "io.mockk:mockk:1.13.10"
|
||||
kotest-assertions = "io.kotest:kotest-assertions-core:5.9.0"
|
||||
mockk = "io.mockk:mockk:1.13.11"
|
||||
|
||||
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
|
||||
voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
|
||||
@@ -113,7 +110,6 @@ google-api-services-drive = "com.google.apis:google-api-services-drive:v3-rev197
|
||||
google-api-client-oauth = "com.google.oauth-client:google-oauth-client:1.34.1"
|
||||
|
||||
[bundles]
|
||||
acra = ["acra-http", "acra-scheduler"]
|
||||
archive = ["common-compress", "junrar"]
|
||||
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
|
||||
js-engine = ["quickjs-android"]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[versions]
|
||||
|
||||
[libraries]
|
||||
firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.6.1"
|
||||
firebase-crashlytics-ktx = "com.google.firebase:firebase-crashlytics-ktx:18.6.3"
|
||||
firebase-analytics = "com.google.firebase:firebase-analytics:22.0.0"
|
||||
firebase-crashlytics-ktx = "com.google.firebase:firebase-crashlytics:19.0.0"
|
||||
firebase-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:2.9.9"
|
||||
|
||||
simularity = "info.debatty:java-string-similarity:2.0.0"
|
||||
@@ -11,4 +11,4 @@ xlog = "com.elvishew:xlog:1.11.0"
|
||||
ratingbar = "me.zhanghai.android.materialratingbar:library:1.4.0"
|
||||
composeRatingbar = "com.github.a914-gowtham:compose-ratingbar:1.2.3"
|
||||
|
||||
versionsx = "com.github.ben-manes:gradle-versions-plugin:0.42.0"
|
||||
versionsx = "com.github.ben-manes:gradle-versions-plugin:0.51.0"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
||||
@@ -203,10 +203,8 @@
|
||||
<string name="pref_local_source_hidden_folders_summery">Allow local source to read hidden folders</string>
|
||||
|
||||
<!-- Backup settings -->
|
||||
<string name="pref_backup_and_sync_summary">Manual & automatic backups and sync</string>
|
||||
<string name="custom_entry_info">Custom entry info</string>
|
||||
<string name="all_read_entries">All read entries</string>
|
||||
<string name="label_backup">Backup</string>
|
||||
<string name="label_sync">Sync</string>
|
||||
<string name="label_triggers">Triggers</string>
|
||||
|
||||
@@ -220,24 +218,16 @@
|
||||
<string name="pref_sync_api_key_summ">Enter the API key to synchronize your library</string>
|
||||
<string name="pref_sync_now_group_title">Sync Actions</string>
|
||||
<string name="pref_sync_now">Sync now</string>
|
||||
<string name="pref_sync_confirmation_title">Sync confirmation</string>
|
||||
<string name="pref_sync_now_subtitle">Initiate immediate synchronization of your data</string>
|
||||
<string name="pref_sync_confirmation_message">Syncing will overwrite your local library with the remote library. Are you sure you want to continue?</string>
|
||||
<string name="pref_sync_service">Service</string>
|
||||
<string name="pref_sync_service_summ">Select the service to sync your library with</string>
|
||||
<string name="pref_sync_service_category">Sync</string>
|
||||
<string name="pref_sync_automatic_category">Automatic Synchronization</string>
|
||||
<string name="pref_sync_interval">Synchronization frequency</string>
|
||||
<string name="pref_choose_what_to_sync">Choose what to sync</string>
|
||||
<string name="success_reset_sync_timestamp">Last sync timestamp reset</string>
|
||||
<string name="syncyomi">SyncYomi</string>
|
||||
<string name="sync_completed_message">Done in %1$s</string>
|
||||
<string name="last_synchronization">Last Synchronization: %1$s</string>
|
||||
<string name="google_drive">Google Drive</string>
|
||||
<string name="pref_google_drive_sign_in">Sign in</string>
|
||||
<string name="google_drive_sign_in_success">Signed in successfully</string>
|
||||
<string name="google_drive_sign_in_failed">Sign in failed</string>
|
||||
<string name="authentication">Authentication</string>
|
||||
<string name="pref_google_drive_purge_sync_data">Clear Sync Data from Google Drive</string>
|
||||
<string name="google_drive_sync_data_purged">Sync data purged from Google Drive</string>
|
||||
<string name="google_drive_sync_data_not_found">No sync data found in Google Drive</string>
|
||||
|
||||
@@ -134,7 +134,6 @@
|
||||
<string name="data_saver_image_format_summary_on">La taille du fichier Jpeg est considérablement plus petite que Webp (ce qui signifie que plus de données sont économisées), mais cela fait également perdre plus de qualité aux images. \n Actuellement compresse en Jpeg</string>
|
||||
<string name="data_saver_image_format_summary_off">La taille du fichier Jpeg est considérablement plus petite que celle de Webp (ce qui signifie que plus de données sont économisées), mais cela fait également perdre plus de qualité aux images. \n. Actuellement compresse en Webp</string>
|
||||
<string name="data_saver_color_bw">Convertir en noir et blanc</string>
|
||||
<string name="data_saver_server">Serveur proxy Bandwidth Hero</string>
|
||||
<string name="data_saver_server_summary">Mettez ici l\'URL du serveur Bandwidth Hero Proxy</string>
|
||||
|
||||
<!-- Log Level -->
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
<string name="data_saver_image_format_summary_on">Ukuran file Jpeg jauh lebih kecil daripada Webp (artinya lebih banyak menghemat data), tetapi itu juga membuat kualitas gambar menjadi lebih buruk.\nSaat ini kompres ke Jpeg</string>
|
||||
<string name="data_saver_image_format_summary_off">Ukuran file Jpeg jauh lebih kecil daripada Webp (artinya lebih banyak menghemat data), tetapi itu juga membuat kualitas gambar menjadi lebih buruk.\nSaat ini kompres ke Webp</string>
|
||||
<string name="data_saver_color_bw">Konversi ke Hitam Putih</string>
|
||||
<string name="data_saver_server">Server Bandwidth Hero Proxy</string>
|
||||
<string name="data_saver_server_summary">Masukkan url server Bandwidth Hero Proxy disini</string>
|
||||
<string name="clear_db_exclude_read">Abaikab manga yang sudah dibaca</string>
|
||||
|
||||
|
||||
@@ -138,7 +138,6 @@
|
||||
<string name="data_saver_image_format_summary_on">O tamanho do arquivo Jpeg é notavelmente menor que o Webp (ou seja, mais dados são salvos), mas também faz as imagens perderem mais qualidade.\nAtual: comprimindo em Jpeg</string>
|
||||
<string name="data_saver_image_format_summary_off">O tamanho do arquivo Jpeg é notavelmente menor que o Webp (ou seja, mais dados são salvos), mas também faz as imagens perderem mais qualidade.\nAtual: comprimindo em Webp</string>
|
||||
<string name="data_saver_color_bw">Converter em Preto e Branco</string>
|
||||
<string name="data_saver_server">Servidor Bandwidth Hero Proxy</string>
|
||||
<string name="data_saver_server_summary">Insira a url do servidor Bandwidth Hero Proxy aqui</string>
|
||||
<string name="clear_db_exclude_read">Manter mangás com capítulos lidos</string>
|
||||
|
||||
|
||||
@@ -179,6 +179,9 @@
|
||||
<string name="pref_mark_read_dupe_chapters_summary">Помечать повторяющиеся главы как «Прочитано» после прочтения</string>
|
||||
<string name="pref_library_mark_duplicate_chapters">Помечать новые повторяющиеся главы как «Прочитано»</string>
|
||||
<string name="pref_library_mark_duplicate_chapters_summary">Автоматически помечать новые главы как «Прочитано», если они были ранее прочитаны</string>
|
||||
<string name="update_30min">Каждые 30 минут</string>
|
||||
<string name="update_1hour">Каждый час</string>
|
||||
<string name="update_3hour">Каждые 3 часа</string>
|
||||
|
||||
<!-- Browse settings -->
|
||||
<string name="pref_hide_feed">Скрыть вкладку «Лента»</string>
|
||||
@@ -194,6 +197,47 @@
|
||||
<!-- Backup settings -->
|
||||
<string name="custom_entry_info">Сведенья пользователя</string>
|
||||
<string name="all_read_entries">Прочитанные серии</string>
|
||||
<string name="label_sync">Синхронизировать</string>
|
||||
<string name="label_triggers">Триггеры</string>
|
||||
|
||||
<!-- Sync settings -->
|
||||
<string name="sync_error">Не удалось синхронизировать библиотеку</string>
|
||||
<string name="sync_complete">Синхронизация библиотеки завершена</string>
|
||||
<string name="sync_in_progress">Синхронизация уже выполняется</string>
|
||||
<string name="pref_sync_host">Хост</string>
|
||||
<string name="pref_sync_host_summ">Введите адрес хоста для синхронизации вашей библиотеки</string>
|
||||
<string name="pref_sync_api_key">Ключ API</string>
|
||||
<string name="pref_sync_api_key_summ">Введите API ключ для синхронизации вашей библиотеки</string>
|
||||
<string name="pref_sync_now_group_title">Действия синхронизации</string>
|
||||
<string name="pref_sync_now">Синхронизировать сейчас</string>
|
||||
<string name="pref_sync_now_subtitle">Начать немедленную синхронизацию ваших данных</string>
|
||||
<string name="pref_sync_service">Сервис</string>
|
||||
<string name="pref_sync_service_category">Синхронизация</string>
|
||||
<string name="pref_sync_automatic_category">Автоматическая синхронизация</string>
|
||||
<string name="pref_sync_interval">Частота синхронизации</string>
|
||||
<string name="pref_choose_what_to_sync">Выберите, что синхронизировать</string>
|
||||
<string name="last_synchronization">Последняя синхронизация: %1$s</string>
|
||||
<string name="google_drive">Google Диск</string>
|
||||
<string name="pref_google_drive_sign_in">Войти в Google Диск</string>
|
||||
<string name="pref_google_drive_purge_sync_data">Очистить данные синхронизации с Google Диска</string>
|
||||
<string name="google_drive_sync_data_purged">Данные синхронизации удалены из Google Диска</string>
|
||||
<string name="google_drive_sync_data_not_found">Данные синхронизации не найдены в Google Диске</string>
|
||||
<string name="google_drive_sync_data_purge_error">Ошибка при очистке данных синхронизации с Google Диска, попробуйте войти снова.</string>
|
||||
<string name="google_drive_login_success">Выполнен вход в Google Диск</string>
|
||||
<string name="google_drive_login_failed">Не удалось войти в Google Диск: %s</string>
|
||||
<string name="google_drive_not_signed_in">Не выполнен вход в Google Диск</string>
|
||||
<string name="error_uploading_sync_data">Ошибка при загрузке данных синхронизации на Google Диск</string>
|
||||
<string name="error_deleting_google_drive_lock_file">Ошибка удаления файла блокировки Google Диска</string>
|
||||
<string name="error_before_sync_gdrive">Ошибка перед синхронизацией: %s</string>
|
||||
<string name="pref_purge_confirmation_title">Подтверждение очистки</string>
|
||||
<string name="pref_purge_confirmation_message">Очистка данных синхронизации приведет к удалению всех данных синхронизации с Google Диска. Вы уверены, что хотите продолжить?</string>
|
||||
<string name="pref_sync_options">Создать триггеры синхронизации</string>
|
||||
<string name="pref_sync_options_summ">Можно использовать для настройки триггеров синхронизации</string>
|
||||
<string name="sync_on_chapter_read">Синхронизовать при прочтении главы</string>
|
||||
<string name="sync_on_chapter_open">Синхронизовать при открытии главы</string>
|
||||
<string name="sync_on_app_start">Синхронизовать при запуске приложения</string>
|
||||
<string name="sync_on_app_resume">Синхронизовать при возвращении в приложение</string>
|
||||
<string name="sync_library">Синхронизировать библиотеку</string>
|
||||
|
||||
<!-- Security settings -->
|
||||
<string name="biometric_lock_times">Биометрическое время блокировки</string>
|
||||
|
||||
@@ -496,7 +496,6 @@
|
||||
<string name="launching_app">應用程式啟動中……</string>
|
||||
<string name="error_with_reason">錯誤:%1$s</string>
|
||||
<string name="could_not_open_entry">無法開啟此作品:\n\n%1$s</string>
|
||||
<string name="entry_id_is_null">作品 ID 為空!</string>
|
||||
<string name="loading_entry">作品載入中……</string>
|
||||
|
||||
<!-- Page previews -->
|
||||
@@ -612,7 +611,6 @@
|
||||
|
||||
<!-- Similar -->
|
||||
<string name="similar">與 %1$s 相似</string>
|
||||
<string name="similar_no_results">相似作品未找到</string>
|
||||
|
||||
<!-- MangaDex relations-->
|
||||
<string name="relation_similar">相似</string>
|
||||
|
||||
@@ -24,7 +24,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)
|
||||
|
||||
+8
-12
@@ -10,7 +10,6 @@ import androidx.compose.foundation.gestures.DraggableAnchors
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.anchoredDraggable
|
||||
import androidx.compose.foundation.gestures.animateTo
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
@@ -39,7 +38,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -54,7 +52,6 @@ private val sheetAnimationSpec = tween<Float>(durationMillis = 350)
|
||||
@Composable
|
||||
fun AdaptiveSheet(
|
||||
isTabletUi: Boolean,
|
||||
tonalElevation: Dp,
|
||||
enableSwipeDismiss: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -78,8 +75,7 @@ fun AdaptiveSheet(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
enabled = true,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
onClick = internalOnDismissRequest,
|
||||
)
|
||||
@@ -91,7 +87,7 @@ fun AdaptiveSheet(
|
||||
modifier = Modifier
|
||||
.requiredWidthIn(max = 460.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
onClick = {},
|
||||
)
|
||||
@@ -99,7 +95,7 @@ fun AdaptiveSheet(
|
||||
.padding(vertical = 16.dp)
|
||||
.then(modifier),
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
tonalElevation = tonalElevation,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
content = {
|
||||
BackHandler(enabled = alpha > 0f, onBack = internalOnDismissRequest)
|
||||
content()
|
||||
@@ -122,14 +118,14 @@ fun AdaptiveSheet(
|
||||
)
|
||||
}
|
||||
val internalOnDismissRequest = {
|
||||
if (anchoredDraggableState.currentValue == 0) {
|
||||
if (anchoredDraggableState.settledValue == 0) {
|
||||
scope.launch { anchoredDraggableState.animateTo(1) }
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
onClick = internalOnDismissRequest,
|
||||
)
|
||||
@@ -147,7 +143,7 @@ fun AdaptiveSheet(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 460.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
onClick = {},
|
||||
)
|
||||
@@ -180,7 +176,7 @@ fun AdaptiveSheet(
|
||||
.navigationBarsPadding()
|
||||
.statusBarsPadding(),
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
tonalElevation = tonalElevation,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
content = {
|
||||
BackHandler(
|
||||
enabled = anchoredDraggableState.targetValue == 0,
|
||||
@@ -192,7 +188,7 @@ fun AdaptiveSheet(
|
||||
|
||||
LaunchedEffect(anchoredDraggableState) {
|
||||
scope.launch { anchoredDraggableState.animateTo(0) }
|
||||
snapshotFlow { anchoredDraggableState.currentValue }
|
||||
snapshotFlow { anchoredDraggableState.settledValue }
|
||||
.drop(1)
|
||||
.filter { it == 1 }
|
||||
.collectLatest {
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@@ -18,9 +17,8 @@ import androidx.compose.ui.unit.dp
|
||||
fun Pill(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MaterialTheme.colorScheme.background,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onBackground,
|
||||
elevation: Dp = 1.dp,
|
||||
color: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize: TextUnit = LocalTextStyle.current.fontSize,
|
||||
) {
|
||||
Surface(
|
||||
@@ -29,7 +27,6 @@ fun Pill(
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
color = color,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = elevation,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
+4
-3
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
@@ -57,6 +56,8 @@ object SettingsItemsPaddings {
|
||||
val Vertical = 10.dp
|
||||
}
|
||||
|
||||
private const val DisabledContentAlpha = 0.38f
|
||||
|
||||
@Composable
|
||||
fun HeadingItem(labelRes: StringResource) {
|
||||
HeadingItem(stringResource(labelRes))
|
||||
@@ -282,7 +283,7 @@ fun TriStateItem(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large),
|
||||
) {
|
||||
val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled
|
||||
val stateAlpha = if (enabled && onClick != null) 1f else DisabledContentAlpha
|
||||
|
||||
Icon(
|
||||
imageVector = when (state) {
|
||||
@@ -295,7 +296,7 @@ fun TriStateItem(
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha)
|
||||
} else {
|
||||
when (onClick) {
|
||||
null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled)
|
||||
null -> MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledContentAlpha)
|
||||
else -> MaterialTheme.colorScheme.primary
|
||||
}
|
||||
},
|
||||
|
||||
-2
@@ -100,7 +100,6 @@ fun Button(
|
||||
val containerColor = colors.containerColor(enabled).value
|
||||
val contentColor = colors.contentColor(enabled).value
|
||||
val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
|
||||
val tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp
|
||||
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
@@ -109,7 +108,6 @@ fun Button(
|
||||
shape = shape,
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = tonalElevation,
|
||||
shadowElevation = shadowElevation,
|
||||
border = border,
|
||||
interactionSource = interactionSource,
|
||||
|
||||
+1
-1
@@ -6,13 +6,13 @@ import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material.ripple
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.LocalAbsoluteTonalElevation
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package tachiyomi.presentation.core.util
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.isImeVisible
|
||||
@@ -42,14 +41,12 @@ fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha)
|
||||
fun Modifier.clickableNoIndication(
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onClick: () -> Unit,
|
||||
): Modifier = composed {
|
||||
Modifier.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onLongClick = onLongClick,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
) = this.combinedClickable(
|
||||
interactionSource = null,
|
||||
indication = null,
|
||||
onLongClick = onLongClick,
|
||||
onClick = onClick,
|
||||
)
|
||||
|
||||
/**
|
||||
* For TextField, the provided [action] will be invoked when
|
||||
|
||||
@@ -17,16 +17,10 @@ import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import kotlinx.serialization.json.encodeToStream
|
||||
import logcat.LogPriority
|
||||
import nl.adaptivity.xmlutil.AndroidXmlReader
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.getComicInfo
|
||||
import tachiyomi.core.metadata.tachiyomi.MangaDetails
|
||||
import tachiyomi.core.common.storage.UniFileTempFileManager
|
||||
import tachiyomi.core.common.storage.addStreamToZip
|
||||
import tachiyomi.core.common.storage.extension
|
||||
@@ -37,6 +31,12 @@ import tachiyomi.core.common.storage.nameWithoutExtension
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.getComicInfo
|
||||
import tachiyomi.core.metadata.tachiyomi.MangaDetails
|
||||
import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -157,17 +157,39 @@ actual class LocalSource(
|
||||
|
||||
// SY -->
|
||||
fun updateMangaInfo(manga: SManga) {
|
||||
val existingFile = fileSystem.getFilesInMangaDirectory(manga.url).find { it.extension == "json" }
|
||||
val file = existingFile
|
||||
?: fileSystem.getMangaDirectory(manga.url)?.createFile("info.json")
|
||||
?: return
|
||||
file.openOutputStream().use {
|
||||
json.encodeToStream(manga.toJson(), it)
|
||||
val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url)
|
||||
val existingFile = mangaDirFiles
|
||||
.firstOrNull { it.name == COMIC_INFO_FILE }
|
||||
val comicInfoArchiveFile = mangaDirFiles
|
||||
.firstOrNull { it.name == COMIC_INFO_ARCHIVE }
|
||||
val existingComicInfo = (existingFile?.openInputStream() ?: comicInfoArchiveFile?.getZipInputStream(COMIC_INFO_FILE))?.use {
|
||||
AndroidXmlReader(it, StandardCharsets.UTF_8.name()).use {
|
||||
xml.decodeFromReader<ComicInfo>(it)
|
||||
}
|
||||
}
|
||||
val newComicInfo = if (existingComicInfo != null) {
|
||||
manga.run {
|
||||
existingComicInfo.copy(
|
||||
series = ComicInfo.Series(title),
|
||||
summary = description?.let { ComicInfo.Summary(it) },
|
||||
writer = author?.let { ComicInfo.Writer(it) },
|
||||
penciller = artist?.let { ComicInfo.Penciller(it) },
|
||||
genre = genre?.let { ComicInfo.Genre(it) },
|
||||
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
||||
ComicInfoPublishingStatus.toComicInfoValue(status.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
manga.getComicInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private fun SManga.toJson(): MangaDetails {
|
||||
return MangaDetails(title, author, artist, description, genre?.split(", "), status)
|
||||
fileSystem.getMangaDirectory(manga.url)?.let {
|
||||
copyComicInfoFile(
|
||||
xml.encodeToString(ComicInfo.serializer(), newComicInfo).byteInputStream(),
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@@ -368,8 +390,8 @@ actual class LocalSource(
|
||||
try {
|
||||
val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2)
|
||||
return fileSystem.getBaseDirectory()
|
||||
?.findFile(mangaDirName, true)
|
||||
?.findFile(chapterName, true)
|
||||
?.findFile(mangaDirName)
|
||||
?.findFile(chapterName)
|
||||
?.let(Format.Companion::valueOf)
|
||||
?: throw Exception(context.stringResource(MR.strings.chapter_not_found))
|
||||
} catch (e: Format.UnknownFormatException) {
|
||||
|
||||
+2
-2
@@ -17,13 +17,13 @@ actual class LocalSourceFileSystem(
|
||||
|
||||
actual fun getMangaDirectory(name: String): UniFile? {
|
||||
return getBaseDirectory()
|
||||
?.findFile(name, true)
|
||||
?.findFile(name)
|
||||
?.takeIf { it.isDirectory }
|
||||
}
|
||||
|
||||
actual fun getFilesInMangaDirectory(name: String): List<UniFile> {
|
||||
return getBaseDirectory()
|
||||
?.findFile(name, true)
|
||||
?.findFile(name)
|
||||
?.takeIf { it.isDirectory }
|
||||
?.listFiles().orEmpty().toList()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user