diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 848c5acc..1e44785a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,7 +12,7 @@ plugins { object Version { private const val MAJOR = 1 private const val MINOR = 4 - private const val PATCH = 2 + private const val PATCH = 3 const val CODE = MAJOR * 10000 + MINOR * 100 + PATCH const val NAME = "$MAJOR.$MINOR.$PATCH" @@ -104,11 +104,6 @@ dependencies { androidTestImplementation(libs.androidx.test.extJunit) } -ksp { - arg("room.incremental", "true") - arg("room.generateKotlin", "true") -} - room { schemaDirectory("$projectDir/schemas") } diff --git a/app/src/main/java/ru/spbu/depnav/data/db/DatabaseModule.kt b/app/src/main/java/ru/spbu/depnav/data/db/DatabaseModule.kt index f78e1964..1c15d393 100644 --- a/app/src/main/java/ru/spbu/depnav/data/db/DatabaseModule.kt +++ b/app/src/main/java/ru/spbu/depnav/data/db/DatabaseModule.kt @@ -43,7 +43,7 @@ object DatabaseModule { DB_ASSET ) .createFromAsset(DB_ASSET) - .fallbackToDestructiveMigration() + .fallbackToDestructiveMigration(false) .build() @Provides diff --git a/app/src/main/java/ru/spbu/depnav/ui/MainActivity.kt b/app/src/main/java/ru/spbu/depnav/ui/MainActivity.kt index aad764cf..2945f713 100644 --- a/app/src/main/java/ru/spbu/depnav/ui/MainActivity.kt +++ b/app/src/main/java/ru/spbu/depnav/ui/MainActivity.kt @@ -25,7 +25,6 @@ import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -47,27 +46,20 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - CompositionLocalProvider( - // Fix for https://issuetracker.google.com/issues/336842920 -- should be removed as - // soon as Compose UI 1.7.0 becomes stable - androidx.lifecycle.compose.LocalLifecycleOwner provides - androidx.compose.ui.platform.LocalLifecycleOwner.current - ) { - val themeMode by prefs.themeModeFlow.collectAsStateWithLifecycle() - val darkTheme = when (themeMode) { - ThemeMode.LIGHT -> false - ThemeMode.DARK -> true - ThemeMode.SYSTEM -> isSystemInDarkTheme() - } - - LaunchedEffect(darkTheme) { - val style = - SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { darkTheme } - enableEdgeToEdge(style, style) - } + val themeMode by prefs.themeModeFlow.collectAsStateWithLifecycle() + val darkTheme = when (themeMode) { + ThemeMode.LIGHT -> false + ThemeMode.DARK -> true + ThemeMode.SYSTEM -> isSystemInDarkTheme() + } - DepNavTheme(darkTheme = darkTheme) { MapScreen(prefs) } + LaunchedEffect(darkTheme) { + val style = + SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { darkTheme } + enableEdgeToEdge(style, style) } + + DepNavTheme(darkTheme = darkTheme) { MapScreen(prefs) } } } } diff --git a/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt b/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt index e15aee0d..79e30ff1 100644 --- a/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt +++ b/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt @@ -29,8 +29,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing @@ -50,23 +48,25 @@ import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +import ovh.plrapps.mapcompose.utils.lerp import ru.spbu.depnav.R import ru.spbu.depnav.ui.theme.DEFAULT_PADDING import ru.spbu.depnav.ui.theme.ON_MAP_SURFACE_ALPHA import ru.spbu.depnav.ui.viewmodel.SearchResults // These are basically copied from SearchBar implementation -private val ACTIVATION_ENTER_SPEC = tween( +private val EXPANSION_ENTER_SPEC = tween( durationMillis = 600, delayMillis = 100, easing = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f) ) -private val ACTIVATION_EXIT_SPEC = tween( +private val EXPANSION_EXIT_SPEC = tween( durationMillis = 350, delayMillis = 100, easing = CubicBezierEasing(0.0f, 1.0f, 0.0f, 1.0f) @@ -76,83 +76,68 @@ private val ACTIVATION_EXIT_SPEC = tween( * Search bar for querying map markers on [ru.spbu.depnav.ui.screen.MapScreen]. */ @Composable -@Suppress( - "LongMethod", // No point in further shrinking - "LongParameterList" // Considered OK for a composable -) +@Suppress("LongParameterList") // Considered OK for a composable @OptIn(ExperimentalMaterial3Api::class) fun MapSearchBar( query: String, onQueryChange: (String) -> Unit, mapTitle: String, - active: Boolean, - onActiveChange: (Boolean) -> Unit, + expanded: Boolean, + onExpandedChange: (Boolean) -> Unit, results: SearchResults, onResultClick: (Int) -> Unit, onMenuClick: () -> Unit, modifier: Modifier = Modifier ) { - val activationAnimationProgress by animateFloatAsState( - targetValue = if (active) 1f else 0f, - animationSpec = if (active) ACTIVATION_ENTER_SPEC else ACTIVATION_EXIT_SPEC, + val expansionAnimationProgress by animateFloatAsState( + targetValue = if (expanded) 1f else 0f, + animationSpec = if (expanded) EXPANSION_ENTER_SPEC else EXPANSION_EXIT_SPEC, label = "Map search bar activation animation progress" ) - val (insetsStartPadding, insetsEndPadding) = with(WindowInsets.safeDrawing.asPaddingValues()) { - val layoutDirection = LocalLayoutDirection.current - calculateStartPadding(layoutDirection) to calculateEndPadding(layoutDirection) - } - - val outerStartPadding = insetsStartPadding * (1 - activationAnimationProgress) - val outerEndPadding = insetsEndPadding * (1 - activationAnimationProgress) - val innerStartPadding = insetsStartPadding * activationAnimationProgress - val innerEndPadding = insetsEndPadding * activationAnimationProgress - - val focusManager = LocalFocusManager.current - - val containerColorAlpha = - ON_MAP_SURFACE_ALPHA + (1 - ON_MAP_SURFACE_ALPHA) * activationAnimationProgress - SearchBar( - query = query, - onQueryChange = onQueryChange, - onSearch = { focusManager.clearFocus() }, - active = active, - onActiveChange = onActiveChange, - modifier = Modifier - .run { - if (active) padding(start = outerStartPadding, end = outerEndPadding) - else windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) - } - .then(modifier), - placeholder = { - Text( - stringResource(R.string.search_on_map, mapTitle), - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - }, - leadingIcon = { - AnimatedLeadingIcon( - active, - onMenuClick = onMenuClick, - modifier = Modifier.padding(start = innerStartPadding), - onNavigateBackClick = { onActiveChange(false) } - ) - }, - trailingIcon = { - AnimatedTrailingIcon( - active, - query.isEmpty(), - onClearClick = { onQueryChange("") }, - modifier = Modifier.padding(end = innerEndPadding) + inputField = { + SearchBarDefaults.InputField( + query = query, + onQueryChange = onQueryChange, + onSearch = with (LocalFocusManager.current) { { clearFocus() } }, + expanded = expanded, + onExpandedChange = onExpandedChange, + modifier = Modifier.windowInsetsPadding( + WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal) * + expansionAnimationProgress + ), + placeholder = { + Text( + stringResource(R.string.search_on_map, mapTitle), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + }, + leadingIcon = { + AnimatedLeadingIcon( + expanded, + onMenuClick = onMenuClick, + onNavigateBackClick = { onExpandedChange(false) } + ) + }, + trailingIcon = { + AnimatedTrailingIcon( + expanded, + queryEmpty = query.isEmpty(), + onClearClick = { onQueryChange("") } + ) + } ) }, + expanded = expanded, + onExpandedChange = onExpandedChange, + modifier = modifier, colors = SearchBarDefaults.colors( containerColor = MaterialTheme.colorScheme.surfaceVariant.copy( - alpha = containerColorAlpha + alpha = lerp(ON_MAP_SURFACE_ALPHA, 1f, expansionAnimationProgress) ) - ) + ), ) { val keyboard = LocalSoftwareKeyboardController.current @@ -160,22 +145,28 @@ fun MapSearchBar( results, onScroll = { onTop -> keyboard?.apply { if (onTop) show() else hide() } }, onResultClick = { - onActiveChange(false) + onExpandedChange(false) onResultClick(it) }, modifier = Modifier + .windowInsetsPadding(WindowInsets.safeDrawing) .padding(horizontal = DEFAULT_PADDING * 1.5f) - .padding( - start = innerStartPadding, - end = innerEndPadding, - bottom = WindowInsets.safeDrawing - .asPaddingValues() - .calculateBottomPadding() - ) ) } } +@Composable +private operator fun WindowInsets.times(num: Float): WindowInsets { + val paddings = asPaddingValues(LocalDensity.current) + val layoutDirection = LocalLayoutDirection.current + return WindowInsets( + paddings.calculateLeftPadding(layoutDirection) * num, + paddings.calculateTopPadding() * num, + paddings.calculateRightPadding(layoutDirection) * num, + paddings.calculateBottomPadding() * num + ) +} + @Composable private fun AnimatedLeadingIcon( searchBarActive: Boolean, diff --git a/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt b/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt index 117dbbb4..0c17a1d5 100644 --- a/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt +++ b/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt @@ -218,12 +218,12 @@ private fun BoxScope.AnimatedSearchBar( onResultClick: (Int) -> Unit, onMenuClick: () -> Unit ) { - var searchBarActive by rememberSaveable { mutableStateOf(false) } + var searchBarExpanded by rememberSaveable { mutableStateOf(false) } if (!visible) { - searchBarActive = false + searchBarExpanded = false } - if (!searchBarActive && query.isNotEmpty()) { + if (!searchBarExpanded && query.isNotEmpty()) { onQueryChange("") } @@ -236,7 +236,7 @@ private fun BoxScope.AnimatedSearchBar( exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut() ) { val horizontalPadding by animateDpAsState( - if (searchBarActive) 0.dp else DEFAULT_PADDING, + if (searchBarExpanded) 0.dp else DEFAULT_PADDING, label = "Map search bar horizontal padding" ) @@ -244,8 +244,8 @@ private fun BoxScope.AnimatedSearchBar( query = query, onQueryChange = onQueryChange, mapTitle = mapTitle, - active = searchBarActive, - onActiveChange = { searchBarActive = it }, + expanded = searchBarExpanded, + onExpandedChange = { searchBarExpanded = it }, results = searchResults, onResultClick = onResultClick, onMenuClick = onMenuClick, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f2a82a2..628e2081 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -kotlin = "2.0.20" -kspPlugin = "2.0.20-1.0.24" -hilt = "2.52" -lifecycle = "2.8.4" -room = "2.6.1" +kotlin = "2.1.20" +kspPlugin = "2.1.20-2.0.0" +hilt = "2.56.2" +lifecycle = "2.8.7" +room = "2.7.0" [libraries] @@ -20,15 +20,15 @@ androidx-room-compiler = { group = "androidx.room", name = "room-compiler", vers google-dagger-hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } google-dagger-hiltCompiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } -androidx-compose-bom = "androidx.compose:compose-bom:2024.08.00" +androidx-compose-bom = "androidx.compose:compose-bom:2025.04.00" androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-ui = { module = "androidx.compose.ui:ui" } androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } -androidx-core-ktx = "androidx.core:core-ktx:1.13.1" -androidx-activity-compose = "androidx.activity:activity-compose:1.9.1" -plrapps-mapcompose = "ovh.plrapps:mapcompose:2.12.6" +androidx-core-ktx = "androidx.core:core-ktx:1.16.0" +androidx-activity-compose = "androidx.activity:activity-compose:1.10.1" +plrapps-mapcompose = "ovh.plrapps:mapcompose:2.16.2" junit = "junit:junit:4.13.2" androidx-test-runner = "androidx.test:runner:1.6.2" @@ -38,7 +38,7 @@ androidx-test-extJunit = "androidx.test.ext:junit:1.2.1" [plugins] -android-application = { id = "com.android.application", version = "8.5.2" } +android-application = { id = "com.android.application", version = "8.9.1" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-plugin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } google-dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8ab18bbe..51edaea4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Aug 27 22:36:14 CEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists