Files
Vellum/app/src/main/java/space/bloggin/vellum/MainActivity.kt
T

187 lines
7.4 KiB
Kotlin

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<Ref>) : 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<Screen> = mutableStateListOf()
var sheet by mutableStateOf<Sheet?>(null)
var sheetStack = mutableStateListOf<Sheet>()
val selection: SnapshotStateList<Ref> = mutableStateListOf()
/** a jump the reader should perform; reader clears it */
var jump by mutableStateOf<Ref?>(null)
var jumpTop by mutableStateOf(false)
/** briefly glows after a jump */
var flash by mutableStateOf<Ref?>(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<Ref>): 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<String>()
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<Ref>, 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()
}
}