git.iko.soy/opencode-android
on 06fd41b help sign in
84 lines 3.3 kB history raw
1package soy.iko.opencode.ui.components
3import androidx.compose.foundation.layout.Box
4import androidx.compose.foundation.layout.Column
5import androidx.compose.foundation.layout.RowScope
6import androidx.compose.foundation.layout.size
7import androidx.compose.material.icons.Icons
8import androidx.compose.material.icons.automirrored.filled.ArrowBack
9import androidx.compose.material3.CircularProgressIndicator
10import androidx.compose.material3.ExperimentalMaterial3Api
11import androidx.compose.material3.Icon
12import androidx.compose.material3.IconButton
13import androidx.compose.material3.MaterialTheme
14import androidx.compose.material3.Text
15import androidx.compose.material3.TopAppBar
16import androidx.compose.material3.TopAppBarDefaults
17import androidx.compose.material3.TopAppBarScrollBehavior
18import androidx.compose.runtime.Composable
19import androidx.compose.ui.Alignment
20import androidx.compose.ui.Modifier
21import androidx.compose.ui.res.stringResource
22import androidx.compose.ui.text.style.TextOverflow
23import androidx.compose.ui.unit.dp
24import soy.iko.opencode.R
26/**
27 * The app's standard top bar: a title (with optional subtitle), an optional up/back button, an
28 * optional inline loading indicator, and trailing actions. Extracted so the many plain
29 * back-title-actions screens share one implementation instead of hand-rolling a [TopAppBar] each.
30 * Screens with a bespoke title (dropdown server switcher, clickable model picker) keep their own bar.
31 */
32@OptIn(ExperimentalMaterial3Api::class)
33@Composable
34fun AppTopBar(
35 title: String,
36 onBack: (() -> Unit)? = null,
37 scrollBehavior: TopAppBarScrollBehavior? = null,
38 subtitle: String? = null,
39 loading: Boolean = false,
40 actions: @Composable RowScope.() -> Unit = {},
41) {
42 TopAppBar(
43 title = {
44 if (subtitle != null) {
45 Column {
46 Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis)
47 Text(
48 subtitle,
49 style = MaterialTheme.typography.labelMedium,
50 color = MaterialTheme.colorScheme.onSurfaceVariant,
51 maxLines = 1,
52 overflow = TextOverflow.Ellipsis,
53 )
54 }
55 } else {
56 Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis)
57 }
58 },
59 navigationIcon = {
60 if (onBack != null) {
61 IconButton(onClick = onBack) {
62 Icon(
63 Icons.AutoMirrored.Filled.ArrowBack,
64 contentDescription = stringResource(R.string.back),
65 )
66 }
67 }
68 },
69 actions = {
70 if (loading) {
71 Box(modifier = Modifier.size(40.dp), contentAlignment = Alignment.Center) {
72 CircularProgressIndicator(
73 Modifier.size(20.dp),
74 strokeWidth = 2.dp,
75 color = MaterialTheme.colorScheme.onSurfaceVariant,
76 )
77 }
78 }
79 actions()
80 },
81 scrollBehavior = scrollBehavior,
82 colors = TopAppBarDefaults.topAppBarColors(),
83 )