Skip to content

Commit 0d2c235

Browse files
committed
library: Add SmallTopAppBar component
1 parent 6452ed1 commit 0d2c235

File tree

3 files changed

+208
-30
lines changed

3 files changed

+208
-30
lines changed

composeApp/src/commonMain/kotlin/UITest.kt

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.animation.fadeIn
55
import androidx.compose.animation.fadeOut
66
import androidx.compose.animation.shrinkHorizontally
77
import androidx.compose.animation.shrinkVertically
8+
import androidx.compose.foundation.layout.BoxWithConstraints
89
import androidx.compose.foundation.layout.PaddingValues
910
import androidx.compose.foundation.layout.captionBarPadding
1011
import androidx.compose.foundation.layout.fillMaxSize
@@ -44,6 +45,7 @@ import top.yukonga.miuix.kmp.basic.NavigationBar
4445
import top.yukonga.miuix.kmp.basic.NavigationItem
4546
import top.yukonga.miuix.kmp.basic.Scaffold
4647
import top.yukonga.miuix.kmp.basic.ScrollBehavior
48+
import top.yukonga.miuix.kmp.basic.SmallTopAppBar
4749
import top.yukonga.miuix.kmp.basic.TopAppBar
4850
import top.yukonga.miuix.kmp.basic.rememberTopAppBarState
4951
import top.yukonga.miuix.kmp.icon.MiuixIcons
@@ -101,21 +103,41 @@ fun UITest(
101103
enter = fadeIn() + expandVertically(),
102104
exit = fadeOut() + shrinkVertically()
103105
) {
104-
TopAppBar(
105-
title = "Miuix",
106-
scrollBehavior = currentScrollBehavior,
107-
actions = {
108-
IconButton(
109-
modifier = Modifier.padding(end = 12.dp),
110-
onClick = { }
111-
) {
112-
Icon(
113-
imageVector = Icons.Rounded.Menu,
114-
contentDescription = "Menu"
115-
)
116-
}
117-
},
118-
)
106+
BoxWithConstraints {
107+
if (maxWidth > 840.dp) {
108+
SmallTopAppBar(
109+
title = "Miuix",
110+
scrollBehavior = currentScrollBehavior,
111+
actions = {
112+
IconButton(
113+
modifier = Modifier.padding(end = 12.dp),
114+
onClick = { }
115+
) {
116+
Icon(
117+
imageVector = Icons.Rounded.Menu,
118+
contentDescription = "Menu"
119+
)
120+
}
121+
}
122+
)
123+
} else {
124+
TopAppBar(
125+
title = "Miuix",
126+
scrollBehavior = currentScrollBehavior,
127+
actions = {
128+
IconButton(
129+
modifier = Modifier.padding(end = 12.dp),
130+
onClick = { }
131+
) {
132+
Icon(
133+
imageVector = Icons.Rounded.Menu,
134+
contentDescription = "Menu"
135+
)
136+
}
137+
}
138+
)
139+
}
140+
}
119141
}
120142
},
121143
bottomBar = {

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/Slider.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
package top.yukonga.miuix.kmp.basic
22

3-
import androidx.compose.animation.animateColorAsState
43
import androidx.compose.animation.core.animateFloatAsState
5-
import androidx.compose.animation.core.spring
64
import androidx.compose.animation.core.tween
75
import androidx.compose.foundation.Canvas
86
import androidx.compose.foundation.LocalIndication
97
import androidx.compose.foundation.background
10-
import androidx.compose.foundation.clickable
118
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
129
import androidx.compose.foundation.indication
1310
import androidx.compose.foundation.interaction.MutableInteractionSource
14-
import androidx.compose.foundation.interaction.collectIsPressedAsState
1511
import androidx.compose.foundation.layout.fillMaxWidth
1612
import androidx.compose.foundation.layout.height
1713
import androidx.compose.runtime.Composable
@@ -26,7 +22,6 @@ import androidx.compose.ui.Alignment
2622
import androidx.compose.ui.Modifier
2723
import androidx.compose.ui.draw.clip
2824
import androidx.compose.ui.draw.drawBehind
29-
import androidx.compose.ui.draw.drawWithContent
3025
import androidx.compose.ui.geometry.CornerRadius
3126
import androidx.compose.ui.geometry.Offset
3227
import androidx.compose.ui.geometry.Size
@@ -188,16 +183,14 @@ object SliderDefaults {
188183
}
189184
}
190185

