2 Commits

Author SHA1 Message Date
075dac1fc7 refactoring 2026-02-27 18:23:36 +03:00
bc07ea421b Impl base app theme, change statusbar color 2026-02-27 18:18:23 +03:00
12 changed files with 233 additions and 53 deletions

View File

@@ -3,7 +3,12 @@ package ru.fincode.tsudesk
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
import dagger.hilt.android.AndroidEntryPoint 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 import ru.fincode.tsudesk.presentation.TSUDeskApp
@AndroidEntryPoint @AndroidEntryPoint
@@ -11,9 +16,16 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
TSUDeskTheme {
ConfigureSystemBars(
statusBarColor = Color.Transparent,
navigationBarColor = TSUDeskThemeExt.colors.brand,
darkIcons = false
)
TSUDeskApp() TSUDeskApp()
} }
} }
} }
}

View File

@@ -1,5 +1,6 @@
package ru.fincode.tsudesk.presentation.main package ru.fincode.tsudesk.presentation.main
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -9,11 +10,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState 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.R
import ru.fincode.tsudesk.core.navigation.TopLevelDestination import ru.fincode.tsudesk.core.navigation.TopLevelDestination
import ru.fincode.tsudesk.core.navigation.navigateToTopLevel import ru.fincode.tsudesk.core.navigation.navigateToTopLevel
import ru.fincode.tsudesk.core.ui.components.BottomBarItem
import ru.fincode.tsudesk.core.ui.components.TsudeskBottomBar
@Composable @Composable
fun MainScaffold( fun MainScaffold(
@@ -40,6 +41,10 @@ fun MainScaffold(
val selected = selectedTopLevel(backStackEntry) val selected = selectedTopLevel(backStackEntry)
Scaffold( Scaffold(
// ВАЖНО: при edge-to-edge отключаем автоматические systemBars insets у Scaffold,
// иначе получаем двойные отступы (Scaffold + windowInsetsPadding в Header'е)
contentWindowInsets = WindowInsets(0),
bottomBar = { bottomBar = {
if (shouldShowBottomBar(backStackEntry)) { if (shouldShowBottomBar(backStackEntry)) {
val uiItems = items.map { item -> val uiItems = items.map { item ->
@@ -63,6 +68,8 @@ fun MainScaffold(
} }
} }
) { innerPadding -> ) { innerPadding ->
// innerPadding учитывает bottomBar и системные элементы Scaffold (если будут),
// но НЕ добавляет statusBars (мы делаем это вручную в нужных местах, например в Header)
content(Modifier.padding(innerPadding)) content(Modifier.padding(innerPadding))
} }
} }

View File

@@ -1,4 +1,4 @@
package ru.fincode.core.ui.components package ru.fincode.tsudesk.core.ui.components
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector

View File

@@ -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 }
)
)
}
}

View File

@@ -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.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme

View File

@@ -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)

View File

@@ -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),
)

View File

@@ -0,0 +1,18 @@
package ru.fincode.tsudesk.core.ui.theme
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,
)
}

View File

@@ -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
)
}
}

View File

@@ -0,0 +1,9 @@
package ru.fincode.tsudesk.core.ui.theme
import androidx.compose.runtime.Composable
object TSUDeskThemeExt {
val colors: ExtendedColors
@Composable get() = LocalExtendedColors.current
}

View File

@@ -0,0 +1,5 @@
package ru.fincode.tsudesk.core.ui.theme
import androidx.compose.material3.Typography
val TSUDeskTypography = Typography()

View File

