mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
Merge branch 'master' into sync-part-1
This commit is contained in:
commit
abe69206dd
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
@ -19,7 +20,7 @@ class BasePreferences(
|
|||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||||
|
|
||||||
enum class ExtensionInstaller(val titleResId: Int) {
|
enum class ExtensionInstaller(@StringRes val titleResId: Int) {
|
||||||
LEGACY(R.string.ext_installer_legacy),
|
LEGACY(R.string.ext_installer_legacy),
|
||||||
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
||||||
SHIZUKU(R.string.ext_installer_shizuku),
|
SHIZUKU(R.string.ext_installer_shizuku),
|
||||||
|
@ -30,7 +30,5 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
||||||
|
|
||||||
fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
|
||||||
|
|
||||||
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.PushPin
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -16,19 +30,25 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.presentation.core.components.material.Divider
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.VerticalDivider
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchScreen(
|
fun GlobalSearchScreen(
|
||||||
state: GlobalSearchState,
|
state: GlobalSearchScreenModel.State,
|
||||||
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
@ -36,19 +56,80 @@ fun GlobalSearchScreen(
|
|||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
GlobalSearchToolbar(
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
searchQuery = state.searchQuery,
|
GlobalSearchToolbar(
|
||||||
progress = state.progress,
|
searchQuery = state.searchQuery,
|
||||||
total = state.total,
|
progress = state.progress,
|
||||||
navigateUp = navigateUp,
|
total = state.total,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
navigateUp = navigateUp,
|
||||||
onSearch = onSearch,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
scrollBehavior = scrollBehavior,
|
onSearch = onSearch,
|
||||||
)
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.horizontalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
// TODO: make this UX better; it only applies when triggering a new search
|
||||||
|
FilterChip(
|
||||||
|
selected = state.sourceFilter == SourceFilter.PinnedOnly,
|
||||||
|
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.PushPin,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.pinned_sources))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
FilterChip(
|
||||||
|
selected = state.sourceFilter == SourceFilter.All,
|
||||||
|
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.DoneAll,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.all))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
VerticalDivider()
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = state.onlyShowHasResults,
|
||||||
|
onClick = { onToggleResults() },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.FilterList,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.has_results))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
GlobalSearchContent(
|
GlobalSearchContent(
|
||||||
items = state.items,
|
items = items,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.ColumnScope
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -25,10 +26,11 @@ import tachiyomi.domain.library.model.sort
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
import tachiyomi.presentation.core.components.RadioItem
|
import tachiyomi.presentation.core.components.SettingsFlowRow
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import tachiyomi.presentation.core.components.SortItem
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
|
import tachiyomi.presentation.core.components.material.ChoiceChip
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibrarySettingsDialog(
|
fun LibrarySettingsDialog(
|
||||||
@ -167,23 +169,26 @@ private fun ColumnScope.SortPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val displayModes = listOf(
|
||||||
|
R.string.action_display_grid to LibraryDisplayMode.CompactGrid,
|
||||||
|
R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
|
||||||
|
R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
|
||||||
|
R.string.action_display_list to LibraryDisplayMode.List,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.DisplayPage(
|
private fun ColumnScope.DisplayPage(
|
||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
HeadingItem(R.string.action_display_mode)
|
|
||||||
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
||||||
listOf(
|
SettingsFlowRow(R.string.action_display_mode) {
|
||||||
R.string.action_display_grid to LibraryDisplayMode.CompactGrid,
|
displayModes.map { (titleRes, mode) ->
|
||||||
R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid,
|
ChoiceChip(
|
||||||
R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid,
|
isSelected = displayMode == mode,
|
||||||
R.string.action_display_list to LibraryDisplayMode.List,
|
onClick = { screenModel.setDisplayMode(mode) },
|
||||||
).map { (titleRes, mode) ->
|
content = { Text(stringResource(titleRes)) },
|
||||||
RadioItem(
|
)
|
||||||
label = stringResource(titleRes),
|
}
|
||||||
selected = displayMode == mode,
|
|
||||||
onClick = { screenModel.setDisplayMode(mode) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayMode != LibraryDisplayMode.List) {
|
if (displayMode != LibraryDisplayMode.List) {
|
||||||
|
@ -29,10 +29,6 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.label_sources),
|
title = stringResource(R.string.label_sources),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = sourcePreferences.searchPinnedSourcesOnly(),
|
|
||||||
title = stringResource(R.string.pref_search_pinned_sources_only),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = sourcePreferences.hideInLibraryItems(),
|
pref = sourcePreferences.hideInLibraryItems(),
|
||||||
title = stringResource(R.string.pref_hide_in_library_items),
|
title = stringResource(R.string.pref_hide_in_library_items),
|
||||||
|
@ -7,7 +7,6 @@ import androidx.compose.runtime.ReadOnlyComposable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringArrayResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.util.collectAsState
|
||||||
@ -164,9 +163,9 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = navModePref,
|
pref = navModePref,
|
||||||
title = stringResource(R.string.pref_viewer_nav),
|
title = stringResource(R.string.pref_viewer_nav),
|
||||||
entries = stringArrayResource(id = R.array.pager_nav).let {
|
entries = ReaderPreferences.TapZones
|
||||||
it.indices.zip(it).toMap()
|
.mapIndexed { index, it -> index to stringResource(it) }
|
||||||
},
|
.toMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.pagerNavInverted(),
|
pref = readerPreferences.pagerNavInverted(),
|
||||||
@ -182,25 +181,16 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = imageScaleTypePref,
|
pref = imageScaleTypePref,
|
||||||
title = stringResource(R.string.pref_image_scale_type),
|
title = stringResource(R.string.pref_image_scale_type),
|
||||||
entries = mapOf(
|
entries = ReaderPreferences.ImageScaleType
|
||||||
1 to stringResource(R.string.scale_type_fit_screen),
|
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
||||||
2 to stringResource(R.string.scale_type_stretch),
|
.toMap(),
|
||||||
3 to stringResource(R.string.scale_type_fit_width),
|
|
||||||
4 to stringResource(R.string.scale_type_fit_height),
|
|
||||||
5 to stringResource(R.string.scale_type_original_size),
|
|
||||||
6 to stringResource(R.string.scale_type_smart_fit),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.zoomStart(),
|
pref = readerPreferences.zoomStart(),
|
||||||
title = stringResource(R.string.pref_zoom_start),
|
title = stringResource(R.string.pref_zoom_start),
|
||||||
entries = mapOf(
|
entries = ReaderPreferences.ZoomStart
|
||||||
1 to stringResource(R.string.zoom_start_automatic),
|
.mapIndexed { index, it -> index + 1 to stringResource(it) }
|
||||||
2 to stringResource(R.string.zoom_start_left),
|
.toMap(),
|
||||||
3 to stringResource(R.string.zoom_start_right),
|
|
||||||
4 to stringResource(R.string.zoom_start_center),
|
|
||||||
),
|
|
||||||
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.cropBorders(),
|
pref = readerPreferences.cropBorders(),
|
||||||
@ -265,9 +255,9 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = navModePref,
|
pref = navModePref,
|
||||||
title = stringResource(R.string.pref_viewer_nav),
|
title = stringResource(R.string.pref_viewer_nav),
|
||||||
entries = stringArrayResource(id = R.array.webtoon_nav).let {
|
entries = ReaderPreferences.TapZones
|
||||||
it.indices.zip(it).toMap()
|
.mapIndexed { index, it -> index to stringResource(it) }
|
||||||
},
|
.toMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.webtoonNavInverted(),
|
pref = readerPreferences.webtoonNavInverted(),
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -15,8 +16,9 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import tachiyomi.core.preference.getAndSet
|
import tachiyomi.core.preference.getAndSet
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.SelectItem
|
import tachiyomi.presentation.core.components.SettingsFlowRow
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
|
import tachiyomi.presentation.core.components.material.ChoiceChip
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
|
||||||
@ -122,12 +124,14 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
)
|
)
|
||||||
|
|
||||||
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
|
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
|
||||||
SelectItem(
|
SettingsFlowRow(R.string.pref_color_filter_mode) {
|
||||||
label = stringResource(R.string.pref_color_filter_mode),
|
colorFilterModes.mapIndexed { index, it ->
|
||||||
options = colorFilterModes.toTypedArray(),
|
ChoiceChip(
|
||||||
selectedIndex = colorFilterMode,
|
isSelected = colorFilterMode == index,
|
||||||
) {
|
onClick = { screenModel.preferences.colorFilterMode().set(index) },
|
||||||
screenModel.preferences.colorFilterMode().set(it)
|
content = { Text(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.presentation.reader.settings
|
package eu.kanade.presentation.reader.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -9,25 +10,27 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.SettingsFlowRow
|
||||||
import tachiyomi.presentation.core.components.RadioItem
|
import tachiyomi.presentation.core.components.material.ChoiceChip
|
||||||
|
|
||||||
|
private val themes = listOf(
|
||||||
|
R.string.black_background to 1,
|
||||||
|
R.string.gray_background to 2,
|
||||||
|
R.string.white_background to 0,
|
||||||
|
R.string.automatic_background to 3,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
||||||
// TODO: show this in a nicer way
|
|
||||||
HeadingItem(R.string.pref_reader_theme)
|
|
||||||
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
||||||
listOf(
|
SettingsFlowRow(R.string.pref_reader_theme) {
|
||||||
R.string.black_background to 1,
|
themes.map { (labelRes, value) ->
|
||||||
R.string.gray_background to 2,
|
ChoiceChip(
|
||||||
R.string.white_background to 0,
|
isSelected = readerTheme == value,
|
||||||
R.string.automatic_background to 3,
|
onClick = { screenModel.preferences.readerTheme().set(value) },
|
||||||
).map { (titleRes, theme) ->
|
content = { Text(stringResource(labelRes)) },
|
||||||
RadioItem(
|
)
|
||||||
label = stringResource(titleRes),
|
}
|
||||||
selected = readerTheme == theme,
|
|
||||||
onClick = { screenModel.preferences.readerTheme().set(theme) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val showPageNumber by screenModel.preferences.showPageNumber().collectAsState()
|
val showPageNumber by screenModel.preferences.showPageNumber().collectAsState()
|
||||||
|
@ -23,9 +23,6 @@ fun ReaderSettingsDialog(
|
|||||||
onHideMenus: () -> Unit,
|
onHideMenus: () -> Unit,
|
||||||
screenModel: ReaderSettingsScreenModel,
|
screenModel: ReaderSettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
// TODO: undimming doesn't seem to work
|
|
||||||
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
|
|
||||||
|
|
||||||
val tabTitles = listOf(
|
val tabTitles = listOf(
|
||||||
stringResource(R.string.pref_category_reading_mode),
|
stringResource(R.string.pref_category_reading_mode),
|
||||||
stringResource(R.string.pref_category_general),
|
stringResource(R.string.pref_category_general),
|
||||||
@ -33,16 +30,6 @@ fun ReaderSettingsDialog(
|
|||||||
)
|
)
|
||||||
val pagerState = rememberPagerState { tabTitles.size }
|
val pagerState = rememberPagerState { tabTitles.size }
|
||||||
|
|
||||||
LaunchedEffect(pagerState.currentPage) {
|
|
||||||
if (pagerState.currentPage == 2) {
|
|
||||||
window?.setDimAmount(0f)
|
|
||||||
onHideMenus()
|
|
||||||
} else {
|
|
||||||
window?.setDimAmount(0.75f)
|
|
||||||
onShowMenus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
@ -51,6 +38,18 @@ fun ReaderSettingsDialog(
|
|||||||
tabTitles = tabTitles,
|
tabTitles = tabTitles,
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
) { page ->
|
) { page ->
|
||||||
|
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||||
|
|
||||||
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
if (pagerState.currentPage == 2) {
|
||||||
|
window?.setDimAmount(0f)
|
||||||
|
onHideMenus()
|
||||||
|
} else {
|
||||||
|
window?.setDimAmount(0.5f)
|
||||||
|
onShowMenus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
.padding(vertical = TabbedDialogPaddings.Vertical)
|
||||||
|
@ -2,40 +2,100 @@ package eu.kanade.presentation.reader.settings
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.domain.manga.model.orientationType
|
||||||
|
import eu.kanade.domain.manga.model.readingModeType
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
|
import tachiyomi.presentation.core.components.SelectItem
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
|
private val readingModeOptions = ReadingModeType.values().map { it.stringRes to it }
|
||||||
|
private val orientationTypeOptions = OrientationType.values().map { it.stringRes to it }
|
||||||
|
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.values().map { it.titleResId to it }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
||||||
HeadingItem(R.string.pref_category_for_this_series)
|
HeadingItem(R.string.pref_category_for_this_series)
|
||||||
|
val manga by screenModel.mangaFlow.collectAsState()
|
||||||
|
|
||||||
// Reading mode
|
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
|
||||||
// Rotation type
|
SelectItem(
|
||||||
|
label = stringResource(R.string.pref_category_reading_mode),
|
||||||
|
options = readingModeOptions.map { stringResource(it.first) }.toTypedArray(),
|
||||||
|
selectedIndex = readingModeOptions.indexOfFirst { it.second == readingMode },
|
||||||
|
) {
|
||||||
|
screenModel.onChangeReadingMode(readingModeOptions[it].second)
|
||||||
|
}
|
||||||
|
|
||||||
// if (pager)
|
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
|
||||||
PagerViewerSettings(screenModel)
|
SelectItem(
|
||||||
|
label = stringResource(R.string.rotation_type),
|
||||||
|
options = orientationTypeOptions.map { stringResource(it.first) }.toTypedArray(),
|
||||||
|
selectedIndex = orientationTypeOptions.indexOfFirst { it.second == orientationType },
|
||||||
|
) {
|
||||||
|
screenModel.onChangeOrientation(orientationTypeOptions[it].second)
|
||||||
|
}
|
||||||
|
|
||||||
WebtoonViewerSettings(screenModel)
|
val viewer by screenModel.viewerFlow.collectAsState()
|
||||||
|
if (viewer is WebtoonViewer) {
|
||||||
|
WebtoonViewerSettings(screenModel)
|
||||||
|
} else {
|
||||||
|
PagerViewerSettings(screenModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
||||||
HeadingItem(R.string.pager_viewer)
|
HeadingItem(R.string.pager_viewer)
|
||||||
|
|
||||||
// Tap zones
|
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
|
||||||
// Invert tap zones
|
SelectItem(
|
||||||
// Scale type
|
label = stringResource(R.string.pref_viewer_nav),
|
||||||
// Zoom start position
|
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(),
|
||||||
|
selectedIndex = navigationModePager,
|
||||||
|
onSelect = { screenModel.preferences.navigationModePager().set(it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (navigationModePager != 5) {
|
||||||
|
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
|
||||||
|
SelectItem(
|
||||||
|
label = stringResource(R.string.pref_read_with_tapping_inverted),
|
||||||
|
options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(),
|
||||||
|
selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == pagerNavInverted },
|
||||||
|
onSelect = {
|
||||||
|
screenModel.preferences.pagerNavInverted().set(tappingInvertModeOptions[it].second)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
|
||||||
|
SelectItem(
|
||||||
|
label = stringResource(R.string.pref_image_scale_type),
|
||||||
|
options = ReaderPreferences.ImageScaleType.map { stringResource(it) }.toTypedArray(),
|
||||||
|
selectedIndex = imageScaleType - 1,
|
||||||
|
onSelect = { screenModel.preferences.imageScaleType().set(it + 1) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val zoomStart by screenModel.preferences.zoomStart().collectAsState()
|
||||||
|
SelectItem(
|
||||||
|
label = stringResource(R.string.pref_zoom_start),
|
||||||
|
options = ReaderPreferences.ZoomStart.map { stringResource(it) }.toTypedArray(),
|
||||||
|
selectedIndex = zoomStart - 1,
|
||||||
|
onSelect = { screenModel.preferences.zoomStart().set(it + 1) },
|
||||||
|
)
|
||||||
|
|
||||||
val cropBorders by screenModel.preferences.cropBorders().collectAsState()
|
val cropBorders by screenModel.preferences.cropBorders().collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
@ -111,8 +171,25 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
|
|||||||
|
|
||||||
HeadingItem(R.string.webtoon_viewer)
|
HeadingItem(R.string.webtoon_viewer)
|
||||||
|
|
||||||
// TODO: Tap zones
|
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
|
||||||
// TODO: Invert tap zones
|
SelectItem(
|
||||||
|
label = stringResource(R.string.pref_viewer_nav),
|
||||||
|
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(),
|
||||||
|
selectedIndex = navigationModeWebtoon,
|
||||||
|
onSelect = { screenModel.preferences.navigationModeWebtoon().set(it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (navigationModeWebtoon != 5) {
|
||||||
|
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
|
||||||
|
SelectItem(
|
||||||
|
label = stringResource(R.string.pref_read_with_tapping_inverted),
|
||||||
|
options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(),
|
||||||
|
selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == webtoonNavInverted },
|
||||||
|
onSelect = {
|
||||||
|
screenModel.preferences.webtoonNavInverted().set(tappingInvertModeOptions[it].second)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
|
@ -41,7 +41,12 @@ internal object ExtensionLoader {
|
|||||||
const val LIB_VERSION_MIN = 1.3
|
const val LIB_VERSION_MIN = 1.3
|
||||||
const val LIB_VERSION_MAX = 1.5
|
const val LIB_VERSION_MAX = 1.5
|
||||||
|
|
||||||
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
private val PACKAGE_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNING_CERTIFICATES
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
||||||
|
}
|
||||||
|
|
||||||
// inorichi's key
|
// inorichi's key
|
||||||
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||||
@ -49,7 +54,7 @@ internal object ExtensionLoader {
|
|||||||
/**
|
/**
|
||||||
* List of the trusted signatures.
|
* List of the trusted signatures.
|
||||||
*/
|
*/
|
||||||
var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
|
var trustedSignatures = mutableSetOf(officialSignature) + preferences.trustedSignatures().get()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of all the installed extensions initialized concurrently.
|
* Return a list of all the installed extensions initialized concurrently.
|
||||||
@ -59,7 +64,6 @@ internal object ExtensionLoader {
|
|||||||
fun loadExtensions(context: Context): List<LoadResult> {
|
fun loadExtensions(context: Context): List<LoadResult> {
|
||||||
val pkgManager = context.packageManager
|
val pkgManager = context.packageManager
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
pkgManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(PACKAGE_FLAGS.toLong()))
|
pkgManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(PACKAGE_FLAGS.toLong()))
|
||||||
} else {
|
} else {
|
||||||
@ -135,7 +139,7 @@ internal object ExtensionLoader {
|
|||||||
return LoadResult.Error
|
return LoadResult.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
val signatureHash = getSignatureHash(pkgInfo)
|
val signatureHash = getSignatureHash(context, pkgInfo)
|
||||||
if (signatureHash == null) {
|
if (signatureHash == null) {
|
||||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||||
return LoadResult.Error
|
return LoadResult.Error
|
||||||
@ -220,12 +224,8 @@ internal object ExtensionLoader {
|
|||||||
*
|
*
|
||||||
* @param pkgInfo The package info of the application.
|
* @param pkgInfo The package info of the application.
|
||||||
*/
|
*/
|
||||||
private fun getSignatureHash(pkgInfo: PackageInfo): String? {
|
private fun getSignatureHash(context: Context, pkgInfo: PackageInfo): String? {
|
||||||
val signatures = pkgInfo.signatures
|
val signatures = PackageInfoCompat.getSignatures(context.packageManager, pkgInfo.packageName)
|
||||||
return if (signatures != null && signatures.isNotEmpty()) {
|
return signatures.firstOrNull()?.let { Hash.sha256(it.toByteArray()) }
|
||||||
Hash.sha256(signatures.first().toByteArray())
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
@ -313,6 +314,7 @@ internal class MigrateDialogScreenModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val isMigrating: Boolean = false,
|
val isMigrating: Boolean = false,
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,7 @@ import eu.kanade.presentation.browse.MigrateSearchScreen
|
|||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
|
|
||||||
|
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
|
||||||
class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -35,6 +35,7 @@ class GlobalSearchScreen(
|
|||||||
var showSingleLoadingScreen by remember {
|
var showSingleLoadingScreen by remember {
|
||||||
mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1)
|
mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1)
|
||||||
}
|
}
|
||||||
|
val filteredSources by screenModel.searchPagerFlow.collectAsState()
|
||||||
|
|
||||||
if (showSingleLoadingScreen) {
|
if (showSingleLoadingScreen) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
@ -57,10 +58,13 @@ class GlobalSearchScreen(
|
|||||||
} else {
|
} else {
|
||||||
GlobalSearchScreen(
|
GlobalSearchScreen(
|
||||||
state = state,
|
state = state,
|
||||||
|
items = filteredSources,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||||
onSearch = screenModel::search,
|
onSearch = screenModel::search,
|
||||||
getManga = { screenModel.getManga(it) },
|
getManga = { screenModel.getManga(it) },
|
||||||
|
onChangeSearchFilter = screenModel::setSourceFilter,
|
||||||
|
onToggleResults = screenModel::toggleFilterResults,
|
||||||
onClickSource = {
|
onClickSource = {
|
||||||
if (!screenModel.incognitoMode.get()) {
|
if (!screenModel.incognitoMode.get()) {
|
||||||
screenModel.lastUsedSourceId.set(it.id)
|
screenModel.lastUsedSourceId.set(it.id)
|
||||||
|
@ -3,7 +3,12 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.presentation.util.ioCoroutineScope
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -15,11 +20,18 @@ class GlobalSearchScreenModel(
|
|||||||
preferences: BasePreferences = Injekt.get(),
|
preferences: BasePreferences = Injekt.get(),
|
||||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
) : SearchScreenModel<GlobalSearchState>(GlobalSearchState(searchQuery = initialQuery)) {
|
) : SearchScreenModel<GlobalSearchScreenModel.State>(State(searchQuery = initialQuery)) {
|
||||||
|
|
||||||
val incognitoMode = preferences.incognitoMode()
|
val incognitoMode = preferences.incognitoMode()
|
||||||
val lastUsedSourceId = sourcePreferences.lastUsedSource()
|
val lastUsedSourceId = sourcePreferences.lastUsedSource()
|
||||||
|
|
||||||
|
val searchPagerFlow = state.map { Pair(it.onlyShowHasResults, it.items) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.map { (onlyShowHasResults, items) ->
|
||||||
|
items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||||
|
}
|
||||||
|
.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
extensionFilter = initialExtensionFilter
|
extensionFilter = initialExtensionFilter
|
||||||
if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) {
|
if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) {
|
||||||
@ -33,6 +45,7 @@ class GlobalSearchScreenModel(
|
|||||||
val pinnedSources = sourcePreferences.pinnedSources().get()
|
val pinnedSources = sourcePreferences.pinnedSources().get()
|
||||||
|
|
||||||
return sourceManager.getCatalogueSources()
|
return sourceManager.getCatalogueSources()
|
||||||
|
.filter { mutableState.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||||
.filter { it.lang in enabledLanguages }
|
.filter { it.lang in enabledLanguages }
|
||||||
.filterNot { "${it.id}" in disabledSources }
|
.filterNot { "${it.id}" in disabledSources }
|
||||||
.sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" }))
|
.sortedWith(compareBy({ "${it.id}" !in pinnedSources }, { "${it.name.lowercase()} (${it.lang})" }))
|
||||||
@ -53,15 +66,29 @@ class GlobalSearchScreenModel(
|
|||||||
override fun getItems(): Map<CatalogueSource, SearchItemResult> {
|
override fun getItems(): Map<CatalogueSource, SearchItemResult> {
|
||||||
return mutableState.value.items
|
return mutableState.value.items
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fun setSourceFilter(filter: SourceFilter) {
|
||||||
@Immutable
|
mutableState.update { it.copy(sourceFilter = filter) }
|
||||||
data class GlobalSearchState(
|
}
|
||||||
val searchQuery: String? = null,
|
|
||||||
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
fun toggleFilterResults() {
|
||||||
) {
|
mutableState.update {
|
||||||
|
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
}
|
||||||
|
}
|
||||||
val total: Int = items.size
|
|
||||||
|
private fun SearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
|
||||||
|
return !onlyShowHasResults || (this is SearchItemResult.Success && !this.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class State(
|
||||||
|
val searchQuery: String? = null,
|
||||||
|
val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
|
||||||
|
val onlyShowHasResults: Boolean = false,
|
||||||
|
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
||||||
|
) {
|
||||||
|
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||||
|
val total: Int = items.size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,16 +68,7 @@ abstract class SearchScreenModel<T>(
|
|||||||
val enabledSources = getEnabledSources()
|
val enabledSources = getEnabledSources()
|
||||||
|
|
||||||
if (filter.isEmpty()) {
|
if (filter.isEmpty()) {
|
||||||
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get()
|
return enabledSources
|
||||||
val pinnedSources = sourcePreferences.pinnedSources().get()
|
|
||||||
|
|
||||||
return enabledSources.filter {
|
|
||||||
if (shouldSearchPinnedOnly) {
|
|
||||||
"${it.id}" in pinnedSources
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionManager.installedExtensionsFlow.value
|
return extensionManager.installedExtensionsFlow.value
|
||||||
@ -137,6 +128,11 @@ abstract class SearchScreenModel<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SourceFilter {
|
||||||
|
All,
|
||||||
|
PinnedOnly,
|
||||||
|
}
|
||||||
|
|
||||||
sealed class SearchItemResult {
|
sealed class SearchItemResult {
|
||||||
object Loading : SearchItemResult()
|
object Loading : SearchItemResult()
|
||||||
|
|
||||||
|
@ -113,6 +113,10 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
private var navigator: Navigator? = null
|
private var navigator: Navigator? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
registerSecureActivity(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
val isLaunch = savedInstanceState == null
|
val isLaunch = savedInstanceState == null
|
||||||
|
|
||||||
@ -429,10 +433,6 @@ class MainActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
registerSecureActivity(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Splash screen
|
// Splash screen
|
||||||
private const val SPLASH_MIN_DURATION = 500 // ms
|
private const val SPLASH_MIN_DURATION = 500 // ms
|
||||||
|
@ -20,6 +20,7 @@ import androidx.compose.material3.SelectableDates
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -265,6 +266,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
.filter { (it.service as? EnhancedTrackService)?.accept(source) ?: true }
|
.filter { (it.service as? EnhancedTrackService)?.accept(source) ?: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val trackItems: List<TrackItem> = emptyList(),
|
val trackItems: List<TrackItem> = emptyList(),
|
||||||
)
|
)
|
||||||
@ -314,6 +316,7 @@ private data class TrackStatusSelectorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val selection: Int,
|
val selection: Int,
|
||||||
)
|
)
|
||||||
@ -369,6 +372,7 @@ private data class TrackChapterSelectorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val selection: Int,
|
val selection: Int,
|
||||||
)
|
)
|
||||||
@ -419,6 +423,7 @@ private data class TrackScoreSelectorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val selection: String,
|
val selection: String,
|
||||||
)
|
)
|
||||||
@ -724,6 +729,7 @@ data class TrackServiceSearchScreen(
|
|||||||
mutableState.update { it.copy(selected = selected) }
|
mutableState.update { it.copy(selected = selected) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val queryResult: Result<List<TrackSearch>>? = null,
|
val queryResult: Result<List<TrackSearch>>? = null,
|
||||||
val selected: TrackSearch? = null,
|
val selected: TrackSearch? = null,
|
||||||
|
@ -69,7 +69,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
|||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
@ -391,7 +390,13 @@ class ReaderActivity : BaseActivity() {
|
|||||||
|
|
||||||
binding.dialogRoot.setComposeContent {
|
binding.dialogRoot.setComposeContent {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val settingsScreenModel = remember { ReaderSettingsScreenModel() }
|
val settingsScreenModel = remember {
|
||||||
|
ReaderSettingsScreenModel(
|
||||||
|
readerState = viewModel.state,
|
||||||
|
onChangeReadingMode = viewModel::setMangaReadingMode,
|
||||||
|
onChangeOrientation = viewModel::setMangaOrientationType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val onDismissRequest = viewModel::closeDialog
|
val onDismissRequest = viewModel::closeDialog
|
||||||
when (state.dialog) {
|
when (state.dialog) {
|
||||||
@ -485,7 +490,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
) {
|
) {
|
||||||
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
||||||
|
|
||||||
viewModel.setMangaReadingMode(newReadingMode.flagValue)
|
viewModel.setMangaReadingMode(newReadingMode)
|
||||||
|
|
||||||
menuToggleToast?.cancel()
|
menuToggleToast?.cancel()
|
||||||
if (!readerPreferences.showReadingMode().get()) {
|
if (!readerPreferences.showReadingMode().get()) {
|
||||||
@ -539,7 +544,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
) {
|
) {
|
||||||
val newOrientation = OrientationType.fromPreference(itemId)
|
val newOrientation = OrientationType.fromPreference(itemId)
|
||||||
|
|
||||||
viewModel.setMangaOrientationType(newOrientation.flagValue)
|
viewModel.setMangaOrientationType(newOrientation)
|
||||||
|
|
||||||
menuToggleToast?.cancel()
|
menuToggleToast?.cancel()
|
||||||
menuToggleToast = toast(newOrientation.stringRes)
|
menuToggleToast = toast(newOrientation.stringRes)
|
||||||
@ -548,16 +553,6 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings sheet
|
// Settings sheet
|
||||||
with(binding.actionSettingsLegacy) {
|
|
||||||
setTooltip(R.string.action_settings)
|
|
||||||
|
|
||||||
var readerSettingSheet: ReaderSettingsSheet? = null
|
|
||||||
|
|
||||||
setOnClickListener {
|
|
||||||
if (readerSettingSheet?.isShowing == true) return@setOnClickListener
|
|
||||||
readerSettingSheet = ReaderSettingsSheet(this@ReaderActivity).apply { show() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with(binding.actionSettings) {
|
with(binding.actionSettings) {
|
||||||
setTooltip(R.string.action_settings)
|
setTooltip(R.string.action_settings)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@ -601,10 +602,10 @@ class ReaderViewModel(
|
|||||||
/**
|
/**
|
||||||
* Updates the viewer position for the open manga.
|
* Updates the viewer position for the open manga.
|
||||||
*/
|
*/
|
||||||
fun setMangaReadingMode(readingModeType: Int) {
|
fun setMangaReadingMode(readingModeType: ReadingModeType) {
|
||||||
val manga = manga ?: return
|
val manga = manga ?: return
|
||||||
runBlocking(Dispatchers.IO) {
|
runBlocking(Dispatchers.IO) {
|
||||||
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id, readingModeType.toLong())
|
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id, readingModeType.flagValue.toLong())
|
||||||
val currChapters = state.value.viewerChapters
|
val currChapters = state.value.viewerChapters
|
||||||
if (currChapters != null) {
|
if (currChapters != null) {
|
||||||
// Save current page
|
// Save current page
|
||||||
@ -637,10 +638,10 @@ class ReaderViewModel(
|
|||||||
/**
|
/**
|
||||||
* Updates the orientation type for the open manga.
|
* Updates the orientation type for the open manga.
|
||||||
*/
|
*/
|
||||||
fun setMangaOrientationType(rotationType: Int) {
|
fun setMangaOrientationType(rotationType: OrientationType) {
|
||||||
val manga = manga ?: return
|
val manga = manga ?: return
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setMangaViewerFlags.awaitSetOrientationType(manga.id, rotationType.toLong())
|
setMangaViewerFlags.awaitSetOrientationType(manga.id, rotationType.flagValue.toLong())
|
||||||
val currChapters = state.value.viewerChapters
|
val currChapters = state.value.viewerChapters
|
||||||
if (currChapters != null) {
|
if (currChapters != null) {
|
||||||
// Save current page
|
// Save current page
|
||||||
@ -790,16 +791,12 @@ class ReaderViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Results of the set as cover feature.
|
|
||||||
*/
|
|
||||||
enum class SetAsCoverResult {
|
enum class SetAsCoverResult {
|
||||||
Success, AddToLibraryFirst, Error
|
Success,
|
||||||
|
AddToLibraryFirst,
|
||||||
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Results of the save image feature.
|
|
||||||
*/
|
|
||||||
sealed class SaveImageResult {
|
sealed class SaveImageResult {
|
||||||
class Success(val uri: Uri) : SaveImageResult()
|
class Success(val uri: Uri) : SaveImageResult()
|
||||||
class Error(val error: Throwable) : SaveImageResult()
|
class Error(val error: Throwable) : SaveImageResult()
|
||||||
@ -844,6 +841,7 @@ class ReaderViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val manga: Manga? = null,
|
val manga: Manga? = null,
|
||||||
val viewerChapters: ViewerChapters? = null,
|
val viewerChapters: ViewerChapters? = null,
|
||||||
|
@ -18,8 +18,6 @@ enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val str
|
|||||||
companion object {
|
companion object {
|
||||||
const val MASK = 0x00000038
|
const val MASK = 0x00000038
|
||||||
|
|
||||||
fun fromPreference(preference: Int?): OrientationType = values().find { it.flagValue == preference } ?: FREE
|
fun fromPreference(preference: Int?): OrientationType = values().find { it.flagValue == preference } ?: DEFAULT
|
||||||
|
|
||||||
fun fromSpinner(position: Int?) = values().find { value -> value.prefValue == position } ?: DEFAULT
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.setting
|
package eu.kanade.tachiyomi.ui.reader.setting
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
@ -122,11 +124,15 @@ class ReaderPreferences(
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) {
|
enum class TappingInvertMode(
|
||||||
NONE,
|
@StringRes val titleResId: Int,
|
||||||
HORIZONTAL(shouldInvertHorizontal = true),
|
val shouldInvertHorizontal: Boolean = false,
|
||||||
VERTICAL(shouldInvertVertical = true),
|
val shouldInvertVertical: Boolean = false,
|
||||||
BOTH(shouldInvertHorizontal = true, shouldInvertVertical = true),
|
) {
|
||||||
|
NONE(R.string.tapping_inverted_none),
|
||||||
|
HORIZONTAL(R.string.tapping_inverted_horizontal, shouldInvertHorizontal = true),
|
||||||
|
VERTICAL(R.string.tapping_inverted_vertical, shouldInvertVertical = true),
|
||||||
|
BOTH(R.string.tapping_inverted_both, shouldInvertHorizontal = true, shouldInvertVertical = true),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ReaderHideThreshold(val threshold: Int) {
|
enum class ReaderHideThreshold(val threshold: Int) {
|
||||||
@ -139,5 +145,30 @@ class ReaderPreferences(
|
|||||||
companion object {
|
companion object {
|
||||||
const val WEBTOON_PADDING_MIN = 0
|
const val WEBTOON_PADDING_MIN = 0
|
||||||
const val WEBTOON_PADDING_MAX = 25
|
const val WEBTOON_PADDING_MAX = 25
|
||||||
|
|
||||||
|
val TapZones = listOf(
|
||||||
|
R.string.label_default,
|
||||||
|
R.string.l_nav,
|
||||||
|
R.string.kindlish_nav,
|
||||||
|
R.string.edge_nav,
|
||||||
|
R.string.right_and_left_nav,
|
||||||
|
R.string.disabled_nav,
|
||||||
|
)
|
||||||
|
|
||||||
|
val ImageScaleType = listOf(
|
||||||
|
R.string.scale_type_fit_screen,
|
||||||
|
R.string.scale_type_stretch,
|
||||||
|
R.string.scale_type_fit_width,
|
||||||
|
R.string.scale_type_fit_height,
|
||||||
|
R.string.scale_type_original_size,
|
||||||
|
R.string.scale_type_smart_fit,
|
||||||
|
)
|
||||||
|
|
||||||
|
val ZoomStart = listOf(
|
||||||
|
R.string.zoom_start_automatic,
|
||||||
|
R.string.zoom_start_left,
|
||||||
|
R.string.zoom_start_right,
|
||||||
|
R.string.zoom_start_center,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.setting
|
package eu.kanade.tachiyomi.ui.reader.setting
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import eu.kanade.presentation.util.ioCoroutineScope
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel
|
||||||
import eu.kanade.tachiyomi.util.preference.toggle
|
import eu.kanade.tachiyomi.util.preference.toggle
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ReaderSettingsScreenModel(
|
class ReaderSettingsScreenModel(
|
||||||
|
readerState: StateFlow<ReaderViewModel.State>,
|
||||||
|
val onChangeReadingMode: (ReadingModeType) -> Unit,
|
||||||
|
val onChangeOrientation: (OrientationType) -> Unit,
|
||||||
val preferences: ReaderPreferences = Injekt.get(),
|
val preferences: ReaderPreferences = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
|
|
||||||
|
val viewerFlow = readerState
|
||||||
|
.map { it.viewer }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.stateIn(ioCoroutineScope, SharingStarted.Lazily, null)
|
||||||
|
|
||||||
|
val mangaFlow = readerState
|
||||||
|
.map { it.manga }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.stateIn(ioCoroutineScope, SharingStarted.Lazily, null)
|
||||||
|
|
||||||
fun togglePreference(preference: (ReaderPreferences) -> Preference<Boolean>) {
|
fun togglePreference(preference: (ReaderPreferences) -> Preference<Boolean>) {
|
||||||
preference(preferences).toggle()
|
preference(preferences).toggle()
|
||||||
}
|
}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.setting
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
||||||
import eu.kanade.domain.manga.model.orientationType
|
|
||||||
import eu.kanade.domain.manga.model.readingModeType
|
|
||||||
import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindToPreference
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class ReaderSettingsSheet(
|
|
||||||
private val activity: ReaderActivity,
|
|
||||||
) : BottomSheetDialog(activity) {
|
|
||||||
|
|
||||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
|
||||||
|
|
||||||
private lateinit var binding: ReaderReadingModeSettingsBinding
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
binding = ReaderReadingModeSettingsBinding.inflate(activity.layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
|
|
||||||
initGeneralPreferences()
|
|
||||||
|
|
||||||
when (activity.viewModel.state.value.viewer) {
|
|
||||||
is PagerViewer -> initPagerPreferences()
|
|
||||||
is WebtoonViewer -> initWebtoonPreferences()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initGeneralPreferences() {
|
|
||||||
binding.viewer.onItemSelectedListener = { position ->
|
|
||||||
val readingModeType = ReadingModeType.fromSpinner(position)
|
|
||||||
activity.viewModel.setMangaReadingMode(readingModeType.flagValue)
|
|
||||||
|
|
||||||
val mangaViewer = activity.viewModel.getMangaReadingMode()
|
|
||||||
if (mangaViewer == ReadingModeType.WEBTOON.flagValue || mangaViewer == ReadingModeType.CONTINUOUS_VERTICAL.flagValue) {
|
|
||||||
initWebtoonPreferences()
|
|
||||||
} else {
|
|
||||||
initPagerPreferences()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.viewer.setSelection(activity.viewModel.manga?.readingModeType?.let { ReadingModeType.fromPreference(it.toInt()).prefValue } ?: ReadingModeType.DEFAULT.prefValue)
|
|
||||||
|
|
||||||
binding.rotationMode.onItemSelectedListener = { position ->
|
|
||||||
val rotationType = OrientationType.fromSpinner(position)
|
|
||||||
activity.viewModel.setMangaOrientationType(rotationType.flagValue)
|
|
||||||
}
|
|
||||||
binding.rotationMode.setSelection(activity.viewModel.manga?.orientationType?.let { OrientationType.fromPreference(it.toInt()).prefValue } ?: OrientationType.DEFAULT.prefValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initPagerPreferences() {
|
|
||||||
binding.webtoonPrefsGroup.root.isVisible = false
|
|
||||||
binding.pagerPrefsGroup.root.isVisible = true
|
|
||||||
|
|
||||||
binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), ReaderPreferences.TappingInvertMode::class.java)
|
|
||||||
|
|
||||||
binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager())
|
|
||||||
readerPreferences.navigationModePager().changes()
|
|
||||||
.onEach {
|
|
||||||
val isTappingEnabled = it != 5
|
|
||||||
binding.pagerPrefsGroup.tappingInverted.isVisible = isTappingEnabled
|
|
||||||
}
|
|
||||||
.launchIn(activity.lifecycleScope)
|
|
||||||
binding.pagerPrefsGroup.scaleType.bindToPreference(readerPreferences.imageScaleType(), 1)
|
|
||||||
|
|
||||||
binding.pagerPrefsGroup.zoomStart.bindToPreference(readerPreferences.zoomStart(), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initWebtoonPreferences() {
|
|
||||||
binding.pagerPrefsGroup.root.isVisible = false
|
|
||||||
binding.webtoonPrefsGroup.root.isVisible = true
|
|
||||||
|
|
||||||
binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), ReaderPreferences.TappingInvertMode::class.java)
|
|
||||||
|
|
||||||
binding.webtoonPrefsGroup.webtoonNav.bindToPreference(readerPreferences.navigationModeWebtoon())
|
|
||||||
readerPreferences.navigationModeWebtoon().changes()
|
|
||||||
.onEach { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 }
|
|
||||||
.launchIn(activity.lifecycleScope)
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,8 +29,6 @@ enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @D
|
|||||||
return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL
|
return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromSpinner(position: Int?) = values().find { value -> value.prefValue == position } ?: DEFAULT
|
|
||||||
|
|
||||||
fun toViewer(preference: Int?, activity: ReaderActivity): Viewer {
|
fun toViewer(preference: Int?, activity: ReaderActivity): Viewer {
|
||||||
return when (fromPreference(preference)) {
|
return when (fromPreference(preference)) {
|
||||||
LEFT_TO_RIGHT -> L2RPagerViewer(activity)
|
LEFT_TO_RIGHT -> L2RPagerViewer(activity)
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.util.preference
|
package eu.kanade.tachiyomi.util.preference
|
||||||
|
|
||||||
import android.widget.CompoundButton
|
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds a checkbox or switch view with a boolean preference.
|
|
||||||
*/
|
|
||||||
fun CompoundButton.bindToPreference(pref: Preference<Boolean>) {
|
|
||||||
isChecked = pref.get()
|
|
||||||
setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) }
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
|
operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
|
||||||
set(get() + item)
|
set(get() + item)
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,6 @@ private fun Context.defaultBrowserPackageName(): String? {
|
|||||||
val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
}
|
}
|
||||||
return resolveInfo
|
return resolveInfo
|
||||||
|
@ -5,10 +5,7 @@ import android.content.Context
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.Display
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.core.content.getSystemService
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -67,14 +64,6 @@ fun Context.isNightMode(): Boolean {
|
|||||||
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||||
}
|
}
|
||||||
|
|
||||||
val Context.displayCompat: Display?
|
|
||||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
display
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
getSystemService<WindowManager>()?.defaultDisplay
|
|
||||||
}
|
|
||||||
|
|
||||||
val Resources.isLTR
|
val Resources.isLTR
|
||||||
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
@file:Suppress("PackageDirectoryMismatch")
|
|
||||||
|
|
||||||
package com.google.android.material.bottomsheet
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns package-private elevation value
|
|
||||||
*/
|
|
||||||
fun <T : View> BottomSheetBehavior<T>.getElevation(): Float {
|
|
||||||
return elevation.takeIf { it >= 0F } ?: 0F
|
|
||||||
}
|
|
@ -1,30 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.util.view
|
package eu.kanade.tachiyomi.util.view
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets navigation bar color to transparent if system's config_navBarNeedsScrim is false,
|
|
||||||
* otherwise it will use the theme navigationBarColor with 70% opacity.
|
|
||||||
*
|
|
||||||
* @see isNavigationBarNeedsScrim
|
|
||||||
*/
|
|
||||||
fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float = 0F) {
|
|
||||||
navigationBarColor = if (context.isNavigationBarNeedsScrim()) {
|
|
||||||
// Set navbar scrim 70% of navigationBarColor
|
|
||||||
ElevationOverlayProvider(context).compositeOverlayIfNeeded(
|
|
||||||
context.getResourceColor(android.R.attr.navigationBarColor, 0.7F),
|
|
||||||
elevation,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Window.setSecureScreen(enabled: Boolean) {
|
fun Window.setSecureScreen(enabled: Boolean) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.appcompat.view.menu.MenuBuilder
|
|
||||||
import androidx.appcompat.widget.PopupMenu
|
|
||||||
import androidx.core.content.withStyledAttributes
|
|
||||||
import androidx.core.view.forEach
|
|
||||||
import androidx.core.view.get
|
|
||||||
import androidx.core.view.size
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.PrefSpinnerBinding
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import tachiyomi.core.preference.Preference
|
|
||||||
|
|
||||||
class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
|
||||||
FrameLayout(context, attrs) {
|
|
||||||
|
|
||||||
private var entries = emptyList<String>()
|
|
||||||
private var selectedPosition = 0
|
|
||||||
private var popup: PopupMenu? = null
|
|
||||||
|
|
||||||
var onItemSelectedListener: ((Int) -> Unit)? = null
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
if (value != null) {
|
|
||||||
popup = makeSettingsPopup()
|
|
||||||
setOnTouchListener(popup?.dragToOpenListener)
|
|
||||||
setOnClickListener {
|
|
||||||
popup?.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val emptyIcon by lazy {
|
|
||||||
AppCompatResources.getDrawable(context, R.drawable.ic_blank_24dp)
|
|
||||||
}
|
|
||||||
private val checkmarkIcon by lazy {
|
|
||||||
AppCompatResources.getDrawable(context, R.drawable.ic_check_24dp)?.mutate()?.apply {
|
|
||||||
setTint(context.getResourceColor(android.R.attr.textColorPrimary))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val binding = PrefSpinnerBinding.inflate(LayoutInflater.from(context), this, false)
|
|
||||||
|
|
||||||
init {
|
|
||||||
addView(binding.root)
|
|
||||||
|
|
||||||
context.withStyledAttributes(set = attrs, attrs = R.styleable.MaterialSpinnerView) {
|
|
||||||
val title = getString(R.styleable.MaterialSpinnerView_title).orEmpty()
|
|
||||||
binding.title.text = title
|
|
||||||
|
|
||||||
val viewEntries = getTextArray(R.styleable.MaterialSpinnerView_android_entries)
|
|
||||||
.orEmpty()
|
|
||||||
.map { it.toString() }
|
|
||||||
entries = viewEntries
|
|
||||||
binding.details.text = viewEntries.firstOrNull().orEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSelection(selection: Int) {
|
|
||||||
if (selectedPosition < (popup?.menu?.size ?: 0)) {
|
|
||||||
popup?.menu?.getItem(selectedPosition)?.let {
|
|
||||||
it.icon = emptyIcon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedPosition = selection
|
|
||||||
popup?.menu?.getItem(selectedPosition)?.let {
|
|
||||||
it.icon = checkmarkIcon
|
|
||||||
}
|
|
||||||
binding.details.text = entries.getOrNull(selection).orEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bindToPreference(pref: Preference<Int>, offset: Int = 0, block: ((Int) -> Unit)? = null) {
|
|
||||||
setSelection(pref.get() - offset)
|
|
||||||
|
|
||||||
popup = makeSettingsPopup(pref, offset, block)
|
|
||||||
setOnTouchListener(popup?.dragToOpenListener)
|
|
||||||
setOnClickListener {
|
|
||||||
popup?.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Enum<T>> bindToPreference(pref: Preference<T>, clazz: Class<T>) {
|
|
||||||
val enumConstants = clazz.enumConstants
|
|
||||||
enumConstants?.indexOf(pref.get())?.let { setSelection(it) }
|
|
||||||
|
|
||||||
popup = makeSettingsPopup(pref, clazz)
|
|
||||||
setOnTouchListener(popup?.dragToOpenListener)
|
|
||||||
setOnClickListener {
|
|
||||||
popup?.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : Enum<T>> makeSettingsPopup(preference: Preference<T>, clazz: Class<T>): PopupMenu {
|
|
||||||
return createPopupMenu { pos ->
|
|
||||||
onItemSelectedListener?.invoke(pos)
|
|
||||||
|
|
||||||
val enumConstants = clazz.enumConstants
|
|
||||||
enumConstants?.get(pos)?.let { enumValue -> preference.set(enumValue) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeSettingsPopup(preference: Preference<Int>, intValues: List<Int?>, block: ((Int) -> Unit)? = null): PopupMenu {
|
|
||||||
return createPopupMenu { pos ->
|
|
||||||
preference.set(intValues[pos] ?: 0)
|
|
||||||
block?.invoke(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeSettingsPopup(preference: Preference<Int>, offset: Int = 0, block: ((Int) -> Unit)? = null): PopupMenu {
|
|
||||||
return createPopupMenu { pos ->
|
|
||||||
preference.set(pos + offset)
|
|
||||||
block?.invoke(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeSettingsPopup(): PopupMenu {
|
|
||||||
return createPopupMenu { pos ->
|
|
||||||
onItemSelectedListener?.invoke(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun menuClicked(menuItem: MenuItem): Int {
|
|
||||||
val pos = menuItem.itemId
|
|
||||||
setSelection(pos)
|
|
||||||
return pos
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
fun createPopupMenu(onItemClick: (Int) -> Unit): PopupMenu {
|
|
||||||
val popup = PopupMenu(context, this, Gravity.END, R.attr.actionOverflowMenuStyle, 0)
|
|
||||||
entries.forEachIndexed { index, entry ->
|
|
||||||
popup.menu.add(0, index, 0, entry)
|
|
||||||
}
|
|
||||||
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
|
|
||||||
popup.menu.forEach {
|
|
||||||
it.icon = emptyIcon
|
|
||||||
}
|
|
||||||
popup.menu[selectedPosition].icon = checkmarkIcon
|
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
|
||||||
val pos = menuClicked(menuItem)
|
|
||||||
onItemClick(pos)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
return popup
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="300"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in">
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:fromYDelta="100%p"
|
|
||||||
android:toYDelta="0" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="300"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in">
|
|
||||||
|
|
||||||
<translate
|
|
||||||
android:fromYDelta="0"
|
|
||||||
android:toYDelta="100%p" />
|
|
||||||
|
|
||||||
</set>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z" />
|
|
||||||
</vector>
|
|
@ -1,58 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/listPreferredItemHeightSmall"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/center_guideline"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Title" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/details"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/dropdown_caret"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/center_guideline"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Details" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/dropdown_caret"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="1dp"
|
|
||||||
android:src="@drawable/ic_expand_more_24dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/details"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/details"
|
|
||||||
app:tint="?android:attr/colorControlNormal"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
|
||||||
android:id="@+id/center_guideline"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintGuide_percent="0.5" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -118,19 +118,6 @@
|
|||||||
app:srcCompat="@drawable/ic_screen_rotation_24dp"
|
app:srcCompat="@drawable/ic_screen_rotation_24dp"
|
||||||
app:tint="?attr/colorOnSurface" />
|
app:tint="?attr/colorOnSurface" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/action_settings_legacy"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/action_settings"
|
|
||||||
android:padding="@dimen/screen_edge_margin"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/action_settings"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/action_rotation"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_settings_24dp"
|
|
||||||
app:tint="?attr/colorOnSurface" />
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -139,7 +126,7 @@
|
|||||||
android:contentDescription="@string/action_settings"
|
android:contentDescription="@string/action_settings"
|
||||||
android:padding="@dimen/screen_edge_margin"
|
android:padding="@dimen/screen_edge_margin"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/action_settings_legacy"
|
app:layout_constraintStart_toEndOf="@id/action_rotation"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@drawable/ic_settings_24dp"
|
app:srcCompat="@drawable/ic_settings_24dp"
|
||||||
app:tint="?attr/colorOnSurface" />
|
app:tint="?attr/colorOnSurface" />
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pager_prefs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:text="@string/pager_viewer"
|
|
||||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/pager_nav"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/pager_nav"
|
|
||||||
app:title="@string/pref_viewer_nav" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/tapping_inverted"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/invert_tapping_mode"
|
|
||||||
app:title="@string/pref_read_with_tapping_inverted" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/scale_type"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/image_scale_type"
|
|
||||||
app:title="@string/pref_image_scale_type" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/zoom_start"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/zoom_start"
|
|
||||||
app:title="@string/pref_zoom_start" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -1,56 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/for_this_series_prefs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:text="@string/pref_category_for_this_series"
|
|
||||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/viewer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/viewers_selector"
|
|
||||||
app:title="@string/pref_category_reading_mode" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/rotation_mode"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:entries="@array/rotation_type"
|
|
||||||
app:title="@string/rotation_type" />
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/pager_prefs_group"
|
|
||||||
layout="@layout/reader_pager_settings"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/webtoon_prefs_group"
|
|
||||||
layout="@layout/reader_webtoon_settings"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
@ -1,32 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/webtoon_prefs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:text="@string/webtoon_viewer"
|
|
||||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/webtoon_nav"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/webtoon_nav"
|
|
||||||
app:title="@string/pref_viewer_nav" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
|
||||||
android:id="@+id/tapping_inverted"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/invert_tapping_mode"
|
|
||||||
app:title="@string/pref_read_with_tapping_inverted" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -9,22 +9,6 @@
|
|||||||
<item>@string/vertical_plus_viewer</item>
|
<item>@string/vertical_plus_viewer</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="image_scale_type">
|
|
||||||
<item>@string/scale_type_fit_screen</item>
|
|
||||||
<item>@string/scale_type_stretch</item>
|
|
||||||
<item>@string/scale_type_fit_width</item>
|
|
||||||
<item>@string/scale_type_fit_height</item>
|
|
||||||
<item>@string/scale_type_original_size</item>
|
|
||||||
<item>@string/scale_type_smart_fit</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="zoom_start">
|
|
||||||
<item>@string/zoom_start_automatic</item>
|
|
||||||
<item>@string/zoom_start_left</item>
|
|
||||||
<item>@string/zoom_start_right</item>
|
|
||||||
<item>@string/zoom_start_center</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="rotation_type">
|
<string-array name="rotation_type">
|
||||||
<item>@string/label_default</item>
|
<item>@string/label_default</item>
|
||||||
<item>@string/rotation_free</item>
|
<item>@string/rotation_free</item>
|
||||||
@ -34,29 +18,4 @@
|
|||||||
<item>@string/rotation_force_landscape</item>
|
<item>@string/rotation_force_landscape</item>
|
||||||
<item>@string/rotation_reverse_portrait</item>
|
<item>@string/rotation_reverse_portrait</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="invert_tapping_mode">
|
|
||||||
<item>@string/tapping_inverted_none</item>
|
|
||||||
<item>@string/tapping_inverted_horizontal</item>
|
|
||||||
<item>@string/tapping_inverted_vertical</item>
|
|
||||||
<item>@string/tapping_inverted_both</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="pager_nav">
|
|
||||||
<item>@string/label_default</item>
|
|
||||||
<item>@string/l_nav</item>
|
|
||||||
<item>@string/kindlish_nav</item>
|
|
||||||
<item>@string/edge_nav</item>
|
|
||||||
<item>@string/right_and_left_nav</item>
|
|
||||||
<item>@string/disabled_nav</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="webtoon_nav">
|
|
||||||
<item>@string/label_default</item>
|
|
||||||
<item>@string/l_nav</item>
|
|
||||||
<item>@string/kindlish_nav</item>
|
|
||||||
<item>@string/edge_nav</item>
|
|
||||||
<item>@string/right_and_left_nav</item>
|
|
||||||
<item>@string/disabled_nav</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<declare-styleable name="MaterialSpinnerView">
|
|
||||||
<attr name="title" format="reference|string"/>
|
|
||||||
<attr name="android:entries"/>
|
|
||||||
<attr name="summary" format="reference|string" />
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
</resources>
|
|
@ -1,6 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<dimen name="dialog_radius">8dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="screen_edge_margin">16dp</dimen>
|
<dimen name="screen_edge_margin">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="appwidget_background_radius">16dp</dimen>
|
<dimen name="appwidget_background_radius">16dp</dimen>
|
||||||
|
@ -20,33 +20,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<!--===========-->
|
|
||||||
<!--BottomSheet-->
|
|
||||||
<!--===========-->
|
|
||||||
|
|
||||||
<style name="ThemeOverlay.Tachiyomi.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
|
||||||
<item name="bottomSheetStyle">@style/Widget.Tachiyomi.BottomSheet.Modal</item>
|
|
||||||
<item name="android:windowAnimationStyle">@style/Animation.Tachiyomi.BottomSheetDialog</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.Tachiyomi.BottomSheet.Modal" parent="Widget.Material3.BottomSheet.Modal">
|
|
||||||
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Tachiyomi.BottomSheet</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ShapeAppearanceOverlay.Tachiyomi.BottomSheet" parent="">
|
|
||||||
<item name="cornerFamily">rounded</item>
|
|
||||||
<item name="cornerSizeTopRight">@dimen/dialog_radius</item>
|
|
||||||
<item name="cornerSizeTopLeft">@dimen/dialog_radius</item>
|
|
||||||
<item name="cornerSizeBottomRight">0dp</item>
|
|
||||||
<item name="cornerSizeBottomLeft">0dp</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Animation.Tachiyomi.BottomSheetDialog" parent="Animation.AppCompat.Dialog">
|
|
||||||
<item name="android:windowEnterAnimation">@anim/bottom_sheet_slide_in</item>
|
|
||||||
<item name="android:windowExitAnimation">@anim/bottom_sheet_slide_out</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<!--===============-->
|
<!--===============-->
|
||||||
<!--Text Appearance-->
|
<!--Text Appearance-->
|
||||||
<!--===============-->
|
<!--===============-->
|
||||||
@ -71,18 +44,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<!--=================-->
|
|
||||||
<!--Widgets.TabLayout-->
|
|
||||||
<!--=================-->
|
|
||||||
<style name="Widget.Tachiyomi.TabLayout" parent="Widget.Material3.TabLayout">
|
|
||||||
<item name="tabGravity">center</item>
|
|
||||||
<item name="tabInlineLabel">true</item>
|
|
||||||
<item name="tabMinWidth">75dp</item>
|
|
||||||
<item name="tabMode">scrollable</item>
|
|
||||||
<item name="tabRippleColor">@color/ripple_toolbar_fainter</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<!--==============-->
|
<!--==============-->
|
||||||
<!--Widgets.Switch-->
|
<!--Widgets.Switch-->
|
||||||
<!--==============-->
|
<!--==============-->
|
||||||
@ -94,16 +55,6 @@
|
|||||||
<item name="elevationOverlayEnabled">@bool/elevationOverlayEnabled</item>
|
<item name="elevationOverlayEnabled">@bool/elevationOverlayEnabled</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!--==============-->
|
|
||||||
<!--Widgets.Slider-->
|
|
||||||
<!--==============-->
|
|
||||||
<style name="Widget.Tachiyomi.Slider" parent="Widget.Material3.Slider">
|
|
||||||
<item name="labelBehavior">gone</item>
|
|
||||||
<item name="tickVisible">false</item>
|
|
||||||
<item name="trackColorInactive">@color/slider_inactive_track</item>
|
|
||||||
<item name="trackColorActive">@color/slider_active_track</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<!--============-->
|
<!--============-->
|
||||||
<!--FastScroller-->
|
<!--FastScroller-->
|
||||||
|
@ -64,17 +64,13 @@
|
|||||||
<item name="android:backgroundDimAmount">0.32</item>
|
<item name="android:backgroundDimAmount">0.32</item>
|
||||||
<item name="windowActionModeOverlay">true</item>
|
<item name="windowActionModeOverlay">true</item>
|
||||||
<item name="actionOverflowButtonStyle">@style/Theme.Tachiyomi.ActionButton.Overflow</item>
|
<item name="actionOverflowButtonStyle">@style/Theme.Tachiyomi.ActionButton.Overflow</item>
|
||||||
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
|
|
||||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Tachiyomi</item>
|
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Tachiyomi</item>
|
||||||
<item name="bottomSheetDialogTheme">@style/ThemeOverlay.Tachiyomi.BottomSheetDialog</item>
|
|
||||||
<item name="textInputStyle">@style/Widget.Material3.TextInputLayout.OutlinedBox</item>
|
<item name="textInputStyle">@style/Widget.Material3.TextInputLayout.OutlinedBox</item>
|
||||||
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
|
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
|
||||||
<item name="toolbarStyle">@style/Widget.Material3.Toolbar.Surface</item>
|
<item name="toolbarStyle">@style/Widget.Material3.Toolbar.Surface</item>
|
||||||
<item name="tabStyle">@style/Widget.Tachiyomi.TabLayout</item>
|
|
||||||
<item name="switchStyle">@style/Widget.Tachiyomi.Switch</item>
|
<item name="switchStyle">@style/Widget.Tachiyomi.Switch</item>
|
||||||
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
|
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
|
||||||
<item name="switchPreferenceCompatStyle">@style/Widget.Tachiyomi.Switch</item>
|
<item name="switchPreferenceCompatStyle">@style/Widget.Tachiyomi.Switch</item>
|
||||||
<item name="sliderStyle">@style/Widget.Tachiyomi.Slider</item>
|
|
||||||
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item>
|
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item>
|
||||||
|
|
||||||
<!-- Preference text appearance -->
|
<!-- Preference text appearance -->
|
||||||
|
@ -7,6 +7,7 @@ import tachiyomi.core.util.lang.withIOContext
|
|||||||
/**
|
/**
|
||||||
* Util for evaluating JavaScript in sources.
|
* Util for evaluating JavaScript in sources.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNUSED", "UNCHECKED_CAST")
|
||||||
class JavaScriptEngine(context: Context) {
|
class JavaScriptEngine(context: Context) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,7 +18,6 @@ class JavaScriptEngine(context: Context) {
|
|||||||
* @param script JavaScript to execute.
|
* @param script JavaScript to execute.
|
||||||
* @return Result of JavaScript code as a primitive type.
|
* @return Result of JavaScript code as a primitive type.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNUSED", "UNCHECKED_CAST")
|
|
||||||
suspend fun <T> evaluate(script: String): T = withIOContext {
|
suspend fun <T> evaluate(script: String): T = withIOContext {
|
||||||
QuickJs.create().use {
|
QuickJs.create().use {
|
||||||
it.evaluate(script) as T
|
it.evaluate(script) as T
|
||||||
|
@ -11,65 +11,60 @@ import java.io.File
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NetworkHelper(
|
class NetworkHelper(
|
||||||
context: Context,
|
private val context: Context,
|
||||||
private val preferences: NetworkPreferences,
|
private val preferences: NetworkPreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val cacheDir = File(context.cacheDir, "network_cache")
|
|
||||||
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
|
||||||
|
|
||||||
val cookieJar = AndroidCookieJar()
|
val cookieJar = AndroidCookieJar()
|
||||||
|
|
||||||
private val userAgentInterceptor by lazy {
|
val client: OkHttpClient = run {
|
||||||
UserAgentInterceptor(::defaultUserAgentProvider)
|
val builder = OkHttpClient.Builder()
|
||||||
}
|
.cookieJar(cookieJar)
|
||||||
private val cloudflareInterceptor by lazy {
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
}
|
.callTimeout(2, TimeUnit.MINUTES)
|
||||||
|
.cache(
|
||||||
|
Cache(
|
||||||
|
directory = File(context.cacheDir, "network_cache"),
|
||||||
|
maxSize = 5L * 1024 * 1024, // 5 MiB
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addInterceptor(UncaughtExceptionInterceptor())
|
||||||
|
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
|
||||||
|
|
||||||
private val baseClientBuilder: OkHttpClient.Builder
|
if (preferences.verboseLogging().get()) {
|
||||||
get() {
|
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
val builder = OkHttpClient.Builder()
|
level = HttpLoggingInterceptor.Level.HEADERS
|
||||||
.cookieJar(cookieJar)
|
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.callTimeout(2, TimeUnit.MINUTES)
|
|
||||||
.addInterceptor(UncaughtExceptionInterceptor())
|
|
||||||
.addInterceptor(userAgentInterceptor)
|
|
||||||
|
|
||||||
if (preferences.verboseLogging().get()) {
|
|
||||||
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.HEADERS
|
|
||||||
}
|
|
||||||
builder.addNetworkInterceptor(httpLoggingInterceptor)
|
|
||||||
}
|
}
|
||||||
|
builder.addNetworkInterceptor(httpLoggingInterceptor)
|
||||||
when (preferences.dohProvider().get()) {
|
|
||||||
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
|
||||||
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
|
||||||
PREF_DOH_ADGUARD -> builder.dohAdGuard()
|
|
||||||
PREF_DOH_QUAD9 -> builder.dohQuad9()
|
|
||||||
PREF_DOH_ALIDNS -> builder.dohAliDNS()
|
|
||||||
PREF_DOH_DNSPOD -> builder.dohDNSPod()
|
|
||||||
PREF_DOH_360 -> builder.doh360()
|
|
||||||
PREF_DOH_QUAD101 -> builder.dohQuad101()
|
|
||||||
PREF_DOH_MULLVAD -> builder.dohMullvad()
|
|
||||||
PREF_DOH_CONTROLD -> builder.dohControlD()
|
|
||||||
PREF_DOH_NJALLA -> builder.dohNajalla()
|
|
||||||
PREF_DOH_SHECAN -> builder.dohShecan()
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
|
builder.addInterceptor(CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider))
|
||||||
|
|
||||||
@Suppress("UNUSED")
|
when (preferences.dohProvider().get()) {
|
||||||
val cloudflareClient by lazy {
|
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
||||||
client.newBuilder()
|
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
||||||
.addInterceptor(cloudflareInterceptor)
|
PREF_DOH_ADGUARD -> builder.dohAdGuard()
|
||||||
.build()
|
PREF_DOH_QUAD9 -> builder.dohQuad9()
|
||||||
|
PREF_DOH_ALIDNS -> builder.dohAliDNS()
|
||||||
|
PREF_DOH_DNSPOD -> builder.dohDNSPod()
|
||||||
|
PREF_DOH_360 -> builder.doh360()
|
||||||
|
PREF_DOH_QUAD101 -> builder.dohQuad101()
|
||||||
|
PREF_DOH_MULLVAD -> builder.dohMullvad()
|
||||||
|
PREF_DOH_CONTROLD -> builder.dohControlD()
|
||||||
|
PREF_DOH_NJALLA -> builder.dohNajalla()
|
||||||
|
PREF_DOH_SHECAN -> builder.dohShecan()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Since extension-lib 1.5
|
||||||
|
*/
|
||||||
|
@Deprecated("The regular client handles Cloudflare by default")
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
val cloudflareClient: OkHttpClient = client
|
||||||
|
|
||||||
fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
|
fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
|
||||||
}
|
}
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -388,7 +388,7 @@
|
|||||||
<string name="white_background">White</string>
|
<string name="white_background">White</string>
|
||||||
<string name="gray_background">Gray</string>
|
<string name="gray_background">Gray</string>
|
||||||
<string name="black_background">Black</string>
|
<string name="black_background">Black</string>
|
||||||
<string name="automatic_background">Automatic</string>
|
<string name="automatic_background">Auto</string>
|
||||||
<string name="pref_viewer_type">Default reading mode</string>
|
<string name="pref_viewer_type">Default reading mode</string>
|
||||||
<string name="l_nav">L shaped</string>
|
<string name="l_nav">L shaped</string>
|
||||||
<string name="kindlish_nav">Kindle-ish</string>
|
<string name="kindlish_nav">Kindle-ish</string>
|
||||||
@ -485,7 +485,6 @@
|
|||||||
<string name="action_track">Track</string>
|
<string name="action_track">Track</string>
|
||||||
|
|
||||||
<!-- Browse section -->
|
<!-- Browse section -->
|
||||||
<string name="pref_search_pinned_sources_only">Only search pinned sources in global search</string>
|
|
||||||
<string name="pref_hide_in_library_items">Hide entries already in library</string>
|
<string name="pref_hide_in_library_items">Hide entries already in library</string>
|
||||||
|
|
||||||
<!-- Backup section -->
|
<!-- Backup section -->
|
||||||
@ -660,6 +659,7 @@
|
|||||||
<string name="latest">Latest</string>
|
<string name="latest">Latest</string>
|
||||||
<string name="popular">Popular</string>
|
<string name="popular">Popular</string>
|
||||||
<string name="browse">Browse</string>
|
<string name="browse">Browse</string>
|
||||||
|
<string name="has_results">Has results</string>
|
||||||
<string name="local_source_help_guide">Local source guide</string>
|
<string name="local_source_help_guide">Local source guide</string>
|
||||||
<string name="no_pinned_sources">You have no pinned sources</string>
|
<string name="no_pinned_sources">You have no pinned sources</string>
|
||||||
<string name="chapter_not_found">Chapter not found</string>
|
<string name="chapter_not_found">Chapter not found</string>
|
||||||
|
@ -4,6 +4,8 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.FlowRowScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@ -61,7 +63,10 @@ fun HeadingItem(
|
|||||||
style = MaterialTheme.typography.header,
|
style = MaterialTheme.typography.header,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
.padding(
|
||||||
|
horizontal = SettingsItemsPaddings.Horizontal,
|
||||||
|
vertical = SettingsItemsPaddings.Vertical,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +208,10 @@ fun SelectItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.menuAnchor()
|
.menuAnchor()
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
.padding(
|
||||||
|
horizontal = SettingsItemsPaddings.Horizontal,
|
||||||
|
vertical = SettingsItemsPaddings.Vertical,
|
||||||
|
),
|
||||||
label = { Text(text = label) },
|
label = { Text(text = label) },
|
||||||
value = options[selectedIndex].toString(),
|
value = options[selectedIndex].toString(),
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
@ -259,7 +267,10 @@ fun TriStateItem(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
.padding(
|
||||||
|
horizontal = SettingsItemsPaddings.Horizontal,
|
||||||
|
vertical = SettingsItemsPaddings.Vertical,
|
||||||
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
@ -306,6 +317,26 @@ fun TextItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsFlowRow(
|
||||||
|
@StringRes labelRes: Int,
|
||||||
|
content: @Composable FlowRowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
HeadingItem(labelRes)
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = SettingsItemsPaddings.Horizontal,
|
||||||
|
top = 0.dp,
|
||||||
|
end = SettingsItemsPaddings.Horizontal,
|
||||||
|
bottom = SettingsItemsPaddings.Vertical,
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BaseSettingsItem(
|
private fun BaseSettingsItem(
|
||||||
label: String,
|
label: String,
|
||||||
@ -316,7 +347,10 @@ private fun BaseSettingsItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical),
|
.padding(
|
||||||
|
horizontal = SettingsItemsPaddings.Horizontal,
|
||||||
|
vertical = SettingsItemsPaddings.Vertical,
|
||||||
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package tachiyomi.presentation.core.components.material
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredHeight
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Chip(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f),
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier,
|
||||||
|
shape = CircleShape,
|
||||||
|
color = backgroundColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
onClick = {},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier.clickable(onClick = onClick)
|
||||||
|
.widthIn(min = 56.dp)
|
||||||
|
.requiredHeight(32.dp)
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
ProvideTextStyle(MaterialTheme.typography.bodySmall, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChoiceChip(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
selectedBackgroundColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
selectedContentColor: Color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
if (isSelected) {
|
||||||
|
Chip(modifier, selectedBackgroundColor, selectedContentColor, onClick, content)
|
||||||
|
} else {
|
||||||
|
Chip(modifier, onClick = onClick, content = content)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package tachiyomi.presentation.core.components.material
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
val StartItemShape = RoundedCornerShape(topStartPercent = 100, bottomStartPercent = 100)
|
||||||
|
val MiddleItemShape = RoundedCornerShape(0)
|
||||||
|
val EndItemShape = RoundedCornerShape(topEndPercent = 100, bottomEndPercent = 100)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SegmentedButtons(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
entries: List<String>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onClick: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
entries.mapIndexed { index, label ->
|
||||||
|
val shape = remember(entries, index) {
|
||||||
|
when (index) {
|
||||||
|
0 -> StartItemShape
|
||||||
|
entries.lastIndex -> EndItemShape
|
||||||
|
else -> MiddleItemShape
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == selectedIndex) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = shape,
|
||||||
|
onClick = { onClick(index) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OutlinedButton(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = shape,
|
||||||
|
onClick = { onClick(index) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SegmentedButtonsPreview() {
|
||||||
|
Column {
|
||||||
|
SegmentedButtons(
|
||||||
|
entries = listOf(
|
||||||
|
"Day",
|
||||||
|
"Week",
|
||||||
|
"Month",
|
||||||
|
"Year",
|
||||||
|
),
|
||||||
|
selectedIndex = 1,
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
SegmentedButtons(
|
||||||
|
entries = listOf(
|
||||||
|
"Foo",
|
||||||
|
"Bar",
|
||||||
|
),
|
||||||
|
selectedIndex = 1,
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.wrapContentSize
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.TabPosition
|
import androidx.compose.material3.TabPosition
|
||||||
import androidx.compose.material3.TabRowDefaults
|
import androidx.compose.material3.TabRowDefaults.SecondaryIndicator
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -50,8 +50,8 @@ fun TabIndicator(
|
|||||||
currentTabPosition: TabPosition,
|
currentTabPosition: TabPosition,
|
||||||
currentPageOffsetFraction: Float,
|
currentPageOffsetFraction: Float,
|
||||||
) {
|
) {
|
||||||
TabRowDefaults.Indicator(
|
SecondaryIndicator(
|
||||||
Modifier
|
modifier = Modifier
|
||||||
.tabIndicatorOffset(currentTabPosition, currentPageOffsetFraction)
|
.tabIndicatorOffset(currentTabPosition, currentPageOffsetFraction)
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
|
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
|
||||||
|
Loading…
Reference in New Issue
Block a user