Migrate bottom reader menu to Compose
(cherry picked from commit 8680accd8e6f458a662dd5454bbcdcde482ce0a7) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt # app/src/main/res/layout/reader_activity.xml
This commit is contained in:
parent
eb8685fa7e
commit
ebb690cbe7
@ -0,0 +1,155 @@
|
||||
package eu.kanade.presentation.reader
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.FormatListNumbered
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
|
||||
@Composable
|
||||
fun BottomReaderBar(
|
||||
// SY -->
|
||||
enabledButtons: Set<String>,
|
||||
// SY <--
|
||||
readingMode: ReadingModeType,
|
||||
onClickReadingMode: () -> Unit,
|
||||
orientationMode: OrientationType,
|
||||
onClickOrientationMode: () -> Unit,
|
||||
cropEnabled: Boolean,
|
||||
onClickCropBorder: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
// SY -->
|
||||
isHttpSource: Boolean,
|
||||
dualPageSplitEnabled: Boolean,
|
||||
doublePages: Boolean,
|
||||
onClickChapterList: () -> Unit,
|
||||
onClickWebView: () -> Unit,
|
||||
onClickShare: () -> Unit,
|
||||
onClickPageLayout: () -> Unit,
|
||||
onClickShiftPage: () -> Unit,
|
||||
// SY <--
|
||||
) {
|
||||
// Match with toolbar background color set in ReaderActivity
|
||||
val backgroundColor = MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevation(3.dp)
|
||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(backgroundColor)
|
||||
.padding(8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// SY -->
|
||||
if (ReaderBottomButton.ViewChapters.isIn(enabledButtons)) {
|
||||
IconButton(onClick = onClickChapterList) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FormatListNumbered,
|
||||
contentDescription = stringResource(R.string.chapters),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (ReaderBottomButton.WebView.isIn(enabledButtons) && isHttpSource) {
|
||||
IconButton(onClick = onClickWebView) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Public,
|
||||
contentDescription = stringResource(R.string.action_open_in_web_view),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (ReaderBottomButton.Share.isIn(enabledButtons) && isHttpSource) {
|
||||
IconButton(onClick = onClickShare) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Share,
|
||||
contentDescription = stringResource(R.string.action_share),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (ReaderBottomButton.ReadingMode.isIn(enabledButtons)) {
|
||||
IconButton(onClick = onClickReadingMode) {
|
||||
Icon(
|
||||
painter = painterResource(readingMode.iconRes),
|
||||
contentDescription = stringResource(R.string.viewer),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val cropBorders = when (readingMode) {
|
||||
ReadingModeType.WEBTOON -> ReaderBottomButton.CropBordersWebtoon
|
||||
ReadingModeType.CONTINUOUS_VERTICAL -> ReaderBottomButton.CropBordersContinuesVertical
|
||||
else -> ReaderBottomButton.CropBordersPager
|
||||
}
|
||||
if (cropBorders.isIn(enabledButtons)) {
|
||||
IconButton(onClick = onClickCropBorder) {
|
||||
Icon(
|
||||
painter = painterResource(if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp),
|
||||
contentDescription = stringResource(R.string.pref_crop_borders),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (ReaderBottomButton.Rotation.isIn(enabledButtons)) {
|
||||
IconButton(onClick = onClickOrientationMode) {
|
||||
Icon(
|
||||
painter = painterResource(orientationMode.iconRes),
|
||||
contentDescription = stringResource(R.string.pref_rotation_type),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!dualPageSplitEnabled &&
|
||||
ReaderBottomButton.PageLayout.isIn(enabledButtons) &&
|
||||
ReadingModeType.isPagerType(readingMode.prefValue)
|
||||
) {
|
||||
IconButton(onClick = onClickPageLayout) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_book_open_variant_24dp),
|
||||
contentDescription = stringResource(R.string.page_layout),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (doublePages) {
|
||||
IconButton(onClick = onClickShiftPage) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_page_next_outline_24dp),
|
||||
contentDescription = stringResource(R.string.shift_double_pages),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
IconButton(onClick = onClickSettings) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = stringResource(R.string.action_settings),
|
||||
)
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package eu.kanade.presentation.reader
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterItem
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import exh.metadata.MetadataUtil
|
||||
import exh.source.isEhBasedManga
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun ChapterListDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
screenModel: ReaderSettingsScreenModel,
|
||||
chapters: List<ReaderChapterItem>,
|
||||
onClickChapter: (Chapter) -> Unit,
|
||||
onBookmark: (Chapter) -> Unit,
|
||||
) {
|
||||
val manga by screenModel.mangaFlow.collectAsState()
|
||||
val context = LocalContext.current
|
||||
val state = rememberLazyListState(chapters.indexOfFirst { it.isCurrent }.coerceAtLeast(0))
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
LazyColumn(
|
||||
state = state,
|
||||
modifier = Modifier.defaultMinSize(minHeight = 200.dp),
|
||||
contentPadding = PaddingValues(vertical = 16.dp),
|
||||
) {
|
||||
items(
|
||||
items = chapters,
|
||||
key = { "chapter-${it.chapter.id}" },
|
||||
) { chapterItem ->
|
||||
MangaChapterListItem(
|
||||
title = chapterItem.chapter.name,
|
||||
date = chapterItem.chapter.dateUpload
|
||||
.takeIf { it > 0L }
|
||||
?.let {
|
||||
// SY -->
|
||||
if (manga?.isEhBasedManga() == true) {
|
||||
MetadataUtil.EX_DATE_FORMAT.format(Date(it))
|
||||
} else {
|
||||
Date(it).toRelativeString(context, chapterItem.dateFormat)
|
||||
}
|
||||
// SY <--
|
||||
},
|
||||
readProgress = null,
|
||||
scanlator = chapterItem.chapter.scanlator,
|
||||
sourceName = null,
|
||||
read = false,
|
||||
bookmark = chapterItem.chapter.bookmark,
|
||||
selected = false,
|
||||
downloadIndicatorEnabled = false,
|
||||
downloadStateProvider = { Download.State.NOT_DOWNLOADED },
|
||||
downloadProgressProvider = { 0 },
|
||||
chapterSwipeStartAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
||||
chapterSwipeEndAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
||||
onLongClick = { /*TODO*/ },
|
||||
onClick = { onClickChapter(chapterItem.chapter) },
|
||||
onDownloadClick = null,
|
||||
onChapterSwipe = {
|
||||
onBookmark(chapterItem.chapter)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -79,6 +79,15 @@ fun ChapterNavigator(
|
||||
val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
// Match with toolbar background color set in ReaderActivity
|
||||
val backgroundColor = MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevation(3.dp)
|
||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||
val buttonColor = IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = backgroundColor,
|
||||
disabledContainerColor = backgroundColor,
|
||||
)
|
||||
|
||||
// We explicitly handle direction based on the reader viewer rather than the system direction
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
Row(
|
||||
@ -87,14 +96,6 @@ fun ChapterNavigator(
|
||||
.padding(horizontal = horizontalPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Match with toolbar background color set in ReaderActivity
|
||||
val backgroundColor = MaterialTheme.colorScheme
|
||||
.surfaceColorAtElevation(3.dp)
|
||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||
val buttonColor = IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = backgroundColor,
|
||||
disabledContainerColor = backgroundColor,
|
||||
)
|
||||
FilledIconButton(
|
||||
enabled = if (isRtl) enabledNext else enabledPrevious,
|
||||
onClick = if (isRtl) onNextChapter else onPreviousChapter,
|
||||
|
@ -31,8 +31,8 @@ import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
||||
import exh.util.dropBlank
|
||||
import exh.util.trimOrNull
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -7,7 +7,7 @@ import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.EditMergedSettingsItemBinding
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
||||
import tachiyomi.domain.manga.model.MergedMangaReference
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -31,6 +31,7 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@ -38,8 +39,11 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.ColorUtils
|
||||
@ -60,6 +64,8 @@ import com.google.android.material.transition.platform.MaterialContainerTransfor
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.manga.model.readingModeType
|
||||
import eu.kanade.presentation.reader.BottomReaderBar
|
||||
import eu.kanade.presentation.reader.ChapterListDialog
|
||||
import eu.kanade.presentation.reader.ChapterNavigator
|
||||
import eu.kanade.presentation.reader.OrientationModeSelectDialog
|
||||
import eu.kanade.presentation.reader.PageIndicatorText
|
||||
@ -71,19 +77,16 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.AddToLibraryFirst
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Error
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Success
|
||||
import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterDialog
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.HttpPageLoader
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
@ -100,9 +103,7 @@ import eu.kanade.tachiyomi.util.system.isLTR
|
||||
import eu.kanade.tachiyomi.util.system.isNightMode
|
||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.copy
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import eu.kanade.tachiyomi.util.view.setTooltip
|
||||
import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
|
||||
import exh.source.isEhBasedSource
|
||||
import exh.util.defaultReaderType
|
||||
@ -126,13 +127,13 @@ import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.Constants
|
||||
import tachiyomi.core.preference.toggle
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.util.lang.withUIContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.math.abs
|
||||
@ -527,8 +528,6 @@ class ReaderActivity : BaseActivity() {
|
||||
if (!readerPreferences.showReadingMode().get()) {
|
||||
menuToggleToast = toast(stringRes)
|
||||
}
|
||||
|
||||
updateCropBordersShortcut()
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -553,6 +552,30 @@ class ReaderActivity : BaseActivity() {
|
||||
hasExtraPage = (state.dialog as? ReaderViewModel.Dialog.PageActions)?.extraPage != null,
|
||||
)
|
||||
}
|
||||
is ReaderViewModel.Dialog.ChapterList -> {
|
||||
var chapters by remember {
|
||||
mutableStateOf(viewModel.getChapters())
|
||||
}
|
||||
ChapterListDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
screenModel = settingsScreenModel,
|
||||
chapters = chapters,
|
||||
onClickChapter = {
|
||||
viewModel.loadNewChapterFromDialog(it)
|
||||
onDismissRequest()
|
||||
},
|
||||
onBookmark = { chapter ->
|
||||
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
|
||||
chapters = chapters.map {
|
||||
if (it.chapter.id == chapter.id) {
|
||||
it.copy(chapter = chapter.copy(bookmark = !chapter.bookmark))
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
@ -560,8 +583,19 @@ class ReaderActivity : BaseActivity() {
|
||||
// SY -->
|
||||
val sliderContent: @Composable (Boolean) -> Unit = a@{ isVertical ->
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
if (state.viewer == null) return@a
|
||||
|
||||
val forceHorizontalSeekbar by readerPreferences.forceHorizontalSeekbar().collectAsState()
|
||||
val landscapeVerticalSeekbar by readerPreferences.landscapeVerticalSeekbar().collectAsState()
|
||||
val configuration = LocalConfiguration.current
|
||||
val verticalSeekbarLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && landscapeVerticalSeekbar
|
||||
val verticalSeekbarHorizontal = configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
val viewerIsVertical = (state.viewer is WebtoonViewer || state.viewer is VerticalPagerViewer)
|
||||
val showVerticalSeekbar = !forceHorizontalSeekbar && (verticalSeekbarLandscape || verticalSeekbarHorizontal) && viewerIsVertical
|
||||
if ((isVertical && !showVerticalSeekbar) || (!isVertical && showVerticalSeekbar)) {
|
||||
return@a
|
||||
}
|
||||
|
||||
val isRtl = state.viewer is R2LPagerViewer
|
||||
|
||||
ChapterNavigator(
|
||||
@ -586,26 +620,103 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
// Init listeners on bottom menu
|
||||
binding.readerNavHorz.setComposeContent {
|
||||
sliderContent(false)
|
||||
}
|
||||
binding.readerNavVert.setComposeContent {
|
||||
sliderContent(true)
|
||||
}
|
||||
|
||||
initBottomShortcuts()
|
||||
binding.readerMenuBottom.setComposeContent {
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
// SY <--
|
||||
updateBottomButtons()
|
||||
if (state.viewer == null) return@setComposeContent
|
||||
|
||||
val cropBorderPaged by readerPreferences.cropBorders().collectAsState()
|
||||
val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState()
|
||||
val cropBorderContinuousVertical by readerPreferences.cropBordersContinuousVertical().collectAsState()
|
||||
val readingMode = viewModel.getMangaReadingMode()
|
||||
val isPagerType = ReadingModeType.isPagerType(readingMode)
|
||||
val isWebtoon = ReadingModeType.WEBTOON.prefValue == readingMode
|
||||
val cropEnabled = if (isPagerType) {
|
||||
cropBorderPaged
|
||||
} else if (isWebtoon) {
|
||||
cropBorderWebtoon
|
||||
} else {
|
||||
cropBorderContinuousVertical
|
||||
}
|
||||
val readerBottomButtons by readerPreferences.readerBottomButtons().collectAsState()
|
||||
val dualPageSplitPaged by readerPreferences.dualPageSplitPaged().collectAsState()
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
// SY -->
|
||||
sliderContent(false)
|
||||
// SY <--
|
||||
|
||||
BottomReaderBar(
|
||||
enabledButtons = readerBottomButtons,
|
||||
readingMode = ReadingModeType.fromPreference(
|
||||
viewModel.getMangaReadingMode(
|
||||
resolveDefault = false,
|
||||
),
|
||||
),
|
||||
onClickReadingMode = viewModel::openReadingModeSelectDialog,
|
||||
orientationMode = OrientationType.fromPreference(
|
||||
viewModel.getMangaOrientationType(
|
||||
resolveDefault = false,
|
||||
),
|
||||
),
|
||||
onClickOrientationMode = viewModel::openOrientationModeSelectDialog,
|
||||
cropEnabled = cropEnabled,
|
||||
onClickCropBorder = {
|
||||
val enabled = viewModel.toggleCropBorders()
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
menuToggleToast = toast(
|
||||
if (enabled) {
|
||||
R.string.on
|
||||
} else {
|
||||
R.string.off
|
||||
},
|
||||
)
|
||||
},
|
||||
onClickSettings = viewModel::openSettingsDialog,
|
||||
// SY -->
|
||||
isHttpSource = remember {
|
||||
viewModel.getSource() != null
|
||||
},
|
||||
dualPageSplitEnabled = dualPageSplitPaged,
|
||||
doublePages = state.doublePages,
|
||||
onClickChapterList = viewModel::openChapterListDialog,
|
||||
onClickWebView = ::openChapterInWebView,
|
||||
onClickShare = {
|
||||
assistUrl?.let {
|
||||
val intent = it.toUri().toShareIntent(this@ReaderActivity, type = "text/plain")
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
|
||||
}
|
||||
},
|
||||
onClickPageLayout = {
|
||||
if (readerPreferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) {
|
||||
(viewModel.state.value.viewer as? PagerViewer)?.config?.let { config ->
|
||||
config.doublePages = !config.doublePages
|
||||
reloadChapters(config.doublePages, true)
|
||||
}
|
||||
} else {
|
||||
readerPreferences.pageLayout().set(1 - readerPreferences.pageLayout().get())
|
||||
}
|
||||
},
|
||||
onClickShiftPage = ::shiftDoublePages,
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
initDropdownMenu()
|
||||
// SY <--
|
||||
|
||||
val toolbarBackground = (binding.toolbar.background as MaterialShapeDrawable).apply {
|
||||
elevation = resources.getDimension(R.dimen.m3_sys_elevation_level2)
|
||||
alpha = if (isNightMode()) 230 else 242 // 90% dark 95% light
|
||||
}
|
||||
binding.toolbarBottom.background = toolbarBackground.copy(this@ReaderActivity)
|
||||
|
||||
val toolbarColor = ColorUtils.setAlphaComponent(
|
||||
toolbarBackground.resolvedTintColor,
|
||||
toolbarBackground.alpha,
|
||||
@ -628,125 +739,6 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
// EXH -->
|
||||
fun initBottomShortcuts() {
|
||||
// Reading mode
|
||||
with(binding.actionReadingMode) {
|
||||
setTooltip(R.string.viewer)
|
||||
|
||||
setOnClickListener {
|
||||
viewModel.openReadingModeSelectDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// Crop borders
|
||||
with(binding.actionCropBorders) {
|
||||
setTooltip(R.string.pref_crop_borders)
|
||||
|
||||
setOnClickListener {
|
||||
// SY -->
|
||||
val mangaViewer = viewModel.getMangaReadingMode()
|
||||
// SY <--
|
||||
val isPagerType = ReadingModeType.isPagerType(mangaViewer)
|
||||
val enabled = if (isPagerType) {
|
||||
readerPreferences.cropBorders().toggle()
|
||||
} else {
|
||||
// SY -->
|
||||
if (ReadingModeType.fromPreference(mangaViewer) == ReadingModeType.CONTINUOUS_VERTICAL) {
|
||||
readerPreferences.cropBordersContinuousVertical().toggle()
|
||||
} else {
|
||||
readerPreferences.cropBordersWebtoon().toggle()
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
menuToggleToast = toast(
|
||||
if (enabled) {
|
||||
R.string.on
|
||||
} else {
|
||||
R.string.off
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
updateCropBordersShortcut()
|
||||
listOf(readerPreferences.cropBorders(), readerPreferences.cropBordersWebtoon() /* SY --> */, readerPreferences.cropBordersContinuousVertical()/* SY <-- */)
|
||||
.forEach { pref ->
|
||||
pref.changes()
|
||||
.onEach { updateCropBordersShortcut() }
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
// Rotation
|
||||
with(binding.actionRotation) {
|
||||
setTooltip(R.string.rotation_type)
|
||||
|
||||
setOnClickListener {
|
||||
viewModel.openOrientationModeSelectDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// Settings sheet
|
||||
with(binding.actionSettings) {
|
||||
setTooltip(R.string.action_settings)
|
||||
|
||||
setOnClickListener {
|
||||
viewModel.openSettingsDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// --> EH
|
||||
with(binding.actionWebView) {
|
||||
setTooltip(R.string.action_open_in_web_view)
|
||||
|
||||
setOnClickListener {
|
||||
openChapterInWebView()
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.actionChapterList) {
|
||||
setTooltip(R.string.chapters)
|
||||
|
||||
setOnClickListener {
|
||||
ReaderChapterDialog(this@ReaderActivity)
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.actionShare) {
|
||||
setTooltip(R.string.action_share)
|
||||
|
||||
setOnClickListener {
|
||||
assistUrl?.let {
|
||||
val intent = it.toUri().toShareIntent(this@ReaderActivity, type = "text/plain")
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.doublePage) {
|
||||
setTooltip(R.string.page_layout)
|
||||
|
||||
setOnClickListener {
|
||||
if (readerPreferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) {
|
||||
(viewModel.state.value.viewer as? PagerViewer)?.config?.let { config ->
|
||||
config.doublePages = !config.doublePages
|
||||
reloadChapters(config.doublePages, true)
|
||||
}
|
||||
updateBottomButtons()
|
||||
} else {
|
||||
readerPreferences.pageLayout().set(1 - readerPreferences.pageLayout().get())
|
||||
}
|
||||
}
|
||||
}
|
||||
with(binding.shiftPageButton) {
|
||||
setTooltip(R.string.shift_double_pages)
|
||||
|
||||
setOnClickListener {
|
||||
shiftDoublePages()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initDropdownMenu() {
|
||||
binding.expandEhButton.setOnClickListener {
|
||||
val newValue = !viewModel.state.value.ehUtilsVisible
|
||||
@ -904,36 +896,6 @@ class ReaderActivity : BaseActivity() {
|
||||
return currentPage?.let { viewModel.state.value.viewerChapters?.currChapter?.pages?.getOrNull(it) }
|
||||
}
|
||||
|
||||
fun updateBottomButtons() {
|
||||
val viewer = viewModel.state.value.viewer
|
||||
val enabledButtons = readerPreferences.readerBottomButtons().get()
|
||||
with(binding) {
|
||||
actionReadingMode.isVisible = ReaderBottomButton.ReadingMode.isIn(enabledButtons)
|
||||
actionRotation.isVisible =
|
||||
ReaderBottomButton.Rotation.isIn(enabledButtons)
|
||||
doublePage.isVisible =
|
||||
viewer is PagerViewer && ReaderBottomButton.PageLayout.isIn(enabledButtons) && !readerPreferences.dualPageSplitPaged().get()
|
||||
actionCropBorders.isVisible =
|
||||
if (viewer is PagerViewer) {
|
||||
ReaderBottomButton.CropBordersPager.isIn(enabledButtons)
|
||||
} else {
|
||||
val continuous = (viewer as? WebtoonViewer)?.isContinuous ?: false
|
||||
if (continuous) {
|
||||
ReaderBottomButton.CropBordersWebtoon.isIn(enabledButtons)
|
||||
} else {
|
||||
ReaderBottomButton.CropBordersContinuesVertical.isIn(enabledButtons)
|
||||
}
|
||||
}
|
||||
actionWebView.isVisible =
|
||||
ReaderBottomButton.WebView.isIn(enabledButtons) && viewModel.getSource() is HttpSource
|
||||
actionShare.isVisible =
|
||||
ReaderBottomButton.Share.isIn(enabledButtons) && viewModel.getSource() is HttpSource
|
||||
actionChapterList.isVisible =
|
||||
ReaderBottomButton.ViewChapters.isIn(enabledButtons)
|
||||
shiftPageButton.isVisible = (viewer as? PagerViewer)?.config?.doublePages ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
|
||||
val viewer = viewModel.state.value.viewer as? PagerViewer ?: return
|
||||
viewer.updateShifting()
|
||||
@ -941,6 +903,7 @@ class ReaderActivity : BaseActivity() {
|
||||
setDoublePageMode(viewer)
|
||||
} else {
|
||||
viewer.config.doublePages = doublePages
|
||||
viewModel.setDoublePages(viewer.config.doublePages)
|
||||
}
|
||||
val currentChapter = viewModel.getCurrentChapter()
|
||||
if (doublePages) {
|
||||
@ -958,6 +921,7 @@ class ReaderActivity : BaseActivity() {
|
||||
private fun setDoublePageMode(viewer: PagerViewer) {
|
||||
val currentOrientation = resources.configuration.orientation
|
||||
viewer.config.doublePages = currentOrientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
viewModel.setDoublePages(viewer.config.doublePages)
|
||||
}
|
||||
|
||||
private fun shiftDoublePages() {
|
||||
@ -973,35 +937,6 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
// EXH <--
|
||||
|
||||
private fun updateOrientationShortcut(preference: Int) {
|
||||
val orientation = OrientationType.fromPreference(preference)
|
||||
binding.actionRotation.setImageResource(orientation.iconRes)
|
||||
}
|
||||
|
||||
private fun updateCropBordersShortcut() {
|
||||
val mangaViewer = viewModel.getMangaReadingMode()
|
||||
val isPagerType = ReadingModeType.isPagerType(mangaViewer)
|
||||
val enabled = if (isPagerType) {
|
||||
readerPreferences.cropBorders().get()
|
||||
} else {
|
||||
// SY -->
|
||||
if (ReadingModeType.fromPreference(mangaViewer) == ReadingModeType.CONTINUOUS_VERTICAL) {
|
||||
readerPreferences.cropBordersContinuousVertical().get()
|
||||
} else {
|
||||
readerPreferences.cropBordersWebtoon().get()
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
binding.actionCropBorders.setImageResource(
|
||||
if (enabled) {
|
||||
R.drawable.ic_crop_24dp
|
||||
} else {
|
||||
R.drawable.ic_crop_off_24dp
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the menu according to [visible] and with an optional parameter to
|
||||
* [animate] the views.
|
||||
@ -1088,13 +1023,8 @@ class ReaderActivity : BaseActivity() {
|
||||
*/
|
||||
private fun setManga(manga: Manga) {
|
||||
val prevViewer = viewModel.state.value.viewer
|
||||
|
||||
val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false))
|
||||
binding.actionReadingMode.setImageResource(viewerMode.iconRes)
|
||||
|
||||
val newViewer = ReadingModeType.toViewer(viewModel.getMangaReadingMode(), this)
|
||||
|
||||
updateCropBordersShortcut()
|
||||
if (window.sharedElementEnterTransition is MaterialContainerTransform) {
|
||||
// Wait until transition is complete to avoid crash on API 26
|
||||
window.sharedElementEnterTransition.doOnEnd {
|
||||
@ -1131,11 +1061,7 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
// SY -->
|
||||
|
||||
// --> Vertical seekbar hide on landscape
|
||||
|
||||
if (
|
||||
!readerPreferences.forceHorizontalSeekbar().get() &&
|
||||
binding.readerNavVert.isVisible = !readerPreferences.forceHorizontalSeekbar().get() &&
|
||||
(
|
||||
(
|
||||
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && readerPreferences.landscapeVerticalSeekbar().get()
|
||||
@ -1143,27 +1069,12 @@ class ReaderActivity : BaseActivity() {
|
||||
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
) &&
|
||||
(viewModel.state.value.viewer is WebtoonViewer || viewModel.state.value.viewer is VerticalPagerViewer)
|
||||
) {
|
||||
binding.readerNavVert.isVisible = true
|
||||
binding.readerNavHorz.isVisible = false
|
||||
} else {
|
||||
binding.readerNavVert.isVisible = false
|
||||
binding.readerNavHorz.isVisible = true
|
||||
}
|
||||
|
||||
// <-- Vertical seekbar hide on landscape
|
||||
|
||||
// --> Left-handed vertical seekbar
|
||||
|
||||
val params = binding.readerNavVert.layoutParams as RelativeLayout.LayoutParams
|
||||
if (readerPreferences.leftVerticalSeekbar().get() && binding.readerNavVert.isVisible) {
|
||||
params.removeRule(RelativeLayout.ALIGN_PARENT_END)
|
||||
binding.readerNavVert.layoutParams = params
|
||||
}
|
||||
|
||||
// <-- Left-handed vertical seekbar
|
||||
|
||||
updateBottomButtons()
|
||||
// SY <--
|
||||
supportActionBar?.title = manga.title
|
||||
|
||||
@ -1416,7 +1327,6 @@ class ReaderActivity : BaseActivity() {
|
||||
if (newOrientation.flag != requestedOrientation) {
|
||||
requestedOrientation = newOrientation.flag
|
||||
}
|
||||
updateOrientationShortcut(viewModel.getMangaOrientationType(resolveDefault = false))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1524,14 +1434,20 @@ class ReaderActivity : BaseActivity() {
|
||||
// SY -->
|
||||
readerPreferences.pageLayout().changes()
|
||||
.drop(1)
|
||||
.onEach { updateBottomButtons() }
|
||||
.onEach {
|
||||
viewModel.setDoublePages(
|
||||
(viewModel.state.value.viewer as? PagerViewer)
|
||||
?.config
|
||||
?.doublePages
|
||||
?: false,
|
||||
)
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
readerPreferences.dualPageSplitPaged().changes()
|
||||
.drop(1)
|
||||
.onEach {
|
||||
if (viewModel.state.value.viewer !is PagerViewer) return@onEach
|
||||
updateBottomButtons()
|
||||
reloadChapters(
|
||||
!it && when (readerPreferences.pageLayout().get()) {
|
||||
PagerConfig.PageLayout.DOUBLE_PAGES -> true
|
||||
|
@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.reader
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.compose.runtime.Immutable
|
||||
@ -71,6 +70,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.preference.toggle
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
@ -97,8 +97,6 @@ import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
@ -333,22 +331,15 @@ class ReaderViewModel(
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun getChapters(context: Context): List<ReaderChapterItem> {
|
||||
fun getChapters(): List<ReaderChapterItem> {
|
||||
val currentChapter = getCurrentChapter()
|
||||
val decimalFormat = DecimalFormat(
|
||||
"#.###",
|
||||
DecimalFormatSymbols()
|
||||
.apply { decimalSeparator = '.' },
|
||||
)
|
||||
|
||||
return chapterList.map {
|
||||
ReaderChapterItem(
|
||||
it.chapter.toDomainChapter()!!,
|
||||
manga!!,
|
||||
it.chapter.id == currentChapter?.chapter?.id,
|
||||
context,
|
||||
UiPreferences.dateFormat(uiPreferences.dateFormat().get()),
|
||||
decimalFormat,
|
||||
chapter = it.chapter.toDomainChapter()!!,
|
||||
manga = manga!!,
|
||||
isCurrent = it.chapter.id == currentChapter?.chapter?.id,
|
||||
dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -411,9 +402,11 @@ class ReaderViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadNewChapterFromDialog(chapter: Chapter) {
|
||||
val newChapter = chapterList.firstOrNull { it.chapter.id == chapter.id } ?: return
|
||||
loadAdjacent(newChapter)
|
||||
fun loadNewChapterFromDialog(chapter: Chapter) {
|
||||
viewModelScope.launchIO {
|
||||
val newChapter = chapterList.firstOrNull { it.chapter.id == chapter.id } ?: return@launchIO
|
||||
loadAdjacent(newChapter)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -716,7 +709,7 @@ class ReaderViewModel(
|
||||
viewModelScope.launchNonCancellable {
|
||||
updateChapter.await(
|
||||
ChapterUpdate(
|
||||
id = chapter.id!!.toLong(),
|
||||
id = chapterId,
|
||||
bookmark = bookmarked,
|
||||
),
|
||||
)
|
||||
@ -804,6 +797,21 @@ class ReaderViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun toggleCropBorders(): Boolean {
|
||||
val readingMode = getMangaReadingMode()
|
||||
val isPagerType = ReadingModeType.isPagerType(readingMode)
|
||||
val isWebtoon = ReadingModeType.WEBTOON.prefValue == readingMode
|
||||
return if (isPagerType) {
|
||||
readerPreferences.cropBorders().toggle()
|
||||
} else if (isWebtoon) {
|
||||
readerPreferences.cropBordersWebtoon().toggle()
|
||||
} else {
|
||||
readerPreferences.cropBordersContinuousVertical().toggle()
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Generate a filename for the given [manga] and [page]
|
||||
*/
|
||||
@ -834,6 +842,14 @@ class ReaderViewModel(
|
||||
fun setIndexPageToShift(index: Int?) {
|
||||
mutableState.update { it.copy(indexPageToShift = index) }
|
||||
}
|
||||
|
||||
fun openChapterListDialog() {
|
||||
mutableState.update { it.copy(dialog = Dialog.ChapterList) }
|
||||
}
|
||||
|
||||
fun setDoublePages(doublePages: Boolean) {
|
||||
mutableState.update { it.copy(doublePages = doublePages) }
|
||||
}
|
||||
// SY <--
|
||||
|
||||
fun showLoadingDialog() {
|
||||
@ -1158,6 +1174,7 @@ class ReaderViewModel(
|
||||
val lastShiftDoubleState: Boolean? = null,
|
||||
val indexPageToShift: Int? = null,
|
||||
val indexChapterToShift: Long? = null,
|
||||
val doublePages: Boolean = false,
|
||||
// SY <--
|
||||
) {
|
||||
val totalPages: Int
|
||||
@ -1169,6 +1186,11 @@ class ReaderViewModel(
|
||||
data object Settings : Dialog
|
||||
data object ReadingModeSelect : Dialog
|
||||
data object OrientationModeSelect : Dialog
|
||||
|
||||
// SY -->
|
||||
data object ChapterList : Dialog
|
||||
// SY <--
|
||||
|
||||
data class PageActions(val page: ReaderPage/* SY --> */, val extraPage: ReaderPage? = null /* SY <-- */) : Dialog
|
||||
}
|
||||
|
||||
|
@ -1,21 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.chapter
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
|
||||
class ReaderChapterAdapter(
|
||||
dialog: OnBookmarkClickListener,
|
||||
) : FlexibleAdapter<ReaderChapterItem>(null, dialog, true) {
|
||||
|
||||
/**
|
||||
* Listener for browse item clicks.
|
||||
*/
|
||||
val clickListener: OnBookmarkClickListener = dialog
|
||||
|
||||
/**
|
||||
* Listener which should be called when user clicks the download icons.
|
||||
*/
|
||||
interface OnBookmarkClickListener {
|
||||
fun bookmarkChapter(chapter: Chapter)
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.chapter
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.ReaderChaptersDialogBinding
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.getChapterSort
|
||||
|
||||
class ReaderChapterDialog(private val activity: ReaderActivity) : ReaderChapterAdapter.OnBookmarkClickListener {
|
||||
private val binding = ReaderChaptersDialogBinding.inflate(activity.layoutInflater)
|
||||
|
||||
var viewModel: ReaderViewModel = activity.viewModel
|
||||
var adapter: FlexibleAdapter<ReaderChapterItem>? = null
|
||||
var dialog: AlertDialog
|
||||
|
||||
init {
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.chapters)
|
||||
.setView(binding.root)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setOnDismissListener { destroy() }
|
||||
.create()
|
||||
|
||||
adapter = ReaderChapterAdapter(this@ReaderChapterDialog)
|
||||
binding.chapterRecycler.adapter = adapter
|
||||
|
||||
adapter?.mItemClickListener = FlexibleAdapter.OnItemClickListener { _, position ->
|
||||
val item = adapter?.getItem(position)
|
||||
if (item != null && item.chapter.id != viewModel.getCurrentChapter()?.chapter?.id) {
|
||||
dialog.dismiss()
|
||||
activity.lifecycleScope.launch {
|
||||
viewModel.loadNewChapterFromDialog(item.chapter)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
binding.chapterRecycler.layoutManager = LinearLayoutManager(activity)
|
||||
activity.lifecycleScope.launch {
|
||||
refreshList()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun refreshList(scroll: Boolean = true) {
|
||||
val chapterSort = getChapterSort(viewModel.manga!!)
|
||||
val chapters = viewModel.getChapters(activity)
|
||||
.sortedWith { a, b ->
|
||||
chapterSort(a.chapter, b.chapter)
|
||||
}
|
||||
|
||||
adapter?.clear()
|
||||
adapter?.updateDataSet(chapters)
|
||||
|
||||
if (scroll) {
|
||||
(binding.chapterRecycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
adapter?.getGlobalPositionOf(chapters.find { it.isCurrent }) ?: 0,
|
||||
(binding.chapterRecycler.height / 2).dpToPx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
adapter = null
|
||||
}
|
||||
|
||||
override fun bookmarkChapter(chapter: Chapter) {
|
||||
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
|
||||
refreshList(scroll = false)
|
||||
}
|
||||
}
|
@ -1,133 +1,12 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.chapter
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.ReaderChapterItemBinding
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import exh.source.isEhBasedManga
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import java.text.DateFormat
|
||||
import java.text.DecimalFormat
|
||||
import java.util.Date
|
||||
|
||||
class ReaderChapterItem(val chapter: Chapter, val manga: Manga, val isCurrent: Boolean, context: Context, val dateFormat: DateFormat, val decimalFormat: DecimalFormat) :
|
||||
AbstractFlexibleItem<ReaderChapterItem.ViewHolder>() {
|
||||
|
||||
val readColor = context.getResourceColor(R.attr.colorOnSurface, 0.38f)
|
||||
val unreadColor = context.getResourceColor(R.attr.colorOnSurface)
|
||||
val bookmarkedColor = context.getResourceColor(R.attr.colorAccent)
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.reader_chapter_item
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): ViewHolder {
|
||||
return ViewHolder(view, adapter as ReaderChapterAdapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: ViewHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?,
|
||||
) {
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ReaderChapterItem
|
||||
|
||||
if (chapter.id != other.chapter.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return chapter.id.hashCode()
|
||||
}
|
||||
|
||||
inner class ViewHolder(view: View, private val adapter: ReaderChapterAdapter) : FlexibleViewHolder(view, adapter) {
|
||||
val binding = ReaderChapterItemBinding.bind(itemView)
|
||||
|
||||
fun bind(item: ReaderChapterItem) {
|
||||
val manga = item.manga
|
||||
val chapter = item.chapter
|
||||
|
||||
binding.chapterTitle.text = when (manga.displayMode) {
|
||||
Manga.CHAPTER_DISPLAY_NUMBER -> {
|
||||
val number = item.decimalFormat.format(chapter.chapterNumber.toDouble())
|
||||
itemView.context.getString(R.string.display_mode_chapter, number)
|
||||
}
|
||||
else -> chapter.name
|
||||
}
|
||||
|
||||
// Set correct text color
|
||||
val chapterColor = when {
|
||||
chapter.read -> item.readColor
|
||||
chapter.bookmark -> item.bookmarkedColor
|
||||
else -> item.unreadColor
|
||||
}
|
||||
binding.chapterTitle.setTextColor(chapterColor)
|
||||
binding.chapterScanlator.setTextColor(chapterColor)
|
||||
|
||||
// bookmarkImage.isVisible = item.bookmark
|
||||
|
||||
val descriptions = mutableListOf<CharSequence>()
|
||||
|
||||
if (chapter.dateUpload > 0) {
|
||||
descriptions.add(item.dateFormat.format(Date(chapter.dateUpload)))
|
||||
}
|
||||
if (!chapter.scanlator.isNullOrBlank() && !manga.isEhBasedManga()) {
|
||||
descriptions.add(chapter.scanlator!!)
|
||||
}
|
||||
|
||||
if (descriptions.isNotEmpty()) {
|
||||
binding.chapterScanlator.text = descriptions.joinTo(SpannableStringBuilder(), " • ")
|
||||
} else {
|
||||
binding.chapterScanlator.text = ""
|
||||
}
|
||||
|
||||
if (chapter.bookmark) {
|
||||
binding.bookmarkImage.setVectorCompat(R.drawable.ic_bookmark_24dp, R.attr.colorAccent)
|
||||
} else {
|
||||
binding.bookmarkImage.setVectorCompat(R.drawable.ic_bookmark_border_24dp, R.attr.colorOnSurface)
|
||||
}
|
||||
|
||||
if (item.isCurrent) {
|
||||
binding.chapterTitle.setTypeface(null, Typeface.BOLD_ITALIC)
|
||||
binding.chapterScanlator.setTypeface(null, Typeface.BOLD_ITALIC)
|
||||
} else {
|
||||
binding.chapterTitle.setTypeface(null, Typeface.NORMAL)
|
||||
binding.chapterScanlator.setTypeface(null, Typeface.NORMAL)
|
||||
}
|
||||
binding.bookmarkLayout.setOnClickListener {
|
||||
adapter.clickListener.bookmarkChapter(chapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ImageView.setVectorCompat(@DrawableRes drawable: Int, @AttrRes tint: Int? = null) {
|
||||
val vector = AppCompatResources.getDrawable(context, drawable)
|
||||
if (tint != null) {
|
||||
vector?.mutate()
|
||||
vector?.setTint(context.getResourceColor(tint))
|
||||
}
|
||||
setImageDrawable(vector)
|
||||
}
|
||||
data class ReaderChapterItem(
|
||||
val chapter: Chapter,
|
||||
val manga: Manga,
|
||||
val isCurrent: Boolean,
|
||||
val dateFormat: DateFormat,
|
||||
)
|
||||
|
@ -2,10 +2,8 @@
|
||||
|
||||
package eu.kanade.tachiyomi.util.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@ -13,9 +11,7 @@ import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -24,7 +20,6 @@ import androidx.compose.runtime.CompositionContext
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@ -60,24 +55,6 @@ fun ComposeView.setComposeContent(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tooltip shown on long press.
|
||||
*
|
||||
* @param stringRes String resource for tooltip.
|
||||
*/
|
||||
inline fun View.setTooltip(@StringRes stringRes: Int) {
|
||||
setTooltip(context.getString(stringRes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tooltip shown on long press.
|
||||
*
|
||||
* @param text Text for tooltip.
|
||||
*/
|
||||
inline fun View.setTooltip(text: String) {
|
||||
TooltipCompat.setTooltipText(this, text)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a popup menu on top of this view.
|
||||
*
|
||||
@ -105,17 +82,6 @@ inline fun View.popupMenu(
|
||||
return popup
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep copy of the provided [Drawable]
|
||||
*/
|
||||
inline fun <reified T : Drawable> T.copy(context: Context): T? {
|
||||
return (constantState?.newDrawable()?.mutate() as? T).apply {
|
||||
if (this is MaterialShapeDrawable) {
|
||||
initializeElevationOverlay(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun View?.isVisibleOnScreen(): Boolean {
|
||||
if (this == null) {
|
||||
return false
|
||||
|
@ -1,13 +1,19 @@
|
||||
package exh.ui.metadata.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.alpha
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import eu.kanade.tachiyomi.source.R
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import exh.util.SourceTagsUtil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -56,4 +62,24 @@ object MetadataUIUtil {
|
||||
setCompoundDrawables(this, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color for the given attribute.
|
||||
*
|
||||
* @param resource the attribute.
|
||||
* @param alphaFactor the alpha number [0,1].
|
||||
*/
|
||||
@ColorInt
|
||||
fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int {
|
||||
val typedArray = obtainStyledAttributes(intArrayOf(resource))
|
||||
val color = typedArray.getColor(0, 0)
|
||||
typedArray.recycle()
|
||||
|
||||
if (alphaFactor < 1f) {
|
||||
val alpha = (color.alpha * alphaFactor).roundToInt()
|
||||
return Color.argb(alpha, color.red, color.green, color.blue)
|
||||
}
|
||||
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
@ -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="#000"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98 0,-0.34 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.09,-0.16 -0.26,-0.25 -0.44,-0.25 -0.06,0 -0.12,0.01 -0.17,0.03l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.06,-0.02 -0.12,-0.03 -0.18,-0.03 -0.17,0 -0.34,0.09 -0.43,0.25l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98 0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.09,0.16 0.26,0.25 0.44,0.25 0.06,0 0.12,-0.01 0.17,-0.03l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.06,0.02 0.12,0.03 0.18,0.03 0.17,0 0.34,-0.09 0.43,-0.25l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM17.45,11.27c0.04,0.31 0.05,0.52 0.05,0.73 0,0.21 -0.02,0.43 -0.05,0.73l-0.14,1.13 0.89,0.7 1.08,0.84 -0.7,1.21 -1.27,-0.51 -1.04,-0.42 -0.9,0.68c-0.43,0.32 -0.84,0.56 -1.25,0.73l-1.06,0.43 -0.16,1.13 -0.2,1.35h-1.4l-0.19,-1.35 -0.16,-1.13 -1.06,-0.43c-0.43,-0.18 -0.83,-0.41 -1.23,-0.71l-0.91,-0.7 -1.06,0.43 -1.27,0.51 -0.7,-1.21 1.08,-0.84 0.89,-0.7 -0.14,-1.13c-0.03,-0.31 -0.05,-0.54 -0.05,-0.74s0.02,-0.43 0.05,-0.73l0.14,-1.13 -0.89,-0.7 -1.08,-0.84 0.7,-1.21 1.27,0.51 1.04,0.42 0.9,-0.68c0.43,-0.32 0.84,-0.56 1.25,-0.73l1.06,-0.43 0.16,-1.13 0.2,-1.35h1.39l0.19,1.35 0.16,1.13 1.06,0.43c0.43,0.18 0.83,0.41 1.23,0.71l0.91,0.7 1.06,-0.43 1.27,-0.51 0.7,1.21 -1.07,0.85 -0.89,0.7 0.14,1.13zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
|
||||
</vector>
|
@ -218,133 +218,15 @@
|
||||
app:layout_constraintGuide_percent="0.85"
|
||||
app:layout_constraintBottom_toTopOf="@id/reader_menu_bottom"/>
|
||||
|
||||
<LinearLayout
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/reader_menu_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:minHeight="52dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/reader_nav_horz"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layoutDirection="ltr" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/toolbar_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_gravity="bottom"
|
||||
android:clickable="true"
|
||||
tools:ignore="KeyboardInaccessibleWidget">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_chapter_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/chapters"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:srcCompat="@drawable/ic_format_list_numbered_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_web_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_open_in_web_view"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_webview_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_share"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_share_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_reading_mode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/viewer"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_reader_default_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_crop_borders"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/pref_crop_borders"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_crop_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_rotation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/pref_rotation_type"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_screen_rotation_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/double_page"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/page_layout"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_book_open_variant_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shift_page_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/shift_double_pages"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_page_next_outline_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_settings"
|
||||
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"
|
||||
android:layout_weight="1"
|
||||
app:srcCompat="@drawable/ic_settings_24dp"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user