From 4f9011e656d9af1caa8f3bbb9bcbfa4bb88d11f0 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 30 Jul 2026 20:57:16 +0300 Subject: [PATCH] Improve register and fix about app Signed-off-by: denis0001-dev --- .../commonMain/kotlin/ru/fromchat/ui/App.kt | 33 ++-- .../kotlin/ru/fromchat/ui/WelcomeScreen.kt | 147 ++++++------------ .../kotlin/ru/fromchat/ui/auth/AuthLayout.kt | 106 +++++++++++++ .../kotlin/ru/fromchat/ui/auth/AuthScreen.kt | 36 ++--- .../ui/components/ExpressiveStepFlow.kt | 8 +- .../ui/components/HazeActionButton.kt | 47 ++++-- .../ui/main/settings/SettingsDetailTopBar.kt | 12 ++ .../settings/server/ServerConfigScreen.kt | 75 ++++++--- 8 files changed, 285 insertions(+), 179 deletions(-) create mode 100644 app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthLayout.kt diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/App.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/App.kt index c1aa4fc..79f8df0 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/App.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/App.kt @@ -358,7 +358,6 @@ fun App( val widthSizeClass = currentWindowAdaptiveInfo().widthSizeClass val isDesktopListDetail = widthSizeClass != WindowWidthSizeClass.COMPACT var pendingMainTab by remember { mutableIntStateOf(MAIN_PAGE_CHATS) } - var pendingPreAuthSettingsDetailRoute by remember { mutableStateOf(null) } // Handle startup/deep-link navigation targets (profile links) LaunchedEffect( @@ -504,21 +503,19 @@ fun App( LaunchedEffect(Unit) { DesktopMenuCommands.commands.collect { command -> if (command != DesktopMenuCommand.OpenAbout) return@collect + // Logged-out: same root About as Welcome/auth overflow — never mount + // the empty chat list–detail shell. + if (ApiClient.token.isNullOrBlank()) { + navController.navigate(SettingsRoutes.About) { + launchSingleTop = true + } + return@collect + } pendingMainTab = MAIN_PAGE_SETTINGS if (isDesktopListDetail) { - if (ApiClient.token.isNullOrBlank()) { - // Pre-auth: the settings detail NavHost isn't mounted until the root - // shell is visible, so we defer settings navigation until `currentRoute` - // becomes `chat`. - pendingPreAuthSettingsDetailRoute = SettingsRoutes.About - navController.navigate("chat") { - launchSingleTop = true - } - } else { - settingsDetailNavController.navigateReplacingMainDetail( - route = SettingsRoutes.About, - ) - } + settingsDetailNavController.navigateReplacingMainDetail( + route = SettingsRoutes.About, + ) } else { navController.navigateReplacingMainDetail( route = SettingsRoutes.About, @@ -527,14 +524,6 @@ fun App( } } - LaunchedEffect(currentRoute, pendingPreAuthSettingsDetailRoute) { - val route = pendingPreAuthSettingsDetailRoute ?: return@LaunchedEffect - if (!isDesktopListDetail) return@LaunchedEffect - if (currentRoute != "chat") return@LaunchedEffect - settingsDetailNavController.navigateReplacingMainDetail(route = route) - pendingPreAuthSettingsDetailRoute = null - } - ScreenSurface { Box(Modifier.fillMaxSize()) { @Composable diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/WelcomeScreen.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/WelcomeScreen.kt index 9b3876a..43a6a21 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/WelcomeScreen.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/WelcomeScreen.kt @@ -4,36 +4,25 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.widthIn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.outlined.BugReport -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold 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 -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.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -41,20 +30,14 @@ import coil3.compose.AsyncImage import org.jetbrains.compose.resources.stringResource import ru.fromchat.Res import ru.fromchat.api.ApiClient -import ru.fromchat.about import ru.fromchat.auth_get_started import ru.fromchat.auth_welcome_tagline import ru.fromchat.auth_welcome_title -import ru.fromchat.logs_title -import ru.fromchat.more +import ru.fromchat.ui.auth.PreAuthOverflowMenu import ru.fromchat.ui.components.ActionButton import ru.fromchat.ui.components.Text -import ru.fromchat.ui.main.settings.SettingsRoutes import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding -private val AUTH_CONTENT_MAX_WIDTH = 600.dp -private val AUTH_CONTENT_MAX_HEIGHT = 800.dp - @Composable fun WelcomeScreen( onGetStarted: () -> Unit, @@ -67,103 +50,69 @@ fun WelcomeScreen( } Scaffold( + containerColor = Color.Transparent, contentWindowInsets = WindowInsets.safeDrawing.union(WindowInsets.extraStatusBars), ) { innerPadding -> - val navController = LocalNavController.current - var menuExpanded by remember { mutableStateOf(false) } - Box( modifier = Modifier .fillMaxSize() .padding(innerPadding), ) { Box(modifier = Modifier.align(Alignment.TopEnd)) { - IconButton(onClick = { menuExpanded = true }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(Res.string.more), - ) - } - DropdownMenu( - expanded = menuExpanded, - onDismissRequest = { menuExpanded = false }, - ) { - DropdownMenuItem( - text = { Text(stringResource(Res.string.about)) }, - leadingIcon = { - Icon(Icons.Default.Info, contentDescription = null) - }, - onClick = { - menuExpanded = false - navController.navigate(SettingsRoutes.About) - }, - ) - DropdownMenuItem( - text = { Text(stringResource(Res.string.logs_title)) }, - leadingIcon = { - Icon(Icons.Outlined.BugReport, contentDescription = null) - }, - onClick = { - menuExpanded = false - navController.navigate(SettingsRoutes.Logs) - }, - ) - } + PreAuthOverflowMenu() } - BoxWithConstraints(modifier = Modifier.fillMaxSize()) { - val constrainWide = maxWidth > AUTH_CONTENT_MAX_WIDTH - Box( - modifier = - if (constrainWide) { - Modifier - .align(Alignment.Center) - .widthIn(max = AUTH_CONTENT_MAX_WIDTH) - .heightIn(max = AUTH_CONTENT_MAX_HEIGHT) - } else { - Modifier.fillMaxSize() - }, + BoxWithConstraints(Modifier.fillMaxSize()) { + val wide = maxWidth > 600.dp + Column( + modifier = Modifier + .align(if (wide) Alignment.Center else Alignment.TopCenter) + .then( + if (wide) { + Modifier + .widthIn(max = 600.dp) + .heightIn(max = 800.dp) + } else { + Modifier.fillMaxSize() + }, + ) + .navigationBarsPadding() + .padding(horizontal = SettingsStepHorizontalPadding) + .padding(bottom = 16.dp), + verticalArrangement = if (wide) Arrangement.Center else Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, ) { - Column( + AsyncImage( + model = Res.getUri("drawable/logo_square.png"), + contentDescription = null, modifier = Modifier - .fillMaxSize() - .navigationBarsPadding() - .padding(horizontal = SettingsStepHorizontalPadding) - .padding(bottom = 16.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AsyncImage( - model = Res.getUri("drawable/logo_square.png"), - contentDescription = null, - modifier = Modifier - .size(112.dp) - .clip(MaterialTheme.shapes.extraLarge), - contentScale = ContentScale.Fit, - ) + .size(112.dp) + .clip(MaterialTheme.shapes.extraLarge), + contentScale = ContentScale.Fit, + ) - Spacer(Modifier.height(24.dp)) + Spacer(Modifier.height(24.dp)) - Text( - text = stringResource(Res.string.auth_welcome_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) + Text( + text = stringResource(Res.string.auth_welcome_title), + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + ) - Spacer(Modifier.height(12.dp)) + Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(Res.string.auth_welcome_tagline), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) + Text( + text = stringResource(Res.string.auth_welcome_tagline), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + ) - Spacer(Modifier.height(40.dp)) + Spacer(Modifier.height(40.dp)) - ActionButton(onClick = onGetStarted) { - Text(stringResource(Res.string.auth_get_started)) - } + ActionButton(onClick = onGetStarted) { + Text(stringResource(Res.string.auth_get_started)) } } } diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthLayout.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthLayout.kt new file mode 100644 index 0000000..9f312e1 --- /dev/null +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthLayout.kt @@ -0,0 +1,106 @@ +package ru.fromchat.ui.auth + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.outlined.BugReport +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +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.unit.dp +import org.jetbrains.compose.resources.stringResource +import ru.fromchat.Res +import ru.fromchat.about +import ru.fromchat.logs_title +import ru.fromchat.more +import ru.fromchat.ui.LocalNavController +import ru.fromchat.ui.components.Text +import ru.fromchat.ui.main.settings.SettingsRoutes + +/** Margin between the window edge and the wide [AuthContentFrame] panel. */ +val AuthWidePanelOuterPadding = 32.dp + +/** + * Centers auth content on wide layouts (max 600×800.dp, [AuthWidePanelOuterPadding] margin). + * Narrow layouts fill the window ([widePanel] = false). + */ +@Composable +fun AuthContentFrame( + modifier: Modifier = Modifier, + content: @Composable BoxScope.(widePanel: Boolean) -> Unit, +) { + BoxWithConstraints(modifier.fillMaxSize()) { + val widePanel = maxWidth > 600.dp + if (widePanel) { + Box( + Modifier + .align(Alignment.Center) + .padding(AuthWidePanelOuterPadding) + .widthIn(max = 600.dp) + .heightIn(max = 800.dp), + ) { + content(true) + } + } else { + Box(Modifier.fillMaxSize()) { + content(false) + } + } + } +} + +/** Overflow menu shared by welcome and auth steps (About, Logs). */ +@Composable +fun PreAuthOverflowMenu() { + val navController = LocalNavController.current + var menuExpanded by remember { mutableStateOf(false) } + + Box { + IconButton(onClick = { menuExpanded = true }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = stringResource(Res.string.more), + ) + } + DropdownMenu( + expanded = menuExpanded, + onDismissRequest = { menuExpanded = false }, + ) { + DropdownMenuItem( + text = { Text(stringResource(Res.string.about)) }, + leadingIcon = { + Icon(Icons.Default.Info, contentDescription = null) + }, + onClick = { + menuExpanded = false + navController.navigate(SettingsRoutes.About) + }, + ) + DropdownMenuItem( + text = { Text(stringResource(Res.string.logs_title)) }, + leadingIcon = { + Icon(Icons.Outlined.BugReport, contentDescription = null) + }, + onClick = { + menuExpanded = false + navController.navigate(SettingsRoutes.Logs) + }, + ) + } + } +} diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthScreen.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthScreen.kt index 8f0049e..97a808c 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthScreen.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/AuthScreen.kt @@ -1,13 +1,15 @@ package ru.fromchat.ui.auth import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.union +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Storage +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -18,7 +20,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.pr0gramm3r101.utils.crypto.deriveAuthSecret @@ -53,6 +54,7 @@ import ru.fromchat.ui.components.Text import ru.fromchat.ui.components.TextCta import ru.fromchat.ui.components.rememberExpressiveStepFlow import ru.fromchat.ui.components.showLoggedSnackbar +import ru.fromchat.ui.extraStatusBars import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding import kotlin.time.Duration.Companion.milliseconds @@ -85,9 +87,6 @@ internal sealed interface RegisterResult { private const val AUTH_SERVER_PROBE_TIMEOUT_MS = 5_000L -private val AUTH_CONTENT_MAX_WIDTH = 600.dp -private val AUTH_CONTENT_MAX_HEIGHT = 800.dp - internal suspend fun probeCurrentServer() = runCatching { withTimeout(AUTH_SERVER_PROBE_TIMEOUT_MS.milliseconds) { probeServer(Settings.readServerConfig()) is ServerProbeResult.Supported @@ -443,19 +442,13 @@ fun AuthScreen( } val yandexStep = yandexParams - BoxWithConstraints(modifier = Modifier.fillMaxSize()) { - val constrainWide = maxWidth > AUTH_CONTENT_MAX_WIDTH - Box( - modifier = - if (constrainWide) { - Modifier - .align(Alignment.Center) - .widthIn(max = AUTH_CONTENT_MAX_WIDTH) - .heightIn(max = AUTH_CONTENT_MAX_HEIGHT) - } else { - Modifier.fillMaxSize() - }, - ) { + // Clear title bar / system bars outside the rounded panel (same as WelcomeScreen). + Box( + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.safeDrawing.union(WindowInsets.extraStatusBars)), + ) { + AuthContentFrame { _ -> ExpressiveStepFlowScaffold( flowState = flowState, pages = listOf( @@ -566,6 +559,9 @@ fun AuthScreen( ), snackbarHostState = snackbarHostState, onBackAtFirstPage = wrappedBackToWelcome, + trailingTopBarActions = { PreAuthOverflowMenu() }, + contentBackground = MaterialTheme.colorScheme.background, + topBarWindowInsets = WindowInsets(0, 0, 0, 0), ) } } diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/ExpressiveStepFlow.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/ExpressiveStepFlow.kt index 3a6614a..c689882 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/ExpressiveStepFlow.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/ExpressiveStepFlow.kt @@ -343,6 +343,8 @@ fun ExpressiveStepFlowScaffold( onBackAtFirstPage: () -> Unit, trailingTopBarActions: @Composable (() -> Unit)? = null, hazeScaffold: Boolean = true, + contentBackground: Color = MaterialTheme.colorScheme.background, + topBarWindowInsets: WindowInsets = WindowInsets.extraStatusBars, ) { val pagerState = flowState.pagerState val scope = flowState.scope @@ -551,7 +553,7 @@ fun ExpressiveStepFlowScaffold( fun HazeTopBar(hazeState: HazeState) { val topBarHazeStyle = HazeMaterials.thin() TopAppBar( - windowInsets = WindowInsets.extraStatusBars, + windowInsets = topBarWindowInsets, title = {}, navigationIcon = { IconButton(onClick = navigateBack) { @@ -585,7 +587,7 @@ fun ExpressiveStepFlowScaffold( Row( modifier = Modifier .fillMaxWidth() - .windowInsetsPadding(WindowInsets.extraStatusBars) + .windowInsetsPadding(topBarWindowInsets) .padding(horizontal = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { @@ -648,7 +650,7 @@ fun ExpressiveStepFlowScaffold( state = listState, modifier = Modifier .fillMaxSize() - .background(scheme.background) + .background(contentBackground) .hazeSource(hazeState) .onGloballyPositioned { listViewportBounds = it.boundsInWindow() diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/HazeActionButton.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/HazeActionButton.kt index a4ed102..4961a5e 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/HazeActionButton.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/components/HazeActionButton.kt @@ -1,6 +1,8 @@ package ru.fromchat.ui.components +import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets @@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.blur.HazeBlurStyle @@ -25,6 +28,7 @@ fun HazeBottomBar( hazeState: HazeState, modifier: Modifier = Modifier, hazeStyle: HazeBlurStyle? = null, + baseColor: Color? = null, content: @Composable () -> Unit, ) { val resolvedStyle = hazeStyle ?: HazeMaterials.thin() @@ -32,6 +36,10 @@ fun HazeBottomBar( Modifier.hazeEffect(state = hazeState) { blurEffect { style = resolvedStyle + progressive = HazeProgressive.verticalGradient( + startIntensity = 0f, + endIntensity = 1f, + ) } } } else { @@ -49,17 +57,30 @@ fun HazeBottomBar( modifier = Modifier .windowInsetsPadding(WindowInsets.ime) .fillMaxWidth() - .then(effectModifier) .then(modifier), ) { - Column( - modifier = Modifier - .fillMaxWidth() - .navigationBarsPadding() - .padding(horizontal = SettingsStepHorizontalPadding) - .padding(top = 0.dp, bottom = 16.dp), - ) { - content() + Box(Modifier.fillMaxWidth()) { + if (baseColor != null) { + Box( + modifier = Modifier + .matchParentSize() + .background(baseColor), + ) + } + Box( + modifier = Modifier + .matchParentSize() + .then(effectModifier), + ) + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(horizontal = SettingsStepHorizontalPadding) + .padding(top = 0.dp, bottom = 16.dp), + ) { + content() + } } } } @@ -71,12 +92,18 @@ fun HazeActionButton( modifier: Modifier = Modifier, innerModifier: Modifier = Modifier, hazeStyle: HazeBlurStyle? = null, + baseColor: Color? = null, enabled: Boolean = true, loading: Boolean = false, interactionSource: MutableInteractionSource? = null, content: @Composable (RowScope.() -> Unit) ) { - HazeBottomBar(hazeState = hazeState, modifier = modifier, hazeStyle = hazeStyle) { + HazeBottomBar( + hazeState = hazeState, + modifier = modifier, + hazeStyle = hazeStyle, + baseColor = baseColor, + ) { ActionButton( onClick = onClick, modifier = innerModifier, diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/SettingsDetailTopBar.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/SettingsDetailTopBar.kt index eda909e..a94efe0 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/SettingsDetailTopBar.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/SettingsDetailTopBar.kt @@ -2,6 +2,7 @@ package ru.fromchat.ui.main.settings import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animate +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api @@ -32,6 +33,8 @@ import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource import ru.fromchat.Res import ru.fromchat.back +import ru.fromchat.ui.extraStatusBars +import ru.fromchat.ui.main.LocalConversationListDetailActive import ru.fromchat.ui.main.detailPaneShowBackButton /** Idle gap after mouse-wheel / trackpad deltas before snapping the collapsing bar. */ @@ -85,11 +88,19 @@ fun SettingsDetailTopBar( // `surfaceContainerLowest` — that token reads as a separate pane fill in // two-pane against the list–detail shell. val paneColor = MaterialTheme.colorScheme.background + // List–detail shell already pads/consumes extraStatusBars; full-screen settings + // (e.g. logged-out About) need the desktop title-bar inset explicitly. + val topBarWindowInsets = if (LocalConversationListDetailActive.current) { + WindowInsets(0, 0, 0, 0) + } else { + WindowInsets.extraStatusBars + } if (settingsDetailUseCollapsingTopBar()) { MediumTopAppBar( title = title, navigationIcon = navigationIcon, scrollBehavior = scrollBehavior, + windowInsets = topBarWindowInsets, // Unscrolled: pane. Collapsed: default scrolledContainerColor (elevation). colors = TopAppBarDefaults.topAppBarColors( containerColor = paneColor, @@ -100,6 +111,7 @@ fun SettingsDetailTopBar( TopAppBar( title = title, navigationIcon = navigationIcon, + windowInsets = topBarWindowInsets, colors = TopAppBarDefaults.topAppBarColors( containerColor = paneColor, scrolledContainerColor = paneColor, diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/server/ServerConfigScreen.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/server/ServerConfigScreen.kt index be2c8d2..335be5f 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/server/ServerConfigScreen.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/main/settings/server/ServerConfigScreen.kt @@ -19,7 +19,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.union +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardOptions @@ -108,6 +111,8 @@ import ru.fromchat.server_config_unsupported_no_instance_id import ru.fromchat.server_ip_hint import ru.fromchat.server_ip_label import ru.fromchat.ui.LocalNavController +import ru.fromchat.ui.auth.AuthContentFrame +import ru.fromchat.ui.extraStatusBars import ru.fromchat.ui.components.CtaShape import ru.fromchat.ui.components.DisabledBringIntoViewSpec import ru.fromchat.ui.components.ExpressiveIconFrame @@ -231,7 +236,12 @@ fun ServerConfigScreen() { disabledContainerColor = Color.Transparent, ) - Box(Modifier.fillMaxSize()) { + val preAuth = ApiClient.token.isNullOrBlank() + + @Composable + fun ServerConfigContent() { + val listBackground = scheme.background + Scaffold( modifier = Modifier.fillMaxSize(), contentWindowInsets = WindowInsets.navigationBars, @@ -333,31 +343,31 @@ fun ServerConfigScreen() { topBar = { val topBarHazeStyle = HazeMaterials.thin() TopAppBar( - title = {}, - navigationIcon = { - if (settingsDetailShowBackButton()) { - IconButton(onClick = navController::navigateUp) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back) + title = {}, + navigationIcon = { + if (settingsDetailShowBackButton()) { + IconButton(onClick = navController::navigateUp) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(Res.string.back) + ) + } + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent, + ), + modifier = Modifier.hazeEffect(state = actionHazeState) { + blurEffect { + style = topBarHazeStyle + progressive = HazeProgressive.verticalGradient( + startIntensity = 1f, + endIntensity = 0f, ) } - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent, - scrolledContainerColor = Color.Transparent - ), - modifier = Modifier.hazeEffect(state = actionHazeState) { - blurEffect { - style = topBarHazeStyle - progressive = HazeProgressive.verticalGradient( - startIntensity = 1f, - endIntensity = 0f, - ) - } - } - ) + }, + ) } ) { innerPadding -> val floatingHeaderClearance = WindowInsets.statusBars.getTop(density).toDp(density) + 68.dp @@ -379,7 +389,7 @@ fun ServerConfigScreen() { modifier = Modifier .fillMaxSize() .consumeWindowInsets(innerPadding) - .background(MaterialTheme.colorScheme.background) + .background(listBackground) .hazeSource(actionHazeState) .onGloballyPositioned { listViewportBounds = it.boundsInWindow() }, contentPadding = PaddingValues(bottom = bottomInsetPadding), @@ -422,6 +432,7 @@ fun ServerConfigScreen() { Text( text = stringResource(Res.string.server_config_title), style = MaterialTheme.typography.headlineMedium, + color = scheme.onSurface, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, ) @@ -568,4 +579,18 @@ fun ServerConfigScreen() { } } } + + if (preAuth) { + Box( + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.safeDrawing.union(WindowInsets.extraStatusBars)), + ) { + AuthContentFrame { _ -> + ServerConfigContent() + } + } + } else { + ServerConfigContent() + } } \ No newline at end of file