191-
192186
@Immutable
193187
class SliderColors(
194188
private val foregroundColor: Color,
195189
private val disabledForegroundColor: Color,
196190
private val backgroundColor: Color
197191
) {
198192
@Stable
199-
internal fun foregroundColor(enabled: Boolean): Color =
200-
if (enabled) foregroundColor else disabledForegroundColor
193+
internal fun foregroundColor(enabled: Boolean): Color = if (enabled) foregroundColor else disabledForegroundColor
201194

202195
@Stable
203196
internal fun backgroundColor(): Color = backgroundColor

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/TopAppBar.kt

Lines changed: 170 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,58 @@ fun TopAppBar(
136136
}
137137
}
138138

139+
@Composable
140+
fun SmallTopAppBar(
141+
title: String,
142+
modifier: Modifier = Modifier,
143+
color: Color = MiuixTheme.colorScheme.background,
144+
navigationIcon: @Composable () -> Unit = {},
145+
actions: @Composable RowScope.() -> Unit = {},
146+
scrollBehavior: ScrollBehavior? = null,
147+
defaultWindowInsetsPadding: Boolean = true,
148+
horizontalPadding: Dp = 28.dp
149+
) {
150+
SideEffect {
151+
// Sets the height offset limit of the SmallTopAppBar to 0f
152+
// To ensure that the content can still scroll normally even when scrollBehavior is passed.
153+
scrollBehavior?.state?.heightOffsetLimit = 0f
154+
}
155+
156+
// Wrap the given actions in a Row.
157+
val actionsRow =
158+
@Composable {
159+
Row(
160+
horizontalArrangement = Arrangement.End,
161+
verticalAlignment = Alignment.CenterVertically,
162+
content = actions
163+
)
164+
}
165+
// Compose a MiuixSurface with a MiuixTopAppBarLayout content.
166+
// The surface's background color is animated as specified above.
167+
// The height of the app bar is determined by subtracting the bar's height offset from the
168+
// app bar's defined constant height value (i.e. the ContainerHeight token).
169+
Surface(
170+
color = color,
171+
modifier = if (defaultWindowInsetsPadding) {
172+
modifier
173+
.windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal))
174+
.windowInsetsPadding(WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal))
175+
} else {
176+
modifier
177+
}
178+
.pointerInput(Unit) {
179+
detectVerticalDragGestures { _, _ -> }
180+
}
181+
) {
182+
SmallTopAppBarLayout(
183+
title = title,
184+
navigationIcon = navigationIcon,
185+
actions = actionsRow,
186+
horizontalPadding = horizontalPadding
187+
)
188+
}
189+
}
190+
139191
/**
140192
* Returns a [ScrollBehavior] that adjusts its properties to affect the colors and
141193
* height of the top app bar.
@@ -472,16 +524,16 @@ private fun interface ScrolledOffset {
472524
}
473525

474526
/**
475-
* The base [Layout] for all top app bars. This function lays out a top app bar navigation icon
527+
* The base [Layout] for [TopAppBar]. This function lays out a top app bar navigation icon
476528
* (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
477529
* the actions are optional.
478530
*
479-
* @param title the top app bar title (header)
480-
* @param navigationIcon a navigation icon [Composable]
481-
* @param actions actions [Composable]
482-
* @param scrolledOffset a function that provides the scroll offset of the top app bar
483-
* @param expandedHeightPx the expanded height of the top app bar in pixels
484-
* @param horizontalPadding the horizontal padding of the [TopAppBar]
531+
* @param title the top app bar title (header).
532+
* @param navigationIcon a navigation icon [Composable].
533+
* @param actions actions [Composable].
534+
* @param scrolledOffset a function that provides the scroll offset of the top app bar.
535+
* @param expandedHeightPx the expanded height of the top app bar in pixels.
536+
* @param horizontalPadding the horizontal padding of the [TopAppBar].
485537
*/
486538
@Composable
487539
private fun TopAppBarLayout(
@@ -629,4 +681,115 @@ private fun TopAppBarLayout(
629681
)
630682
}
631683
}
684+
}
685+
686+
687+
/**
688+
* The base [Layout] for [SmallTopAppBar]. This function lays out a top app bar navigation icon
689+
* (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
690+
* the actions are optional.
691+
*
692+
* @param title the top app bar title (header).
693+
* @param navigationIcon a navigation icon [Composable].
694+
* @param actions actions [Composable].
695+
* @param horizontalPadding the horizontal padding of the [SmallTopAppBar].
696+
*/
697+
@Composable
698+
private fun SmallTopAppBarLayout(
699+
title: String,
700+
navigationIcon: @Composable () -> Unit,
701+
actions: @Composable () -> Unit,
702+
horizontalPadding: Dp
703+
) {
704+
Layout(
705+
{
706+
Box(
707+
Modifier
708+
.layoutId("navigationIcon")
709+
) {
710+
navigationIcon()
711+
}
712+
Box(
713+
Modifier
714+
.layoutId("title")
715+
.padding(horizontal = horizontalPadding)
716+
) {
717+
Text(
718+
text = title,
719+
maxLines = 1,
720+
fontSize = MiuixTheme.textStyles.title3.fontSize,
721+
fontWeight = FontWeight.Medium
722+
)
723+
}
724+
Box(
725+
Modifier
726+
.layoutId("actionIcons")
727+
) {
728+
actions()
729+
}
730+
},
731+
modifier = Modifier
732+
.windowInsetsPadding(WindowInsets.statusBars)
733+
.windowInsetsPadding(WindowInsets.captionBar.only(WindowInsetsSides.Top))
734+
.heightIn(max = 56.dp)
735+
) { measurables, constraints ->
736+
val navigationIconPlaceable =
737+
measurables
738+
.fastFirst { it.layoutId == "navigationIcon" }
739+
.measure(constraints.copy(minWidth = 0))
740+
741+
val actionIconsPlaceable =
742+
measurables
743+
.fastFirst { it.layoutId == "actionIcons" }
744+
.measure(constraints.copy(minWidth = 0))
745+
746+
val maxTitleWidth =
747+
if (constraints.maxWidth == Constraints.Infinity) {
748+
constraints.maxWidth
749+
} else {
750+
(constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
751+
.coerceAtLeast(0)
752+
}
753+
754+
val titlePlaceable =
755+
measurables
756+
.fastFirst { it.layoutId == "title" }
757+
.measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
758+
759+
760+
val layoutHeight =
761+
if (constraints.maxHeight == Constraints.Infinity) {
762+
constraints.maxHeight
763+
} else {
764+
constraints.maxHeight
765+
}
766+
767+
layout(constraints.maxWidth, layoutHeight) {
768+
val verticalCenter = 60.dp.roundToPx() / 2
769+
770+
// Navigation icon
771+
navigationIconPlaceable.placeRelative(
772+
x = 0,
773+
y = verticalCenter - navigationIconPlaceable.height / 2
774+
)
775+
776+
// Title
777+
var baseX = (constraints.maxWidth - titlePlaceable.width) / 2
778+
if (baseX < navigationIconPlaceable.width) {
779+
baseX += (navigationIconPlaceable.width - baseX)
780+
} else if (baseX + titlePlaceable.width > constraints.maxWidth - actionIconsPlaceable.width) {
781+
baseX += ((constraints.maxWidth - actionIconsPlaceable.width) - (baseX + titlePlaceable.width))
782+
}
783+
titlePlaceable.placeRelative(
784+
x = baseX,
785+
y = verticalCenter - titlePlaceable.height / 2
786+
)
787+
788+
// Action icons
789+
actionIconsPlaceable.placeRelative(
790+
x = constraints.maxWidth - actionIconsPlaceable.width,
791+
y = verticalCenter - actionIconsPlaceable.height / 2
792+
)
793+
}
794+
}
632795
}

0 commit comments

Comments
 (0)