~repos /only-bible-app
git clone
https://pyrossh.dev/repos/only-bible-app.git
The only bible app you will ever need. No ads. No in-app purchases. No distractions.
file:
composeApp/src/commonMain/kotlin/dev/pyrossh/onlyBible/screens/ChapterScreen.kt
package dev.pyrossh.onlyBible.screensimport androidx.compose.animation.AnimatedContentTransitionScopeimport androidx.compose.foundation.layout.Arrangementimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.PaddingValuesimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.lazy.LazyColumnimport androidx.compose.foundation.lazy.LazyListStateimport androidx.compose.foundation.lazy.itemsimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.outlined.MoreVertimport androidx.compose.material.icons.rounded.Searchimport androidx.compose.material3.ButtonDefaults.ContentPaddingimport androidx.compose.material3.Iconimport androidx.compose.material3.IconButtonimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TextButtonimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.collectAsStateimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.runtime.saveable.listSaverimport androidx.compose.runtime.saveable.rememberSaveableimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.input.pointer.pointerInputimport androidx.compose.ui.text.TextStyleimport androidx.compose.ui.text.font.FontWeightimport androidx.compose.ui.unit.dpimport androidx.compose.ui.unit.spimport dev.pyrossh.onlyBible.AppViewModelimport dev.pyrossh.onlyBible.composables.BibleSelectorimport dev.pyrossh.onlyBible.composables.ChapterSelectorimport dev.pyrossh.onlyBible.composables.EmbeddedSearchBarimport dev.pyrossh.onlyBible.composables.TextSettingsBottomSheetimport dev.pyrossh.onlyBible.composables.VerseHeadingimport dev.pyrossh.onlyBible.composables.VerseTextimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.IOimport kotlinx.coroutines.launchimport kotlinx.serialization.Serializableimport utils.LocalNavControllerimport dev.pyrossh.onlyBible.utils.detectSwipeimport utils.getBackwardPairimport utils.getForwardPair
@Serializabledata class ChapterScreenProps( val bookIndex: Int, val chapterIndex: Int, val verseIndex: Int, val dir: String = Dir.Left.name,)
enum class Dir { Left, Right;
fun slideDirection(): AnimatedContentTransitionScope.SlideDirection { return when (this) { Left -> AnimatedContentTransitionScope.SlideDirection.Left Right -> AnimatedContentTransitionScope.SlideDirection.Right } }
fun reverse(): Dir { return if (this == Left) Right else Left }}
@Composablefun ChapterScreen( model: AppViewModel, bookIndex: Int, chapterIndex: Int, verseIndex: Int,) { val scope = rememberCoroutineScope() val navController = LocalNavController.current var isSettingsShown by remember { mutableStateOf(false) } var isSearchShown by remember { mutableStateOf(false) } var chapterSelectorShown by remember { mutableStateOf(false) } var bibleSelectorShown by remember { mutableStateOf(false) } val bookNames by model.bookNames.collectAsState() val verses by model.verses.collectAsState() val state = rememberSaveable(saver = listSaver( save = { model.verseIndex = it.firstVisibleItemIndex listOf(it.firstVisibleItemIndex, it.firstVisibleItemScrollOffset) }, restore = { LazyListState( firstVisibleItemIndex = model.verseIndex, ) } )) { LazyListState( firstVisibleItemIndex = verseIndex, ) } val chapterVerses = verses.filter { it.bookIndex == bookIndex && it.chapterIndex == chapterIndex } LaunchedEffect(Unit) { model.clearSelectedVerses() model.bookIndex = bookIndex model.chapterIndex = chapterIndex model.verseIndex = verseIndex } Scaffold( modifier = Modifier .fillMaxSize(), ) { innerPadding -> if (bibleSelectorShown) { BibleSelector( bible = model.bible, onSelected = {// view.playSoundEffect(SoundEffectConstants.CLICK) bibleSelectorShown = false scope.launch(Dispatchers.IO) { model.loadBible(it) } }, onClose = { bibleSelectorShown = false }, ) } if (chapterSelectorShown) { ChapterSelector( bible = model.bible, bookNames = bookNames, startBookIndex = bookIndex, onClose = { chapterSelectorShown = false }, ) } if (isSearchShown) { EmbeddedSearchBar( modifier = Modifier.padding(horizontal = 16.dp), model = model, onDismiss = { isSearchShown = false }, ) }
if (isSettingsShown) { TextSettingsBottomSheet( model = model, onDismiss = { isSettingsShown = false } ) }
Column( modifier = Modifier .fillMaxSize() .padding(innerPadding) .padding(horizontal = 16.dp) ) { Row( modifier = Modifier .fillMaxWidth(), ) { TextButton( contentPadding = PaddingValues( top = ContentPadding.calculateTopPadding(), //8dp end = 12.dp, bottom = ContentPadding.calculateBottomPadding() ), onClick = {// view.playSoundEffect(SoundEffectConstants.CLICK) chapterSelectorShown = true } ) { Text( text = "${bookNames[bookIndex]} ${chapterIndex + 1}", style = TextStyle( fontSize = 22.sp, fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.onSurface, ) ) } Row( modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.End, ) { IconButton( onClick = {// view.playSoundEffect(SoundEffectConstants.CLICK) model.clearSelectedVerses() isSearchShown = true }, ) { Icon( imageVector = Icons.Rounded.Search, contentDescription = "Search", tint = MaterialTheme.colorScheme.onSurface, ) } TextButton(onClick = {// view.playSoundEffect(SoundEffectConstants.CLICK) bibleSelectorShown = true }) { Text( text = model.bible.shortName(), style = TextStyle( fontSize = 18.sp, fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.onSurface, ), ) } TextButton( onClick = {// view.playSoundEffect(SoundEffectConstants.CLICK) isSettingsShown = true }) { Icon( imageVector = Icons.Outlined.MoreVert, contentDescription = "More", tint = MaterialTheme.colorScheme.onSurface, ) } } }
LazyColumn( state = state, verticalArrangement = Arrangement.spacedBy(16.dp + (model.lineSpacingDelta * 2).dp), modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectSwipe( onSwipeLeft = { val pair = getForwardPair(bookIndex, chapterIndex) navController.navigate( ChapterScreenProps( bookIndex = pair.first, chapterIndex = pair.second, verseIndex = 0, ) ) }, onSwipeRight = { val pair = getBackwardPair(bookIndex, chapterIndex) navController.navigate( ChapterScreenProps( bookIndex = pair.first, chapterIndex = pair.second, verseIndex = 0, dir = Dir.Right.name, ) ) }, ) } ) { items(chapterVerses) { v -> if (v.heading.isNotEmpty()) { VerseHeading( text = v.heading, fontType = model.fontType, fontSizeDelta = model.fontSizeDelta, ) } VerseText( model = model, fontType = model.fontType, fontSizeDelta = model.fontSizeDelta, fontBoldEnabled = model.fontBoldEnabled, verse = v, highlightWord = null, ) } } } }}