@@ -3,7 +3,21 @@ package ru.fincode.tsudesk.feature.schedule.presentation.screen
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll 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.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState 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.Close
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp 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.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import ru.fincode.tsudesk.core.common.model.DataResult 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.dowRuShort
import ru.fincode.tsudesk.feature.schedule.presentation.util.todayString import ru.fincode.tsudesk.feature.schedule.presentation.util.todayString
@@ -60,31 +88,38 @@ private fun ScheduleHeader(
onIntent: (ScheduleIntent) -> Unit onIntent: (ScheduleIntent) -> Unit
) { ) {
val shape = RoundedCornerShape(bottomStart = 20.dp, bottomEnd = 20.dp) val shape = RoundedCornerShape(bottomStart = 20.dp, bottomEnd = 20.dp)
val brand = TSUDeskThemeExt.colors
val headerBrush = Brush.linearGradient( val headerBrush = Brush.linearGradient(
listOf( listOf(
MaterialTheme.colorScheme.error, brand.brand,
MaterialTheme.colorScheme.error.copy(alpha = 0.75f) brand.brand.copy(alpha = 0.75f)
) )
) )
Column( Box(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(shape) .clip(shape)
.background(headerBrush) .background(headerBrush)
.padding(top = 20.dp, start = 16.dp, end = 16.dp, bottom = 14.dp) ) {
Column(
Modifier
// inset только для содержимого, фон остаётся до верха
.windowInsetsPadding(WindowInsets.statusBars)
.padding(start = 16.dp, end = 16.dp, bottom = 14.dp)
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Text( Text(
text = "Расписание", text = "Расписание",
color = MaterialTheme.colorScheme.onError, color = brand.onBrand,
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
Text( Text(
text = "ТулГУ • Осенний семестр", text = "ТулГУ • Осенний семестр",
color = MaterialTheme.colorScheme.onError.copy(alpha = 0.8f), color = brand.onBrand.copy(alpha = 0.8f),
fontSize = 11.sp, fontSize = 11.sp,
fontWeight = FontWeight.Medium fontWeight = FontWeight.Medium
) )
@@ -105,19 +140,17 @@ private fun ScheduleHeader(
isLoading = state.isLoading, isLoading = state.isLoading,
onExpanded = { onIntent(ScheduleIntent.SetGroupMenuExpanded(it)) }, onExpanded = { onIntent(ScheduleIntent.SetGroupMenuExpanded(it)) },
onValueChange = { onIntent(ScheduleIntent.ChangeGroupInput(it)) }, onValueChange = { onIntent(ScheduleIntent.ChangeGroupInput(it)) },
// выбор из списка -> сразу загрузка
onSelectRecent = { group -> onSelectRecent = { group ->
onIntent(ScheduleIntent.SelectRecentGroup(group)) onIntent(ScheduleIntent.SelectRecentGroup(group))
onIntent(ScheduleIntent.ApplyGroup) onIntent(ScheduleIntent.ApplyGroup)
}, },
onRemoveRecent = { onIntent(ScheduleIntent.RemoveRecentGroup(it)) }, onRemoveRecent = { onIntent(ScheduleIntent.RemoveRecentGroup(it)) },
onApply = { onIntent(ScheduleIntent.ApplyGroup) }, onApply = { onIntent(ScheduleIntent.ApplyGroup) },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
} }
}
@Composable @Composable
private fun ModeToggle( private fun ModeToggle(
@@ -566,9 +599,10 @@ private fun LessonCard(lesson: UiLesson) {
@Composable @Composable
private fun LessonBadge(text: String, kind: BadgeKind) { private fun LessonBadge(text: String, kind: BadgeKind) {
val brand = TSUDeskThemeExt.colors
val (bg, fg) = when (kind) { val (bg, fg) = when (kind) {
BadgeKind.INFO -> MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) to MaterialTheme.colorScheme.primary 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.LAB -> MaterialTheme.colorScheme.tertiary.copy(alpha = 0.12f) to MaterialTheme.colorScheme.tertiary
BadgeKind.MUTED -> MaterialTheme.colorScheme.surfaceVariant to MaterialTheme.colorScheme.onSurfaceVariant BadgeKind.MUTED -> MaterialTheme.colorScheme.surfaceVariant to MaterialTheme.colorScheme.onSurfaceVariant
} }