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 @@ -13,6 +13,7 @@ import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.openbitfun.mobile.app.ui.shell.MobileScreen
import com.openbitfun.mobile.app.platform.StartupRevealPreference
import com.openbitfun.mobile.app.platform.AppLocaleController
import com.openbitfun.mobile.app.ui.preview.MobileDesignGallery
import com.openbitfun.mobile.app.ui.preview.mobileDesignScenario
Expand All @@ -26,6 +27,8 @@ class MainActivity : ComponentActivity() {
AppLocaleController.applySaved(this)
super.onCreate(savedInstanceState)
showStartupBrand = savedInstanceState == null
&& !intent.getBooleanExtra(DESIGN_PREVIEW_EXTRA, false)
&& StartupRevealPreference.claim(this)
enableEdgeToEdge()
setContent {
if (intent.getBooleanExtra(DESIGN_PREVIEW_EXTRA, false)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.openbitfun.mobile.app.platform

import android.content.Context

/** Installation-local presentation state; account changes and upgrades do not reset it. */
internal object StartupRevealPreference {
@Synchronized
fun claim(context: Context): Boolean {
val preferences = context.getSharedPreferences("startup_presentation", Context.MODE_PRIVATE)
if (preferences.getBoolean("brand_reveal_shown", false)) return false
// Claim before playback, including when the first reveal is interrupted.
preferences.edit().putBoolean("brand_reveal_shown", true).apply()
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ internal fun ConversationHeader(
modifier = Modifier
.fillMaxWidth()
.height(
if (hasSubtitle) MobileDesignGeometry.ConversationHeaderHeight
else MobileDesignGeometry.ConversationHeaderCompactHeight,
MobileDesignGeometry.ConversationHeaderHeight,
)
.padding(horizontal = MobileDesignGeometry.ContentGutter, vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ internal fun GeneralChatScreen(
Row(
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.height(com.openbitfun.mobile.app.ui.theme.generated.MobileDesignGeometry.ConversationHeaderHeight)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ internal fun OpenBitFunCompactDrawer(
translationX = drawerWidthPx * contentProgress.value
scaleX = 1f - (1f - CONTENT_SCALE_X) * contentProgress.value
scaleY = 1f - (1f - CONTENT_SCALE_Y) * contentProgress.value
transformOrigin = TransformOrigin(0f, 0.5f)
transformOrigin = TransformOrigin(0f, com.openbitfun.mobile.app.ui.theme.generated.MobileDesignGeometry.ConversationHeaderHeight.toPx() / 2f / size.height.coerceAtLeast(1f))
}
.then(
if (contentCard.phase == ContentCardPhase.Receded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ internal fun AppSidebar(
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 20.dp, end = 20.dp, top = 4.dp, bottom = 16.dp),
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (signedIn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ internal fun SidebarCircleButton(
@Composable
internal fun ConnectionDot(phase: ConnectionPhase) {
val tone = ConnectionStatusPresenter.tone(phase)
// Keep recovery neutral across native sidebars; the status label and
// accessibility state distinguish checking from a disconnected link.
// Keep automatic recovery quiet; color and accessibility state express reachability.
val color: Color = when (tone) {
ConnectionTone.OK -> openBitFunColors.statusSuccess
ConnectionTone.BUSY -> MaterialTheme.colorScheme.onSurfaceVariant
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.openbitfun.mobile.app.ui.shell.sidebar

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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
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
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
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.core.feature.connection.ConnectionPhase

@Composable
internal fun SidebarDeviceSelectorRow(
deviceName: String,
online: Boolean,
selected: Boolean,
loading: Boolean,
phase: ConnectionPhase? = null,
onSelect: () -> Unit,
) {
val deviceLabel = deviceName
val healthLabel = phase?.let { stringResource(it.labelRes()) }
Row(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.clip(RoundedCornerShape(10.dp))
.background(if (selected) MaterialTheme.colorScheme.surfaceContainerHigh else MaterialTheme.colorScheme.surface.copy(alpha = 0f))
.clickable(enabled = online || (selected && phase in listOf(ConnectionPhase.FAILED, ConnectionPhase.DISCONNECTED)), role = Role.Button, onClick = onSelect)
.semantics(mergeDescendants = true) {
this.selected = selected
contentDescription = deviceLabel
healthLabel?.let { stateDescription = it }
}
.padding(start = 10.dp, end = 6.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.ic_symbol_desktop),
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.size(21.dp),
)
Text(
deviceName,
fontSize = 14.sp,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f),
)
if (loading) {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.onSurfaceVariant,
strokeWidth = 1.5.dp,
modifier = Modifier.size(14.dp),
)
} else {
ConnectionDot(phase ?: if (online) ConnectionPhase.CONNECTED else ConnectionPhase.DISCONNECTED)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
Expand All @@ -18,7 +19,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
Expand All @@ -34,44 +34,38 @@ import com.openbitfun.mobile.app.ui.common.SignedOutConnectionActions
internal const val SIDEBAR_NEW_CHAT_TEST_TAG: String = "app-sidebar-new-chat"
internal const val SIDEBAR_SETTINGS_TEST_TAG: String = "app-sidebar-settings"

/**
* The signed-in footer, ported from `AuthenticatedFooter` in `AppSidebar.ets`.
*
* Carded and floating over the list rather than docked below it: the list scrolls
* behind it, so starting a new conversation stays reachable no matter how far
* down the history a user has gone.
*/
/** Shared tools/settings navigation for compact and wide sidebars. */
@Composable
internal fun SidebarAuthenticatedFooter(onConnect: () -> Unit, onOpenSettings: () -> Unit) {
val newChatLabel = stringResource(R.string.device_tools)
internal fun SidebarAuthenticatedFooter(onOpenTools: () -> Unit, onOpenSettings: () -> Unit) {
val toolsLabel = stringResource(R.string.device_tools)
Row(
modifier = Modifier.fillMaxWidth().height(56.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.width(116.dp)
.height(46.dp)
.shadow(2.dp, RoundedCornerShape(23.dp))
.clip(RoundedCornerShape(23.dp))
.widthIn(min = 104.dp)
.height(48.dp)
.clip(RoundedCornerShape(24.dp))
.background(MaterialTheme.colorScheme.surface)
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(23.dp))
.clickable(role = Role.Button, onClick = onConnect)
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(24.dp))
.clickable(role = Role.Button, onClick = onOpenTools)
.semantics(mergeDescendants = true) {
contentDescription = newChatLabel
contentDescription = toolsLabel
}
.testTag(SIDEBAR_NEW_CHAT_TEST_TAG),
horizontalArrangement = Arrangement.spacedBy(6.dp, Alignment.CenterHorizontally),
.testTag(SIDEBAR_NEW_CHAT_TEST_TAG)
.padding(horizontal = 14.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.ic_symbol_folder),
painterResource(R.drawable.ic_symbol_wrench_and_screwdriver),
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.size(18.dp),
modifier = Modifier.size(24.dp),
)
Text(
newChatLabel,
toolsLabel,
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface,
Expand All @@ -81,7 +75,7 @@ internal fun SidebarAuthenticatedFooter(onConnect: () -> Unit, onOpenSettings: (
SidebarCircleButton(
icon = R.drawable.ic_symbol_gearshape,
contentDescription = stringResource(R.string.navigation_settings),
diameter = 46,
diameter = 48,
onClick = onOpenSettings,
modifier = Modifier.testTag(SIDEBAR_SETTINGS_TEST_TAG),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal fun SidebarAuthenticatedHeader(
onToggleSearch: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().height(com.openbitfun.mobile.app.ui.theme.generated.MobileDesignGeometry.ConversationHeaderHeight),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
Expand Down
Loading