Refactor reader bottom bar to presentation package

(cherry picked from commit 7c012010558d5e7a2ea32a8aaef3c908fdc52799)

# 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:
arkon 2023-10-12 22:42:49 -04:00 committed by Jobobby04
parent 6e3dc97458
commit 7693076c89
5 changed files with 323 additions and 212 deletions

View File

@ -1,4 +1,4 @@
package eu.kanade.presentation.reader package eu.kanade.presentation.reader.appbars
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme

View File

@ -1,4 +1,4 @@
package eu.kanade.presentation.reader package eu.kanade.presentation.reader.appbars
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource

View File

@ -0,0 +1,211 @@
package eu.kanade.presentation.reader.appbars
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
private val animationSpec = tween<IntOffset>(200)
// SY -->
enum class NavBarType {
VerticalRight,
VerticalLeft,
Bottom,
}
@Composable
fun BoxIgnoreLayoutDirection(modifier: Modifier, content: @Composable BoxScope.() -> Unit) {
val layoutDirection = LocalLayoutDirection.current
CompositionLocalProvider(
LocalLayoutDirection provides LayoutDirection.Ltr
) {
Box(modifier) {
CompositionLocalProvider(
LocalLayoutDirection provides layoutDirection
) {
content()
}
}
}
}
// SY <--
@Composable
fun ReaderAppBars(
visible: Boolean,
viewer: Viewer?,
onNextChapter: () -> Unit,
enabledNext: Boolean,
onPreviousChapter: () -> Unit,
enabledPrevious: Boolean,
currentPage: Int,
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
readingMode: ReadingModeType,
onClickReadingMode: () -> Unit,
orientationMode: OrientationType,
onClickOrientationMode: () -> Unit,
cropEnabled: Boolean,
onClickCropBorder: () -> Unit,
onClickSettings: () -> Unit,
// SY -->
navBarType: NavBarType,
currentPageText: String,
enabledButtons: Set<String>,
isHttpSource: Boolean,
dualPageSplitEnabled: Boolean,
doublePages: Boolean,
onClickChapterList: () -> Unit,
onClickWebView: () -> Unit,
onClickShare: () -> Unit,
onClickPageLayout: () -> Unit,
onClickShiftPage: () -> Unit,
) {
val isRtl = viewer is R2LPagerViewer
// SY -->
BoxIgnoreLayoutDirection(
Modifier.fillMaxWidth()
) {
AnimatedVisibility(
visible = visible && navBarType == NavBarType.VerticalLeft,
enter = slideInHorizontally(
initialOffsetX = { -it },
animationSpec = animationSpec,
),
exit = slideOutHorizontally(
targetOffsetX = { -it },
animationSpec = animationSpec,
),
modifier = Modifier.padding(bottom = 48.dp, top = 120.dp)
.align(Alignment.TopStart)
) {
ChapterNavigator(
isRtl = isRtl,
onNextChapter = onNextChapter,
enabledNext = enabledNext,
onPreviousChapter = onPreviousChapter,
enabledPrevious = enabledPrevious,
currentPage = currentPage,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
isVerticalSlider = true,
currentPageText = currentPageText,
)
}
AnimatedVisibility(
visible = visible && navBarType == NavBarType.VerticalRight,
enter = slideInHorizontally(
initialOffsetX = { it },
animationSpec = animationSpec,
),
exit = slideOutHorizontally(
targetOffsetX = { it },
animationSpec = animationSpec,
),
modifier = Modifier.padding(bottom = 48.dp, top = 120.dp)
.align(Alignment.TopEnd)
) {
ChapterNavigator(
isRtl = isRtl,
onNextChapter = onNextChapter,
enabledNext = enabledNext,
onPreviousChapter = onPreviousChapter,
enabledPrevious = enabledPrevious,
currentPage = currentPage,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
isVerticalSlider = true,
currentPageText = currentPageText,
)
}
// SY <--
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween,
) {
Spacer(modifier = Modifier.weight(1f))
AnimatedVisibility(
visible = visible,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = animationSpec,
),
exit = slideOutVertically(
targetOffsetY = { it },
animationSpec = animationSpec,
),
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (navBarType == NavBarType.Bottom) {
ChapterNavigator(
isRtl = isRtl,
onNextChapter = onNextChapter,
enabledNext = enabledNext,
onPreviousChapter = onPreviousChapter,
enabledPrevious = enabledPrevious,
currentPage = currentPage,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
isVerticalSlider = false,
currentPageText = currentPageText,
)
}
BottomReaderBar(
// SY -->
enabledButtons = enabledButtons,
// SY <--
readingMode = readingMode,
onClickReadingMode = onClickReadingMode,
orientationMode = orientationMode,
onClickOrientationMode = onClickOrientationMode,
cropEnabled = cropEnabled,
onClickCropBorder = onClickCropBorder,
onClickSettings = onClickSettings,
// SY -->
isHttpSource = isHttpSource,
dualPageSplitEnabled = dualPageSplitEnabled,
doublePages = doublePages,
onClickChapterList = onClickChapterList,
onClickWebView = onClickWebView,
onClickShare = onClickShare,
onClickPageLayout = onClickPageLayout,
onClickShiftPage = onClickShiftPage
)
}
}
}
}
}

