diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt b/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt index 57644eeb4..e0beb2a67 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt @@ -95,6 +95,7 @@ import androidx.compose.ui.util.fastForEach import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.app.ActivityOptionsCompat +import androidx.lifecycle.compose.LifecycleResumeEffect import com.programmersbox.uiviews.ChangingSettingsRepository import com.programmersbox.uiviews.GridChoice import com.programmersbox.uiviews.R @@ -496,13 +497,10 @@ fun ManagedActivityResultLauncher.launchCatching( fun HideSystemBarsWhileOnScreen() { val changingSettingsRepository: ChangingSettingsRepository = koinInject() - LifecycleHandle( - onStop = { changingSettingsRepository.showNavBar.tryEmit(true) }, - onDestroy = { changingSettingsRepository.showNavBar.tryEmit(true) }, - onCreate = { changingSettingsRepository.showNavBar.tryEmit(false) }, - onStart = { changingSettingsRepository.showNavBar.tryEmit(false) }, - onResume = { changingSettingsRepository.showNavBar.tryEmit(false) } - ) + LifecycleResumeEffect(Unit) { + changingSettingsRepository.showNavBar.tryEmit(false) + onPauseOrDispose { changingSettingsRepository.showNavBar.tryEmit(true) } + } } @OptIn(ExperimentalMaterial3Api::class) diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt index 0d58d70c4..59d2c9806 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.LibraryBooks +import androidx.compose.material.icons.filled.BorderBottom import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.filled.Gesture @@ -385,6 +386,14 @@ class GenericManga( settingTitle = { Text("Allow User Gestures for Chapter List in Reader") }, settingIcon = { Icon(Icons.Default.Gesture, null, modifier = Modifier.fillMaxSize()) } ) + + var useFloatingBottomBar by mangaSettingsHandling.rememberUseFloatingReaderBottomBar() + SwitchSetting( + value = useFloatingBottomBar, + updateValue = { useFloatingBottomBar = it }, + settingTitle = { Text("Use a Floating Bottom Bar in Reader") }, + settingIcon = { Icon(Icons.Default.BorderBottom, null, modifier = Modifier.fillMaxSize()) } + ) } } diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaSettings.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaSettings.kt index 7f0479a68..dc1333ef4 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaSettings.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaSettings.kt @@ -105,6 +105,13 @@ class MangaSettingsHandling(context: Context) { defaultValue = true ) + @Composable + fun rememberUseFloatingReaderBottomBar() = preferences.rememberPreference( + key = { it.useFloatingReaderBottomBar }, + update = { setUseFloatingReaderBottomBar(it) }, + defaultValue = true + ) + inner class SettingInfo( val flow: Flow, private val updateValue: suspend MangaSettings.Builder.(T) -> MangaSettings.Builder, diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/AppBars.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/AppBars.kt index 7b0cec6ea..3d047586d 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/AppBars.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/AppBars.kt @@ -9,11 +9,22 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandHorizontally import androidx.compose.animation.shrinkHorizontally import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.material.icons.filled.ChevronLeft +import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.GridOn +import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Numbers import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.BottomAppBar @@ -22,10 +33,16 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.FloatingAppBarDefaults +import androidx.compose.material3.FloatingAppBarScrollBehavior +import androidx.compose.material3.HorizontalFloatingAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults @@ -172,6 +189,87 @@ internal fun ReaderTopBar( ) } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +internal fun FloatingBottomBar( + vm: ReadViewModel, + onPageSelectClick: () -> Unit, + onSettingsClick: () -> Unit, + chapterChange: () -> Unit, + onChapterShow: () -> Unit, + modifier: Modifier = Modifier, + exitAlwaysScrollBehavior: FloatingAppBarScrollBehavior? = null, +) { + Box( + modifier = Modifier.fillMaxWidth() + ) { + var showFloatBar by remember { mutableStateOf(false) } + HorizontalFloatingAppBar( + modifier = modifier + .align(Alignment.BottomEnd) + .windowInsetsPadding(WindowInsets.navigationBars) + .offset(y = -FloatingAppBarDefaults.ScreenOffset), + expanded = showFloatBar, + leadingContent = { + val prevShown = vm.currentChapter < vm.list.lastIndex + val nextShown = vm.currentChapter > 0 + + AnimatedVisibility( + visible = prevShown && vm.list.size > 1, + enter = expandHorizontally(expandFrom = Alignment.Start), + exit = shrinkHorizontally(shrinkTowards = Alignment.Start) + ) { + PreviousIconButton( + previousChapter = chapterChange, + vm = vm, + ) + } + + GoBackIconButton() + + AnimatedVisibility( + visible = nextShown && vm.list.size > 1, + enter = expandHorizontally(), + exit = shrinkHorizontally() + ) { + NextIconButton( + nextChapter = chapterChange, + vm = vm, + ) + } + + IconButton( + onClick = onPageSelectClick, + ) { Icon(Icons.Default.GridOn, null) } + + IconButton( + onClick = onChapterShow, + ) { Icon(Icons.Default.Numbers, null) } + }, + trailingContent = { + IconButton( + onClick = onSettingsClick, + //modifier = Modifier.weight(1f) + ) { Icon(Icons.Default.Settings, null) } + }, + scrollBehavior = exitAlwaysScrollBehavior, + content = { + FilledIconButton( + onClick = { showFloatBar = !showFloatBar }, + modifier = Modifier + .width(64.dp) + ) { + Icon( + if (showFloatBar) Icons.Default.ChevronRight else Icons.Default.ChevronLeft, + contentDescription = "Localized description" + ) + } + }, + ) + } +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable internal fun BottomBar( vm: ReadViewModel, @@ -299,3 +397,37 @@ private fun PreviousButton( modifier = modifier ) { Text(stringResource(id = R.string.loadPreviousChapter)) } } + +@Composable +private fun PreviousIconButton( + vm: ReadViewModel, + modifier: Modifier = Modifier, + previousChapter: () -> Unit, +) { + IconButton( + onClick = { vm.addChapterToWatched(++vm.currentChapter, previousChapter) }, + modifier = modifier + ) { Icon(Icons.Default.ArrowBack, null) } +} + +@Composable +private fun GoBackIconButton(modifier: Modifier = Modifier) { + val navController = LocalNavController.current + OutlinedIconButton( + onClick = { navController.popBackStack() }, + modifier = modifier, + border = BorderStroke(ButtonDefaults.outlinedButtonBorder(true).width, MaterialTheme.colorScheme.primary) + ) { Icon(Icons.Default.Home, null) } +} + +@Composable +private fun NextIconButton( + vm: ReadViewModel, + modifier: Modifier = Modifier, + nextChapter: () -> Unit, +) { + FilledIconButton( + onClick = { vm.addChapterToWatched(--vm.currentChapter, nextChapter) }, + modifier = modifier + ) { Icon(Icons.Default.ArrowForward, null) } +} \ No newline at end of file diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReaderCompose.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReaderCompose.kt index 9bd19cfa2..0655b615a 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReaderCompose.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReaderCompose.kt @@ -26,6 +26,8 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.DrawerValue import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FloatingAppBarDefaults +import androidx.compose.material3.FloatingAppBarExitDirection import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalDrawerSheet @@ -44,6 +46,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp @@ -190,6 +193,10 @@ fun ReadView( val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) var showBottomSheet by remember { mutableStateOf(false) } + val floatingBottomBar by mangaSettingsHandling.rememberUseFloatingReaderBottomBar() + + val exitAlwaysScrollBehavior = FloatingAppBarDefaults.exitAlwaysScrollBehavior(exitDirection = FloatingAppBarExitDirection.Bottom) + BackHandler(drawerState.isOpen || showBottomSheet) { scope.launch { when { @@ -274,25 +281,37 @@ fun ReadView( } }, bottomBar = { - AnimatedVisibility( - visible = showItems, - enter = slideInVertically { it } + fadeIn(), - exit = slideOutVertically { it } + fadeOut() - ) { - BottomBar( + if (floatingBottomBar) { + FloatingBottomBar( onPageSelectClick = { showBottomSheet = true }, onSettingsClick = { settingsPopup = true }, chapterChange = ::showToast, onChapterShow = { scope.launch { drawerState.open() } }, vm = readVm, - showBlur = showBlur, - isAmoledMode = isAmoledMode, - modifier = if (showBlur) Modifier.hazeChild(hazeState, style = HazeMaterials.thin()) { - //progressive = HazeProgressive.verticalGradient(startIntensity = 0f, endIntensity = 1f) - } else Modifier + exitAlwaysScrollBehavior = exitAlwaysScrollBehavior ) + } else { + AnimatedVisibility( + visible = showItems, + enter = slideInVertically { it } + fadeIn(), + exit = slideOutVertically { it } + fadeOut() + ) { + BottomBar( + onPageSelectClick = { showBottomSheet = true }, + onSettingsClick = { settingsPopup = true }, + chapterChange = ::showToast, + onChapterShow = { scope.launch { drawerState.open() } }, + vm = readVm, + showBlur = showBlur, + isAmoledMode = isAmoledMode, + modifier = if (showBlur) Modifier.hazeChild(hazeState, style = HazeMaterials.thin()) { + //progressive = HazeProgressive.verticalGradient(startIntensity = 0f, endIntensity = 1f) + } else Modifier, + ) + } } }, + modifier = Modifier.nestedScroll(exitAlwaysScrollBehavior) ) { p -> Box( modifier = if (showBlur) diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/Sheets.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/Sheets.kt index d09750e88..0f0af9ae9 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/Sheets.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/Sheets.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BatteryAlert +import androidx.compose.material.icons.filled.BorderBottom import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.FormatLineSpacing @@ -141,6 +142,14 @@ internal fun SettingsSheet( settingIcon = { Icon(Icons.Default.Gesture, null, modifier = Modifier.fillMaxSize()) } ) + var useFloatingBottomBar by mangaSettingsHandling.rememberUseFloatingReaderBottomBar() + SwitchSetting( + value = useFloatingBottomBar, + updateValue = { useFloatingBottomBar = it }, + settingTitle = { Text("Use a Floating Bottom Bar in Reader") }, + settingIcon = { Icon(Icons.Default.BorderBottom, null, modifier = Modifier.fillMaxSize()) } + ) + HorizontalDivider() var showReaderTypeDropdown by remember { mutableStateOf(false) } diff --git a/mangaworld/src/main/proto/manga_settings.proto b/mangaworld/src/main/proto/manga_settings.proto index 101fa6842..4dba36afb 100644 --- a/mangaworld/src/main/proto/manga_settings.proto +++ b/mangaworld/src/main/proto/manga_settings.proto @@ -12,6 +12,7 @@ message MangaSettings { ImageLoaderType imageLoaderType = 6; bool useFlipPager = 7; bool allowUserDrawerGesture = 8; + bool useFloatingReaderBottomBar = 9; } enum PlayingStartAction {