From bc07ea421b2da119bee8836046ff58fb6fed70f9 Mon Sep 17 00:00:00 2001 From: Shcherbatykh Oleg Date: Fri, 27 Feb 2026 18:18:23 +0300 Subject: [PATCH] Impl base app theme, change statusbar color --- .../java/ru/fincode/tsudesk/MainActivity.kt | 18 ++- .../tsudesk/presentation/main/MainScaffold.kt | 11 +- .../core/ui/components/BottomBarItem.kt | 2 +- .../tsudesk/core/ui/components/SystemBars.kt | 38 ++++++ .../core/ui/components/TsudeskBottomBar.kt | 2 +- .../ru/fincode/tsudesk/core/ui/theme/Color.kt | 7 + .../tsudesk/core/ui/theme/ColorScheme.kt | 22 +++ .../tsudesk/core/ui/theme/ExtendedColors.kt | 24 ++++ .../tsudesk/core/ui/theme/TSUDeskTheme.kt | 28 ++++ .../ru/fincode/tsudesk/core/ui/theme/Type.kt | 5 + .../presentation/screen/ScheduleScreen.kt | 126 +++++++++++------- 11 files changed, 230 insertions(+), 53 deletions(-) rename core/ui/src/main/java/ru/fincode/{ => tsudesk}/core/ui/components/BottomBarItem.kt (77%) create mode 100644 core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/SystemBars.kt rename core/ui/src/main/java/ru/fincode/{ => tsudesk}/core/ui/components/TsudeskBottomBar.kt (96%) create mode 100644 core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Color.kt create mode 100644 core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ColorScheme.kt create mode 100644 core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ExtendedColors.kt create mode 100644 core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/TSUDeskTheme.kt create mode 100644 core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Type.kt diff --git a/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt b/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt index 237b890..380da1c 100644 --- a/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt +++ b/app/src/main/java/ru/fincode/tsudesk/MainActivity.kt @@ -3,7 +3,12 @@ package ru.fincode.tsudesk import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.ui.graphics.Color +import androidx.core.view.WindowCompat import dagger.hilt.android.AndroidEntryPoint +import ru.fincode.tsudesk.core.ui.components.ConfigureSystemBars +import ru.fincode.tsudesk.core.ui.theme.TSUDeskTheme +import ru.fincode.tsudesk.core.ui.theme.TSUDeskThemeExt import ru.fincode.tsudesk.presentation.TSUDeskApp @AndroidEntryPoint @@ -11,9 +16,16 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + WindowCompat.setDecorFitsSystemWindows(window, false) setContent { - TSUDeskApp() + TSUDeskTheme { + ConfigureSystemBars( + statusBarColor = Color.Transparent, + navigationBarColor = TSUDeskThemeExt.colors.brand, + darkIcons = false + ) + TSUDeskApp() + } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt b/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt index 06338b8..ebe9a6d 100644 --- a/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt +++ b/app/src/main/java/ru/fincode/tsudesk/presentation/main/MainScaffold.kt @@ -1,5 +1,6 @@ package ru.fincode.tsudesk.presentation.main +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -9,11 +10,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState -import ru.fincode.core.ui.components.BottomBarItem -import ru.fincode.core.ui.components.TsudeskBottomBar import ru.fincode.tsudesk.R import ru.fincode.tsudesk.core.navigation.TopLevelDestination import ru.fincode.tsudesk.core.navigation.navigateToTopLevel +import ru.fincode.tsudesk.core.ui.components.BottomBarItem +import ru.fincode.tsudesk.core.ui.components.TsudeskBottomBar @Composable fun MainScaffold( @@ -40,6 +41,10 @@ fun MainScaffold( val selected = selectedTopLevel(backStackEntry) Scaffold( + // ВАЖНО: при edge-to-edge отключаем автоматические systemBars insets у Scaffold, + // иначе получаем двойные отступы (Scaffold + windowInsetsPadding в Header'е) + contentWindowInsets = WindowInsets(0), + bottomBar = { if (shouldShowBottomBar(backStackEntry)) { val uiItems = items.map { item -> @@ -63,6 +68,8 @@ fun MainScaffold( } } ) { innerPadding -> + // innerPadding учитывает bottomBar и системные элементы Scaffold (если будут), + // но НЕ добавляет statusBars (мы делаем это вручную в нужных местах, например в Header) content(Modifier.padding(innerPadding)) } } \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/core/ui/components/BottomBarItem.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/BottomBarItem.kt similarity index 77% rename from core/ui/src/main/java/ru/fincode/core/ui/components/BottomBarItem.kt rename to core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/BottomBarItem.kt index 94e3347..79b7410 100644 --- a/core/ui/src/main/java/ru/fincode/core/ui/components/BottomBarItem.kt +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/BottomBarItem.kt @@ -1,4 +1,4 @@ -package ru.fincode.core.ui.components +package ru.fincode.tsudesk.core.ui.components import androidx.compose.ui.graphics.vector.ImageVector diff --git a/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/SystemBars.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/SystemBars.kt new file mode 100644 index 0000000..4b820ff --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/SystemBars.kt @@ -0,0 +1,38 @@ +package ru.fincode.tsudesk.core.ui.components + +import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext + +@Composable +fun ConfigureSystemBars( + statusBarColor: Color, + navigationBarColor: Color = statusBarColor, + darkIcons: Boolean = false, // false = светлые иконки +) { + val context = LocalContext.current + val status = statusBarColor.toArgb() + val nav = navigationBarColor.toArgb() + + SideEffect { + val activity = context as? ComponentActivity ?: return@SideEffect + + activity.enableEdgeToEdge( + statusBarStyle = if (darkIcons) { + SystemBarStyle.light(status, status) + } else { + SystemBarStyle.dark(status) + }, + navigationBarStyle = SystemBarStyle.auto( + lightScrim = nav, + darkScrim = nav, + detectDarkMode = { darkIcons } + ) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/core/ui/components/TsudeskBottomBar.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/TsudeskBottomBar.kt similarity index 96% rename from core/ui/src/main/java/ru/fincode/core/ui/components/TsudeskBottomBar.kt rename to core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/TsudeskBottomBar.kt index 22f409d..64b38e2 100644 --- a/core/ui/src/main/java/ru/fincode/core/ui/components/TsudeskBottomBar.kt +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/components/TsudeskBottomBar.kt @@ -1,4 +1,4 @@ -package ru.fincode.core.ui.components +package ru.fincode.tsudesk.core.ui.components import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme diff --git a/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Color.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Color.kt new file mode 100644 index 0000000..f99ad52 --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Color.kt @@ -0,0 +1,7 @@ +package ru.fincode.tsudesk.core.ui.theme + +import androidx.compose.ui.graphics.Color + +// Brand (ТулГУ / TSUDesk) +val BrandRed = Color(0xFF7C1D1D) // замени на точный бордовый +val OnBrand = Color(0xFFFFFFFF) \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ColorScheme.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ColorScheme.kt new file mode 100644 index 0000000..706394e --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ColorScheme.kt @@ -0,0 +1,22 @@ +package ru.fincode.tsudesk.core.ui.theme + +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme + +val LightColorScheme = lightColorScheme( + primary = androidx.compose.ui.graphics.Color(0xFF6750A4), + onPrimary = androidx.compose.ui.graphics.Color.White, + secondary = androidx.compose.ui.graphics.Color(0xFF625B71), + onSecondary = androidx.compose.ui.graphics.Color.White, + tertiary = androidx.compose.ui.graphics.Color(0xFF7D5260), + onTertiary = androidx.compose.ui.graphics.Color.White, +) + +val DarkColorScheme = darkColorScheme( + primary = androidx.compose.ui.graphics.Color(0xFFD0BCFF), + onPrimary = androidx.compose.ui.graphics.Color(0xFF381E72), + secondary = androidx.compose.ui.graphics.Color(0xFFCCC2DC), + onSecondary = androidx.compose.ui.graphics.Color(0xFF332D41), + tertiary = androidx.compose.ui.graphics.Color(0xFFEFB8C8), + onTertiary = androidx.compose.ui.graphics.Color(0xFF492532), +) \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ExtendedColors.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ExtendedColors.kt new file mode 100644 index 0000000..5fd1400 --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/ExtendedColors.kt @@ -0,0 +1,24 @@ +package ru.fincode.tsudesk.core.ui.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color + +data class ExtendedColors( + val brand: Color, + val onBrand: Color, + val brandSoft: Color, +) + +val LocalExtendedColors = staticCompositionLocalOf { + ExtendedColors( + brand = Color.Unspecified, + onBrand = Color.Unspecified, + brandSoft = Color.Unspecified, + ) +} + +object TSUDeskThemeExt { + val colors: ExtendedColors + @Composable get() = LocalExtendedColors.current +} \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/TSUDeskTheme.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/TSUDeskTheme.kt new file mode 100644 index 0000000..4e58784 --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/TSUDeskTheme.kt @@ -0,0 +1,28 @@ +package ru.fincode.tsudesk.core.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider + +@Composable +fun TSUDeskTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val scheme = if (darkTheme) DarkColorScheme else LightColorScheme + + val extended = ExtendedColors( + brand = BrandRed, + onBrand = OnBrand, + brandSoft = BrandRed.copy(alpha = 0.12f), + ) + + CompositionLocalProvider(LocalExtendedColors provides extended) { + MaterialTheme( + colorScheme = scheme, + typography = TSUDeskTypography, + content = content + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Type.kt b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Type.kt new file mode 100644 index 0000000..bb00b68 --- /dev/null +++ b/core/ui/src/main/java/ru/fincode/tsudesk/core/ui/theme/Type.kt @@ -0,0 +1,5 @@ +package ru.fincode.tsudesk.core.ui.theme + +import androidx.compose.material3.Typography + +val TSUDeskTypography = Typography() \ No newline at end of file diff --git a/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleScreen.kt b/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleScreen.kt index 21d21e5..7c22990 100644 --- a/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleScreen.kt +++ b/feature/schedule/src/main/java/ru/fincode/tsudesk/feature/schedule/presentation/screen/ScheduleScreen.kt @@ -3,7 +3,21 @@ package ru.fincode.tsudesk.feature.schedule.presentation.screen import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +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.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState @@ -14,9 +28,23 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp -import androidx.compose.material3.* +import androidx.compose.material3.AssistChip +import androidx.compose.material3.AssistChipDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -28,7 +56,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import ru.fincode.tsudesk.core.common.model.DataResult -import ru.fincode.tsudesk.feature.schedule.presentation.* +import ru.fincode.tsudesk.core.ui.theme.TSUDeskThemeExt import ru.fincode.tsudesk.feature.schedule.presentation.util.dowRuShort import ru.fincode.tsudesk.feature.schedule.presentation.util.todayString @@ -60,62 +88,67 @@ private fun ScheduleHeader( onIntent: (ScheduleIntent) -> Unit ) { val shape = RoundedCornerShape(bottomStart = 20.dp, bottomEnd = 20.dp) + val brand = TSUDeskThemeExt.colors + val headerBrush = Brush.linearGradient( listOf( - MaterialTheme.colorScheme.error, - MaterialTheme.colorScheme.error.copy(alpha = 0.75f) + brand.brand, + brand.brand.copy(alpha = 0.75f) ) ) - Column( + Box( Modifier .fillMaxWidth() .clip(shape) .background(headerBrush) - .padding(top = 20.dp, start = 16.dp, end = 16.dp, bottom = 14.dp) ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Column(Modifier.weight(1f)) { - Text( - text = "Расписание", - color = MaterialTheme.colorScheme.onError, - fontSize = 20.sp, - fontWeight = FontWeight.Bold - ) - Text( - text = "ТулГУ • Осенний семестр", - color = MaterialTheme.colorScheme.onError.copy(alpha = 0.8f), - fontSize = 11.sp, - fontWeight = FontWeight.Medium + Column( + Modifier + // inset только для содержимого, фон остаётся до верха + .windowInsetsPadding(WindowInsets.statusBars) + .padding(start = 16.dp, end = 16.dp, bottom = 14.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Column(Modifier.weight(1f)) { + Text( + text = "Расписание", + color = brand.onBrand, + fontSize = 20.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "ТулГУ • Осенний семестр", + color = brand.onBrand.copy(alpha = 0.8f), + fontSize = 11.sp, + fontWeight = FontWeight.Medium + ) + } + + ModeToggle( + mode = state.viewMode, + onMode = { onIntent(ScheduleIntent.SetViewMode(it)) } ) } - ModeToggle( - mode = state.viewMode, - onMode = { onIntent(ScheduleIntent.SetViewMode(it)) } + Spacer(Modifier.height(12.dp)) + + GroupSelector( + groupInput = state.groupInput, + recentGroups = state.recentGroups, + expanded = state.isGroupMenuExpanded, + isLoading = state.isLoading, + onExpanded = { onIntent(ScheduleIntent.SetGroupMenuExpanded(it)) }, + onValueChange = { onIntent(ScheduleIntent.ChangeGroupInput(it)) }, + onSelectRecent = { group -> + onIntent(ScheduleIntent.SelectRecentGroup(group)) + onIntent(ScheduleIntent.ApplyGroup) + }, + onRemoveRecent = { onIntent(ScheduleIntent.RemoveRecentGroup(it)) }, + onApply = { onIntent(ScheduleIntent.ApplyGroup) }, + modifier = Modifier.fillMaxWidth() ) } - - Spacer(Modifier.height(12.dp)) - - GroupSelector( - groupInput = state.groupInput, - recentGroups = state.recentGroups, - expanded = state.isGroupMenuExpanded, - isLoading = state.isLoading, - onExpanded = { onIntent(ScheduleIntent.SetGroupMenuExpanded(it)) }, - onValueChange = { onIntent(ScheduleIntent.ChangeGroupInput(it)) }, - - // выбор из списка -> сразу загрузка - onSelectRecent = { group -> - onIntent(ScheduleIntent.SelectRecentGroup(group)) - onIntent(ScheduleIntent.ApplyGroup) - }, - - onRemoveRecent = { onIntent(ScheduleIntent.RemoveRecentGroup(it)) }, - onApply = { onIntent(ScheduleIntent.ApplyGroup) }, - modifier = Modifier.fillMaxWidth() - ) } } @@ -566,9 +599,10 @@ private fun LessonCard(lesson: UiLesson) { @Composable private fun LessonBadge(text: String, kind: BadgeKind) { + val brand = TSUDeskThemeExt.colors val (bg, fg) = when (kind) { BadgeKind.INFO -> MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) to MaterialTheme.colorScheme.primary - BadgeKind.PRIMARY -> MaterialTheme.colorScheme.error.copy(alpha = 0.12f) to MaterialTheme.colorScheme.error + BadgeKind.PRIMARY -> brand.brandSoft to brand.brand BadgeKind.LAB -> MaterialTheme.colorScheme.tertiary.copy(alpha = 0.12f) to MaterialTheme.colorScheme.tertiary BadgeKind.MUTED -> MaterialTheme.colorScheme.surfaceVariant to MaterialTheme.colorScheme.onSurfaceVariant }