Skip to content

Commit 4e56c9d

Browse files
committed
feat: Add FloatingToolbar and update dependencies
- Add `FloatingToolbar` composable for creating customizable floating toolbars with various positioning options. - Update `androidx.activity:activity-compose` dependency to version 1.10.1. - Add `slideInVertically` and `slideOutVertically` animations in UITest. - Add `useFloatingToolbar` and `floatingToolbarPosition` state and related control in UITest and ThirdPage. - Add `isBottomRow` and `needsBottomInset` functions in FloatingToolbar. - Add `toAlignment` function in ToolbarPosition. - Update `FloatingToolbar` padding and arrangement. - Add floating toolbar control in `ThirdPage` and `UITest`. - Add `navigationBars` padding for `FloatingToolbar`. - Update example `Preview` to add more params.
1 parent 35d0442 commit 4e56c9d

File tree

5 files changed

+261
-4
lines changed

5 files changed

+261
-4
lines changed

example/src/androidMain/kotlin/Preview.android.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ fun ThirdPagePreview() {
6060
{},
6161
false,
6262
{},
63+
0,
64+
{},
65+
false,
66+
{},
6367
false,
6468
{},
6569
remember { mutableIntStateOf(0) }

example/src/commonMain/kotlin/ThirdPage.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ fun ThirdPage(
3333
onShowTopAppBarChange: (Boolean) -> Unit,
3434
showBottomBar: Boolean,
3535
onShowBottomBarChange: (Boolean) -> Unit,
36+
useFloatingToolbar: Boolean,
37+
onUseFloatingToolbarChange: (Boolean) -> Unit,
38+
floatingToolbarPosition: Int,
39+
onFloatingToolbarPositionChange: (Int) -> Unit,
3640
showFloatingActionButton: Boolean,
3741
onShowFloatingActionButtonChange: (Boolean) -> Unit,
3842
enablePageUserScroll: Boolean,
@@ -67,6 +71,27 @@ fun ThirdPage(
6771
checked = showBottomBar,
6872
onCheckedChange = onShowBottomBarChange
6973
)
74+
SuperSwitch(
75+
title = "Use Floating Toolbar",
76+
checked = useFloatingToolbar,
77+
onCheckedChange = onUseFloatingToolbarChange
78+
)
79+
SuperDropdown(
80+
title = "Floating Toolbar Position",
81+
items = listOf(
82+
"LeftTop",
83+
"LeftCenter",
84+
"LeftBottom",
85+
"RightTop",
86+
"RightCenter",
87+
"RightBottom",
88+
"BottomLeft",
89+
"BottomCenter",
90+
"BottomRight"
91+
),
92+
selectedIndex = floatingToolbarPosition,
93+
onSelectedIndexChange = onFloatingToolbarPositionChange
94+
)
7095
SuperSwitch(
7196
title = "Show Floating Action Button",
7297
checked = showFloatingActionButton,
@@ -135,4 +160,4 @@ fun dialog(showDialog: MutableState<Boolean>) {
135160
}
136161
}
137162
)
138-
}
163+
}

