mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Stop Android from crashing on subject maps and ship a new installable version.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
db0422a952
commit
cc36881bb9
6 changed files with 177 additions and 206 deletions
|
|
@ -13,8 +13,8 @@ android {
|
|||
applicationId = "org.maturita.maturita"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "android"
|
||||
versionCode = 3
|
||||
versionName = "android-3"
|
||||
buildConfigField("String", "COMMIT", "\"$commit\"")
|
||||
buildConfigField("String", "UPDATE_REPO", "\"pepaskopec-blip/maturita.c\"")
|
||||
buildConfigField("String", "UPDATE_BRANCH", "\"builds\"")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
package org.maturita.maturita
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
|
|
@ -60,9 +66,61 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val crashPrefs = getSharedPreferences("crash", MODE_PRIVATE)
|
||||
val prev = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, error ->
|
||||
try {
|
||||
crashPrefs.edit().putString("last", error.stackTraceToString()).commit()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
prev?.uncaughtException(thread, error)
|
||||
}
|
||||
val lastCrash = crashPrefs.getString("last", null)
|
||||
if (lastCrash != null) {
|
||||
setContentView(crashView(lastCrash) {
|
||||
crashPrefs.edit().remove("last").apply()
|
||||
recreate()
|
||||
})
|
||||
return
|
||||
}
|
||||
enableEdgeToEdge()
|
||||
setContent { MaturitaApp(vm) }
|
||||
}
|
||||
|
||||
private fun crashView(dump: String, retry: () -> Unit): LinearLayout {
|
||||
val pad = (20 * resources.displayMetrics.density).toInt()
|
||||
val title = TextView(this).apply {
|
||||
text = "maturita.c spadla"
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f)
|
||||
setPadding(0, 0, 0, pad / 2)
|
||||
}
|
||||
val hint = TextView(this).apply {
|
||||
text = "Pošli tenhle text, ať jde pád opravit."
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f)
|
||||
setPadding(0, 0, 0, pad / 2)
|
||||
}
|
||||
val body = TextView(this).apply {
|
||||
text = dump
|
||||
setTextIsSelectable(true)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
||||
typeface = android.graphics.Typeface.MONOSPACE
|
||||
}
|
||||
val scroll = ScrollView(this).apply {
|
||||
addView(body, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
}
|
||||
val retryBtn = Button(this).apply {
|
||||
text = "Zkusit znovu"
|
||||
setOnClickListener { retry() }
|
||||
}
|
||||
return LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(pad, pad, pad, pad)
|
||||
addView(title, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
addView(hint, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
addView(scroll, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f))
|
||||
addView(retryBtn, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class Content(root: JSONObject) {
|
|||
}
|
||||
val subjects = raw.arr("subjects")
|
||||
val german = raw.arr("german")
|
||||
val net = raw.obj("net")!!
|
||||
val net = raw.obj("net") ?: J(JSONObject())
|
||||
val netLessons = net.arr("lessons")
|
||||
val netTasks = net.arr("tasks")
|
||||
val netAnswers: List<List<J>> = run {
|
||||
|
|
@ -59,8 +59,12 @@ class Content(root: JSONObject) {
|
|||
|
||||
companion object {
|
||||
fun load(context: Context): Content {
|
||||
return try {
|
||||
val text = context.assets.open("content.json").bufferedReader().use { it.readText() }
|
||||
return Content(JSONObject(text))
|
||||
Content(JSONObject(text))
|
||||
} catch (_: Exception) {
|
||||
Content(JSONObject())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ import androidx.compose.foundation.layout.width
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
|
|
@ -191,7 +190,8 @@ private fun AssemblyEx(vm: AppViewModel, title: String, sub: String?, spec: J, o
|
|||
FlowRow(Modifier.fillMaxWidth().padding(bottom = 6.dp).clip(RoundedCornerShape(14.dp)).background(p.surface0).padding(8.dp), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
if (placed[i].isEmpty()) Text("…", color = p.overlay)
|
||||
placed[i].forEach { idx ->
|
||||
Chip(item.strs("words")[idx], p) {
|
||||
val word = item.strs("words").getOrNull(idx) ?: return@forEach
|
||||
Chip(word, p) {
|
||||
placed[i].remove(idx)
|
||||
pools[i].add(idx)
|
||||
}
|
||||
|
|
@ -199,7 +199,8 @@ private fun AssemblyEx(vm: AppViewModel, title: String, sub: String?, spec: J, o
|
|||
}
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
pools[i].toList().forEach { idx ->
|
||||
Chip(item.strs("words")[idx], p) {
|
||||
val word = item.strs("words").getOrNull(idx) ?: return@forEach
|
||||
Chip(word, p) {
|
||||
pools[i].remove(idx)
|
||||
placed[i].add(idx)
|
||||
}
|
||||
|
|
@ -663,26 +664,43 @@ private fun ComboLine(
|
|||
val p = vm.palette
|
||||
var open by remember { mutableStateOf(false) }
|
||||
val box = @Composable {
|
||||
Box {
|
||||
Column {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(p.mantle)
|
||||
.border(1.dp, if (revealed && picks.getOrNull(idx).orEmpty().isNotEmpty()) {
|
||||
if (answerAccepts(normalizeAnswer(picks[idx]), answer)) p.success else p.error
|
||||
if (answerAccepts(normalizeAnswer(picks.getOrNull(idx).orEmpty()), answer)) p.success else p.error
|
||||
} else p.text.copy(0.1f), RoundedCornerShape(12.dp))
|
||||
.clickable { open = true }
|
||||
.clickable { open = !open }
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
) {
|
||||
val pick = picks.getOrNull(idx).orEmpty()
|
||||
Text(pick.ifEmpty { "—" }, color = if (pick.isEmpty()) p.overlay else p.text)
|
||||
}
|
||||
DropdownMenu(open, { open = false }) {
|
||||
if (open) {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(top = 4.dp)
|
||||
.heightIn(max = 220.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(p.surface0)
|
||||
.border(1.dp, p.text.copy(0.08f), RoundedCornerShape(12.dp)),
|
||||
) {
|
||||
pool.forEach { w ->
|
||||
DropdownMenuItem({ Text(w) }, {
|
||||
Text(
|
||||
w,
|
||||
color = p.text,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
if (idx in picks.indices) picks[idx] = w
|
||||
open = false
|
||||
})
|
||||
}
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -950,13 +968,28 @@ private fun VlsmEx(vm: AppViewModel, @Suppress("UNUSED_PARAMETER") lesson: J) {
|
|||
@Composable
|
||||
private fun PrefixMenu(value: String, p: Palette, onPick: (String) -> Unit) {
|
||||
var open by remember { mutableStateOf(false) }
|
||||
Box(Modifier.padding(vertical = 4.dp)) {
|
||||
Column(Modifier.padding(vertical = 4.dp)) {
|
||||
Box(
|
||||
Modifier.clip(RoundedCornerShape(12.dp)).background(p.mantle).clickable { open = true }.padding(10.dp),
|
||||
Modifier.clip(RoundedCornerShape(12.dp)).background(p.mantle).clickable { open = !open }.padding(10.dp),
|
||||
) { Text(value.ifEmpty { "/…" }, color = p.text) }
|
||||
DropdownMenu(open, { open = false }) {
|
||||
if (open) {
|
||||
Column(
|
||||
Modifier
|
||||
.heightIn(max = 220.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(p.surface0),
|
||||
) {
|
||||
(8..30).forEach { n ->
|
||||
DropdownMenuItem({ Text("/$n") }, { onPick("/$n"); open = false })
|
||||
Text(
|
||||
"/$n",
|
||||
color = p.text,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onPick("/$n"); open = false }
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,30 @@
|
|||
package org.maturita.maturita.ui
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.maturita.maturita.data.Palette
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sin
|
||||
|
||||
data class MapNode(
|
||||
val id: String,
|
||||
|
|
@ -47,137 +36,40 @@ data class MapNode(
|
|||
val onClick: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
data class NodePos(val x: Float, val y: Float)
|
||||
|
||||
data class Serpentine(
|
||||
val points: List<NodePos>,
|
||||
val width: Float,
|
||||
val height: Float,
|
||||
val litUntil: Float,
|
||||
)
|
||||
|
||||
fun serpentine(
|
||||
n: Int,
|
||||
avail: Float,
|
||||
mx: Float,
|
||||
my: Float,
|
||||
spac: Float,
|
||||
gap: Float,
|
||||
wave: Float,
|
||||
lift: Float = 0f,
|
||||
minScale: Float = 0.55f,
|
||||
): Serpentine {
|
||||
if (n < 1) return Serpentine(emptyList(), avail, my * 2, 0f)
|
||||
val scale = min(1f, maxOf(minScale, avail / (2 * mx + maxOf(n - 1, 0) * spac)))
|
||||
val smx = mx * scale
|
||||
val smy = my * scale
|
||||
val sspac = spac * scale
|
||||
val sgap = gap * scale
|
||||
val swave = wave * scale
|
||||
val slift = lift * scale
|
||||
var rows = 1
|
||||
while (rows <= n) {
|
||||
val cols = (n + rows - 1) / rows
|
||||
if (2 * smx + (cols - 1) * sspac <= avail + 1) break
|
||||
rows++
|
||||
}
|
||||
rows = min(rows, n)
|
||||
val cols = maxOf(1, (n + rows - 1) / rows)
|
||||
val cw = 2 * smx + (cols - 1) * sspac
|
||||
val ch = 2 * smy + (rows - 1) * sgap + slift
|
||||
val phase = (2.0 * PI * 1.7 / maxOf(n - 1, 1)).toFloat()
|
||||
val pts = MutableList(n) { NodePos(0f, 0f) }
|
||||
for (r in 0 until rows) {
|
||||
val base = r * cols
|
||||
val len = min(cols, n - base)
|
||||
val fwd = r % 2 == 0
|
||||
for (c in 0 until len) {
|
||||
val i = base + c
|
||||
val cc = if (fwd) c else cols - 1 - c
|
||||
pts[i] = NodePos(
|
||||
smx + cc * sspac,
|
||||
smy + slift + r * sgap + swave * sin(i * phase),
|
||||
)
|
||||
}
|
||||
}
|
||||
return Serpentine(pts, cw, ch, 0f)
|
||||
}
|
||||
|
||||
private fun catmull(pts: List<NodePos>, t: Float): NodePos {
|
||||
val n = pts.size
|
||||
if (n == 1) return pts[0]
|
||||
var k = t.toInt()
|
||||
var u = t - k
|
||||
if (k < 0) { k = 0; u = 0f }
|
||||
if (k >= n - 1) { k = n - 2; u = 1f }
|
||||
val p1 = pts[k]
|
||||
val p2 = pts[k + 1]
|
||||
val p0 = if (k - 1 >= 0) pts[k - 1] else NodePos(p1.x - (p2.x - p1.x), p1.y - (p2.y - p1.y))
|
||||
val p3 = if (k + 2 < n) pts[k + 2] else NodePos(p2.x + (p2.x - p1.x), p2.y + (p2.y - p1.y))
|
||||
val u2 = u * u
|
||||
val u3 = u2 * u
|
||||
val x = 0.5f * (2 * p1.x + (-p0.x + p2.x) * u + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * u2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * u3)
|
||||
val y = 0.5f * (2 * p1.y + (-p0.y + p2.y) * u + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * u2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * u3)
|
||||
return NodePos(x, y)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PathMap(
|
||||
nodes: List<MapNode>,
|
||||
palette: Palette,
|
||||
litUntil: Float,
|
||||
@Suppress("UNUSED_PARAMETER") litUntil: Float,
|
||||
modifier: Modifier = Modifier,
|
||||
nodeSize: Float = 72f,
|
||||
mx: Float = 90f,
|
||||
my: Float = 110f,
|
||||
spac: Float = 170f,
|
||||
gap: Float = 170f,
|
||||
wave: Float = 28f,
|
||||
lift: Float = 0f,
|
||||
extra: List<Pair<NodePos, @Composable () -> Unit>> = emptyList(),
|
||||
extraPoints: List<NodePos> = emptyList(),
|
||||
@Suppress("UNUSED_PARAMETER") nodeSize: Float = 72f,
|
||||
@Suppress("UNUSED_PARAMETER") mx: Float = 90f,
|
||||
@Suppress("UNUSED_PARAMETER") my: Float = 110f,
|
||||
@Suppress("UNUSED_PARAMETER") spac: Float = 170f,
|
||||
@Suppress("UNUSED_PARAMETER") gap: Float = 170f,
|
||||
@Suppress("UNUSED_PARAMETER") wave: Float = 28f,
|
||||
@Suppress("UNUSED_PARAMETER") lift: Float = 0f,
|
||||
) {
|
||||
if (nodes.isEmpty()) return
|
||||
val density = LocalDensity.current
|
||||
fun Float.toDp(): androidx.compose.ui.unit.Dp = with(density) { this@toDp.toDp() }
|
||||
BoxWithConstraints(modifier.fillMaxSize()) {
|
||||
val avail = constraints.maxWidth.toFloat().coerceAtLeast(1f)
|
||||
val geo = serpentine(nodes.size, avail, mx, my, spac, gap, wave, lift)
|
||||
Box(
|
||||
Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.verticalScroll(rememberScrollState()),
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Box(Modifier.width(geo.width.toDp()).height((geo.height + 80f).toDp())) {
|
||||
Canvas(Modifier.fillMaxSize()) {
|
||||
if (geo.points.size < 2) return@Canvas
|
||||
fun pathBetween(t0: Float, t1: Float): Path {
|
||||
val p = Path()
|
||||
val steps = maxOf(1, ((t1 - t0) * 48).toInt())
|
||||
val start = catmull(geo.points, t0)
|
||||
p.moveTo(start.x, start.y)
|
||||
for (s in 1..steps) {
|
||||
val t = t0 + (t1 - t0) * s / steps
|
||||
val pt = catmull(geo.points, t)
|
||||
p.lineTo(pt.x, pt.y)
|
||||
}
|
||||
return p
|
||||
}
|
||||
val end = (nodes.size - 1).toFloat()
|
||||
drawPath(pathBetween(0f, end), palette.rail, style = Stroke(12f, cap = StrokeCap.Round, join = StrokeJoin.Round))
|
||||
if (litUntil > 0f) {
|
||||
drawPath(pathBetween(0f, litUntil), palette.accent, style = Stroke(12f, cap = StrokeCap.Round, join = StrokeJoin.Round))
|
||||
}
|
||||
extraPoints.forEach { }
|
||||
}
|
||||
nodes.forEachIndexed { i, node ->
|
||||
val pt = geo.points[i]
|
||||
Box(
|
||||
val click = node.onClick
|
||||
CardBox(
|
||||
palette,
|
||||
Modifier
|
||||
.offset { IntOffset((pt.x - nodeSize / 2).toInt(), (pt.y - nodeSize / 2).toInt()) }
|
||||
.size(nodeSize.toDp()),
|
||||
contentAlignment = Alignment.Center,
|
||||
.fillMaxWidth()
|
||||
.alpha(if (node.locked) 0.48f else 1f)
|
||||
.then(
|
||||
if (!node.locked && click != null) Modifier.clickable { click() }
|
||||
else Modifier,
|
||||
),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
val bg = when {
|
||||
node.locked -> palette.lockedBg
|
||||
node.current || node.done -> palette.accent
|
||||
|
|
@ -190,48 +82,27 @@ fun PathMap(
|
|||
}
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.size(44.dp)
|
||||
.clip(CircleShape)
|
||||
.background(bg)
|
||||
.then(
|
||||
if (node.locked) Modifier.border(1.dp, palette.lockedBorder, CircleShape)
|
||||
else Modifier
|
||||
)
|
||||
.then(if (!node.locked && node.onClick != null) Modifier.clickable { node.onClick.invoke() } else Modifier),
|
||||
.background(bg),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (node.finish) {
|
||||
FinishFlag(palette, "${i + 1}", nodeSize.toDp())
|
||||
} else {
|
||||
androidx.compose.foundation.layout.Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("${i + 1}", color = fg, fontWeight = FontWeight.Black, fontSize = 18.sp)
|
||||
if (node.locked) LockIcon(palette.overlay, 14.dp)
|
||||
else if (node.done) CheckIcon(palette.onAccent, 14.dp)
|
||||
}
|
||||
}
|
||||
when {
|
||||
node.locked -> LockIcon(palette.overlay, 16.dp)
|
||||
node.done -> CheckIcon(palette.onAccent, 16.dp)
|
||||
else -> Text("${i + 1}", color = fg, fontWeight = FontWeight.Black, fontSize = 16.sp)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text(
|
||||
node.label,
|
||||
color = if (node.locked) palette.overlay else palette.text,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.offset { IntOffset((pt.x - 70).toInt(), (pt.y + nodeSize / 2 + 8).toInt()) }
|
||||
.width(140.dp),
|
||||
fontWeight = if (node.current) FontWeight.Bold else FontWeight.SemiBold,
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
extra.forEach { (pt, content) ->
|
||||
Box(Modifier.offset { IntOffset(pt.x.toInt(), pt.y.toInt()) }) { content() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun branchPoint(points: List<NodePos>, index: Int = 1, dx: Float = 40f, lift: Float = 110f): NodePos? {
|
||||
if (index !in points.indices) return null
|
||||
val p = points[index]
|
||||
return NodePos(p.x + dx, p.y - lift)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ fun WelcomeScreen(vm: AppViewModel) {
|
|||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
) {
|
||||
Text("maturita.c", color = p.text, fontWeight = FontWeight.Bold, fontSize = 15.sp)
|
||||
Text(
|
||||
"sestavení ${org.maturita.maturita.BuildConfig.VERSION_NAME} · ${org.maturita.maturita.BuildConfig.COMMIT.take(7)}",
|
||||
color = p.overlay,
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
|
|
@ -184,7 +189,7 @@ fun SettingsSheet(vm: AppViewModel) {
|
|||
Text(vm.tr("theme"), color = p.subtext, fontSize = 12.sp, fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
themeNames.forEachIndexed { i, name ->
|
||||
val id = ThemeId.entries[i]
|
||||
val id = ThemeId.entries.getOrNull(i) ?: return@forEachIndexed
|
||||
val sw = themePalette(id, vm.mode)
|
||||
val sel = vm.themeId == id
|
||||
Row(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue