~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/AppViewModel.kt
package dev.pyrossh.onlyBible
import androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableIntStateOfimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.setValueimport androidx.lifecycle.ViewModelimport androidx.lifecycle.viewModelScopeimport com.russhwolf.settings.Settingsimport dev.pyrossh.onlyBible.domain.Bibleimport dev.pyrossh.onlyBible.domain.Verseimport dev.pyrossh.onlyBible.domain.biblesimport dev.pyrossh.onlyBible.resources.Resimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.FlowPreviewimport kotlinx.coroutines.IOimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.SharingStartedimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.flow.combineimport kotlinx.coroutines.flow.debounceimport kotlinx.coroutines.flow.stateInimport kotlinx.coroutines.launchimport kotlinx.serialization.json.Jsonimport kotlinx.serialization.json.JsonObjectimport kotlinx.serialization.json.JsonPrimitiveimport kotlinx.serialization.json.intimport kotlinx.serialization.json.jsonObjectimport kotlinx.serialization.json.jsonPrimitiveimport org.jetbrains.compose.resources.ExperimentalResourceApi
class AppViewModel : ViewModel() {
var isLoading by mutableStateOf(true) var error by mutableStateOf<Exception?>(null) var isAudioPlaying by mutableStateOf(false) val verses = MutableStateFlow(listOf<Verse>()) val bookNames = MutableStateFlow(listOf<String>()) private val highlightedVerses = MutableStateFlow(JsonObject(mapOf()))
var bible by mutableStateOf(bibles.first()) var bookIndex by mutableIntStateOf(0) var chapterIndex by mutableIntStateOf(0) var verseIndex by mutableIntStateOf(0) var fontType by mutableStateOf(FontType.Sans) var fontSizeDelta by mutableIntStateOf(0) var fontBoldEnabled by mutableStateOf(false) var lineSpacingDelta by mutableIntStateOf(0) var themeType by mutableStateOf(ThemeType.Auto) val selectedVerses = MutableStateFlow(listOf<Verse>()) val searchText = MutableStateFlow("")
init { SpeechService.init({ isAudioPlaying = true }, { isAudioPlaying = false }) }
override fun onCleared() { super.onCleared() SpeechService.dispose({}, {}) }
@OptIn(FlowPreview::class) val searchedVerses = searchText.asStateFlow() .debounce(300) .combine(verses.asStateFlow()) { text, verses -> verses.filter { verse -> if (text.trim().isEmpty()) false else verse.text.lowercase().contains( text.trim().lowercase() ) } }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = listOf() )
fun onSearchTextChange(text: String) { searchText.value = text }
fun setSelectedVerses(verses: List<Verse>) { selectedVerses.value = verses }
fun clearSelectedVerses() { selectedVerses.value = listOf() }
fun loadData(s: Settings) { println("loadData") viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.Main) { isLoading = true } val bibleFileName = s.getString("bible", "en_kjv") bookIndex = s.getInt("bookIndex", 0) chapterIndex = s.getInt("chapterIndex", 0) verseIndex = s.getInt("verseIndex", 0) fontType = FontType.valueOf( s.getString("fontType", FontType.Sans.name) ) fontSizeDelta = s.getInt("fontSizeDelta", 0) fontBoldEnabled = s.getBoolean("fontBoldEnabled", false) lineSpacingDelta = s.getInt("lineSpacingDelta", 0) themeType = ThemeType.valueOf( s.getString("themeType", ThemeType.Auto.name) ) highlightedVerses.value = Json.parseToJsonElement( s.getString("highlightedVerses", "{}") ).jsonObject val localBible = bibles.find { it.filename() == bibleFileName } ?: bibles.first() loadBible(localBible) viewModelScope.launch(Dispatchers.Main) { isLoading = false } } }
@OptIn(ExperimentalResourceApi::class) suspend fun loadBible(b: Bible) { println("loadBible") try { val buffer = Res.readBytes("files/${b.filename()}.txt").decodeToString() val localVerses = buffer.split("\n").filter { it.isNotEmpty() }.map { val arr = it.split("|") val bookName = arr[0] val book = arr[1].toInt() val chapter = arr[2].toInt() val verseNo = arr[3].toInt() val heading = arr[4] val verseText = arr.subList(5, arr.size).joinToString("|") Verse( id = "${book}:${chapter}:${verseNo}", bookIndex = book, bookName = bookName, chapterIndex = chapter, verseIndex = verseNo, heading = heading, text = verseText, ) } viewModelScope.launch(Dispatchers.Main) { bible = b verses.value = localVerses bookNames.value = localVerses.distinctBy { it.bookName }.map { it.bookName } } } catch (e: Exception) { println("-----------------------COULD NOT LOAD FILE") viewModelScope.launch(Dispatchers.Main) { isLoading = false error = e } e.printStackTrace() } }
fun saveData(s: Settings) { println("saveData") viewModelScope.launch(Dispatchers.IO) { s.putString("bible", bible.filename()) s.putInt("bookIndex", bookIndex) s.putInt("chapterIndex", chapterIndex) s.putInt("verseIndex", verseIndex) s.putString("fontType", fontType.name) s.putInt("fontSizeDelta", fontSizeDelta) s.putBoolean("fontBoldEnabled", fontBoldEnabled) s.putInt("lineSpacingDelta", lineSpacingDelta) s.putString("themeType", themeType.name) s.putString("highlightedVerses", highlightedVerses.value.toString()) } }
fun getHighlightForVerse(v: Verse): Int? { if (highlightedVerses.value.containsKey(v.id)) return highlightedVerses.value[v.id]?.jsonPrimitive?.int return null }
fun addHighlightedVerses(verses: List<Verse>, colorIndex: Int) { highlightedVerses.value = JsonObject( highlightedVerses.value + verses.associateBy({ it.id }, { JsonPrimitive(colorIndex) }) ) }
fun removeHighlightedVerses(verses: List<Verse>) { highlightedVerses.value = JsonObject( highlightedVerses.value.filterKeys { !verses.map { v -> v.id }.contains(it) } ) }}