mirror of
https://github.com/mihonapp/mihon.git
synced 2025-10-20 10:09:43 +02:00
Merge branch 'master' of https://github.com/tachiyomiorg/tachiyomi into sync-part-final
This commit is contained in:
@@ -22,8 +22,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi"
|
||||
|
||||
versionCode = 114
|
||||
versionName = "0.14.7"
|
||||
versionCode = 115
|
||||
versionName = "0.15.0"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
|
@@ -41,6 +41,7 @@ internal class GuidesStep(
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -14,6 +15,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@@ -38,6 +40,8 @@ internal class StorageStep : OnboardingStep {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val handler = LocalUriHandler.current
|
||||
|
||||
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
|
||||
|
||||
Column(
|
||||
@@ -64,6 +68,19 @@ internal class StorageStep : OnboardingStep {
|
||||
) {
|
||||
Text(stringResource(MR.strings.onboarding_storage_action_select))
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
|
||||
Text(stringResource(MR.strings.onboarding_storage_help_info, stringResource(MR.strings.app_name)))
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { handler.openUri("https://tachiyomi.org/docs/faq/storage") },
|
||||
) {
|
||||
Text(stringResource(MR.strings.onboarding_storage_help_action))
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
@@ -1,34 +1,26 @@
|
||||
package eu.kanade.presentation.more.settings.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.ui.model.TabletUiMode
|
||||
import eu.kanade.domain.ui.model.ThemeMode
|
||||
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
@@ -107,11 +99,8 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
uiPreferences: UiPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val langs = remember { getLangs(context) }
|
||||
var currentLanguage by remember {
|
||||
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
|
||||
}
|
||||
val now = remember { Instant.now().toEpochMilli() }
|
||||
|
||||
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
||||
@@ -119,26 +108,12 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
UiPreferences.dateFormat(dateFormat).format(now)
|
||||
}
|
||||
|
||||
LaunchedEffect(currentLanguage) {
|
||||
val locale = if (currentLanguage.isEmpty()) {
|
||||
LocaleListCompat.getEmptyLocaleList()
|
||||
} else {
|
||||
LocaleListCompat.forLanguageTags(currentLanguage)
|
||||
}
|
||||
AppCompatDelegate.setApplicationLocales(locale)
|
||||
}
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_display),
|
||||
preferenceItems = persistentListOf(
|
||||
Preference.PreferenceItem.BasicListPreference(
|
||||
value = currentLanguage,
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_app_language),
|
||||
entries = langs,
|
||||
onValueChanged = { newValue ->
|
||||
currentLanguage = newValue
|
||||
true
|
||||
},
|
||||
onClick = { navigator.push(AppLanguageScreen()) },
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = uiPreferences.tabletUiMode(),
|
||||
@@ -173,30 +148,6 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
),
|
||||
)
|
||||
}
|
||||
private fun getLangs(context: Context): ImmutableMap<String, String> {
|
||||
val langs = mutableListOf<Pair<String, String>>()
|
||||
val parser = context.resources.getXml(R.xml.locales_config)
|
||||
var eventType = parser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
||||
for (i in 0..<parser.attributeCount) {
|
||||
if (parser.getAttributeName(i) == "name") {
|
||||
val langTag = parser.getAttributeValue(i)
|
||||
val displayName = LocaleHelper.getDisplayName(langTag)
|
||||
if (displayName.isNotEmpty()) {
|
||||
langs.add(Pair(langTag, displayName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = parser.next()
|
||||
}
|
||||
|
||||
langs.sortBy { it.second }
|
||||
langs.add(0, Pair("", context.stringResource(MR.strings.label_default)))
|
||||
|
||||
return langs.toMap().toImmutableMap()
|
||||
}
|
||||
}
|
||||
|
||||
private val DateFormats = listOf(
|
||||
|
@@ -0,0 +1,128 @@
|
||||
package eu.kanade.presentation.more.settings.screen.appearance
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.util.Locale
|
||||
|
||||
class AppLanguageScreen : Screen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val langs = remember { getLangs(context) }
|
||||
var currentLanguage by remember {
|
||||
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
|
||||
}
|
||||
|
||||
LaunchedEffect(currentLanguage) {
|
||||
val locale = if (currentLanguage.isEmpty()) {
|
||||
LocaleListCompat.getEmptyLocaleList()
|
||||
} else {
|
||||
LocaleListCompat.forLanguageTags(currentLanguage)
|
||||
}
|
||||
AppCompatDelegate.setApplicationLocales(locale)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(MR.strings.pref_app_language),
|
||||
navigateUp = navigator::pop,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
) {
|
||||
items(langs) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
currentLanguage = it.langTag
|
||||
},
|
||||
headlineContent = { Text(it.displayName) },
|
||||
supportingContent = {
|
||||
it.localizedDisplayName?.let {
|
||||
Text(it)
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
if (currentLanguage == it.langTag) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLangs(context: Context): ImmutableList<Language> {
|
||||
val langs = mutableListOf<Language>()
|
||||
val parser = context.resources.getXml(R.xml.locales_config)
|
||||
var eventType = parser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
||||
for (i in 0..<parser.attributeCount) {
|
||||
if (parser.getAttributeName(i) == "name") {
|
||||
val langTag = parser.getAttributeValue(i)
|
||||
val displayName = LocaleHelper.getDisplayName(langTag)
|
||||
if (displayName.isNotEmpty()) {
|
||||
langs.add(Language(langTag, displayName, Locale.forLanguageTag(langTag).displayName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = parser.next()
|
||||
}
|
||||
|
||||
langs.sortBy { it.displayName }
|
||||
langs.add(0, Language("", context.stringResource(MR.strings.label_default), null))
|
||||
|
||||
return langs.toImmutableList()
|
||||
}
|
||||
|
||||
private data class Language(
|
||||
val langTag: String,
|
||||
val displayName: String,
|
||||
val localizedDisplayName: String?,
|
||||
)
|
||||
}
|
@@ -10,6 +10,7 @@ import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a .rar or .cbr file.
|
||||
@@ -20,13 +21,18 @@ internal class RarPageLoader(file: File) : PageLoader() {
|
||||
|
||||
override var isLocal: Boolean = true
|
||||
|
||||
/**
|
||||
* Pool for copying compressed files to an input stream.
|
||||
*/
|
||||
private val pool = Executors.newFixedThreadPool(1)
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
return rar.fileHeaders.asSequence()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.mapIndexed { i, header ->
|
||||
ReaderPage(i).apply {
|
||||
stream = { getStream(rar, header) }
|
||||
stream = { getStream(header) }
|
||||
status = Page.State.READY
|
||||
}
|
||||
}
|
||||
@@ -40,15 +46,16 @@ internal class RarPageLoader(file: File) : PageLoader() {
|
||||
override fun recycle() {
|
||||
super.recycle()
|
||||
rar.close()
|
||||
pool.shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream for the given [header].
|
||||
*/
|
||||
private fun getStream(rar: Archive, header: FileHeader): InputStream {
|
||||
private fun getStream(header: FileHeader): InputStream {
|
||||
val pipeIn = PipedInputStream()
|
||||
val pipeOut = PipedOutputStream(pipeIn)
|
||||
synchronized(this) {
|
||||
pool.execute {
|
||||
try {
|
||||
pipeOut.use {
|
||||
rar.extractFile(header, it)
|
||||
|
@@ -235,7 +235,7 @@ class PagerPageHolder(
|
||||
*/
|
||||
private fun setError() {
|
||||
progressIndicator.hide()
|
||||
showErrorLayout(withOpenInWebView = false)
|
||||
showErrorLayout()
|
||||
}
|
||||
|
||||
override fun onImageLoaded() {
|
||||
@@ -248,8 +248,7 @@ class PagerPageHolder(
|
||||
*/
|
||||
override fun onImageLoadError() {
|
||||
super.onImageLoadError()
|
||||
progressIndicator.hide()
|
||||
showErrorLayout(withOpenInWebView = true)
|
||||
setError()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,23 +259,27 @@ class PagerPageHolder(
|
||||
viewer.activity.hideMenu()
|
||||
}
|
||||
|
||||
private fun showErrorLayout(withOpenInWebView: Boolean): ReaderErrorBinding {
|
||||
private fun showErrorLayout(): ReaderErrorBinding {
|
||||
if (errorLayout == null) {
|
||||
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
errorLayout?.actionRetry?.viewer = viewer
|
||||
errorLayout?.actionRetry?.setOnClickListener {
|
||||
page.chapter.pageLoader?.retryPage(page)
|
||||
}
|
||||
val imageUrl = page.imageUrl
|
||||
if (imageUrl.orEmpty().startsWith("http", true)) {
|
||||
}
|
||||
|
||||
val imageUrl = page.imageUrl
|
||||
errorLayout?.actionOpenInWebView?.isVisible = imageUrl != null
|
||||
if (imageUrl != null) {
|
||||
if (imageUrl.startsWith("http", true)) {
|
||||
errorLayout?.actionOpenInWebView?.viewer = viewer
|
||||
errorLayout?.actionOpenInWebView?.setOnClickListener {
|
||||
val intent = WebViewActivity.newIntent(context, imageUrl!!)
|
||||
val intent = WebViewActivity.newIntent(context, imageUrl)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
errorLayout?.actionOpenInWebView?.isVisible = withOpenInWebView
|
||||
|
||||
errorLayout?.root?.isVisible = true
|
||||
return errorLayout!!
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@ class WebtoonPageHolder(
|
||||
refreshLayoutParams()
|
||||
|
||||
frame.onImageLoaded = { onImageDecoded() }
|
||||
frame.onImageLoadError = { onImageDecodeError() }
|
||||
frame.onImageLoadError = { setError() }
|
||||
frame.onScaleChanged = { viewer.activity.hideMenu() }
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ class WebtoonPageHolder(
|
||||
*/
|
||||
private fun setError() {
|
||||
progressContainer.isVisible = false
|
||||
initErrorLayout(withOpenInWebView = false)
|
||||
initErrorLayout()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,14 +251,6 @@ class WebtoonPageHolder(
|
||||
removeErrorLayout()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the image fails to decode.
|
||||
*/
|
||||
private fun onImageDecodeError() {
|
||||
progressContainer.isVisible = false
|
||||
initErrorLayout(withOpenInWebView = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new progress bar.
|
||||
*/
|
||||
@@ -278,22 +270,26 @@ class WebtoonPageHolder(
|
||||
/**
|
||||
* Initializes a button to retry pages.
|
||||
*/
|
||||
private fun initErrorLayout(withOpenInWebView: Boolean): ReaderErrorBinding {
|
||||
private fun initErrorLayout(): ReaderErrorBinding {
|
||||
if (errorLayout == null) {
|
||||
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), frame, true)
|
||||
errorLayout?.root?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, (parentHeight * 0.8).toInt())
|
||||
errorLayout?.actionRetry?.setOnClickListener {
|
||||
page?.let { it.chapter.pageLoader?.retryPage(it) }
|
||||
}
|
||||
val imageUrl = page?.imageUrl
|
||||
if (imageUrl.orEmpty().startsWith("http", true)) {
|
||||
}
|
||||
|
||||
val imageUrl = page?.imageUrl
|
||||
errorLayout?.actionOpenInWebView?.isVisible = imageUrl != null
|
||||
if (imageUrl != null) {
|
||||
if (imageUrl.startsWith("http", true)) {
|
||||
errorLayout?.actionOpenInWebView?.setOnClickListener {
|
||||
val intent = WebViewActivity.newIntent(context, imageUrl!!)
|
||||
val intent = WebViewActivity.newIntent(context, imageUrl)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
errorLayout?.actionOpenInWebView?.isVisible = withOpenInWebView
|
||||
|
||||
return errorLayout!!
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user