mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
feat(desktop): unify macOS window chrome
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package com.vnidrop.app
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -10,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
@@ -36,6 +39,7 @@ import com.vnidrop.app.ui.shell.ScreenScrollContainer
|
||||
import com.vnidrop.app.core.TransferDirection
|
||||
import com.vnidrop.app.ui.state.WindowClass
|
||||
import com.vnidrop.app.ui.state.windowClassFor
|
||||
import com.vnidrop.app.ui.theme.LocalVniDropColors
|
||||
import com.vnidrop.app.ui.theme.VniDropTheme
|
||||
import com.vnidrop.app.ui.theme.rememberResolvedDarkTheme
|
||||
import kotlinx.coroutines.flow.filter
|
||||
@@ -43,7 +47,12 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
@Composable
|
||||
fun App(dependencies: AppDependencies) {
|
||||
fun App(
|
||||
dependencies: AppDependencies,
|
||||
windowChromeTopInset: Dp = 0.dp,
|
||||
windowContentTopStartRadius: Dp = 0.dp,
|
||||
windowChrome: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
val graphHolder = viewModel { AppGraphViewModel(dependencies) }
|
||||
val graph = graphHolder.graph
|
||||
|
||||
@@ -122,55 +131,67 @@ fun App(dependencies: AppDependencies) {
|
||||
val darkTheme = rememberResolvedDarkTheme(appState.themeMode)
|
||||
PlatformSystemAppearance(darkTheme)
|
||||
VniDropTheme(isDarkTheme = darkTheme) {
|
||||
BoxWithConstraints {
|
||||
val windowClass = windowClassFor(maxWidth.value)
|
||||
val showSendAction = appState.destination == AppDestination.Send &&
|
||||
windowClass == WindowClass.Phone &&
|
||||
sendState.selectedTransferId?.let { selectedId ->
|
||||
sendCoreState.transfers.any { it.transferId == selectedId }
|
||||
} != true &&
|
||||
sendCoreState.transfers.any { it.direction == TransferDirection.Send }
|
||||
val showReceiveAction = appState.destination == AppDestination.Receive &&
|
||||
windowClass == WindowClass.Phone &&
|
||||
!receiveState.isAcquisitionOpen &&
|
||||
receiveCoreState.transfers.any { it.direction == TransferDirection.Receive }
|
||||
AppShell(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
selectedDestination = appState.destination,
|
||||
windowClass = windowClass,
|
||||
onDestinationSelected = appViewModel::selectDestination,
|
||||
overlay = {
|
||||
VniDropSnackbarHost(graph.messages, Modifier.align(Alignment.BottomCenter))
|
||||
},
|
||||
floatingAction = if (showSendAction) {
|
||||
{
|
||||
SendFloatingAction(
|
||||
onClick = sendViewModel::openComposer,
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
||||
)
|
||||
}
|
||||
} else if (showReceiveAction) {
|
||||
{
|
||||
ReceiveFloatingAction(
|
||||
onClick = receiveViewModel::openAcquisition,
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(LocalVniDropColors.current.backgroundSurface200),
|
||||
) {
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = windowChromeTopInset),
|
||||
) {
|
||||
when (appState.destination) {
|
||||
AppDestination.Send -> SendRoute(sendViewModel, windowClass)
|
||||
AppDestination.Receive -> ReceiveRoute(receiveViewModel, windowClass)
|
||||
AppDestination.Settings -> ScreenScrollContainer { SettingsRoute(settingsViewModel, windowClass) }
|
||||
val windowClass = windowClassFor(maxWidth.value)
|
||||
val showSendAction = appState.destination == AppDestination.Send &&
|
||||
windowClass == WindowClass.Phone &&
|
||||
sendState.selectedTransferId?.let { selectedId ->
|
||||
sendCoreState.transfers.any { it.transferId == selectedId }
|
||||
} != true &&
|
||||
sendCoreState.transfers.any { it.direction == TransferDirection.Send }
|
||||
val showReceiveAction = appState.destination == AppDestination.Receive &&
|
||||
windowClass == WindowClass.Phone &&
|
||||
!receiveState.isAcquisitionOpen &&
|
||||
receiveCoreState.transfers.any { it.direction == TransferDirection.Receive }
|
||||
AppShell(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
selectedDestination = appState.destination,
|
||||
windowClass = windowClass,
|
||||
mainContentTopStartRadius = windowContentTopStartRadius,
|
||||
onDestinationSelected = appViewModel::selectDestination,
|
||||
overlay = {
|
||||
VniDropSnackbarHost(graph.messages, Modifier.align(Alignment.BottomCenter))
|
||||
},
|
||||
floatingAction = if (showSendAction) {
|
||||
{
|
||||
SendFloatingAction(
|
||||
onClick = sendViewModel::openComposer,
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
||||
)
|
||||
}
|
||||
} else if (showReceiveAction) {
|
||||
{
|
||||
ReceiveFloatingAction(
|
||||
onClick = receiveViewModel::openAcquisition,
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
) {
|
||||
when (appState.destination) {
|
||||
AppDestination.Send -> SendRoute(sendViewModel, windowClass)
|
||||
AppDestination.Receive -> ReceiveRoute(receiveViewModel, windowClass)
|
||||
AppDestination.Settings -> ScreenScrollContainer { SettingsRoute(settingsViewModel, windowClass) }
|
||||
}
|
||||
}
|
||||
ApprovalModalHost(
|
||||
state = approvalState,
|
||||
onAccept = graph.approvalCoordinator::accept,
|
||||
onRefuse = graph.approvalCoordinator::refuse,
|
||||
)
|
||||
}
|
||||
ApprovalModalHost(
|
||||
state = approvalState,
|
||||
onAccept = graph.approvalCoordinator::accept,
|
||||
onRefuse = graph.approvalCoordinator::refuse,
|
||||
)
|
||||
windowChrome?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vnidrop.app.ui.theme.LocalVniDropColors
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
@@ -36,6 +37,7 @@ import org.jetbrains.compose.resources.stringResource
|
||||
fun AppSidebarNavigation(
|
||||
selected: AppDestination,
|
||||
onDestinationSelected: (AppDestination) -> Unit,
|
||||
dividerTopInset: Dp = 0.dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = LocalVniDropColors.current
|
||||
@@ -62,7 +64,8 @@ fun AppSidebarNavigation(
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = dividerTopInset)
|
||||
.width(1.dp)
|
||||
.fillMaxHeight()
|
||||
.background(colors.borderDefault),
|
||||
|
||||
@@ -10,9 +10,12 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vnidrop.app.ui.navigation.AppBottomNavigation
|
||||
import com.vnidrop.app.ui.navigation.AppDestination
|
||||
@@ -26,6 +29,7 @@ fun AppShell(
|
||||
modifier: Modifier = Modifier,
|
||||
selectedDestination: AppDestination,
|
||||
windowClass: WindowClass,
|
||||
mainContentTopStartRadius: Dp = 0.dp,
|
||||
onDestinationSelected: (AppDestination) -> Unit,
|
||||
overlay: @Composable BoxScope.() -> Unit = {},
|
||||
floatingAction: (@Composable BoxScope.() -> Unit)? = null,
|
||||
@@ -49,6 +53,7 @@ fun AppShell(
|
||||
} else {
|
||||
WideShell(
|
||||
selectedDestination = selectedDestination,
|
||||
mainContentTopStartRadius = mainContentTopStartRadius,
|
||||
onDestinationSelected = onDestinationSelected,
|
||||
overlay = overlay,
|
||||
floatingAction = floatingAction,
|
||||
@@ -61,17 +66,38 @@ fun AppShell(
|
||||
@Composable
|
||||
private fun WideShell(
|
||||
selectedDestination: AppDestination,
|
||||
mainContentTopStartRadius: Dp,
|
||||
onDestinationSelected: (AppDestination) -> Unit,
|
||||
overlay: @Composable BoxScope.() -> Unit,
|
||||
floatingAction: (@Composable BoxScope.() -> Unit)?,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
val colors = LocalVniDropColors.current
|
||||
val roundedContent = mainContentTopStartRadius > 0.dp
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(if (roundedContent) Modifier.background(colors.backgroundSurface200) else Modifier),
|
||||
) {
|
||||
AppSidebarNavigation(
|
||||
selected = selectedDestination,
|
||||
dividerTopInset = mainContentTopStartRadius,
|
||||
onDestinationSelected = onDestinationSelected,
|
||||
)
|
||||
Box(modifier = Modifier.weight(1f).fillMaxSize()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxSize()
|
||||
.then(
|
||||
if (roundedContent) {
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(topStart = mainContentTopStartRadius))
|
||||
.background(colors.backgroundDashCanvas)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
),
|
||||
) {
|
||||
content()
|
||||
floatingAction?.invoke(this)
|
||||
Box(Modifier.fillMaxSize().padding(bottom = if (floatingAction == null) 0.dp else 72.dp)) { overlay() }
|
||||
|
||||
Reference in New Issue
Block a user