Add collapse / expand to LeftPanel (#54)

* feature: Add DS module

* feature: Use FloconTheme

* feature: Migrate MaterialTheme to FloconTheme

* fix: Merge

* feature: Add wide navigation rail

* feature: Rework panel

* feature: Rework panel

* fix: Discussion

* fix: Size

---------

Co-authored-by: Raphael Teyssandier <rteyssandier@sephora.fr>
This commit is contained in:
Raphael Teyssandier 2025-08-05 17:03:54 +02:00 committed by GitHub
parent af9d975123
commit 9e0e3b09df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 468 additions and 251 deletions

View file

@ -1,17 +1,38 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package io.github.openflocon.flocondesktop.main.ui
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ChevronRight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.common.ui.FloconColors
import io.github.openflocon.flocondesktop.features.analytics.ui.view.AnalyticsScreen
import io.github.openflocon.flocondesktop.features.dashboard.ui.view.DashboardScreen
import io.github.openflocon.flocondesktop.features.database.ui.view.DatabaseScreen
@ -28,6 +49,9 @@ import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelItem
import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelState
import io.github.openflocon.flocondesktop.main.ui.settings.SettingsScreen
import io.github.openflocon.flocondesktop.main.ui.view.leftpannel.LeftPanelView
import io.github.openflocon.flocondesktop.main.ui.view.leftpannel.PanelMaxWidth
import io.github.openflocon.flocondesktop.main.ui.view.leftpannel.PanelMinWidth
import io.github.openflocon.library.designsystem.components.FloconIcon
import org.koin.compose.viewmodel.koinViewModel
@Composable
@ -60,87 +84,122 @@ private fun MainScreen(
onClickLeftPanelItem: (LeftPanelItem) -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier) {
// TODO navigation
Row(modifier = Modifier.fillMaxSize()) {
LeftPanelView(
modifier = Modifier.width(300.dp)
.fillMaxHeight(),
onClickItem = onClickLeftPanelItem,
state = leftPanelState,
devicesState = devicesState,
onDeviceSelected = onDeviceSelected,
)
var expanded by remember { mutableStateOf(true) }
val width by animateDpAsState(targetValue = if (expanded) PanelMaxWidth else PanelMinWidth)
var windowSize by remember { mutableStateOf(IntSize.Zero) }
val position by animateDpAsState(
targetValue = if (expanded) PanelMaxWidth else PanelMinWidth
)
val rotate by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
Box(modifier = Modifier.fillMaxSize().shadow(6.dp)) {
when (subScreen) {
SubScreen.Network ->
NetworkScreen(
modifier = Modifier
.fillMaxSize(),
)
Row(
modifier = modifier
.fillMaxSize()
.onGloballyPositioned {
windowSize = it.size // TODO Add windowsize lib
}
) {
LeftPanelView(
modifier = Modifier
.width(width)
.fillMaxHeight(),
expanded = expanded,
onClickItem = onClickLeftPanelItem,
state = leftPanelState,
devicesState = devicesState,
onDeviceSelected = onDeviceSelected
)
Box(
modifier = Modifier
.fillMaxSize()
.shadow(6.dp)
) {
when (subScreen) {
SubScreen.Network ->
NetworkScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Database ->
DatabaseScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Database ->
DatabaseScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Images ->
ImagesScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Images ->
ImagesScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Files ->
FilesScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Files ->
FilesScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Tables ->
TableScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Tables ->
TableScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.SharedPreferences ->
SharedPreferencesScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.SharedPreferences ->
SharedPreferencesScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Dashboard ->
DashboardScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Dashboard ->
DashboardScreen(
modifier = Modifier
.fillMaxSize(),
)
SubScreen.Settings -> {
SettingsScreen(
modifier =
SubScreen.Settings -> {
SettingsScreen(
modifier =
Modifier
.fillMaxSize(),
)
}
)
}
SubScreen.Deeplinks -> {
DeeplinkScreen(
modifier =
SubScreen.Deeplinks -> {
DeeplinkScreen(
modifier =
Modifier
.fillMaxSize(),
)
}
)
}
SubScreen.Analytics -> {
AnalyticsScreen(
modifier =
SubScreen.Analytics -> {
AnalyticsScreen(
modifier =
Modifier
.fillMaxSize(),
)
}
)
}
}
}
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.width(20.dp)
.height(60.dp)
.graphicsLayer {
translationX = position.toPx() - size.width / 2
translationY = (windowSize.height / 2) - (size.height / 2)
}
.clip(RoundedCornerShape(4.dp))
.background(FloconColors.pannel) // TODO Change
.clickable(onClick = { expanded = !expanded })
) {
FloconIcon(
imageVector = Icons.Outlined.ChevronRight,
tint = Color.LightGray,
modifier = Modifier.rotate(rotate)
)
}
}

View file

@ -2,24 +2,34 @@
package io.github.openflocon.flocondesktop.main.ui.view
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.PhoneDisabled
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ExposedDropdownMenuBoxScope
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -27,6 +37,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import flocondesktop.composeapp.generated.resources.Res
@ -37,100 +48,142 @@ import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel
import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel
import io.github.openflocon.flocondesktop.main.ui.model.previewDeviceItemUiModelPreview
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconCircularProgressIndicator
import io.github.openflocon.library.designsystem.components.FloconIcon
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
internal fun DeviceSelectorView(
pannelExpanded: Boolean,
devicesState: DevicesStateUiModel,
onDeviceSelected: (DeviceItemUiModel) -> Unit,
modifier: Modifier = Modifier,
) {
val shape = RoundedCornerShape(12.dp)
var dropDownExpanded by remember { mutableStateOf(false) }
val topRadius by animateDpAsState(
targetValue = if (dropDownExpanded) 0.dp else 12.dp
)
var expanded by remember { mutableStateOf(false) }
LaunchedEffect(pannelExpanded) {
if (!pannelExpanded)
dropDownExpanded = false
}
Box(
modifier = modifier
.shadow(elevation = 4.dp, shape = shape, clip = true)
.background(color = FloconColors.pannel)
.clickable {
expanded = true
},
ExposedDropdownMenuBox(
expanded = dropDownExpanded,
onExpandedChange = {
if (pannelExpanded)
dropDownExpanded = it
}
) {
Column(
modifier = Modifier.padding(
horizontal = 8.dp,
vertical = 4.dp,
),
AnimatedContent(
targetState = devicesState,
modifier = modifier
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(
topStart = topRadius,
topEnd = topRadius,
bottomEnd = 12.dp,
bottomStart = 12.dp
),
clip = true
)
.background(color = FloconColors.pannel)
.padding(horizontal = 8.dp, vertical = 4.dp)
) { targetState ->
when (targetState) {
DevicesStateUiModel.Empty -> Empty(expanded = pannelExpanded)
DevicesStateUiModel.Loading -> Loading()
is DevicesStateUiModel.WithDevices -> Device(
state = targetState,
pannelExpanded = pannelExpanded
)
}
}
ExposedDropdownMenu(
expanded = dropDownExpanded,
onDismissRequest = { dropDownExpanded = false },
containerColor = FloconColors.pannel, // TODO Change
shadowElevation = 0.dp,
shape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp),
modifier = Modifier
.exposedDropdownSize()
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
when (devicesState) {
DevicesStateUiModel.Loading -> {
// hide
}
DevicesStateUiModel.Empty -> {
Text(
text = "No Devices Found",
modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp),
style = FloconTheme.typography.bodyMedium,
color = FloconTheme.colorScheme.onSurface,
if (devicesState is DevicesStateUiModel.WithDevices) {
devicesState.devices.forEach { device ->
DeviceView(
device = device,
pannelExpanded = pannelExpanded,
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.clickable(enabled = pannelExpanded) {
onDeviceSelected(device)
dropDownExpanded = false
},
)
}
is DevicesStateUiModel.WithDevices -> {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it }
) {
DeviceView(
device = devicesState.selected,
modifier = Modifier
.fillMaxWidth()
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
)
DropdownMenu( // TODO Change to ExposedDropdownMenu when width is fixed https://issuetracker.google.com/issues/205589613
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.exposedDropdownSize(matchTextFieldWidth = true)
) {
devicesState.devices.forEach { device ->
DeviceView(
device = device,
modifier = Modifier.fillMaxWidth().clickable {
onDeviceSelected(device)
expanded = false // Close the dropdown after selection
},
)
// DropdownMenuItem(
// text = {
// DeviceView(
// device = device,
// )
// },
// onClick = {
// onDeviceSelected(device)
// expanded = false // Close the dropdown after selection
// },
// modifier = Modifier.fillMaxWidth(),
// )
}
}
}
}
}
HorizontalDivider(color = Color.LightGray) // TODO Change
}
}
}
@Composable
private fun Empty(
expanded: Boolean
) {
Crossfade(expanded) {
if (it) {
Text(
text = "No Devices Found",
modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp),
style = FloconTheme.typography.bodyMedium,
color = FloconTheme.colorScheme.onSurface,
)
} else {
FloconIcon(
imageVector = Icons.Outlined.PhoneDisabled,
tint = Color.White,
modifier = Modifier
.fillMaxHeight()
.wrapContentHeight()
)
}
}
}
@Composable
private fun Loading() {
FloconCircularProgressIndicator()
}
@Composable
private fun ExposedDropdownMenuBoxScope.Device(
pannelExpanded: Boolean,
state: DevicesStateUiModel.WithDevices
) {
DeviceView(
device = state.selected,
pannelExpanded = pannelExpanded,
modifier = Modifier
.fillMaxWidth()
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
)
}
@Composable
private fun DeviceView(
device: DeviceItemUiModel,
pannelExpanded: Boolean,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(horizontal = 8.dp, vertical = 4.dp),
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Image(
@ -138,22 +191,27 @@ private fun DeviceView(
painter = painterResource(Res.drawable.smartphone),
contentDescription = null,
)
Column(modifier = Modifier.padding(start = 4.dp)) {
Text(
text = device.deviceName, // Device Name
color = FloconColorScheme.onSurface,
style = FloconTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold),
)
Text(
text = device.appName,
color = FloconColorScheme.onSurface.copy(alpha = 0.5f),
style = FloconTheme.typography.bodySmall.copy(fontWeight = FontWeight.Thin),
)
Text(
text = device.appPackageName,
color = FloconColorScheme.onSurface.copy(alpha = 0.5f),
style = FloconTheme.typography.bodySmall.copy(fontWeight = FontWeight.Thin),
)
AnimatedVisibility(
visible = pannelExpanded,
exit = fadeOut(tween(100))
) {
Column(modifier = Modifier.padding(start = 4.dp)) {
Text(
text = device.deviceName, // Device Name
color = FloconColorScheme.onSurface,
style = FloconTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold),
)
Text(
text = device.appName,
color = FloconColorScheme.onSurface.copy(alpha = 0.5f),
style = FloconTheme.typography.bodySmall.copy(fontWeight = FontWeight.Thin),
)
Text(
text = device.appPackageName,
color = FloconColorScheme.onSurface.copy(alpha = 0.5f),
style = FloconTheme.typography.bodySmall.copy(fontWeight = FontWeight.Thin),
)
}
}
}
}
@ -164,6 +222,7 @@ private fun DeviceViewPreview() {
FloconTheme {
DeviceView(
device = previewDeviceItemUiModelPreview(),
pannelExpanded = false
)
}
}

View file

@ -12,6 +12,6 @@ fun LeftPannelDivider(modifier: Modifier = Modifier) {
HorizontalDivider(
modifier = modifier.padding(horizontal = 4.dp),
thickness = 1.dp,
color = Color.Gray,
color = Color.Gray, // TODO Change
)
}

View file

@ -1,20 +1,25 @@
@file:Suppress("UnusedReceiverParameter")
package io.github.openflocon.flocondesktop.main.ui.view.leftpannel
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
@ -36,16 +41,23 @@ import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel
import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel
import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelItem
import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelState
import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPannelSection
import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.previewLeftPannelState
import io.github.openflocon.flocondesktop.main.ui.model.previewDevicesStateUiModel
import io.github.openflocon.flocondesktop.main.ui.view.DeviceSelectorView
import io.github.openflocon.library.designsystem.FloconTheme
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import java.awt.Menu
val PanelMaxWidth = 275.dp
val PanelMinWidth = 64.dp
val PanelContentMinSize = 40.dp
@Composable
fun LeftPanelView(
state: LeftPanelState,
expanded: Boolean,
onClickItem: (LeftPanelItem) -> Unit,
devicesState: DevicesStateUiModel, // Pass the state of devices
onDeviceSelected: (DeviceItemUiModel) -> Unit, // Callback when a device is selected
@ -53,83 +65,105 @@ fun LeftPanelView(
) {
Column(
modifier = modifier
.background(FloconColorScheme.surface)
.background(FloconTheme.colorScheme.surface)
.padding(horizontal = 12.dp, vertical = 16.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 8.dp),
Title(expanded = expanded)
Spacer(modifier = Modifier.height(12.dp))
MenuSection(
items = state.sections,
expanded = expanded,
onClickItem = onClickItem
)
Spacer(modifier = Modifier.height(12.dp))
Spacer(Modifier.weight(1f))
MenuItems(
items = state.bottomItems,
expanded = expanded,
onClickItem = onClickItem
)
LeftPannelDivider(modifier = Modifier.padding(vertical = 12.dp))
DeviceSelectorView(
pannelExpanded = expanded,
modifier = Modifier
.fillMaxWidth()
.height(64.dp),
devicesState = devicesState,
onDeviceSelected = onDeviceSelected,
)
}
}
@Composable
fun Title(
expanded: Boolean
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(PanelContentMinSize),
) {
Image(
modifier = Modifier
.size(PanelContentMinSize)
.clip(RoundedCornerShape(8.dp)),
painter = painterResource(Res.drawable.app_icon_small),
contentDescription = "Description de mon image",
)
AnimatedVisibility(
visible = expanded,
enter = fadeIn() + slideInHorizontally(),
exit = fadeOut() + slideOutHorizontally()
) {
Image(
modifier = Modifier.size(40.dp)
.clip(RoundedCornerShape(8.dp)),
painter = painterResource(Res.drawable.app_icon_small),
contentDescription = "Description de mon image",
)
Spacer(modifier = Modifier.width(12.dp))
Text(
"Flocon",
text = "Flocon",
fontSize = 32.sp,
style = FloconTheme.typography.titleLarge.copy(
color = FloconColorScheme.onSurface,
fontWeight = FontWeight.Bold,
),
modifier = Modifier.padding(start = 12.dp)
)
}
}
}
Spacer(
modifier = Modifier.fillMaxWidth()
.height(12.dp),
@Composable
private fun ColumnScope.MenuSection(
items: List<LeftPannelSection>,
expanded: Boolean,
onClickItem: (LeftPanelItem) -> Unit
) {
items.fastForEachIndexed { index, section ->
PannelLabel(
expanded = expanded,
text = section.title
)
MenuItems(
items = section.items,
expanded = expanded,
onClickItem = onClickItem
)
}
}
Column(
modifier = Modifier.fillMaxWidth()
.weight(1f)
.verticalScroll(rememberScrollState()),
) {
state.sections.fastForEachIndexed { index, section ->
PannelLabel(
text = section.title,
modifier = if (index != 0) Modifier.padding(top = 12.dp) else Modifier,
)
section.items.fastForEach { item ->
PannelView(
modifier = Modifier.fillMaxWidth(),
icon = item.icon,
text = item.text,
isSelected = item.isSelected,
onClick = {
onClickItem(item)
},
)
}
}
}
// settings
Column(modifier = Modifier) {
state.bottomItems.fastForEach { item ->
PannelView(
modifier = Modifier.fillMaxWidth(),
icon = item.icon,
text = item.text,
isSelected = item.isSelected,
onClick = {
onClickItem(item)
},
)
}
LeftPannelDivider(
modifier = Modifier.fillMaxWidth()
.padding(vertical = 12.dp),
)
DeviceSelectorView(
modifier = Modifier.fillMaxWidth(),
devicesState = devicesState,
onDeviceSelected = onDeviceSelected,
)
}
@Composable
private fun ColumnScope.MenuItems(
items: List<LeftPanelItem>,
expanded: Boolean,
onClickItem: (LeftPanelItem) -> Unit
) {
items.fastForEach { item ->
PannelView(
modifier = Modifier
.height(PanelContentMinSize)
.fillMaxWidth(),
icon = item.icon,
text = item.text,
expanded = expanded,
isSelected = item.isSelected,
onClick = { onClickItem(item) },
)
}
}
@ -148,7 +182,8 @@ private fun LeftPannelViewPreview() {
},
modifier = Modifier.wrapContentHeight(),
onDeviceSelected = {},
devicesState = previewDevicesStateUiModel(),
expanded = false,
devicesState = previewDevicesStateUiModel()
)
}
}

