Add app/src/main/java/space/bloggin/vellum
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user