Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
Expand All @@ -37,6 +38,11 @@ internal fun SignedOutConnectionActions(
buttonHeight: Dp = 48.dp,
spacing: Dp = 10.dp,
fontSize: Int = 16,
// The quiet half of the pair. On a page it is a card; in the sidebar it is
// chrome, so the layer is the caller's to name rather than assumed here.
quietBackground: Color = MaterialTheme.colorScheme.surface,
quietBorder: Color = MaterialTheme.colorScheme.outlineVariant,
quietContent: Color = MaterialTheme.colorScheme.onSurface,
) {
val shape = RoundedCornerShape(buttonHeight / 2)
Column(
Expand All @@ -49,8 +55,8 @@ internal fun SignedOutConnectionActions(
.fillMaxWidth()
.height(buttonHeight)
.clip(shape)
.background(if (primaryScan) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface)
.border(1.dp, if (primaryScan) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant, shape)
.background(if (primaryScan) MaterialTheme.colorScheme.primary else quietBackground)
.border(1.dp, if (primaryScan) MaterialTheme.colorScheme.primary else quietBorder, shape)
.clickable(enabled = enabled, role = Role.Button, onClick = onScan)
.semantics(mergeDescendants = true) {
contentDescription = scanLabel
Expand All @@ -61,7 +67,7 @@ internal fun SignedOutConnectionActions(
scanLabel,
fontSize = fontSize.sp,
fontWeight = FontWeight.Bold,
color = if (primaryScan) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface,
color = if (primaryScan) MaterialTheme.colorScheme.onPrimary else quietContent,
)
}
}
Expand All @@ -70,8 +76,8 @@ internal fun SignedOutConnectionActions(
.fillMaxWidth()
.height(buttonHeight)
.clip(shape)
.background(if (primaryScan) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.primary)
.border(1.dp, if (primaryScan) MaterialTheme.colorScheme.outlineVariant else MaterialTheme.colorScheme.primary, shape)
.background(if (primaryScan) quietBackground else MaterialTheme.colorScheme.primary)
.border(1.dp, if (primaryScan) quietBorder else MaterialTheme.colorScheme.primary, shape)
.clickable(enabled = enabled, role = Role.Button, onClick = onOpenAccount)
.semantics(mergeDescendants = true) {
contentDescription = accountLabel
Expand All @@ -82,7 +88,7 @@ internal fun SignedOutConnectionActions(
accountLabel,
fontSize = fontSize.sp,
fontWeight = FontWeight.Bold,
color = if (primaryScan) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onPrimary,
color = if (primaryScan) quietContent else MaterialTheme.colorScheme.onPrimary,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,13 +433,19 @@ private fun RemoteSessionListHeader(
diameter = 38,
onClick = onToggleViewSettings,
modifier = Modifier.testTag(VIEW_SETTINGS_TOGGLE_TEST_TAG),
background = MaterialTheme.colorScheme.surface,
border = MaterialTheme.colorScheme.outlineVariant,
tint = MaterialTheme.colorScheme.onSurface,
)
SidebarCircleButton(
icon = R.drawable.ic_symbol_magnifyingglass,
contentDescription = stringResource(R.string.sidebar_search),
diameter = 38,
onClick = onToggleSearch,
modifier = Modifier.testTag(SESSION_SEARCH_TOGGLE_TEST_TAG),
background = MaterialTheme.colorScheme.surface,
border = MaterialTheme.colorScheme.outlineVariant,
tint = MaterialTheme.colorScheme.onSurface,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import com.openbitfun.mobile.app.ui.remote.ConnectView
import com.openbitfun.mobile.app.ui.settings.GeneralSettingsScreen
import com.openbitfun.mobile.app.ui.settings.SettingsScreen
import com.openbitfun.mobile.app.ui.shell.sidebar.AppSidebar
import com.openbitfun.mobile.app.ui.theme.openBitFunColors
import com.openbitfun.mobile.app.viewmodel.AccountViewModel
import com.openbitfun.mobile.core.feature.account.AccountIntent
import com.openbitfun.mobile.core.feature.account.AccountUiState
Expand Down Expand Up @@ -540,6 +541,10 @@ internal fun MobileScreen() {
if (sidebarWidth > 0) {
PermanentDrawerSheet(
Modifier.width(sidebarWidth.dp).testTag(MASTER_DETAIL_TEST_TAG),
// Material fills a drawer sheet from surfaceContainerLow;
// the rail paints its own chrome, so the sheet gets out
// of the way rather than tinting a second layer under it.
drawerContainerColor = openBitFunColors.sidebar.background,
) { sidebar() }
PaneSeparator(
if (previewVisible) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -36,6 +38,7 @@ import com.openbitfun.mobile.app.R
import com.openbitfun.mobile.app.ui.remote.SessionActionPopup
import com.openbitfun.mobile.app.ui.remote.SessionActionSheet
import com.openbitfun.mobile.app.ui.remote.SessionDetailsSheet
import com.openbitfun.mobile.app.ui.theme.openBitFunColors
import com.openbitfun.mobile.core.feature.account.AccountDeviceUi
import com.openbitfun.mobile.core.feature.connection.ConnectionPhase
import com.openbitfun.mobile.core.feature.connection.RemoteControlSource
Expand Down Expand Up @@ -107,71 +110,85 @@ internal fun AppSidebar(
var remoteActionAnchor by remember { mutableStateOf(IntRect.Zero) }
var remoteDetailsSessionId by rememberSaveable { mutableStateOf<String?>(null) }

Box(modifier = modifier.fillMaxSize().testTag(SIDEBAR_TEST_TAG)) {
// No spacedBy: the MiniApps row carries its own 4dp/8dp rhythm, and a
// column-level gap on top of it would push the section header away.
Column(
modifier = Modifier
// The rail is chrome, not another page: it paints the desktop client's
// `surface.chrome` itself and hands every unstyled descendant the matching
// ink, so nothing inside has to remember which layer it is on.
CompositionLocalProvider(LocalContentColor provides openBitFunColors.sidebar.ink) {
Box(
modifier = modifier
.fillMaxSize()
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 16.dp),
.background(openBitFunColors.sidebar.background)
.testTag(SIDEBAR_TEST_TAG),
) {
if (signedIn) {
SidebarAuthenticatedHeader(searchOpen, query, onQueryChange, onToggleSearch)
} else {
Text(stringResource(R.string.app_name), style = MaterialTheme.typography.titleLarge)
}
// No spacedBy: the MiniApps row carries its own 4dp/8dp rhythm, and a
// column-level gap on top of it would push the section header away.
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 16.dp),
) {
if (signedIn) {
SidebarAuthenticatedHeader(searchOpen, query, onQueryChange, onToggleSearch)
} else {
Text(
stringResource(R.string.app_name),
style = MaterialTheme.typography.titleLarge,
color = openBitFunColors.sidebar.ink,
)
}

com.openbitfun.mobile.app.ui.miniapps.MiniAppsButton(sidebar = true)
com.openbitfun.mobile.app.ui.miniapps.MiniAppsButton(sidebar = true)

Column(Modifier.weight(1f).verticalScroll(rememberScrollState()).padding(bottom = 142.dp)) {
SidebarRemoteWorkspaceSection(
connectionPhase = connectionPhase,
controlSource = remoteControlSource,
devices = remoteDevices,
selectedDeviceId = remoteSelectedDeviceId,
deviceName = remoteDeviceName,
remoteState = remoteState,
workspaceState = workspaceState,
workspaceDirectory = workspaceDirectory,
selectedSessionId = remoteSelectedSessionId.takeIf { remoteActive },
onConnect = onScanDesktop,
onRetryActive = onRetryRemoteDevice,
onRefreshDevices = onRefreshRemoteDevices,
refreshingDevices = refreshingRemoteDevices,
directoryRefreshError = directoryRefreshError,
onSelectDevice = onSelectRemoteDevice,
onOpenSession = onOpenRemoteSession,
onOpenActions = { row, anchor ->
remoteActionAnchor = anchor
remoteActionSession = row
},
onCreateInWorkspace = onCreateRemoteInWorkspace,
onOpenWorkspace = onOpenRemoteWorkspace,
onExpandWorkspace = onExpandRemoteWorkspace,
onRetryWorkspaceSessions = onRetryRemoteWorkspaceSessions,
onAddWorkspace = onAddRemoteWorkspace,
onWorkspaceTool = onWorkspaceTool,
)
}
Column(Modifier.weight(1f).verticalScroll(rememberScrollState()).padding(bottom = 142.dp)) {
SidebarRemoteWorkspaceSection(
connectionPhase = connectionPhase,
controlSource = remoteControlSource,
devices = remoteDevices,
selectedDeviceId = remoteSelectedDeviceId,
deviceName = remoteDeviceName,
remoteState = remoteState,
workspaceState = workspaceState,
workspaceDirectory = workspaceDirectory,
selectedSessionId = remoteSelectedSessionId.takeIf { remoteActive },
onConnect = onScanDesktop,
onRetryActive = onRetryRemoteDevice,
onRefreshDevices = onRefreshRemoteDevices,
refreshingDevices = refreshingRemoteDevices,
directoryRefreshError = directoryRefreshError,
onSelectDevice = onSelectRemoteDevice,
onOpenSession = onOpenRemoteSession,
onOpenActions = { row, anchor ->
remoteActionAnchor = anchor
remoteActionSession = row
},
onCreateInWorkspace = onCreateRemoteInWorkspace,
onOpenWorkspace = onOpenRemoteWorkspace,
onExpandWorkspace = onExpandRemoteWorkspace,
onRetryWorkspaceSessions = onRetryRemoteWorkspaceSessions,
onAddWorkspace = onAddRemoteWorkspace,
onWorkspaceTool = onWorkspaceTool,
)
}

}
}

// Over the list, not after it: the 84dp tail the list reserves is what
// keeps the last conversation from ending up underneath this.
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp, bottom = 16.dp),
) {
if (signedIn) {
SidebarAuthenticatedFooter({ onWorkspaceTool("", null, false) }, onOpenSettings)
} else {
SidebarSignedOutFooter(
showScan = connectionPhase != ConnectionPhase.CONNECTED,
onScanDesktop = onScanDesktop,
onOpenAccount = onOpenAccount,
)
// Over the list, not after it: the 84dp tail the list reserves is what
// keeps the last conversation from ending up underneath this.
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp, bottom = 16.dp),
) {
if (signedIn) {
SidebarAuthenticatedFooter({ onWorkspaceTool("", null, false) }, onOpenSettings)
} else {
SidebarSignedOutFooter(
showScan = connectionPhase != ConnectionPhase.CONNECTED,
onScanDesktop = onScanDesktop,
onOpenAccount = onOpenAccount,
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ import com.openbitfun.mobile.core.feature.connection.ConnectionTone
* The round carded button the sidebar's header and footer are built from,
* ported from the 38x38 and 46x46 stacks in `AppSidebar.ets`.
*
* Carded rather than a bare `IconButton`: the drawer's own background is the
* page colour, so an icon drawn straight onto it has no edge and reads as
* decoration instead of as something to press.
* Carded rather than a bare `IconButton`: the drawer's own background is flat
* chrome, so an icon drawn straight onto it has no edge and reads as decoration
* instead of as something to press.
*
* The colours default to the sidebar's own chrome because that is where this
* shape belongs. The session-list header reuses the shape on a page surface, so
* it passes the page roles instead — same button, correct layer.
*/
@Composable
internal fun SidebarCircleButton(
Expand All @@ -38,21 +42,24 @@ internal fun SidebarCircleButton(
diameter: Int,
onClick: () -> Unit,
modifier: Modifier,
background: Color = openBitFunColors.sidebar.raised,
border: Color = openBitFunColors.sidebar.line,
tint: Color = openBitFunColors.sidebar.ink,
) {
Box(
modifier = modifier
.size(diameter.dp)
.shadow(2.dp, CircleShape)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, CircleShape)
.background(background)
.border(1.dp, border, CircleShape)
.clickable(role = Role.Button, onClick = onClick),
contentAlignment = Alignment.Center,
) {
Icon(
painterResource(icon),
contentDescription = contentDescription,
tint = MaterialTheme.colorScheme.onSurface,
tint = tint,
modifier = Modifier.size((diameter * 0.46f).dp),
)
}
Expand All @@ -65,9 +72,9 @@ internal fun ConnectionDot(phase: ConnectionPhase) {
// Keep automatic recovery quiet; color and accessibility state express reachability.
val color: Color = when (tone) {
ConnectionTone.OK -> openBitFunColors.statusSuccess
ConnectionTone.BUSY -> MaterialTheme.colorScheme.onSurfaceVariant
ConnectionTone.BUSY -> openBitFunColors.sidebar.muted
ConnectionTone.ERROR -> MaterialTheme.colorScheme.error
ConnectionTone.MUTED -> MaterialTheme.colorScheme.onSurfaceVariant
ConnectionTone.MUTED -> openBitFunColors.sidebar.muted
}
Box(
Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
Expand All @@ -30,6 +29,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.openbitfun.mobile.app.R
import com.openbitfun.mobile.app.ui.common.labelRes
import com.openbitfun.mobile.app.ui.theme.openBitFunColors
import com.openbitfun.mobile.core.feature.connection.ConnectionPhase

@Composable
Expand All @@ -48,7 +48,7 @@ internal fun SidebarDeviceSelectorRow(
.fillMaxWidth()
.height(52.dp)
.clip(RoundedCornerShape(10.dp))
.background(if (selected) MaterialTheme.colorScheme.surfaceContainerHigh else MaterialTheme.colorScheme.surface.copy(alpha = 0f))
.background(if (selected) openBitFunColors.sidebar.selection else openBitFunColors.transparent)
.clickable(enabled = online || (selected && phase in listOf(ConnectionPhase.FAILED, ConnectionPhase.DISCONNECTED)), role = Role.Button, onClick = onSelect)
.semantics(mergeDescendants = true) {
this.selected = selected
Expand All @@ -62,21 +62,21 @@ internal fun SidebarDeviceSelectorRow(
Icon(
painterResource(R.drawable.ic_symbol_desktop),
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
tint = openBitFunColors.sidebar.ink,
modifier = Modifier.size(21.dp),
)
Text(
deviceName,
fontSize = 14.sp,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
color = MaterialTheme.colorScheme.onSurface,
color = openBitFunColors.sidebar.ink,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f),
)
if (loading) {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.onSurfaceVariant,
color = openBitFunColors.sidebar.muted,
strokeWidth = 1.5.dp,
modifier = Modifier.size(14.dp),
)
Expand Down
Loading
Loading