From a7f35efa384c8a9110cf217979b75e853849da24 Mon Sep 17 00:00:00 2001 From: Adit Lal Date: Sat, 29 Jun 2024 19:22:05 +0530 Subject: [PATCH 1/4] Adds step 6.a - Adds material 3 --- composeApp/build.gradle.kts | 1 + gradle/libs.versions.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 03928af..550de39 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -42,6 +42,7 @@ kotlin { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) + implementation(compose.material3) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14af02e..6dfb1a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ androidx-constraintlayout = "2.1.4" androidx-core-ktx = "1.13.1" androidx-espresso-core = "3.5.1" androidx-material = "1.12.0" +material3 = "1.0.1" androidx-test-junit = "1.1.5" compose-plugin = "1.6.10" core = "1.5.0" @@ -28,7 +29,6 @@ runtime = "2.0.0" sqlDelight = "2.0.1" material3Android = "1.2.1" lifecycleViewmodelCompose = "2.7.0" -material3 = "1.2.1" kotlinVersion = "2.0.0" voyager = "1.0.0" @@ -52,7 +52,7 @@ logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } -androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } +androidx-material3-android = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" } koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } From 37ad2e8bb88f43f10c90f2dede3627732e6aee6b Mon Sep 17 00:00:00 2001 From: Adit Lal Date: Sat, 29 Jun 2024 19:31:01 +0530 Subject: [PATCH 2/4] Adds step 6.b - Adds getWindowSize for measuring any components requiring measurements of screen --- .../app/academy/utils/WindowSize.android.kt | 12 +++++++++++ .../kotlin/app/academy/utils/WindowSize.kt | 7 +++++++ .../app/academy/utils/WindowSize.ios.kt | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 composeApp/src/androidMain/kotlin/app/academy/utils/WindowSize.android.kt create mode 100644 composeApp/src/commonMain/kotlin/app/academy/utils/WindowSize.kt create mode 100644 composeApp/src/iosMain/kotlin/app/academy/utils/WindowSize.ios.kt diff --git a/composeApp/src/androidMain/kotlin/app/academy/utils/WindowSize.android.kt b/composeApp/src/androidMain/kotlin/app/academy/utils/WindowSize.android.kt new file mode 100644 index 0000000..3552715 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/app/academy/utils/WindowSize.android.kt @@ -0,0 +1,12 @@ +package app.academy.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp + +@Composable +actual fun getWindowSize(): DpSize { + val screen = LocalConfiguration.current + return DpSize(screen.screenWidthDp.dp, screen.screenHeightDp.dp) +} diff --git a/composeApp/src/commonMain/kotlin/app/academy/utils/WindowSize.kt b/composeApp/src/commonMain/kotlin/app/academy/utils/WindowSize.kt new file mode 100644 index 0000000..7d03197 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/app/academy/utils/WindowSize.kt @@ -0,0 +1,7 @@ +package app.academy.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.DpSize + +@Composable +expect fun getWindowSize(): DpSize diff --git a/composeApp/src/iosMain/kotlin/app/academy/utils/WindowSize.ios.kt b/composeApp/src/iosMain/kotlin/app/academy/utils/WindowSize.ios.kt new file mode 100644 index 0000000..41d4aa6 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/app/academy/utils/WindowSize.ios.kt @@ -0,0 +1,20 @@ +package app.academy.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import kotlinx.cinterop.ExperimentalForeignApi +import platform.UIKit.UIScreen + +@Composable +@OptIn(ExperimentalComposeUiApi::class) +actual fun getWindowSize(): DpSize { + val screen = LocalWindowInfo.current.containerSize + val width = screen.width + val height = screen.height + // Convert points to dp + val density = UIScreen.mainScreen.scale + return DpSize((width / density).dp, (height / density).dp) +} From ed3b32b604365a825fd47653200a6918168b4cec Mon Sep 17 00:00:00 2001 From: Adit Lal Date: Sat, 29 Jun 2024 19:31:13 +0530 Subject: [PATCH 3/4] Adds step 6.c - Adds AnimatedSearchBar component --- .../academy/components/AnimatedSearchBar.kt | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt diff --git a/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt b/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt new file mode 100644 index 0000000..84a87a7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt @@ -0,0 +1,98 @@ +package app.academy.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SearchBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.academy.model.Note +import app.academy.utils.getWindowSize + +@OptIn(ExperimentalMaterial3Api::class) +@ExperimentalFoundationApi +@Composable +private fun AnimatedSearchBar( + query: String, + isSearchBarActive: Boolean, + onQueryChange: (String) -> Unit, + onClearSearchQueryButtonClick: () -> Unit, + onActiveChange: (Boolean) -> Unit, + onBackButtonClick: () -> Unit, + onNoteDismissed: (Note) -> Unit, + onNoteItemClick: (Note) -> Unit, + modifier: Modifier = Modifier, + suggestionsForQuery: List +) { + val leadingIcon = @Composable { + AnimatedContent(targetState = isSearchBarActive, label = "") { isActive -> + if (isActive) { + IconButton(onClick = onBackButtonClick) { + Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null) + } + } else { + Icon(imageVector = Icons.Filled.Search, contentDescription = null) + } + } + } + + val trailingIcon = @Composable { + AnimatedContent(targetState = isSearchBarActive, label = "") { isActive -> + if (isActive) { + IconButton( + onClick = onClearSearchQueryButtonClick, + content = { Icon(imageVector = Icons.Filled.Close, contentDescription = null) } + ) + } + } + } + val windowSizeWithoutInsets = getWindowSize() + Column(modifier = modifier) { + SearchBar( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .sizeIn( + // need sizeIn modifier to prevent exception being thrown about size being set to infinity when search bar is expanded + maxWidth = windowSizeWithoutInsets.width, + maxHeight = windowSizeWithoutInsets.height + ), + query = query, + onQueryChange = onQueryChange, + onSearch = { + // no need for this callback because this app uses instant search + }, + active = isSearchBarActive, + onActiveChange = onActiveChange, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + placeholder = { Text(text = "Search notes") }, + ) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(top = 8.dp) + ) { + NoteItems( + notes = suggestionsForQuery, + onClick = onNoteItemClick, + onDismissed = onNoteDismissed + ) + } + } + } +} From cd9b1710fcf69524512214e89b78f043a8db7209 Mon Sep 17 00:00:00 2001 From: Adit Lal Date: Sat, 29 Jun 2024 19:43:29 +0530 Subject: [PATCH 4/4] Adds step 6.c - Adds AnimatedSearchBar to Homescreen --- .../academy/components/AnimatedSearchBar.kt | 2 +- .../kotlin/app/academy/ui/HomeScreen.kt | 45 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt b/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt index 84a87a7..6575648 100644 --- a/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt +++ b/composeApp/src/commonMain/kotlin/app/academy/components/AnimatedSearchBar.kt @@ -27,7 +27,7 @@ import app.academy.utils.getWindowSize @OptIn(ExperimentalMaterial3Api::class) @ExperimentalFoundationApi @Composable -private fun AnimatedSearchBar( +fun AnimatedSearchBar( query: String, isSearchBarActive: Boolean, onQueryChange: (String) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/app/academy/ui/HomeScreen.kt b/composeApp/src/commonMain/kotlin/app/academy/ui/HomeScreen.kt index 28e40a7..bbe24ee 100644 --- a/composeApp/src/commonMain/kotlin/app/academy/ui/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/app/academy/ui/HomeScreen.kt @@ -1,17 +1,60 @@ package app.academy.ui +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn 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.Modifier +import androidx.compose.ui.unit.dp +import app.academy.components.AnimatedSearchBar import app.academy.components.NoteItems -import app.academy.model.dummyNotes import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow object HomeScreen : Screen { + @OptIn(ExperimentalFoundationApi::class) @Composable override fun Content() { + val navigator = LocalNavigator.currentOrThrow + var currentSearchQuery by remember { mutableStateOf("") } + var isSearchBarActive by remember { mutableStateOf(false) } LazyColumn(Modifier.fillMaxSize()) { + item { + // Do not add status bars padding to AnimatedSearchBar. + // If the padding is added, then the search bar wouldn't + // fill the entire screen when it is expanded. + AnimatedSearchBar( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + query = currentSearchQuery, + isSearchBarActive = isSearchBarActive, + onQueryChange = { + currentSearchQuery = it + // TODO call search + }, + onBackButtonClick = { isSearchBarActive = false }, + onActiveChange = { isSearchBarActive = it }, + onClearSearchQueryButtonClick = { + currentSearchQuery = "" + // TODO call search + }, + suggestionsForQuery = emptyList(),// TODO fetch via search ui state + onNoteDismissed = { + //TODO note dismiss + }, + onNoteItemClick = { + //TODO on note click + } + ) + } NoteItems(emptyList(), onClick = {}) { //onDismissed }