View File

@ -27,21 +27,14 @@ import android.view.WindowManager
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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
@ -69,13 +62,13 @@ import com.google.android.material.transition.platform.MaterialContainerTransfor
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.readingModeType import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.presentation.reader.BottomReaderBar
import eu.kanade.presentation.reader.ChapterListDialog import eu.kanade.presentation.reader.ChapterListDialog
import eu.kanade.presentation.reader.ChapterNavigator
import eu.kanade.presentation.reader.OrientationModeSelectDialog import eu.kanade.presentation.reader.OrientationModeSelectDialog
import eu.kanade.presentation.reader.PageIndicatorText import eu.kanade.presentation.reader.PageIndicatorText
import eu.kanade.presentation.reader.ReaderPageActionsDialog import eu.kanade.presentation.reader.ReaderPageActionsDialog
import eu.kanade.presentation.reader.ReadingModeSelectDialog import eu.kanade.presentation.reader.ReadingModeSelectDialog
import eu.kanade.presentation.reader.appbars.NavBarType
import eu.kanade.presentation.reader.appbars.ReaderAppBars
import eu.kanade.presentation.reader.settings.ReaderSettingsDialog import eu.kanade.presentation.reader.settings.ReaderSettingsDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -98,7 +91,6 @@ 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.PagerConfig import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
@ -458,9 +450,9 @@ class ReaderActivity : BaseActivity() {
margin(top = true, horizontal = true) margin(top = true, horizontal = true)
} }
} }
binding.readerMenuBottom.applyInsetter { binding.dialogRoot.applyInsetter {
type(navigationBars = true) { type(navigationBars = true) {
margin(bottom = true, horizontal = true) margin(vertical = true, horizontal = true)
} }
} }
@ -501,6 +493,112 @@ class ReaderActivity : BaseActivity() {
) )
} }
val cropBorderPaged by readerPreferences.cropBorders().collectAsState()
val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState()
// SY -->
val readingMode = viewModel.getMangaReadingMode()
val isPagerType = ReadingModeType.isPagerType(readingMode)
val isWebtoon = ReadingModeType.WEBTOON.flagValue == readingMode
val cropBorderContinuousVertical by readerPreferences.cropBordersContinuousVertical().collectAsState()
val cropEnabled = if (isPagerType) {
cropBorderPaged
} else if (isWebtoon) {
cropBorderWebtoon
} else {
cropBorderContinuousVertical
}
val readerBottomButtons by readerPreferences.readerBottomButtons().collectAsState()
val dualPageSplitPaged by readerPreferences.dualPageSplitPaged().collectAsState()
val forceHorizontalSeekbar by readerPreferences.forceHorizontalSeekbar().collectAsState()
val landscapeVerticalSeekbar by readerPreferences.landscapeVerticalSeekbar().collectAsState()
val leftHandedVerticalSeekbar by readerPreferences.leftVerticalSeekbar().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
val navBarType = when {
!showVerticalSeekbar -> NavBarType.Bottom
leftHandedVerticalSeekbar -> NavBarType.VerticalLeft
else -> NavBarType.VerticalRight
}
// SY <--
ReaderAppBars(
visible = state.menuVisible,
viewer = state.viewer,
onNextChapter = ::loadNextChapter,
enabledNext = state.viewerChapters?.nextChapter != null,
onPreviousChapter = ::loadPreviousChapter,
enabledPrevious = state.viewerChapters?.prevChapter != null,
currentPage = state.currentPage,
totalPages = state.totalPages,
onSliderValueChange = {
isScrollingThroughPages = true
moveToPageIndex(it)
},
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 -->
currentPageText = state.currentPageText,
navBarType = navBarType,
enabledButtons = readerBottomButtons,
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 <--
)
val onDismissRequest = viewModel::closeDialog val onDismissRequest = viewModel::closeDialog
when (state.dialog) { when (state.dialog) {
is ReaderViewModel.Dialog.Loading -> { is ReaderViewModel.Dialog.Loading -> {
@ -589,141 +687,6 @@ class ReaderActivity : BaseActivity() {
} }
// SY --> // 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(
isRtl = isRtl,
// SY -->
isVerticalSlider = isVertical,
// SY <--
onNextChapter = ::loadNextChapter,
enabledNext = state.viewerChapters?.nextChapter != null,
onPreviousChapter = ::loadPreviousChapter,
enabledPrevious = state.viewerChapters?.prevChapter != null,
currentPage = state.currentPage,
// SY -->
currentPageText = state.currentPageText,
// SY <--
totalPages = state.totalPages,
onSliderValueChange = {
isScrollingThroughPages = true
moveToPageIndex(it)
},
)
}
// Init listeners on bottom menu
binding.readerNavVert.setComposeContent {
sliderContent(true)
}
binding.readerMenuBottom.setComposeContent {
val state by viewModel.state.collectAsState()
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.flagValue == readingMode
val cropEnabled = if (isPagerType) {
cropBorderPaged
} else if (isWebtoon) {
cropBorderWebtoon
} else {
cropBorderContinuousVertical
}
val readerBottomButtons by readerPreferences.readerBottomButtons().collectAsState()
val dualPageSplitPaged by readerPreferences.dualPageSplitPaged().collectAsState()
AnimatedVisibility(
visible = state.menuVisible,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = tween(200),
),
exit = slideOutVertically(
targetOffsetY = { it },
animationSpec = tween(200),
),
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// SY -->
sliderContent(false)
// SY <--
BottomReaderBar(
// SY -->
enabledButtons = readerBottomButtons,
// SY <--
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() initDropdownMenu()
// SY <-- // SY <--
@ -1038,22 +1001,6 @@ class ReaderActivity : BaseActivity() {
showReadingModeToast(viewModel.getMangaReadingMode()) showReadingModeToast(viewModel.getMangaReadingMode())
} }
// SY -->
binding.readerNavVert.isVisible = !readerPreferences.forceHorizontalSeekbar().get() &&
(
(
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && readerPreferences.landscapeVerticalSeekbar().get()
) ||
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
) &&
(viewModel.state.value.viewer is WebtoonViewer || viewModel.state.value.viewer is VerticalPagerViewer)
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
}
// SY <--
supportActionBar?.title = manga.title supportActionBar?.title = manga.title
loadingIndicator = ReaderProgressIndicator(this) loadingIndicator = ReaderProgressIndicator(this)

View File

@ -181,53 +181,6 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/above_guideline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.3"
app:layout_constraintTop_toBottomOf="@id/header"/>
<RelativeLayout
android:id="@+id/seekbar_vert_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@id/below_guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/above_guideline"
tools:ignore="NotSibling">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/reader_nav_vert"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:gravity="center"/>
</RelativeLayout>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/below_guideline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.85"
app:layout_constraintBottom_toTopOf="@id/reader_menu_bottom"/>
<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:minHeight="52dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.compose.ui.platform.ComposeView <androidx.compose.ui.platform.ComposeView