View file

@ -1,8 +1,15 @@
package io.github.openflocon.flocondesktop.main.ui.view.leftpannel
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@ -12,18 +19,34 @@ import io.github.openflocon.library.designsystem.FloconTheme
@Composable
fun PannelLabel(
text: String,
expanded: Boolean,
modifier: Modifier = Modifier,
) {
Text(
Crossfade(
targetState = expanded,
modifier = modifier
.padding(
start = 12.dp,
bottom = 4.dp,
),
text = text,
style = FloconTheme.typography.bodyLarge.copy(
fontWeight = FontWeight.Thin,
color = FloconColorScheme.onSurface.copy(alpha = 0.5f),
),
)
.fillMaxWidth()
.height(28.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.CenterStart
) {
if (it) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(start = 12.dp, bottom = 4.dp),
text = text,
style = FloconTheme.typography.bodyLarge.copy(
fontWeight = FontWeight.Thin,
color = FloconColorScheme.onSurface.copy(alpha = 0.5f),
)
)
} else {
HorizontalDivider()
}
}
}
}

View file

@ -1,18 +1,28 @@
@file:OptIn(ExperimentalSharedTransitionApi::class)
package io.github.openflocon.flocondesktop.main.ui.view.leftpannel
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@ -28,35 +38,49 @@ fun PannelView(
icon: ImageVector,
text: String,
isSelected: Boolean,
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val shape = RoundedCornerShape(8.dp)
val shadow by animateDpAsState(
targetValue = if (isSelected) 6.dp else 0.dp,
label = "shadow"
)
val color by animateColorAsState(
targetValue = if (isSelected) FloconColors.pannel else FloconTheme.colorScheme.surface,
label = "color"
)
Row(
modifier = modifier
.then(
if (isSelected)
Modifier
.shadow(elevation = 6.dp, shape = shape, clip = true)
.background(FloconColors.pannel)
else Modifier,
)
.height(28.dp)
.shadow(elevation = shadow, shape = shape, clip = true, ambientColor = color, spotColor = color)
.background(color)
.clickable(onClick = onClick, interactionSource = null, indication = null)
.padding(horizontal = 12.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.size(16.dp),
modifier = Modifier
.size(16.dp),
imageVector = icon,
contentDescription = "Description de mon image",
tint = FloconColorScheme.onSurface
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = text,
color = FloconColorScheme.onSurface,
style = FloconTheme.typography.bodyMedium,
)
AnimatedVisibility(
expanded,
enter = fadeIn(),
exit = fadeOut(tween(100))
) {
Text(
text = text,
color = FloconColorScheme.onSurface,
style = FloconTheme.typography.bodyMedium,
maxLines = 1,
modifier = Modifier.padding(start = 12.dp)
)
}
}
}
@ -69,6 +93,7 @@ private fun PannelViewPreview() {
text = "text",
isSelected = false,
onClick = {},
expanded = false
)
}
}
@ -82,6 +107,7 @@ private fun PannelViewPreview_Selected() {
text = "text",
isSelected = true,
onClick = {},
expanded = false
)
}
}

