package space.bloggin.vellum import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.material3.MaterialTheme import androidx.compose.material3.lightColorScheme import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.core.view.WindowCompat sealed class Screen { data object GoTo : Screen() data object Search : Screen() data class Library(val tab: Int = 0) : Screen() data object PlansScreen : Screen() data object TopicsScreen : Screen() data class TopicDetail(val topic: Topic) : Screen() data object Appearance : Screen() data object SettingsScreen : Screen() data object About : Screen() data class ShareImg(val text: String, val label: String) : Screen() } sealed class Sheet { data class StrongsWords(val ref: Ref) : Sheet() data class StrongsDef(val id: String, val word: String) : Sheet() data class Xrefs(val ref: Ref) : Sheet() data class Compare(val refs: List) : Sheet() data class NoteEdit(val ref: Ref) : Sheet() data object ReadAloud : Sheet() data object AutoScroll : Sheet() data object Translations : Sheet() } class AppState(val ctx: Context, val bible: Bible, val user: UserData, val settings: Settings, val speech: Speech) { val screens: SnapshotStateList = mutableStateListOf() var sheet by mutableStateOf(null) var sheetStack = mutableStateListOf() val selection: SnapshotStateList = mutableStateListOf() /** a jump the reader should perform; reader clears it */ var jump by mutableStateOf(null) var jumpTop by mutableStateOf(false) /** briefly glows after a jump */ var flash by mutableStateOf(null) var autoScroll by mutableStateOf(false) var drawerOpen by mutableStateOf(false) /** current chapter shown at the top of the reader */ var currentBook by mutableIntStateOf(1) var currentChapter by mutableIntStateOf(1) var currentVerse by mutableIntStateOf(1) init { val last = settings.lastItem if (last in 1 until bible.items.size) { val it = bible.items[last] currentBook = maxOf(1, Item.book(it)); currentChapter = maxOf(1, Item.chapter(it)); currentVerse = maxOf(1, Item.verse(it)) } } fun open(s: Screen) { drawerOpen = false; screens.add(s) } fun back(): Boolean { when { sheet != null -> { sheet = if (sheetStack.isNotEmpty()) sheetStack.removeAt(sheetStack.lastIndex) else null } drawerOpen -> drawerOpen = false screens.isNotEmpty() -> screens.removeAt(screens.lastIndex) selection.isNotEmpty() -> selection.clear() autoScroll -> autoScroll = false else -> return false } return true } fun showSheet(s: Sheet, stack: Boolean = false) { if (stack && sheet != null) sheetStack.add(sheet!!) else sheetStack.clear() sheet = s } fun closeSheet() { sheetStack.clear(); sheet = null } fun goTo(r: Ref) { screens.clear(); sheet = null; sheetStack.clear(); drawerOpen = false jump = r if (r.verse > 0) flash = r } fun goTop() { screens.clear(); sheet = null; drawerOpen = false; jumpTop = true } // ------------ selection helpers ------------- fun sortedSelection() = selection.sortedBy { it.key() } fun selectionLabel(): String = labelFor(sortedSelection()) fun labelFor(refs: List): String { if (refs.isEmpty()) return "" val first = refs.first() val sameChapter = refs.all { it.book == first.book && it.chapter == first.chapter } if (!sameChapter) return refs.joinToString("; ") { bible.refLabel(it) } val vs = refs.map { it.verse } val parts = ArrayList() var start = vs[0]; var prev = vs[0] for (v in vs.drop(1) + listOf(-1)) { if (v == prev + 1) { prev = v; continue } parts.add(if (start == prev) "$start" else "$start-$prev") start = v; prev = v } return bible.book(first.book).name + " " + first.chapter + ":" + parts.joinToString(",") } fun textFor(refs: List, tr: Int = settings.tr): String = refs.joinToString(" ") { bible.plainVerse(tr, it.book, it.chapter, it.verse) }.trim() fun copy(text: String, label: String) { val cm = ctx.getSystemService(ClipboardManager::class.java) cm.setPrimaryClip(ClipData.newPlainText(label, "“$text”\n— $label (${bible.trCode(settings.tr)})")) Toast.makeText(ctx, "Copied $label", Toast.LENGTH_SHORT).show() } fun shareText(text: String, label: String) { val i = Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_TEXT, "“$text”\n— $label (${bible.trCode(settings.tr)})") } ctx.startActivity(Intent.createChooser(i, "Share verse").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) } fun toast(s: String) = Toast.makeText(ctx, s, Toast.LENGTH_SHORT).show() } class MainActivity : ComponentActivity() { lateinit var state: AppState override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val settings = Settings.get(this) val bible = Bible.get(this) val user = UserData.get(this) val speech = Speech(this, bible, settings) state = AppState(this, bible, user, settings, speech) handleIntent(intent, first = true) Daily.schedule(this) VotdWidget.refreshAll(this) setContent { val bd = settings.activeBg LaunchedEffect(bd.dark) { enableEdgeToEdge( statusBarStyle = if (bd.dark) SystemBarStyle.dark(0) else SystemBarStyle.light(0, 0), navigationBarStyle = if (bd.dark) SystemBarStyle.dark(0) else SystemBarStyle.light(0, 0), ) } val scheme = if (bd.dark) darkColorScheme(primary = bd.ink, surface = bd.panel, onSurface = bd.ink, background = bd.panel) else lightColorScheme(primary = bd.ink, surface = bd.panel, onSurface = bd.ink, background = bd.panel) MaterialTheme(colorScheme = scheme) { CompositionLocalProvider(LocalBackdrop provides bd) { BackHandler(enabled = state.sheet != null || state.drawerOpen || state.screens.isNotEmpty() || state.selection.isNotEmpty() || state.autoScroll) { state.back() } App(state) } } } } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleIntent(intent, first = false) } private fun handleIntent(i: Intent?, first: Boolean) { val k = i?.getIntExtra("ref", 0) ?: 0 if (k > 0) state.goTo(Ref.fromKey(k)) else if (first && state.settings.openAtVotd) state.jumpTop = true } override fun onDestroy() { state.speech.shutdown() super.onDestroy() } }