example/src/commonMain/kotlin/UITest.kt

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ 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.animation.slideInVertically
9+
import androidx.compose.animation.slideOutVertically
810
import androidx.compose.foundation.layout.BoxWithConstraints
911
import androidx.compose.foundation.layout.PaddingValues
1012
import androidx.compose.foundation.layout.WindowInsets
@@ -16,6 +18,7 @@ import androidx.compose.foundation.layout.imePadding
1618
import androidx.compose.foundation.layout.navigationBars
1719
import androidx.compose.foundation.layout.only
1820
import androidx.compose.foundation.layout.padding
21+
import androidx.compose.foundation.layout.size
1922
import androidx.compose.foundation.layout.statusBarsPadding
2023
import androidx.compose.foundation.layout.windowInsetsPadding
2124
import androidx.compose.foundation.pager.HorizontalPager
@@ -42,6 +45,7 @@ import dev.chrisbanes.haze.hazeEffect
4245
import dev.chrisbanes.haze.hazeSource
4346
import kotlinx.coroutines.launch
4447
import top.yukonga.miuix.kmp.basic.FloatingActionButton
48+
import top.yukonga.miuix.kmp.basic.FloatingToolbar
4549
import top.yukonga.miuix.kmp.basic.Icon
4650
import top.yukonga.miuix.kmp.basic.IconButton
4751
import top.yukonga.miuix.kmp.basic.ListPopup
@@ -54,9 +58,11 @@ import top.yukonga.miuix.kmp.basic.PopupPositionProvider
5458
import top.yukonga.miuix.kmp.basic.Scaffold
5559
import top.yukonga.miuix.kmp.basic.ScrollBehavior
5660
import top.yukonga.miuix.kmp.basic.SmallTopAppBar
61+
import top.yukonga.miuix.kmp.basic.ToolbarPosition
5762
import top.yukonga.miuix.kmp.basic.TopAppBar
5863
import top.yukonga.miuix.kmp.extra.DropdownImpl
5964
import top.yukonga.miuix.kmp.icon.MiuixIcons
65+
import top.yukonga.miuix.kmp.icon.icons.basic.Check
6066
import top.yukonga.miuix.kmp.icon.icons.other.GitHub
6167
import top.yukonga.miuix.kmp.icon.icons.useful.ImmersionMore
6268
import top.yukonga.miuix.kmp.icon.icons.useful.NavigatorSwitch
@@ -70,6 +76,8 @@ data class UIState(
7076
val showFPSMonitor: Boolean = false,
7177
val showTopAppBar: Boolean = true,
7278
val showBottomBar: Boolean = true,
79+
val useFloatingToolbar: Boolean = false,
80+
val floatingToolbarPosition: Int = 7,
7381
val showFloatingActionButton: Boolean = true,
7482
val enablePageUserScroll: Boolean = false,
7583
val isTopPopupExpanded: Boolean = false
@@ -131,7 +139,7 @@ fun UITest(
131139
},
132140
bottomBar = {
133141
AnimatedVisibility(
134-
visible = uiState.showBottomBar,
142+
visible = uiState.showBottomBar && !uiState.useFloatingToolbar,
135143
enter = fadeIn() + expandVertically(),
136144
exit = fadeOut() + shrinkVertically()
137145
) {
@@ -185,6 +193,68 @@ fun UITest(
185193
)
186194
}
187195

196+
AnimatedVisibility(
197+
visible = uiState.useFloatingToolbar,
198+
enter = fadeIn() + slideInVertically(initialOffsetY = { it }) + expandVertically(),
199+
exit = fadeOut() + slideOutVertically(targetOffsetY = { it }) + shrinkVertically()
200+
) {
201+
FloatingToolbar(
202+
position = when (uiState.floatingToolbarPosition) {
203+
0 -> ToolbarPosition.LeftTop
204+
1 -> ToolbarPosition.LeftCenter
205+
2 -> ToolbarPosition.LeftBottom
206+
3 -> ToolbarPosition.RightTop
207+
4 -> ToolbarPosition.RightCenter
208+
5 -> ToolbarPosition.RightBottom
209+
6 -> ToolbarPosition.BottomLeft
210+
7 -> ToolbarPosition.BottomCenter
211+
8 -> ToolbarPosition.BottomRight
212+
else -> ToolbarPosition.BottomCenter
213+
},
214+
modifier = Modifier.hazeEffect(hazeState) {
215+
style = hazeStyle
216+
blurRadius = 25.dp
217+
noiseFactor = 0f
218+
},
219+
color = Color.Transparent
220+
) {
221+
// IconButton with dynamic tint
222+
listOf(
223+
MiuixIcons.Useful.NavigatorSwitch to 0,
224+
MiuixIcons.Useful.Order to 1,
225+
MiuixIcons.Useful.Settings to 2
226+
).forEach { (icon, pageIndex) ->
227+
IconButton(
228+
modifier = Modifier.size(48.dp),
229+
onClick = {
230+
coroutineScope.launch {
231+
pagerState.animateScrollToPage(pageIndex)
232+
}
233+
}
234+
) {
235+
Icon(
236+
icon,
237+
contentDescription = null,
238+
tint = if (selectedPage == pageIndex) MiuixTheme.colorScheme.onSurfaceContainer else MiuixTheme.colorScheme.onSurfaceContainerVariant
239+
)
240+
}
241+
}
242+
243+
// FPS Monitor toggle button
244+
IconButton(
245+
onClick = {
246+
uiState = uiState.copy(showFPSMonitor = !uiState.showFPSMonitor)
247+
}
248+
) {
249+
Icon(
250+
MiuixIcons.Basic.Check,
251+
contentDescription = null,
252+
tint = if (uiState.showFPSMonitor) MiuixTheme.colorScheme.primaryVariant else MiuixTheme.colorScheme.onSurfaceContainerVariant
253+
)
254+
}
255+
}
256+
}
257+
188258
AnimatedVisibility(
189259
visible = uiState.showFPSMonitor,
190260
enter = fadeIn() + expandHorizontally(),
@@ -351,6 +421,10 @@ fun AppHorizontalPager(
351421
onShowTopAppBarChange = { onUiStateChange(uiState.copy(showTopAppBar = it)) },
352422
showBottomBar = uiState.showBottomBar,
353423
onShowBottomBarChange = { onUiStateChange(uiState.copy(showBottomBar = it)) },
424+
useFloatingToolbar = uiState.useFloatingToolbar,
425+
onUseFloatingToolbarChange = { onUiStateChange(uiState.copy(useFloatingToolbar = it)) },
426+
floatingToolbarPosition = uiState.floatingToolbarPosition,
427+
onFloatingToolbarPositionChange = { onUiStateChange(uiState.copy(floatingToolbarPosition = it)) },
354428
showFloatingActionButton = uiState.showFloatingActionButton,
355429
onShowFloatingActionButtonChange = { onUiStateChange(uiState.copy(showFloatingActionButton = it)) },
356430
enablePageUserScroll = uiState.enablePageUserScroll,

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ android-compileSdk = "36"
44
android-targetSdk = "36"
55

66
android-gradle-plugin = "8.9.2"
7-
androidx-activity-compose = "1.10.0"
7+
androidx-activity-compose = "1.10.1"
88
androidx-window = "1.3.0"
99
androidx-graphics-shapes = "1.0.0-alpha06"
1010
compose-plugin = "1.8.0-beta02"
@@ -25,4 +25,4 @@ android-library = { id = "com.android.library", version.ref = "android-gradle-pl
2525
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
2626
jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
2727
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
28-
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
28+
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package top.yukonga.miuix.kmp.basic
2+
3+
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.animation.ExperimentalAnimationApi
5+
import androidx.compose.animation.fadeIn
6+
import androidx.compose.animation.fadeOut
7+
import androidx.compose.animation.togetherWith
8+
import androidx.compose.foundation.layout.Arrangement
9+
import androidx.compose.foundation.layout.Box
10+
import androidx.compose.foundation.layout.Column
11+
import androidx.compose.foundation.layout.PaddingValues
12+
import androidx.compose.foundation.layout.Row
13+
import androidx.compose.foundation.layout.WindowInsets
14+
import androidx.compose.foundation.layout.asPaddingValues
15+
import androidx.compose.foundation.layout.fillMaxSize
16+
import androidx.compose.foundation.layout.navigationBars
17+
import androidx.compose.foundation.layout.padding
18+
import androidx.compose.runtime.Composable
19+
import androidx.compose.ui.Alignment
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.graphics.Color
22+
import androidx.compose.ui.unit.Dp
23+
import androidx.compose.ui.unit.dp
24+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.BottomCenter
25+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.BottomLeft
26+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.BottomRight
27+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.LeftBottom
28+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.LeftCenter
29+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.LeftTop
30+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.RightBottom
31+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.RightCenter
32+
import top.yukonga.miuix.kmp.basic.ToolbarPosition.RightTop
33+
import top.yukonga.miuix.kmp.theme.MiuixTheme
34+
35+
@OptIn(ExperimentalAnimationApi::class)
36+
@Composable
37+
/**
38+
* A floating toolbar that can be positioned at various screen edges.
39+
* Supports animated transitions between positions and customizable content.
40+
*
41+
* @param modifier Modifier applied to the toolbar content.
42+
* @param cornerRadius Corner radius of the toolbar background.
43+
* @param position ToolbarPosition specifying where the toolbar is aligned.
44+
* @param color Background color of the toolbar.
45+
* @param insideMargin Padding between the toolbar and screen edges.
46+
* @param buttons Composable content of the toolbar (e.g., buttons).
47+
*/
48+
fun FloatingToolbar(
49+
modifier: Modifier = Modifier,
50+
cornerRadius: Dp = CardDefaults.CornerRadius,
51+
position: ToolbarPosition = BottomCenter,
52+
color: Color = MiuixTheme.colorScheme.background,
53+
insideMargin: PaddingValues = PaddingValues(12.dp, 8.dp),
54+
buttons: @Composable () -> Unit
55+
) {
56+
val alignment = position.toAlignment()
57+
val isHorizontal = position.isBottomRow()
58+
val bottomPadding = if (position.needsBottomInset())
59+
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
60+
else 0.dp
61+
62+
Box(Modifier.fillMaxSize()) {
63+
Box(
64+
modifier = Modifier
65+
.align(alignment)
66+
.padding(insideMargin)
67+
.padding(bottom = bottomPadding)
68+
) {
69+
Card(
70+
cornerRadius = cornerRadius,
71+
color = color
72+
) {
73+
AnimatedContent(
74+
targetState = position,
75+
transitionSpec = { fadeIn() togetherWith fadeOut() },
76+
label = "toolbar-animation"
77+
) {
78+
if (isHorizontal) {
79+
Row(
80+
horizontalArrangement = Arrangement.spacedBy(12.dp),
81+
verticalAlignment = Alignment.CenterVertically,
82+
modifier = modifier.padding(vertical = 8.dp, horizontal = 8.dp)
83+
) { buttons() }
84+
} else {
85+
Column(
86+
verticalArrangement = Arrangement.spacedBy(12.dp),
87+
horizontalAlignment = Alignment.CenterHorizontally,
88+
modifier = modifier.padding(vertical = 8.dp, horizontal = 8.dp)
89+
) { buttons() }
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
/**
98+
* Returns true if the position is one of the bottom row options.
99+
*/
100+
fun ToolbarPosition.isBottomRow(): Boolean = this in listOf(
101+
BottomLeft,
102+
BottomCenter,
103+
BottomRight
104+
)
105+
106+
/**
107+
* Returns true if the position should include bottom system inset padding.
108+
*/
109+
fun ToolbarPosition.needsBottomInset(): Boolean = this in listOf(
110+
LeftBottom,
111+
RightBottom,
112+
BottomLeft,
113+
BottomCenter,
114+
BottomRight
115+
)
116+
117+
/**
118+
* Represents the position of a toolbar in a layout.
119+
*
120+
* @property LeftTop Top left corner of the container
121+
* @property LeftCenter Center left of the container
122+
* @property LeftBottom Bottom left corner of the container
123+
* @property RightTop Top right corner of the container
124+
* @property RightCenter Center right of the container
125+
* @property RightBottom Bottom right corner of the container
126+
* @property BottomLeft Left side of the bottom row
127+
* @property BottomCenter Center of the bottom row
128+
* @property BottomRight Right side of the bottom row
129+
*/
130+
enum class ToolbarPosition {
131+
LeftTop,
132+
LeftCenter,
133+
LeftBottom,
134+
RightTop,
135+
RightCenter,
136+
RightBottom,
137+
BottomLeft,
138+
BottomCenter,
139+
BottomRight;
140+
141+
fun toAlignment(): Alignment {
142+
return when (this) {
143+
LeftTop -> Alignment.TopStart
144+
LeftCenter -> Alignment.CenterStart
145+
LeftBottom -> Alignment.BottomStart
146+
RightTop -> Alignment.TopEnd
147+
RightCenter -> Alignment.CenterEnd
148+
RightBottom -> Alignment.BottomEnd
149+
BottomLeft -> Alignment.BottomStart
150+
BottomCenter -> Alignment.BottomCenter
151+
BottomRight -> Alignment.BottomEnd
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)