View file

@ -27,7 +27,6 @@ room = "2.7.1"
ksp = "2.2.0-2.0.2"
ktlintGradle = "13.0.0"
aboutLibraries = "12.2.4"
other-molecule = "2.1.0"
kotlinStdlib = "2.2.0"
runner = "1.5.2"
core = "1.5.0"

View file

@ -0,0 +1,12 @@
package io.github.openflocon.library.designsystem.components
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun FloconCircularProgressIndicator(
modifier: Modifier = Modifier
) {
CircularProgressIndicator(modifier = modifier)
}

View file

@ -1,18 +1,22 @@
package io.github.openflocon.library.designsystem.components
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@Composable
fun FloconIcon(
imageVector: ImageVector,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current
) {
Icon(
imageVector = imageVector,
contentDescription = null,
tint = tint,
modifier = modifier
)
}

View file

@ -32,7 +32,7 @@ internal val FloconDarkColorScheme = darkColorScheme(
onPrimaryContainer = FloconColors.onPrimary,
secondary = FloconColors.secondary,
onSecondary = FloconColors.onSecondary,
secondaryContainer = FloconColors.secondary.copy(alpha = 0.2f),
secondaryContainer = FloconColors.secondary,//.copy(alpha = 0.2f),
onSecondaryContainer = FloconColors.onSecondary,
tertiary = FloconColors.tertiary,
onTertiary = FloconColors.onTertiary,