diff --git a/examples/jetsnack/android/build.gradle.kts b/examples/jetsnack/android/build.gradle.kts index b8904fd7ece..2c73ab43eb9 100644 --- a/examples/jetsnack/android/build.gradle.kts +++ b/examples/jetsnack/android/build.gradle.kts @@ -13,6 +13,7 @@ repositories { } dependencies { + implementation(compose.components.resources) implementation(project(":common")) implementation("androidx.activity:activity-compose:1.5.0") } diff --git a/examples/jetsnack/android/src/main/java/com/example/android/MainActivity.kt b/examples/jetsnack/android/src/main/java/com/example/android/MainActivity.kt index faa1aee2c12..0971268a4fd 100644 --- a/examples/jetsnack/android/src/main/java/com/example/android/MainActivity.kt +++ b/examples/jetsnack/android/src/main/java/com/example/android/MainActivity.kt @@ -3,30 +3,13 @@ package com.example.android import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.compose.material.MaterialTheme -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import com.example.jetsnack.R +import androidx.compose.material3.MaterialTheme import com.example.jetsnack.ui.JetsnackApp -import com.example.jetsnack.ui.theme.Karla -import com.example.jetsnack.ui.theme.Montserrat class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Montserrat = FontFamily( - Font(R.font.montserrat_light, FontWeight.Light), - Font(R.font.montserrat_regular, FontWeight.Normal), - Font(R.font.montserrat_medium, FontWeight.Medium), - Font(R.font.montserrat_semibold, FontWeight.SemiBold) - ) - Karla = FontFamily( - Font(R.font.karla_regular, FontWeight.Normal), - Font(R.font.karla_bold, FontWeight.Bold) - ) - setContent { MaterialTheme { JetsnackApp() diff --git a/examples/jetsnack/common/build.gradle.kts b/examples/jetsnack/common/build.gradle.kts index 08100273ed3..437781a6b89 100644 --- a/examples/jetsnack/common/build.gradle.kts +++ b/examples/jetsnack/common/build.gradle.kts @@ -38,14 +38,16 @@ kotlin { dependencies { api(compose.runtime) api(compose.foundation) - api(compose.material) + api(compose.ui) + api(compose.material3) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.lifecycle.viewModelCompose) + implementation(libs.androidx.lifecycle.viewModel) + implementation(compose.materialIconsExtended) implementation(compose.components.resources) implementation(libs.kotlinx.coroutines) } } - val nonAndroidMain by creating { - dependsOn(commonMain) - } val commonTest by getting { dependencies { implementation(kotlin("test")) @@ -73,22 +75,23 @@ kotlin { } } val desktopMain by getting { - dependsOn(nonAndroidMain) + dependsOn(commonMain) dependencies { api(compose.preview) + implementation(libs.kotlinx.coroutines.swing) } } val desktopTest by getting val wasmJsMain by getting { + dependsOn(commonMain) dependencies { implementation(kotlin("stdlib")) } - dependsOn(nonAndroidMain) } val iosMain by getting { - dependsOn(nonAndroidMain) + dependsOn(commonMain) } } } diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/drawableResources.android.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/drawableResources.android.kt deleted file mode 100644 index f17000c7799..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/drawableResources.android.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.jetsnack - -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.painter.Painter - -@Composable -actual fun painterResource(id: Int): Painter { - return androidx.compose.ui.res.painterResource(id) -} - -actual val MppR.drawable.empty_state_search: Int - get() = R.drawable.empty_state_search - diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/stringResource.android.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/stringResource.android.kt deleted file mode 100644 index 68b497ec9eb..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/stringResource.android.kt +++ /dev/null @@ -1,114 +0,0 @@ -@file:Suppress("PrivatePropertyName") - -package com.example.jetsnack - -import androidx.compose.runtime.Composable - -@Composable -actual fun stringResource(id: Int): String { - return androidx.compose.ui.res.stringResource(id) -} - -@Composable -actual fun stringResource(id: Int, part: String): String { - return androidx.compose.ui.res.stringResource(id, part) -} - -@Composable -actual fun stringResource(id: Int, count: Int): String { - return androidx.compose.ui.res.stringResource(id, count) -} - - -// Filters -actual val MppR.string.label_filters: Int get() = R.string.label_filters - -// Qty -actual val MppR.string.quantity: Int get() = R.string.quantity - -actual val MppR.string.label_decrease: Int get() = R.string.label_decrease - -actual val MppR.string.label_increase: Int get() = R.string.label_increase - - -// Snack detail -actual val MppR.string.label_back: Int get() = R.string.label_back - -actual val MppR.string.detail_header: Int get() = R.string.detail_header - -actual val MppR.string.detail_placeholder: Int get() = R.string.detail_placeholder - -actual val MppR.string.see_more: Int get() = R.string.see_more - -actual val MppR.string.see_less: Int get() = R.string.see_less - -actual val MppR.string.ingredients: Int get() = R.string.ingredients - -actual val MppR.string.ingredients_list: Int get() = R.string.ingredients_list - -actual val MppR.string.add_to_cart: Int get() = R.string.add_to_cart - -// Home -actual val MppR.string.label_select_delivery: Int get() = R.string.label_select_delivery - - -// Filter -actual val MppR.string.max_calories: Int get() = R.string.max_calories - -actual val MppR.string.per_serving: Int get() = R.string.per_serving - -actual val MppR.string.sort: Int get() = R.string.sort - -actual val MppR.string.lifestyle: Int get() = R.string.lifestyle - -actual val MppR.string.category: Int get() = R.string.category - -actual val MppR.string.price: Int get() = R.string.price - -actual val MppR.string.reset: Int get() = R.string.reset - -actual val MppR.string.close: Int get() = R.string.close - -// Profile - -actual val MppR.string.work_in_progress: Int get() = R.string.work_in_progress - -actual val MppR.string.grab_beverage: Int get() = R.string.grab_beverage - -// Home -actual val MppR.string.home_feed: Int get() = R.string.home_feed - -actual val MppR.string.home_search: Int get() = R.string.home_search - -actual val MppR.string.home_cart: Int get() = R.string.home_cart - -actual val MppR.string.home_profile: Int get() = R.string.home_profile - - -// Search -actual val MppR.string.search_no_matches: Int get() = R.string.search_no_matches - -actual val MppR.string.search_no_matches_retry: Int get() = R.string.search_no_matches_retry - -actual val MppR.string.label_add: Int get() = R.string.label_add - -actual val MppR.string.search_count: Int get() = R.string.search_count - -actual val MppR.string.label_search: Int get() = R.string.label_search - -actual val MppR.string.search_jetsnack: Int get() = R.string.search_jetsnack - -actual val MppR.string.cart_increase_error: Int get() = R.string.cart_increase_error -actual val MppR.string.cart_decrease_error: Int get() = R.string.cart_decrease_error - - -// Cart -actual val MppR.plurals.cart_order_count: Int get() = R.plurals.cart_order_count -actual val MppR.string.cart_order_header: Int get() = R.string.cart_order_header -actual val MppR.string.remove_item: Int get() = R.string.remove_item -actual val MppR.string.cart_summary_header: Int get() = R.string.cart_summary_header -actual val MppR.string.cart_subtotal_label: Int get() = R.string.cart_subtotal_label -actual val MppR.string.cart_shipping_label: Int get() = R.string.cart_shipping_label -actual val MppR.string.cart_total_label: Int get() = R.string.cart_total_label -actual val MppR.string.cart_checkout: Int get() = R.string.cart_checkout -actual val MppR.string.label_remove: Int get() = R.string.label_remove \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/JetsnackScaffoldContent.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/JetsnackScaffoldContent.kt deleted file mode 100644 index 61281fa8635..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/JetsnackScaffoldContent.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.example.jetsnack.ui - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.navigation -import androidx.navigation.navArgument -import com.example.jetsnack.ui.home.Feed -import com.example.jetsnack.ui.home.HomeSections -import com.example.jetsnack.ui.home.Profile -import com.example.jetsnack.ui.home.cart.Cart -import com.example.jetsnack.ui.home.search.Search -import com.example.jetsnack.ui.snackdetail.SnackDetail - -@Composable -actual fun JetsnackScaffoldContent( - innerPaddingModifier: PaddingValues, - appState: MppJetsnackAppState -) { - NavHost( - navController = appState.navController, - startDestination = MainDestinations.HOME_ROUTE, - modifier = Modifier.padding(innerPaddingModifier) - ) { - jetsnackNavGraph( - onSnackSelected = appState::navigateToSnackDetail, - upPress = appState::upPress - ) - } -} - -private fun NavGraphBuilder.jetsnackNavGraph( - onSnackSelected: (Long, NavBackStackEntry) -> Unit, - upPress: () -> Unit, -) { - navigation( - route = MainDestinations.HOME_ROUTE, - startDestination = HomeSections.FEED.route - ) { - addHomeGraph(onSnackSelected) - } - composable( - "${MainDestinations.SNACK_DETAIL_ROUTE}/{${MainDestinations.SNACK_ID_KEY}}", - arguments = listOf(navArgument(MainDestinations.SNACK_ID_KEY) { type = NavType.LongType }) - ) { backStackEntry -> - val arguments = requireNotNull(backStackEntry.arguments) - val snackId = arguments.getLong(MainDestinations.SNACK_ID_KEY) - SnackDetail(snackId, upPress, onSnackClick = { onSnackSelected(snackId, backStackEntry) }) - } -} - -fun NavGraphBuilder.addHomeGraph( - onSnackSelected: (Long, NavBackStackEntry) -> Unit, - modifier: Modifier = Modifier -) { - composable(HomeSections.FEED.route) { from -> - Feed(onSnackClick = { id -> onSnackSelected(id, from) }, modifier) - } - composable(HomeSections.SEARCH.route) { from -> - Search(onSnackClick = { id -> onSnackSelected(id, from) }, modifier) - } - composable(HomeSections.CART.route) { from -> - Cart(onSnackClick = { id -> onSnackSelected(id, from) }, modifier) - } - composable(HomeSections.PROFILE.route) { - Profile(modifier) - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/MppJetsnackAppState.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/MppJetsnackAppState.kt deleted file mode 100644 index 69f2edbf509..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/MppJetsnackAppState.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.example.jetsnack.ui - -import android.content.res.Resources -import androidx.compose.material.ScaffoldState -import androidx.compose.material.rememberScaffoldState -import androidx.compose.runtime.* -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.lifecycle.Lifecycle -import androidx.navigation.* -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import com.example.jetsnack.model.SnackbarManager -import com.example.jetsnack.ui.home.HomeSections -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -@Stable -actual class MppJetsnackAppState( - actual val scaffoldState: ScaffoldState, - actual val snackbarManager: SnackbarManager, - actual val coroutineScope: CoroutineScope, - val navController: NavHostController, - val resources: Resources -) { - - init { - coroutineScope.launch { - snackbarManager.messages.collect { currentMessages -> - if (currentMessages.isNotEmpty()) { - val message = currentMessages[0] - val text = resources.getText(message.message).toString() - - // Display the snackbar on the screen. `showSnackbar` is a function - // that suspends until the snackbar disappears from the screen - scaffoldState.snackbarHostState.showSnackbar(text) - // Once the snackbar is gone or dismissed, notify the SnackbarManager - snackbarManager.setMessageShown(message.id) - } - } - } - } - - private val bottomBarRoutes = bottomBarTabs.map { it.route } - - actual val bottomBarTabs: Array - get() = HomeSections.values() - actual val currentRoute: String? - get() = navController.currentDestination?.route - - - @Composable - actual fun shouldShowBottomBar(): Boolean { - return navController - .currentBackStackEntryAsState().value?.destination?.route in bottomBarRoutes - } - - actual fun navigateToBottomBarRoute(route: String) { - if (route != currentRoute) { - navController.navigate(route) { - launchSingleTop = true - restoreState = true - // Pop up backstack to the first destination and save state. This makes going back - // to the start destination when pressing back in any other bottom tab. - popUpTo(findStartDestination(navController.graph).id) { - saveState = true - } - } - } - } - - fun navigateToSnackDetail(snackId: Long, from: NavBackStackEntry) { - // In order to discard duplicated navigation events, we check the Lifecycle - if (from.lifecycleIsResumed()) { - navController.navigate("${MainDestinations.SNACK_DETAIL_ROUTE}/$snackId") - } - } - - fun upPress() { - navController.navigateUp() - } -} - -@Suppress("UsePropertyAccessSyntax") -private fun NavBackStackEntry.lifecycleIsResumed() = - this.getLifecycle().currentState == Lifecycle.State.RESUMED - -private val NavGraph.startDestination: NavDestination? - get() = findNode(startDestinationId) - -/** - * Copied from similar function in NavigationUI.kt - * - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt - */ -private tailrec fun findStartDestination(graph: NavDestination): NavDestination { - return if (graph is NavGraph) findStartDestination(graph.startDestination!!) else graph -} - -@Composable -actual fun rememberMppJetsnackAppState(): MppJetsnackAppState { - val scaffoldState = rememberScaffoldState() - val navController = rememberNavController() - val resources = resources() - val snackbarManager = SnackbarManager - val coroutineScope = rememberCoroutineScope() - - return remember(scaffoldState, navController, snackbarManager, resources, coroutineScope) { - MppJetsnackAppState(scaffoldState, snackbarManager, coroutineScope, navController, resources) - } -} - -/** - * A composable function that returns the [Resources]. It will be recomposed when `Configuration` - * gets updated. - */ -@Composable -@ReadOnlyComposable -private fun resources(): Resources { - LocalConfiguration.current - return LocalContext.current.resources -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt deleted file mode 100644 index bfd48c50787..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.example.jetsnack.ui.components - -import android.annotation.SuppressLint -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.core.TweenSpec -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.with -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -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.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.layout.ContentScale -import com.example.common.generated.resources.Res -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.jetbrains.compose.resources.ExperimentalResourceApi - -private val imagesCache = mutableMapOf() - -@SuppressLint("UnusedContentLambdaTargetStateParameter") -@OptIn(ExperimentalAnimationApi::class, ExperimentalResourceApi::class) -@Composable -actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifier: Modifier) { - var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) } - - - AnimatedContent(img, transitionSpec = { - fadeIn(TweenSpec()) with fadeOut(TweenSpec()) - }) { - if (img != null) { - Image(img!!, contentDescription = contentDescription, modifier = modifier, contentScale = ContentScale.Crop) - } else { - Box(modifier = modifier) - } - } - - LaunchedEffect(imageUrl) { - if (imagesCache.contains(imageUrl)) { - img = imagesCache[imageUrl] - } else { - withContext(Dispatchers.IO) { - img = try { - Res.readBytes(imageUrl).toAndroidBitmap().asImageBitmap().also { - imagesCache[imageUrl] = it - img = it - } - } catch (e: Throwable) { - e.printStackTrace() - null - } - } - } - } -} - -fun ByteArray.toAndroidBitmap(): Bitmap { - return BitmapFactory.decodeByteArray(this, 0, size) -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/SnackDialog.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/SnackDialog.kt deleted file mode 100644 index 3d9cd7f2f91..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/SnackDialog.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.jetsnack.ui.home - -import androidx.compose.runtime.Composable -import androidx.compose.ui.window.Dialog - -@Composable -actual fun SnackDialog(onCloseRequest: () -> Unit, content: @Composable () -> Unit) { - Dialog(onDismissRequest = onCloseRequest, content = content) -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.android.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.android.kt deleted file mode 100644 index 87035a37d5c..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.android.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetsnack.ui.home.cart - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ChainStyle -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import com.example.jetsnack.R -import com.example.jetsnack.model.OrderLine -import com.example.jetsnack.model.SnackRepo -import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.QuantitySelector -import com.example.jetsnack.ui.components.SnackImage -import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.utils.formatPrice - -@Composable -actual fun provideCartViewModel(): CartViewModel { - return viewModel(factory = CartViewModel.provideFactory()) -} - -@Composable -actual fun ActualCartItem( - orderLine: OrderLine, - removeSnack: (Long) -> Unit, - increaseItemCount: (Long) -> Unit, - decreaseItemCount: (Long) -> Unit, - onSnackClick: (Long) -> Unit, - modifier: Modifier -) { - val snack = orderLine.snack - ConstraintLayout( - modifier = modifier - .fillMaxWidth() - .clickable { onSnackClick(snack.id) } - .background(JetsnackTheme.colors.uiBackground) - .padding(horizontal = 24.dp) - - ) { - val (divider, image, name, tag, priceSpacer, price, remove, quantity) = createRefs() - createVerticalChain(name, tag, priceSpacer, price, chainStyle = ChainStyle.Packed) - SnackImage( - imageUrl = snack.imageUrl, - contentDescription = null, - modifier = Modifier - .size(100.dp) - .constrainAs(image) { - top.linkTo(parent.top, margin = 16.dp) - bottom.linkTo(parent.bottom, margin = 16.dp) - start.linkTo(parent.start) - } - ) - Text( - text = snack.name, - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary, - modifier = Modifier.constrainAs(name) { - linkTo( - start = image.end, - startMargin = 16.dp, - end = remove.start, - endMargin = 16.dp, - bias = 0f - ) - } - ) - IconButton( - onClick = { removeSnack(snack.id) }, - modifier = Modifier - .constrainAs(remove) { - top.linkTo(parent.top) - end.linkTo(parent.end) - } - .padding(top = 12.dp) - ) { - Icon( - imageVector = Icons.Filled.Close, - tint = JetsnackTheme.colors.iconSecondary, - contentDescription = stringResource(R.string.label_remove) - ) - } - Text( - text = snack.tagline, - style = MaterialTheme.typography.body1, - color = JetsnackTheme.colors.textHelp, - modifier = Modifier.constrainAs(tag) { - linkTo( - start = image.end, - startMargin = 16.dp, - end = parent.end, - endMargin = 16.dp, - bias = 0f - ) - } - ) - Spacer( - Modifier - .height(8.dp) - .constrainAs(priceSpacer) { - linkTo(top = tag.bottom, bottom = price.top) - } - ) - Text( - text = formatPrice(snack.price), - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textPrimary, - modifier = Modifier.constrainAs(price) { - linkTo( - start = image.end, - end = quantity.start, - startMargin = 16.dp, - endMargin = 16.dp, - bias = 0f - ) - } - ) - QuantitySelector( - count = orderLine.count, - decreaseItemCount = { decreaseItemCount(snack.id) }, - increaseItemCount = { increaseItemCount(snack.id) }, - modifier = Modifier.constrainAs(quantity) { - baseline.linkTo(price.baseline) - end.linkTo(parent.end) - } - ) - JetsnackDivider( - Modifier.constrainAs(divider) { - linkTo(start = parent.start, end = parent.end) - top.linkTo(parent.bottom) - } - ) - } -} - -@Composable -private fun CartPreview() { - JetsnackTheme { - Cart( - orderLines = SnackRepo.getCart(), - removeSnack = {}, - increaseItemCount = {}, - decreaseItemCount = {}, - inspiredByCart = SnackRepo.getInspiredByCart(), - onSnackClick = {} - ) - } -} - -@Composable -actual fun rememberQuantityString(res: Int, qty: Int, vararg args: Any): String { - val resources = LocalContext.current.resources - return remember(qty, resources) { resources.getQuantityString(R.plurals.cart_order_count, qty, qty) } -} - -@Composable -actual fun getCartContentInsets(): WindowInsets { - return WindowInsets.statusBars.add(WindowInsets(top = 56.dp)) -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.android.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.android.kt deleted file mode 100644 index e0c5f8f018e..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.android.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetsnack.ui.home.cart - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.example.jetsnack.model.OrderLine -import com.example.jetsnack.model.SnackRepo -import com.example.jetsnack.model.SnackbarManager -import kotlinx.coroutines.flow.StateFlow - -/** - * Factory for CartViewModel that takes SnackbarManager as a dependency - */ -fun CartViewModel.Companion.provideFactory( - snackbarManager: SnackbarManager = SnackbarManager, - snackRepository: SnackRepo = SnackRepo -): ViewModelProvider.Factory = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return CartViewModel(snackbarManager, snackRepository) as T - } -} - -@OptIn(kotlin.ExperimentalMultiplatform::class) -actual abstract class JetSnackCartViewModel actual constructor() : ViewModel() { - @Composable - actual fun collectOrderLinesAsState(flow: StateFlow>): State> { - return flow.collectAsStateWithLifecycle() - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/snackCollectionListItemWindowInsets.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/snackCollectionListItemWindowInsets.kt deleted file mode 100644 index ea94ef244e5..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/home/snackCollectionListItemWindowInsets.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.jetsnack.ui.home - -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.add -import androidx.compose.foundation.layout.statusBars -import androidx.compose.runtime.Composable -import androidx.compose.ui.unit.dp - -@Composable -actual fun snackCollectionListItemWindowInsets(): WindowInsets { - return WindowInsets.statusBars.add(WindowInsets(top = 56.dp)) -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/snackdetail/jetSnackNavigationBarsPadding.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/snackdetail/jetSnackNavigationBarsPadding.kt deleted file mode 100644 index de2c3375fe7..00000000000 --- a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/snackdetail/jetSnackNavigationBarsPadding.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.jetsnack.ui.snackdetail - -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -actual fun Modifier.jetSnackNavigationBarsPadding(): Modifier = this.navigationBarsPadding() -@Composable -actual fun Modifier.jetSnackStatusBarsPadding(): Modifier = this.statusBarsPadding() -@Composable -actual fun Modifier.jetSnackSystemBarsPadding(): Modifier = this.systemBarsPadding() \ No newline at end of file diff --git a/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/utils/formatPrice.kt b/examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/utils/Currency.android.kt similarity index 100% rename from examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/utils/formatPrice.kt rename to examples/jetsnack/common/src/androidMain/kotlin/com/example/jetsnack/ui/utils/Currency.android.kt diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/almonds.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/almonds.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/almonds.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/almonds.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/apple_chips.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_chips.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/apple_chips.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_chips.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/apple_juice.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_juice.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/apple_juice.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_juice.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/apple_pie.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_pie.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/apple_pie.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_pie.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/apple_sauce.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_sauce.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/apple_sauce.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/apple_sauce.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/apples.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/apples.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/apples.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/apples.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/cheese.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/cheese.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/cheese.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/cheese.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/chips.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/chips.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/chips.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/chips.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/cupcake.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/cupcake.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/cupcake.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/cupcake.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/desserts.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/desserts.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/desserts.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/desserts.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/donut.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/donut.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/donut.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/donut.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/eclair.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/eclair.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/eclair.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/eclair.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/drawable/empty_state_search.xml b/examples/jetsnack/common/src/commonMain/composeResources/drawable/empty_state_search.xml new file mode 100644 index 00000000000..e2fa0cdac8a --- /dev/null +++ b/examples/jetsnack/common/src/commonMain/composeResources/drawable/empty_state_search.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/froyo.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/froyo.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/froyo.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/froyo.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/fruit.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/fruit.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/fruit.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/fruit.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/gingerbread.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/gingerbread.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/gingerbread.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/gingerbread.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/gluten_free.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/gluten_free.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/gluten_free.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/gluten_free.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/grapes.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/grapes.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/grapes.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/grapes.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/honeycomb.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/honeycomb.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/honeycomb.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/honeycomb.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/ice_cream_sandwich.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/ice_cream_sandwich.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/ice_cream_sandwich.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/ice_cream_sandwich.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/jelly_bean.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/jelly_bean.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/jelly_bean.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/jelly_bean.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/kitkat.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/kitkat.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/kitkat.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/kitkat.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/kiwi.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/kiwi.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/kiwi.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/kiwi.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/lollipop.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/lollipop.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/lollipop.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/lollipop.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/mango.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/mango.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/mango.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/mango.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/marshmallow.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/marshmallow.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/marshmallow.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/marshmallow.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/nougat.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/nougat.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/nougat.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/nougat.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/nuts.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/nuts.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/nuts.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/nuts.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/oreo.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/oreo.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/oreo.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/oreo.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/organic.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/organic.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/organic.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/organic.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/paleo.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/paleo.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/paleo.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/paleo.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/pie.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/pie.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/pie.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/pie.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/placeholder.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/placeholder.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/placeholder.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/placeholder.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/popcorn.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/popcorn.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/popcorn.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/popcorn.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/pretzels.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/pretzels.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/pretzels.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/pretzels.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/smoothies.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/smoothies.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/smoothies.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/smoothies.jpg diff --git a/examples/jetsnack/common/src/commonMain/composeResources/files/vegan.jpg b/examples/jetsnack/common/src/commonMain/composeResources/drawable/vegan.jpg similarity index 100% rename from examples/jetsnack/common/src/commonMain/composeResources/files/vegan.jpg rename to examples/jetsnack/common/src/commonMain/composeResources/drawable/vegan.jpg diff --git a/examples/jetsnack/common/src/androidMain/res/font/karla_bold.ttf b/examples/jetsnack/common/src/commonMain/composeResources/font/karla_bold.ttf similarity index 100% rename from examples/jetsnack/common/src/androidMain/res/font/karla_bold.ttf rename to examples/jetsnack/common/src/commonMain/composeResources/font/karla_bold.ttf diff --git a/examples/jetsnack/common/src/androidMain/res/font/karla_regular.ttf b/examples/jetsnack/common/src/commonMain/composeResources/font/karla_regular.ttf similarity index 100% rename from examples/jetsnack/common/src/androidMain/res/font/karla_regular.ttf rename to examples/jetsnack/common/src/commonMain/composeResources/font/karla_regular.ttf diff --git a/examples/jetsnack/common/src/androidMain/res/font/montserrat_light.ttf b/examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_light.ttf similarity index 100% rename from examples/jetsnack/common/src/androidMain/res/font/montserrat_light.ttf rename to examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_light.ttf diff --git a/examples/jetsnack/common/src/androidMain/res/font/montserrat_medium.ttf b/examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_medium.ttf similarity index 100% rename from examples/jetsnack/common/src/androidMain/res/font/montserrat_medium.ttf rename to examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_medium.ttf diff --git a/examples/jetsnack/common/src/androidMain/res/font/montserrat_regular.ttf b/examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_regular.ttf similarity index 100% rename from examples/jetsnack/common/src/androidMain/res/font/montserrat_regular.ttf rename to examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_regular.ttf diff --git a/examples/jetsnack/common/src/androidMain/res/font/montserrat_semibold.ttf b/examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_semibold.ttf similarity index 100% rename from examples/jetsnack/common/src/androidMain/res/font/montserrat_semibold.ttf rename to examples/jetsnack/common/src/commonMain/composeResources/font/montserrat_semibold.ttf diff --git a/examples/jetsnack/common/src/commonMain/composeResources/values/strings.xml b/examples/jetsnack/common/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000000..5864b09b36a --- /dev/null +++ b/examples/jetsnack/common/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,65 @@ + + Jetsnack + Back + + + Home + Search + My Cart + Profile + + + Filters + Select delivery address + + + Search Jetsnack + No matches for ā€œ%1$sā€ + Try broadening your search + %1$d items + Add to cart + Perform search + + + Details + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut tempus, sem vitae convallis imperdiet, lectus nunc pharetra diam, ac rhoncus quam eros eu risus. Nulla pulvinar condimentum erat, pulvinar tempus turpis blandit ut. Etiam sed ipsum sed lacus eleifend hendrerit eu quis quam. Etiam ligula eros, finibus vestibulum tortor ac, ultrices accumsan dolor. Vivamus vel nisl a libero lobortis posuere. Aenean facilisis nibh vel ultrices bibendum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse ac est vitae lacus commodo efficitur at ut massa. Etiam vestibulum sit amet sapien sed varius. Aliquam non ipsum imperdiet, pulvinar enim nec, mollis risus. Fusce id tincidunt nisl. + Ingredients + Vanilla, Almond Flour, Eggs, Butter, Cream, Sugar + Qty + ADD TO CART + + + Order (%1$s) + + %1$d item + %1$d items + + Summary + Subtotal + Shipping & Handling + Total + Checkout + There was an error and the quantity couldn\'t be increased. Please try again. + There was an error and the quantity couldn\'t be decreased. Please try again. + Remove item + + + Increase + Decrease + This is currently work in progress + Grab a beverage and check back later! + SEE MORE + SEE LESS + Remove Item + Reset + Sort + Price + Category + Max Calories + LifeStyle + per serving + Android\'s Favorite (default) + Rating + Alphabetical + Close + \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/AndroidxComposeMaterialIcons.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/AndroidxComposeMaterialIcons.kt deleted file mode 100644 index 5740149512f..00000000000 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/AndroidxComposeMaterialIcons.kt +++ /dev/null @@ -1,220 +0,0 @@ -package com.example.jetsnack - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.materialIcon -import androidx.compose.material.icons.materialPath -import androidx.compose.ui.graphics.vector.ImageVector - -public val Icons.Filled.Android: ImageVector - get() { - if (_android != null) { - return _android!! - } - _android = materialIcon(name = "Filled.Android") { - materialPath { - moveTo(17.6f, 9.48f) - lineToRelative(1.84f, -3.18f) - curveToRelative(0.16f, -0.31f, 0.04f, -0.69f, -0.26f, -0.85f) - curveToRelative(-0.29f, -0.15f, -0.65f, -0.06f, -0.83f, 0.22f) - lineToRelative(-1.88f, 3.24f) - curveToRelative(-2.86f, -1.21f, -6.08f, -1.21f, -8.94f, 0.0f) - lineTo(5.65f, 5.67f) - curveToRelative(-0.19f, -0.29f, -0.58f, -0.38f, -0.87f, -0.2f) - curveTo(4.5f, 5.65f, 4.41f, 6.01f, 4.56f, 6.3f) - lineTo(6.4f, 9.48f) - curveTo(3.3f, 11.25f, 1.28f, 14.44f, 1.0f, 18.0f) - horizontalLineToRelative(22.0f) - curveTo(22.72f, 14.44f, 20.7f, 11.25f, 17.6f, 9.48f) - close() - moveTo(7.0f, 15.25f) - curveToRelative(-0.69f, 0.0f, -1.25f, -0.56f, -1.25f, -1.25f) - curveToRelative(0.0f, -0.69f, 0.56f, -1.25f, 1.25f, -1.25f) - reflectiveCurveTo(8.25f, 13.31f, 8.25f, 14.0f) - curveTo(8.25f, 14.69f, 7.69f, 15.25f, 7.0f, 15.25f) - close() - moveTo(17.0f, 15.25f) - curveToRelative(-0.69f, 0.0f, -1.25f, -0.56f, -1.25f, -1.25f) - curveToRelative(0.0f, -0.69f, 0.56f, -1.25f, 1.25f, -1.25f) - reflectiveCurveToRelative(1.25f, 0.56f, 1.25f, 1.25f) - curveTo(18.25f, 14.69f, 17.69f, 15.25f, 17.0f, 15.25f) - close() - } - } - return _android!! - } - -private var _android: ImageVector? = null - -public val Icons.Filled.SortByAlpha: ImageVector - get() { - if (_sortByAlpha != null) { - return _sortByAlpha!! - } - _sortByAlpha = materialIcon(name = "Filled.SortByAlpha") { - materialPath { - moveTo(14.94f, 4.66f) - horizontalLineToRelative(-4.72f) - lineToRelative(2.36f, -2.36f) - close() - moveTo(10.25f, 19.37f) - horizontalLineToRelative(4.66f) - lineToRelative(-2.33f, 2.33f) - close() - moveTo(6.1f, 6.27f) - lineTo(1.6f, 17.73f) - horizontalLineToRelative(1.84f) - lineToRelative(0.92f, -2.45f) - horizontalLineToRelative(5.11f) - lineToRelative(0.92f, 2.45f) - horizontalLineToRelative(1.84f) - lineTo(7.74f, 6.27f) - lineTo(6.1f, 6.27f) - close() - moveTo(4.97f, 13.64f) - lineToRelative(1.94f, -5.18f) - lineToRelative(1.94f, 5.18f) - lineTo(4.97f, 13.64f) - close() - moveTo(15.73f, 16.14f) - horizontalLineToRelative(6.12f) - verticalLineToRelative(1.59f) - horizontalLineToRelative(-8.53f) - verticalLineToRelative(-1.29f) - lineToRelative(5.92f, -8.56f) - horizontalLineToRelative(-5.88f) - verticalLineToRelative(-1.6f) - horizontalLineToRelative(8.3f) - verticalLineToRelative(1.26f) - lineToRelative(-5.93f, 8.6f) - close() - } - } - return _sortByAlpha!! - } - -private var _sortByAlpha: ImageVector? = null - -public val Icons.Rounded.FilterList: ImageVector - get() { - if (_filterList != null) { - return _filterList!! - } - _filterList = materialIcon(name = "Rounded.FilterList") { - materialPath { - moveTo(11.0f, 18.0f) - horizontalLineToRelative(2.0f) - curveToRelative(0.55f, 0.0f, 1.0f, -0.45f, 1.0f, -1.0f) - reflectiveCurveToRelative(-0.45f, -1.0f, -1.0f, -1.0f) - horizontalLineToRelative(-2.0f) - curveToRelative(-0.55f, 0.0f, -1.0f, 0.45f, -1.0f, 1.0f) - reflectiveCurveToRelative(0.45f, 1.0f, 1.0f, 1.0f) - close() - moveTo(3.0f, 7.0f) - curveToRelative(0.0f, 0.55f, 0.45f, 1.0f, 1.0f, 1.0f) - horizontalLineToRelative(16.0f) - curveToRelative(0.55f, 0.0f, 1.0f, -0.45f, 1.0f, -1.0f) - reflectiveCurveToRelative(-0.45f, -1.0f, -1.0f, -1.0f) - lineTo(4.0f, 6.0f) - curveToRelative(-0.55f, 0.0f, -1.0f, 0.45f, -1.0f, 1.0f) - close() - moveTo(7.0f, 13.0f) - horizontalLineToRelative(10.0f) - curveToRelative(0.55f, 0.0f, 1.0f, -0.45f, 1.0f, -1.0f) - reflectiveCurveToRelative(-0.45f, -1.0f, -1.0f, -1.0f) - lineTo(7.0f, 11.0f) - curveToRelative(-0.55f, 0.0f, -1.0f, 0.45f, -1.0f, 1.0f) - reflectiveCurveToRelative(0.45f, 1.0f, 1.0f, 1.0f) - close() - } - } - return _filterList!! - } - -private var _filterList: ImageVector? = null - -public val Icons.Filled.Remove: ImageVector - get() { - if (_remove != null) { - return _remove!! - } - _remove = materialIcon(name = "Filled.Remove") { - materialPath { - moveTo(19.0f, 13.0f) - horizontalLineTo(5.0f) - verticalLineToRelative(-2.0f) - horizontalLineToRelative(14.0f) - verticalLineToRelative(2.0f) - close() - } - } - return _remove!! - } - -private var _remove: ImageVector? = null - -public val Icons.Outlined.ExpandMore: ImageVector - get() { - if (_expandMore != null) { - return _expandMore!! - } - _expandMore = materialIcon(name = "Outlined.ExpandMore") { - materialPath { - moveTo(16.59f, 8.59f) - lineTo(12.0f, 13.17f) - lineTo(7.41f, 8.59f) - lineTo(6.0f, 10.0f) - lineToRelative(6.0f, 6.0f) - lineToRelative(6.0f, -6.0f) - lineToRelative(-1.41f, -1.41f) - close() - } - } - return _expandMore!! - } - -private var _expandMore: ImageVector? = null - -public val Icons.Filled.DeleteForever: ImageVector - get() { - if (_deleteForever != null) { - return _deleteForever!! - } - _deleteForever = materialIcon(name = "Filled.DeleteForever") { - materialPath { - moveTo(6.0f, 19.0f) - curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f) - horizontalLineToRelative(8.0f) - curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f) - lineTo(18.0f, 7.0f) - lineTo(6.0f, 7.0f) - verticalLineToRelative(12.0f) - close() - moveTo(8.46f, 11.88f) - lineToRelative(1.41f, -1.41f) - lineTo(12.0f, 12.59f) - lineToRelative(2.12f, -2.12f) - lineToRelative(1.41f, 1.41f) - lineTo(13.41f, 14.0f) - lineToRelative(2.12f, 2.12f) - lineToRelative(-1.41f, 1.41f) - lineTo(12.0f, 15.41f) - lineToRelative(-2.12f, 2.12f) - lineToRelative(-1.41f, -1.41f) - lineTo(10.59f, 14.0f) - lineToRelative(-2.13f, -2.12f) - close() - moveTo(15.5f, 4.0f) - lineToRelative(-1.0f, -1.0f) - horizontalLineToRelative(-5.0f) - lineToRelative(-1.0f, 1.0f) - lineTo(5.0f, 4.0f) - verticalLineToRelative(2.0f) - horizontalLineToRelative(14.0f) - lineTo(19.0f, 4.0f) - close() - } - } - return _deleteForever!! - } - -private var _deleteForever: ImageVector? = null \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/DrawableRes.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/DrawableRes.kt deleted file mode 100644 index 5d2f3a27e08..00000000000 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/DrawableRes.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.jetsnack - -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.painter.Painter - -@Composable -expect fun painterResource(id: Int): Painter - -expect val MppR.drawable.empty_state_search: Int \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/StringRes.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/StringRes.kt deleted file mode 100644 index b91177b878c..00000000000 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/StringRes.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.example.jetsnack - -import androidx.compose.runtime.Composable - -@Composable -expect fun stringResource(id: Int): String - -@Composable -expect fun stringResource(id: Int, part: String): String - -@Composable -expect fun stringResource(id: Int, count: Int): String - -object MppR { - object string {} - - object drawable {} - - object plurals {} -} - -expect val MppR.plurals.cart_order_count: Int - -// Filters -expect val MppR.string.label_filters: Int - -// Qty -expect val MppR.string.quantity: Int -expect val MppR.string.label_decrease: Int -expect val MppR.string.label_increase: Int - - -// Snack detail -expect val MppR.string.label_back: Int -expect val MppR.string.detail_header: Int -expect val MppR.string.detail_placeholder: Int -expect val MppR.string.see_more: Int -expect val MppR.string.see_less: Int -expect val MppR.string.ingredients: Int -expect val MppR.string.ingredients_list: Int -expect val MppR.string.add_to_cart: Int - -// Home - -expect val MppR.string.label_select_delivery: Int - -// Filter -expect val MppR.string.max_calories: Int -expect val MppR.string.per_serving: Int -expect val MppR.string.sort: Int -expect val MppR.string.lifestyle: Int -expect val MppR.string.category: Int -expect val MppR.string.price: Int -expect val MppR.string.reset: Int -expect val MppR.string.close: Int - -// Profile - -expect val MppR.string.work_in_progress: Int -expect val MppR.string.grab_beverage: Int - -// Home -expect val MppR.string.home_feed: Int -expect val MppR.string.home_search: Int -expect val MppR.string.home_cart: Int -expect val MppR.string.home_profile: Int - -// Search -expect val MppR.string.search_no_matches: Int -expect val MppR.string.search_no_matches_retry: Int -expect val MppR.string.label_add: Int -expect val MppR.string.search_count: Int -expect val MppR.string.label_search: Int -expect val MppR.string.search_jetsnack: Int - -expect val MppR.string.cart_increase_error: Int -expect val MppR.string.cart_decrease_error: Int - -// Cart -expect val MppR.string.cart_order_header: Int -expect val MppR.string.remove_item: Int -expect val MppR.string.cart_summary_header: Int -expect val MppR.string.cart_subtotal_label: Int -expect val MppR.string.cart_shipping_label: Int -expect val MppR.string.cart_total_label: Int -expect val MppR.string.cart_checkout: Int -expect val MppR.string.label_remove: Int \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Filter.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Filter.kt index 37a9599978b..8b0e8ddf9cb 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Filter.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Filter.kt @@ -17,12 +17,12 @@ package com.example.jetsnack.model import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Android +import androidx.compose.material.icons.filled.SortByAlpha import androidx.compose.material.icons.filled.Star import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.vector.ImageVector -import com.example.jetsnack.Android -import com.example.jetsnack.SortByAlpha @Stable class Filter( @@ -32,6 +32,7 @@ class Filter( ) { val enabled = mutableStateOf(enabled) } + val filters = listOf( Filter(name = "Organic"), Filter(name = "Gluten-free"), diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Search.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Search.kt index cc949b01502..d83af434d7f 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Search.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Search.kt @@ -17,9 +17,15 @@ package com.example.jetsnack.model import androidx.compose.runtime.Immutable +import com.example.common.generated.resources.* +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.chips +import com.example.common.generated.resources.desserts +import com.example.common.generated.resources.fruit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import org.jetbrains.compose.resources.DrawableResource /** * A fake repo for searching. @@ -44,7 +50,7 @@ data class SearchCategoryCollection( @Immutable data class SearchCategory( val name: String, - val imageUrl: String + val image: DrawableResource ) @Immutable @@ -58,26 +64,26 @@ data class SearchSuggestionGroup( * Static data */ -private val searchCategoryCollections = listOf( +private val searchCategoryCollections = listOf( SearchCategoryCollection( id = 0L, name = "Categories", categories = listOf( SearchCategory( name = "Chips & crackers", - imageUrl = "files/chips.jpg" + image = Res.drawable.chips ), SearchCategory( name = "Fruit snacks", - imageUrl = "files/fruit.jpg" + image = Res.drawable.fruit ), SearchCategory( name = "Desserts", - imageUrl = "files/desserts.jpg" + image = Res.drawable.desserts ), SearchCategory( name = "Nuts ", - imageUrl = "files/nuts.jpg" + image = Res.drawable.nuts ) ) ), @@ -87,27 +93,27 @@ private val searchCategoryCollections = listOf( categories = listOf( SearchCategory( name = "Organic", - imageUrl = "files/organic.jpg" + image = Res.drawable.organic ), SearchCategory( name = "Gluten Free", - imageUrl = "files/gluten_free.jpg" + image = Res.drawable.gluten_free ), SearchCategory( name = "Paleo", - imageUrl = "files/paleo.jpg" + image = Res.drawable.paleo ), SearchCategory( name = "Vegan", - imageUrl = "files/vegan.jpg" + image = Res.drawable.vegan ), SearchCategory( name = "Vegitarian", - imageUrl = "files/grapes.jpg" + image = Res.drawable.organic ), SearchCategory( name = "Whole30", - imageUrl = "files/popcorn.jpg" + image = Res.drawable.paleo ) ) ) diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Snack.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Snack.kt index a01ced20837..5bb1a0d1ab9 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Snack.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/Snack.kt @@ -17,13 +17,18 @@ package com.example.jetsnack.model import androidx.compose.runtime.Immutable +import com.example.common.generated.resources.* import com.example.common.generated.resources.Res +import com.example.common.generated.resources.cupcake +import com.example.common.generated.resources.donut +import com.example.common.generated.resources.eclair +import org.jetbrains.compose.resources.DrawableResource @Immutable data class Snack( val id: Long, val name: String, - val imageUrl: String, + val image: DrawableResource, val price: Long, val tagline: String = "", val tags: Set = emptySet() @@ -38,190 +43,190 @@ val snacks = listOf( id = 1L, name = "Cupcake", tagline = "A tag line", - imageUrl = "files/cupcake.jpg", + image = Res.drawable.cupcake, price = 299 ), Snack( id = 2L, name = "Donut", tagline = "A tag line", - imageUrl = "files/donut.jpg", + image = Res.drawable.donut, price = 290 ), Snack( id = 3L, name = "Eclair", tagline = "A tag line", - imageUrl = "files/eclair.jpg", + image = Res.drawable.eclair, price = 289 ), Snack( id = 4L, name = "Froyo", tagline = "A tag line", - imageUrl = "files/froyo.jpg", + image = Res.drawable.froyo, price = 288 ), Snack( id = 5L, name = "Gingerbread", tagline = "A tag line", - imageUrl = "files/gingerbread.jpg", + image = Res.drawable.gingerbread, price = 499 ), Snack( id = 6L, name = "Honeycomb", tagline = "A tag line", - imageUrl = "files/honeycomb.jpg", + image = Res.drawable.honeycomb, price = 309 ), Snack( id = 7L, name = "Ice Cream Sandwich", tagline = "A tag line", - imageUrl = "files/ice_cream_sandwich.jpg", + image = Res.drawable.ice_cream_sandwich, price = 1299 ), Snack( id = 8L, name = "Jellybean", tagline = "A tag line", - imageUrl = "files/jelly_bean.jpg", + image = Res.drawable.jelly_bean, price = 109 ), Snack( id = 9L, name = "KitKat", tagline = "A tag line", - imageUrl = "files/kitkat.jpg", + image = Res.drawable.kitkat, price = 549 ), Snack( id = 10L, name = "Lollipop", tagline = "A tag line", - imageUrl = "files/lollipop.jpg", + image = Res.drawable.lollipop, price = 209 ), Snack( id = 11L, name = "Marshmallow", tagline = "A tag line", - imageUrl = "files/marshmallow.jpg", + image = Res.drawable.marshmallow, price = 219 ), Snack( id = 12L, name = "Nougat", tagline = "A tag line", - imageUrl = "files/nougat.jpg", + image = Res.drawable.nougat, price = 309 ), Snack( id = 13L, name = "Oreo", tagline = "A tag line", - imageUrl = "files/oreo.jpg", + image = Res.drawable.oreo, price = 339 ), Snack( id = 14L, name = "Pie", tagline = "A tag line", - imageUrl = "files/pie.jpg", + image = Res.drawable.pie, price = 249 ), Snack( id = 15L, name = "Chips", - imageUrl = "files/chips.jpg", + image = Res.drawable.chips, price = 277 ), Snack( id = 16L, name = "Pretzels", - imageUrl = "files/pretzels.jpg", + image = Res.drawable.pretzels, price = 154 ), Snack( id = 17L, name = "Smoothies", - imageUrl = "files/smoothies.jpg", + image = Res.drawable.smoothies, price = 257 ), Snack( id = 18L, name = "Popcorn", - imageUrl = "files/popcorn.jpg", + image = Res.drawable.popcorn, price = 167 ), Snack( id = 19L, name = "Almonds", - imageUrl = "files/almonds.jpg", + image = Res.drawable.almonds, price = 123 ), Snack( id = 20L, name = "Cheese", - imageUrl = "files/cheese.jpg", + image = Res.drawable.cheese, price = 231 ), Snack( id = 21L, name = "Apples", tagline = "A tag line", - imageUrl = "files/apples.jpg", + image = Res.drawable.apples, price = 221 ), Snack( id = 22L, name = "Apple sauce", tagline = "A tag line", - imageUrl = "files/apple_sauce.jpg", + image = Res.drawable.apple_sauce, price = 222 ), Snack( id = 23L, name = "Apple chips", tagline = "A tag line", - imageUrl = "files/apple_chips.jpg", + image = Res.drawable.apple_chips, price = 231 ), Snack( id = 24L, name = "Apple juice", tagline = "A tag line", - imageUrl = "files/apple_juice.jpg", + image = Res.drawable.apple_juice, price = 241 ), Snack( id = 25L, name = "Apple pie", tagline = "A tag line", - imageUrl = "files/apple_pie.jpg", + image = Res.drawable.apple_pie, price = 225 ), Snack( id = 26L, name = "Grapes", tagline = "A tag line", - imageUrl = "files/grapes.jpg", + image = Res.drawable.grapes, price = 266 ), Snack( id = 27L, name = "Kiwi", tagline = "A tag line", - imageUrl = "files/kiwi.jpg", + image = Res.drawable.kiwi, price = 127 ), Snack( id = 28L, name = "Mango", tagline = "A tag line", - imageUrl = "files/mango.jpg", + image = Res.drawable.mango, price = 128 ) ) diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackCollection.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackCollection.kt index 936d45d38b3..ad12b402844 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackCollection.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackCollection.kt @@ -17,6 +17,7 @@ package com.example.jetsnack.model import androidx.compose.runtime.Immutable +import kotlin.random.Random @Immutable data class SnackCollection( @@ -57,33 +58,33 @@ private val tastyTreats = SnackCollection( ) private val popular = SnackCollection( - id = 2L, + id = Random.nextLong(), name = "Popular on Jetsnack", snacks = snacks.subList(14, 19) ) private val wfhFavs = tastyTreats.copy( - id = 3L, + id = Random.nextLong(), name = "WFH favourites" ) private val newlyAdded = popular.copy( - id = 4L, + id = Random.nextLong(), name = "Newly Added" ) private val exclusive = tastyTreats.copy( - id = 5L, + id = Random.nextLong(), name = "Only on Jetsnack" ) private val also = tastyTreats.copy( - id = 6L, + id = Random.nextLong(), name = "Customers also bought" ) private val inspiredByCart = tastyTreats.copy( - id = 7L, + id = Random.nextLong(), name = "Inspired by your cart" ) @@ -96,8 +97,8 @@ private val snackCollections = listOf( ) private val related = listOf( - also, - popular + also.copy(id = Random.nextLong()), + popular.copy(id = Random.nextLong()) ) private val cart = listOf( diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackbarManager.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackbarManager.kt index 568aac92a1d..ffd5249e4a7 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackbarManager.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/model/SnackbarManager.kt @@ -20,8 +20,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import org.jetbrains.compose.resources.StringResource -data class Message(val id: Long, val message: Int /*@StringRes*/) +data class Message(val id: Long, val messageId: StringResource) /** * Class responsible for managing Snackbar messages to show on the screen @@ -31,11 +32,11 @@ object SnackbarManager { private val _messages: MutableStateFlow> = MutableStateFlow(emptyList()) val messages: StateFlow> get() = _messages.asStateFlow() - fun showMessage(message: Int /*@StringRes*/) { + fun showMessage(message: StringResource) { _messages.update { currentMessages -> currentMessages + Message( id = createRandomUUID(), - message = message + messageId = message ) } } diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackApp.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackApp.kt index daeab90159c..d634a30252d 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackApp.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackApp.kt @@ -16,44 +16,149 @@ package com.example.jetsnack.ui -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material.SnackbarHost +import androidx.compose.animation.* +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.SnackbarHost import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.navArgument import com.example.jetsnack.ui.components.JetsnackScaffold import com.example.jetsnack.ui.components.JetsnackSnackbar +import com.example.jetsnack.ui.components.rememberJetsnackScaffoldState +import com.example.jetsnack.ui.home.HomeSections import com.example.jetsnack.ui.home.JetsnackBottomBar -import com.example.jetsnack.ui.snackdetail.jetSnackSystemBarsPadding +import com.example.jetsnack.ui.home.addHomeGraph +import com.example.jetsnack.ui.home.composableWithCompositionLocal +import com.example.jetsnack.ui.navigation.MainDestinations +import com.example.jetsnack.ui.navigation.rememberJetsnackNavController +import com.example.jetsnack.ui.snackdetail.SnackDetail +import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring +import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.JetsnackTheme +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun JetsnackApp() { JetsnackTheme { - val appState = rememberMppJetsnackAppState() - JetsnackScaffold( - bottomBar = { - if (appState.shouldShowBottomBar()) { + val jetsnackNavController = rememberJetsnackNavController() + SharedTransitionLayout { + CompositionLocalProvider( + LocalSharedTransitionScope provides this + ) { + NavHost( + navController = jetsnackNavController.navController, + startDestination = MainDestinations.HOME_ROUTE + ) { + composableWithCompositionLocal( + route = MainDestinations.HOME_ROUTE + ) { backStackEntry -> + MainContainer( + onSnackSelected = jetsnackNavController::navigateToSnackDetail + ) + } + + composableWithCompositionLocal( + "${MainDestinations.SNACK_DETAIL_ROUTE}/" + + "{${MainDestinations.SNACK_ID_KEY}}" + + "?origin={${MainDestinations.ORIGIN}}", + arguments = listOf( + navArgument(MainDestinations.SNACK_ID_KEY) { + type = NavType.LongType + } + ), + + ) { backStackEntry -> + val arguments = requireNotNull(backStackEntry.arguments) + val snackId = arguments.getLong(MainDestinations.SNACK_ID_KEY) + val origin = arguments.getString(MainDestinations.ORIGIN) + SnackDetail( + snackId, + origin = origin ?: "", + upPress = jetsnackNavController::upPress + ) + } + } + } + } + } +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun MainContainer( + modifier: Modifier = Modifier, + onSnackSelected: (Long, String, NavBackStackEntry) -> Unit +) { + val jetsnackScaffoldState = rememberJetsnackScaffoldState() + val nestedNavController = rememberJetsnackNavController() + val navBackStackEntry by nestedNavController.navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalStateException("No SharedElementScope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalStateException("No SharedElementScope found") + JetsnackScaffold( + bottomBar = { + with(animatedVisibilityScope) { + with(sharedTransitionScope) { JetsnackBottomBar( - tabs = appState.bottomBarTabs, - currentRoute = appState.currentRoute!!, - navigateToRoute = appState::navigateToBottomBarRoute + tabs = HomeSections.entries.toTypedArray(), + currentRoute = currentRoute ?: HomeSections.FEED.route, + navigateToRoute = nestedNavController::navigateToBottomBarRoute, + modifier = Modifier + .renderInSharedTransitionScopeOverlay( + zIndexInOverlay = 1f, + ) + .animateEnterExit( + enter = fadeIn(nonSpatialExpressiveSpring()) + slideInVertically( + spatialExpressiveSpring() + ) { + it + }, + exit = fadeOut(nonSpatialExpressiveSpring()) + slideOutVertically( + spatialExpressiveSpring() + ) { + it + } + ) ) } - }, - snackbarHost = { - SnackbarHost( - hostState = it, - modifier = Modifier.jetSnackSystemBarsPadding(), - snackbar = { snackbarData -> JetsnackSnackbar(snackbarData) } - ) - }, - scaffoldState = appState.scaffoldState - ) { innerPaddingModifier -> - JetsnackScaffoldContent(innerPaddingModifier, appState) + } + }, + modifier = modifier, + snackbarHost = { + SnackbarHost( + hostState = it, + modifier = Modifier.systemBarsPadding(), + snackbar = { snackbarData -> JetsnackSnackbar(snackbarData) } + ) + }, + snackBarHostState = jetsnackScaffoldState.snackBarHostState, + ) { padding -> + NavHost( + navController = nestedNavController.navController, + startDestination = HomeSections.FEED.route + ) { + addHomeGraph( + onSnackSelected = onSnackSelected, + modifier = Modifier + .padding(padding) + .consumeWindowInsets(padding) + ) } } } -@Composable -expect fun JetsnackScaffoldContent(innerPaddingModifier: PaddingValues, appState: MppJetsnackAppState) +val LocalNavAnimatedVisibilityScope = compositionLocalOf { null } +@OptIn(ExperimentalSharedTransitionApi::class) +val LocalSharedTransitionScope = compositionLocalOf { null } diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackAppState.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackAppState.kt deleted file mode 100644 index ede9a66ba9f..00000000000 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/JetsnackAppState.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetsnack.ui - -import androidx.compose.material.ScaffoldState -import androidx.compose.material.rememberScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import com.example.jetsnack.model.SnackbarManager -import com.example.jetsnack.ui.home.HomeSections -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -/** - * Destinations used in the [JetsnackApp]. - */ -object MainDestinations { - const val HOME_ROUTE = "home" - const val SNACK_DETAIL_ROUTE = "snack" - const val SNACK_ID_KEY = "snackId" -} - - -@Composable -expect fun rememberMppJetsnackAppState(): MppJetsnackAppState - -@Stable -expect class MppJetsnackAppState { - - val scaffoldState: ScaffoldState - val snackbarManager: SnackbarManager - val coroutineScope: CoroutineScope - val bottomBarTabs: Array - val currentRoute: String? - - @Composable - fun shouldShowBottomBar(): Boolean - - fun navigateToBottomBarRoute(route: String) -} - -/** - * Responsible for holding state related to [JetsnackApp] and containing UI-related logic. - */ -@Stable -class JetsnackAppState( - val scaffoldState: ScaffoldState, -// val navController: NavHostController, - private val snackbarManager: SnackbarManager, -// private val resources: Resources, - coroutineScope: CoroutineScope -) { - // Process snackbars coming from SnackbarManager - init { - coroutineScope.launch { - snackbarManager.messages.collect { currentMessages -> - if (currentMessages.isNotEmpty()) { - val message = currentMessages[0] - // TODO: implement - val text = "TODO: resources.getText(message.messageId)" - - // Display the snackbar on the screen. `showSnackbar` is a function - // that suspends until the snackbar disappears from the screen - scaffoldState.snackbarHostState.showSnackbar(text.toString()) - // Once the snackbar is gone or dismissed, notify the SnackbarManager - snackbarManager.setMessageShown(message.id) - } - } - } - } - - // ---------------------------------------------------------- - // BottomBar state source of truth - // ---------------------------------------------------------- - - val bottomBarTabs = HomeSections.values() - private val bottomBarRoutes = bottomBarTabs.map { it.route } - - // Reading this attribute will cause recompositions when the bottom bar needs shown, or not. - // Not all routes need to show the bottom bar. - val shouldShowBottomBar: Boolean - @Composable get() = true -// navController -// .currentBackStackEntryAsState().value?.destination?.route in bottomBarRoutes - - // ---------------------------------------------------------- - // Navigation state source of truth - // ---------------------------------------------------------- - - val currentRoute: String? - get() = HomeSections.FEED.route//navController.currentDestination?.route - - fun upPress() { -// navController.navigateUp() - } - - fun navigateToBottomBarRoute(route: String) { -// if (route != currentRoute) { -// navController.navigate(route) { -// launchSingleTop = true -// restoreState = true -// // Pop up backstack to the first destination and save state. This makes going back -// // to the start destination when pressing back in any other bottom tab. -// popUpTo(findStartDestination(navController.graph).id) { -// saveState = true -// } -// } -// } - } - -// fun navigateToSnackDetail(snackId: Long, from: NavBackStackEntry) { - // In order to discard duplicated navigation events, we check the Lifecycle -// if (from.lifecycleIsResumed()) { -// navController.navigate("${MainDestinations.SNACK_DETAIL_ROUTE}/$snackId") -// } -// } -} - -/** - * If the lifecycle is not resumed it means this NavBackStackEntry already processed a nav event. - * - * This is used to de-duplicate navigation events. - */ -//private fun NavBackStackEntry.lifecycleIsResumed() = -// this.getLifecycle().currentState == Lifecycle.State.RESUMED -// -//private val NavGraph.startDestination: NavDestination? -// get() = findNode(startDestinationId) - -/** - * Copied from similar function in NavigationUI.kt - * - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt - */ -//private tailrec fun findStartDestination(graph: NavDestination): NavDestination { -// return if (graph is NavGraph) findStartDestination(graph.startDestination!!) else graph -//} - -/** - * A composable function that returns the [Resources]. It will be recomposed when `Configuration` - * gets updated. - */ -//@Composable -//@ReadOnlyComposable -//private fun resources(): Resources { -// LocalConfiguration.current -// return LocalContext.current.resources -//} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/utils/SystemUi.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/SnackSharedElementKey.kt similarity index 62% rename from examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/utils/SystemUi.kt rename to examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/SnackSharedElementKey.kt index 2b28986900b..a40b6091d09 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/utils/SystemUi.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/SnackSharedElementKey.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,20 @@ * limitations under the License. */ -package com.example.jetsnack.ui.utils +package com.example.jetsnack.ui -/** - * Moved to https://google.github.io/accompanist/systemuicontroller/ - */ +data class SnackSharedElementKey( + val snackId: Long, + val origin: String, + val type: SnackSharedElementType +) + +enum class SnackSharedElementType { + Bounds, + Image, + Title, + Tagline, + Background +} + +object FilterSharedElementKey diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt index 73fa507eea4..b46831ffe91 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Button.kt @@ -16,7 +16,6 @@ package com.example.jetsnack.ui.components -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -29,11 +28,10 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideTextStyle -import androidx.compose.material.Text -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -41,13 +39,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.semantics.Role import com.example.jetsnack.ui.theme.JetsnackTheme @Composable - fun JetsnackButton( onClick: () -> Unit, modifier: Modifier = Modifier, @@ -83,18 +79,15 @@ fun JetsnackButton( ) ) { ProvideTextStyle( - value = MaterialTheme.typography.button + value = MaterialTheme.typography.labelLarge ) { Row( - @Suppress("DEPRECATION_ERROR") Modifier .defaultMinSize( minWidth = ButtonDefaults.MinWidth, minHeight = ButtonDefaults.MinHeight ) - // TODO This should be replaced by non-deprecated alternative after the original example migrates to Jetpack Compose 1.7: - // https://github.com/android/compose-samples/blob/3bc6b7d7c74571ea74776ec5b15518b40de4d31b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt#L95 - .indication(interactionSource, rememberRipple()) + .indication(interactionSource, ripple()) .padding(contentPadding), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, @@ -104,26 +97,4 @@ fun JetsnackButton( } } -private val ButtonShape = RoundedCornerShape(percent = 50) - -//@Preview -@Composable -private fun ButtonPreview() { - JetsnackTheme { - JetsnackButton(onClick = {}) { - Text(text = "Demo") - } - } -} - -//@Preview -@Composable -private fun RectangleButtonPreview() { - JetsnackTheme { - JetsnackButton( - onClick = {}, shape = RectangleShape - ) { - Text(text = "Demo") - } - } -} +private val ButtonShape = RoundedCornerShape(percent = 50) \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Card.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Card.kt index d2953e315cd..5129d676b60 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Card.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Card.kt @@ -16,11 +16,8 @@ package com.example.jetsnack.ui.components -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.layout.padding -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -48,14 +45,4 @@ fun JetsnackCard( border = border, content = content ) -} - -//@Preview -@Composable -private fun CardPreview() { - JetsnackTheme { - JetsnackCard { - Text(text = "Demo", modifier = Modifier.padding(16.dp)) - } - } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Divider.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Divider.kt index 28dd6a84c29..fb7521f67ef 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Divider.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Divider.kt @@ -16,12 +16,8 @@ package com.example.jetsnack.ui.components -//import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size -import androidx.compose.material.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp @@ -32,25 +28,13 @@ import com.example.jetsnack.ui.theme.JetsnackTheme fun JetsnackDivider( modifier: Modifier = Modifier, color: Color = JetsnackTheme.colors.uiBorder.copy(alpha = DividerAlpha), - thickness: Dp = 1.dp, - startIndent: Dp = 0.dp + thickness: Dp = 1.dp ) { - Divider( + HorizontalDivider( modifier = modifier, color = color, - thickness = thickness, - startIndent = startIndent + thickness = thickness ) } private const val DividerAlpha = 0.12f - -//@Preview -@Composable -private fun DividerPreview() { - JetsnackTheme { - Box(Modifier.size(height = 10.dp, width = 100.dp)) { - JetsnackDivider(Modifier.align(Alignment.Center)) - } - } -} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Filters.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Filters.kt index 7e7ed982c97..330c8454528 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Filters.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Filters.kt @@ -16,26 +16,24 @@ package com.example.jetsnack.ui.components +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.animateColorAsState -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.FilterList +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -44,40 +42,54 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp -import com.example.jetsnack.FilterList -import com.example.jetsnack.MppR -import com.example.jetsnack.label_filters +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.label_filters import com.example.jetsnack.model.Filter -import com.example.jetsnack.stringResource +import com.example.jetsnack.ui.FilterSharedElementKey import com.example.jetsnack.ui.theme.JetsnackTheme +import org.jetbrains.compose.resources.stringResource +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun FilterBar( filters: List, - onShowFilters: () -> Unit + onShowFilters: () -> Unit, + filterScreenVisible: Boolean, + sharedTransitionScope: SharedTransitionScope ) { - - LazyRow( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(start = 12.dp, end = 8.dp), - modifier = Modifier.heightIn(min = 56.dp) - ) { - item { - IconButton(onClick = onShowFilters) { - Icon( - imageVector = Icons.Rounded.FilterList, - tint = JetsnackTheme.colors.brand, - contentDescription = stringResource(MppR.string.label_filters), - modifier = Modifier.diagonalGradientBorder( - colors = JetsnackTheme.colors.interactiveSecondary, - shape = CircleShape - ) - ) + with(sharedTransitionScope) { + LazyRow( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(start = 12.dp, end = 8.dp), + modifier = Modifier.heightIn(min = 56.dp) + ) { + item { + AnimatedVisibility(visible = !filterScreenVisible) { + IconButton( + onClick = onShowFilters, + modifier = Modifier + .sharedBounds( + rememberSharedContentState(FilterSharedElementKey), + animatedVisibilityScope = this@AnimatedVisibility, + resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds + ) + ) { + Icon( + imageVector = Icons.Rounded.FilterList, + tint = JetsnackTheme.colors.brand, + contentDescription = stringResource(Res.string.label_filters), + modifier = Modifier.diagonalGradientBorder( + colors = JetsnackTheme.colors.interactiveSecondary, + shape = CircleShape + ) + ) + } + } + } + items(filters) { filter -> + FilterChip(filter = filter, shape = MaterialTheme.shapes.small) } - } - items(filters) { filter -> - FilterChip(filter = filter, shape = MaterialTheme.shapes.small) } } } @@ -90,7 +102,8 @@ fun FilterChip( ) { val (selected, setSelected) = filter.enabled val backgroundColor by animateColorAsState( - if (selected) JetsnackTheme.colors.brandSecondary else JetsnackTheme.colors.uiBackground + if (selected) JetsnackTheme.colors.brandSecondary else JetsnackTheme.colors.uiBackground, + label = "background color" ) val border = Modifier.fadeInDiagonalGradientBorder( showBorder = !selected, @@ -98,11 +111,12 @@ fun FilterChip( shape = shape ) val textColor by animateColorAsState( - if (selected) Color.Black else JetsnackTheme.colors.textSecondary + if (selected) Color.Black else JetsnackTheme.colors.textSecondary, + label = "text color" ) JetsnackSurface( - modifier = modifier.height(28.dp), + modifier = modifier, color = backgroundColor, contentColor = textColor, shape = shape, @@ -134,7 +148,7 @@ fun FilterChip( ) { Text( text = filter.name, - style = MaterialTheme.typography.caption, + style = MaterialTheme.typography.bodySmall, maxLines = 1, modifier = Modifier.padding( horizontal = 20.dp, @@ -144,19 +158,3 @@ fun FilterChip( } } } - -//@Preview -@Composable -private fun FilterDisabledPreview() { - JetsnackTheme { - FilterChip(Filter(name = "Demo", enabled = false), Modifier.padding(4.dp)) - } -} - -//@Preview -@Composable -private fun FilterEnabledPreview() { - JetsnackTheme { - FilterChip(Filter(name = "Demo", enabled = true)) - } -} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Gradient.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Gradient.kt index ec26fe92f00..28eb641141c 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Gradient.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Gradient.kt @@ -21,12 +21,14 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -47,13 +49,30 @@ fun Modifier.offsetGradientBackground( offset: Float = 0f ) = background( Brush.horizontalGradient( - colors, + colors = colors, startX = -offset, endX = width - offset, tileMode = TileMode.Mirror ) ) +fun Modifier.offsetGradientBackground( + colors: List, + width: Density.() -> Float, + offset: Density.() -> Float = { 0f } +) = drawBehind { + val actualOffset = offset() + + drawRect( + Brush.horizontalGradient( + colors = colors, + startX = -actualOffset, + endX = width() - actualOffset, + tileMode = TileMode.Mirror + ) + ) +} + fun Modifier.diagonalGradientBorder( colors: List, borderSize: Dp = 2.dp, @@ -71,11 +90,14 @@ fun Modifier.fadeInDiagonalGradientBorder( shape: Shape ) = composed { val animatedColors = List(colors.size) { i -> - animateColorAsState(if (showBorder) colors[i] else colors[i].copy(alpha = 0f)).value + animateColorAsState( + if (showBorder) colors[i] else colors[i].copy(alpha = 0f), + label = "animated color" + ).value } diagonalGradientBorder( colors = animatedColors, borderSize = borderSize, shape = shape ) -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/GradientTintedIconButton.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/GradientTintedIconButton.kt index ccafa8fe010..6dc687485ee 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/GradientTintedIconButton.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/GradientTintedIconButton.kt @@ -23,10 +23,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -93,17 +91,4 @@ fun JetsnackGradientTintedIconButton( modifier = modifierColor ) } -} - -//@Preview -@Composable -private fun GradientTintedIconButtonPreview() { - JetsnackTheme { - JetsnackGradientTintedIconButton( - imageVector = Icons.Default.Add, - onClick = {}, - contentDescription = "Demo", - modifier = Modifier.padding(4.dp) - ) - } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/QuantitySelector.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/QuantitySelector.kt index 06fd7e435ce..938ac113491 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/QuantitySelector.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/QuantitySelector.kt @@ -17,27 +17,27 @@ package com.example.jetsnack.ui.components import androidx.compose.animation.Crossfade -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -import androidx.compose.material.ContentAlpha -import androidx.compose.material.LocalContentAlpha -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Remove +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.example.jetsnack.* +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.label_decrease +import com.example.common.generated.resources.label_increase +import com.example.common.generated.resources.quantity import com.example.jetsnack.ui.theme.JetsnackTheme +import org.jetbrains.compose.resources.stringResource @Composable fun QuantitySelector( @@ -47,20 +47,19 @@ fun QuantitySelector( modifier: Modifier = Modifier ) { Row(modifier = modifier) { - CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { - Text( - text = stringResource(MppR.string.quantity), - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary, - modifier = Modifier - .padding(end = 18.dp) - .align(Alignment.CenterVertically) - ) - } + Text( + text = stringResource(Res.string.quantity), + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textSecondary, + fontWeight = FontWeight.Normal, + modifier = Modifier + .padding(end = 18.dp) + .align(Alignment.CenterVertically) + ) JetsnackGradientTintedIconButton( imageVector = Icons.Default.Remove, onClick = decreaseItemCount, - contentDescription = stringResource(MppR.string.label_decrease), + contentDescription = stringResource(Res.string.label_decrease), modifier = Modifier.align(Alignment.CenterVertically) ) Crossfade( @@ -70,7 +69,7 @@ fun QuantitySelector( ) { Text( text = "$it", - style = MaterialTheme.typography.subtitle2, + style = MaterialTheme.typography.titleSmall, fontSize = 18.sp, color = JetsnackTheme.colors.textPrimary, textAlign = TextAlign.Center, @@ -80,30 +79,8 @@ fun QuantitySelector( JetsnackGradientTintedIconButton( imageVector = Icons.Default.Add, onClick = increaseItemCount, - contentDescription = stringResource(MppR.string.label_increase), + contentDescription = stringResource(Res.string.label_increase), modifier = Modifier.align(Alignment.CenterVertically) ) } -} - -//@Preview -@Composable -fun QuantitySelectorPreview() { - JetsnackTheme { - JetsnackSurface { - QuantitySelector(1, {}, {}) - } - } -} - -//@Preview -@Composable -fun QuantitySelectorPreviewRtl() { - JetsnackTheme { - JetsnackSurface { - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { - QuantitySelector(1, {}, {}) - } - } - } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Scaffold.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Scaffold.kt index 337a02d8c6e..4ecd20ae116 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Scaffold.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Scaffold.kt @@ -16,65 +16,89 @@ package com.example.jetsnack.ui.components -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material.DrawerDefaults -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FabPosition -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold -import androidx.compose.material.ScaffoldState -import androidx.compose.material.SnackbarHost -import androidx.compose.material.SnackbarHostState -import androidx.compose.material.rememberScaffoldState +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.Dp +import com.example.jetsnack.model.SnackbarManager import com.example.jetsnack.ui.theme.JetsnackTheme +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.stringResource /** - * Wrap Material [androidx.compose.material.Scaffold] and set [JetsnackTheme] colors. + * Wrap Material [androidx.compose.material3.Scaffold] and set [JetsnackTheme] colors. */ -@OptIn(ExperimentalMaterialApi::class) @Composable fun JetsnackScaffold( modifier: Modifier = Modifier, - scaffoldState: ScaffoldState = rememberScaffoldState(), + snackBarHostState: SnackbarHostState = remember { SnackbarHostState() }, topBar: @Composable (() -> Unit) = {}, bottomBar: @Composable (() -> Unit) = {}, snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }, floatingActionButton: @Composable (() -> Unit) = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, - isFloatingActionButtonDocked: Boolean = false, - drawerContent: @Composable (ColumnScope.() -> Unit)? = null, - drawerShape: Shape = MaterialTheme.shapes.large, - drawerElevation: Dp = DrawerDefaults.Elevation, - drawerBackgroundColor: Color = JetsnackTheme.colors.uiBackground, - drawerContentColor: Color = JetsnackTheme.colors.textSecondary, - drawerScrimColor: Color = JetsnackTheme.colors.uiBorder, backgroundColor: Color = JetsnackTheme.colors.uiBackground, contentColor: Color = JetsnackTheme.colors.textSecondary, content: @Composable (PaddingValues) -> Unit ) { Scaffold( modifier = modifier, - scaffoldState = scaffoldState, topBar = topBar, bottomBar = bottomBar, - snackbarHost = snackbarHost, + snackbarHost = { + snackbarHost(snackBarHostState) + }, floatingActionButton = floatingActionButton, floatingActionButtonPosition = floatingActionButtonPosition, - isFloatingActionButtonDocked = isFloatingActionButtonDocked, - drawerContent = drawerContent, - drawerShape = drawerShape, - drawerElevation = drawerElevation, - drawerBackgroundColor = drawerBackgroundColor, - drawerContentColor = drawerContentColor, - drawerScrimColor = drawerScrimColor, - backgroundColor = backgroundColor, + containerColor = backgroundColor, contentColor = contentColor, content = content ) } + +/** + * Remember and creates an instance of [JetsnackScaffoldState] + */ +@Composable +fun rememberJetsnackScaffoldState( + snackBarHostState: SnackbarHostState = remember { SnackbarHostState() }, + snackbarManager: SnackbarManager = SnackbarManager, + coroutineScope: CoroutineScope = rememberCoroutineScope() +): JetsnackScaffoldState = remember(snackBarHostState, snackbarManager, coroutineScope) { + JetsnackScaffoldState(snackBarHostState, snackbarManager, coroutineScope) +} + +/** + * Responsible for holding [ScaffoldState], handles the logic of showing snackbar messages + */ +@Stable +class JetsnackScaffoldState( + val snackBarHostState: SnackbarHostState, + private val snackbarManager: SnackbarManager, + coroutineScope: CoroutineScope +) { + // Process snackbars coming from SnackbarManager + init { + coroutineScope.launch { + snackbarManager.messages.collect { currentMessages -> + if (currentMessages.isNotEmpty()) { + val message = currentMessages[0] + val text = currentMessages.toString()//stringResource(message.messageId) + // Notify the SnackbarManager so it can remove the current message from the list + snackbarManager.setMessageShown(message.id) + // Display the snackbar on the screen. `showSnackbar` is a function + // that suspends until the snackbar disappears from the screen + snackBarHostState.showSnackbar(text.toString()) + } + } + } + } +} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snackbar.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snackbar.kt index 0c745c2875d..73ca1da5dd5 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snackbar.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snackbar.kt @@ -16,19 +16,17 @@ package com.example.jetsnack.ui.components -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Snackbar -import androidx.compose.material.SnackbarData +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Snackbar +import androidx.compose.material3.SnackbarData import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme /** - * An alternative to [androidx.compose.material.Snackbar] utilizing + * An alternative to [androidx.compose.material3.Snackbar] utilizing * [com.example.jetsnack.ui.theme.JetsnackColors] */ @Composable @@ -39,17 +37,15 @@ fun JetsnackSnackbar( shape: Shape = MaterialTheme.shapes.small, backgroundColor: Color = JetsnackTheme.colors.uiBackground, contentColor: Color = JetsnackTheme.colors.textSecondary, - actionColor: Color = JetsnackTheme.colors.brand, - elevation: Dp = 6.dp + actionColor: Color = JetsnackTheme.colors.brand ) { Snackbar( snackbarData = snackbarData, modifier = modifier, actionOnNewLine = actionOnNewLine, shape = shape, - backgroundColor = backgroundColor, + containerColor = backgroundColor, contentColor = contentColor, - actionColor = actionColor, - elevation = elevation + actionColor = actionColor ) -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snacks.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snacks.kt index 5842fd4e1ef..aa5cdf0d7c0 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snacks.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Snacks.kt @@ -17,49 +17,53 @@ package com.example.jetsnack.ui.components //import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.animation.* +import androidx.compose.animation.core.animateDp +import androidx.compose.foundation.Image +import androidx.compose.foundation.border import androidx.compose.foundation.clickable -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.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material.icons.outlined.ArrowForward +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.example.jetsnack.model.CollectionType import com.example.jetsnack.model.Snack import com.example.jetsnack.model.SnackCollection -import com.example.jetsnack.model.snacks +import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope +import com.example.jetsnack.ui.LocalSharedTransitionScope +import com.example.jetsnack.ui.SnackSharedElementKey +import com.example.jetsnack.ui.SnackSharedElementType +import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring +import com.example.jetsnack.ui.snackdetail.snackDetailBoundsTransform import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.utils.mirroringIcon +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.imageResource private val HighlightCardWidth = 170.dp private val HighlightCardPadding = 16.dp +private val Density.cardWidthWithPaddingPx + get() = (HighlightCardWidth + HighlightCardPadding).toPx() // The Cards show a gradient which spans 3 cards and scrolls with parallax. private val gradientWidth @@ -71,7 +75,7 @@ private val gradientWidth @Composable fun SnackCollection( snackCollection: SnackCollection, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, index: Int = 0, highlight: Boolean = true @@ -85,7 +89,7 @@ fun SnackCollection( ) { Text( text = snackCollection.name, - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.brand, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -98,52 +102,56 @@ fun SnackCollection( modifier = Modifier.align(Alignment.CenterVertically) ) { Icon( - imageVector = mirroringIcon( - ltrIcon = Icons.Outlined.ArrowForward, - rtlIcon = Icons.Outlined.ArrowBack - ), + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, tint = JetsnackTheme.colors.brand, contentDescription = null ) } } if (highlight && snackCollection.type == CollectionType.Highlight) { - HighlightedSnacks(index, snackCollection.snacks, onSnackClick) + HighlightedSnacks(snackCollection.id, index, snackCollection.snacks, onSnackClick) } else { - Snacks(snackCollection.snacks, onSnackClick) + Snacks(snackCollection.id, snackCollection.snacks, onSnackClick) } } } @Composable private fun HighlightedSnacks( + snackCollectionId: Long, index: Int, snacks: List, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { - val scroll = rememberScrollState(0) + val rowState = rememberLazyListState() + val cardWidthWithPaddingPx = with(LocalDensity.current) { cardWidthWithPaddingPx } + + val scrollProvider = { + // Simple calculation of scroll distance for homogenous item types with the same width. + val offsetFromStart = cardWidthWithPaddingPx * rowState.firstVisibleItemIndex + offsetFromStart + rowState.firstVisibleItemScrollOffset + } + val gradient = when ((index / 2) % 2) { 0 -> JetsnackTheme.colors.gradient6_1 else -> JetsnackTheme.colors.gradient6_2 } - // The Cards show a gradient which spans 3 cards and scrolls with parallax. - val gradientWidth = with(LocalDensity.current) { - (6 * (HighlightCardWidth + HighlightCardPadding).toPx()) - } + LazyRow( + state = rowState, modifier = modifier, horizontalArrangement = Arrangement.spacedBy(16.dp), contentPadding = PaddingValues(start = 24.dp, end = 24.dp) ) { itemsIndexed(snacks) { index, snack -> HighlightSnackItem( - snack, - onSnackClick, - index, - gradient, - gradientWidth, - scroll.value + snackCollectionId = snackCollectionId, + snack = snack, + onSnackClick = onSnackClick, + index = index, + gradient = gradient, + scrollProvider = scrollProvider ) } } @@ -151,8 +159,9 @@ private fun HighlightedSnacks( @Composable private fun Snacks( + snackCollectionId: Long, snacks: List, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { LazyRow( @@ -160,15 +169,17 @@ private fun Snacks( contentPadding = PaddingValues(start = 12.dp, end = 12.dp) ) { items(snacks) { snack -> - SnackItem(snack, onSnackClick) + SnackItem(snack, snackCollectionId, onSnackClick) } } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun SnackItem( snack: Snack, - onSnackClick: (Long) -> Unit, + snackCollectionId: Long, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { JetsnackSurface( @@ -178,98 +189,253 @@ fun SnackItem( end = 4.dp, bottom = 8.dp ) + ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .clickable(onClick = { onSnackClick(snack.id) }) - .padding(8.dp) - ) { - SnackImage( - imageUrl = snack.imageUrl, - elevation = 4.dp, - contentDescription = null, - modifier = Modifier.size(120.dp) - ) - Text( - text = snack.name, - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary, - modifier = Modifier.padding(top = 8.dp) - ) + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalStateException("No sharedTransitionScope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalStateException("No animatedVisibilityScope found") + + with(sharedTransitionScope) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .clickable(onClick = { + onSnackClick(snack.id, snackCollectionId.toString()) + }) + .padding(8.dp) + ) { + SnackImage( + image = snack.image, + elevation = 1.dp, + contentDescription = null, + modifier = Modifier + .size(120.dp) + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Image + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform + ) + ) + Text( + text = snack.name, + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textSecondary, + modifier = Modifier + .padding(top = 8.dp) + .wrapContentWidth() + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Title + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds(), + boundsTransform = snackDetailBoundsTransform + ) + ) + } } } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun HighlightSnackItem( + snackCollectionId: Long, snack: Snack, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, index: Int, gradient: List, - gradientWidth: Float, - scroll: Int, + scrollProvider: () -> Float, modifier: Modifier = Modifier ) { - val left = index * with(LocalDensity.current) { - (HighlightCardWidth + HighlightCardPadding).toPx() - } - JetsnackCard( - modifier = modifier - .size( - width = 170.dp, - height = 250.dp - ) - .padding(bottom = 16.dp) - ) { - Column( - modifier = Modifier - .clickable(onClick = { onSnackClick(snack.id) }) - .fillMaxSize() + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalStateException("No Scope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalStateException("No Scope found") + with(sharedTransitionScope) { + val roundedCornerAnimation by animatedVisibilityScope.transition + .animateDp(label = "rounded corner") { enterExit: EnterExitState -> + when (enterExit) { + EnterExitState.PreEnter -> 0.dp + EnterExitState.Visible -> 20.dp + EnterExitState.PostExit -> 20.dp + } + } + JetsnackCard( + elevation = 0.dp, + shape = RoundedCornerShape(roundedCornerAnimation), + modifier = modifier + .padding(bottom = 16.dp) + .sharedBounds( + sharedContentState = rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Bounds + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform, + clipInOverlayDuringTransition = OverlayClip( + RoundedCornerShape( + roundedCornerAnimation + ) + ), + enter = fadeIn(), + exit = fadeOut() + ) + .size( + width = HighlightCardWidth, + height = 250.dp + ) + .border( + 1.dp, + JetsnackTheme.colors.uiBorder.copy(alpha = 0.12f), + RoundedCornerShape(roundedCornerAnimation) + ) + ) { - Box( + Column( modifier = Modifier - .height(160.dp) - .fillMaxWidth() + .clickable(onClick = { + onSnackClick( + snack.id, + snackCollectionId.toString() + ) + }) + .fillMaxSize() + ) { - val gradientOffset = left - (scroll / 3f) Box( modifier = Modifier - .height(100.dp) + .height(160.dp) .fillMaxWidth() - .offsetGradientBackground(gradient, gradientWidth, gradientOffset) + ) { + Box( + modifier = Modifier + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Background + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() + ) + .height(100.dp) + .fillMaxWidth() + .offsetGradientBackground( + colors = gradient, + width = { + // The Cards show a gradient which spans 6 cards and + // scrolls with parallax. + 6 * cardWidthWithPaddingPx + }, + offset = { + val left = index * cardWidthWithPaddingPx + val gradientOffset = left - (scrollProvider() / 3f) + gradientOffset + } + ) + ) + + SnackImage( + image = snack.image, + contentDescription = null, + modifier = Modifier + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Image + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + exit = fadeOut(nonSpatialExpressiveSpring()), + enter = fadeIn(nonSpatialExpressiveSpring()), + boundsTransform = snackDetailBoundsTransform + ) + .align(Alignment.BottomCenter) + .size(120.dp) + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = snack.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge, + color = JetsnackTheme.colors.textSecondary, + modifier = Modifier + .padding(horizontal = 16.dp) + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Title + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + boundsTransform = snackDetailBoundsTransform, + resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() + ) + .wrapContentWidth() ) - SnackImage( - imageUrl = snack.imageUrl, - contentDescription = null, + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = snack.tagline, + style = MaterialTheme.typography.bodyLarge, + color = JetsnackTheme.colors.textHelp, modifier = Modifier - .size(120.dp) - .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp) + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Tagline + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + boundsTransform = snackDetailBoundsTransform, + resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() + ) + .wrapContentWidth() ) } - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = snack.name, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h6, - color = JetsnackTheme.colors.textSecondary, - modifier = Modifier.padding(horizontal = 16.dp) - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = snack.tagline, - style = MaterialTheme.typography.body1, - color = JetsnackTheme.colors.textHelp, - modifier = Modifier.padding(horizontal = 16.dp) - ) } } } @Composable fun SnackImage( - imageUrl: String, + image: DrawableResource, contentDescription: String?, modifier: Modifier = Modifier, elevation: Dp = 0.dp @@ -280,29 +446,11 @@ fun SnackImage( shape = CircleShape, modifier = modifier ) { - SnackAsyncImage(imageUrl, contentDescription, Modifier.fillMaxSize()) - } -} - -@Composable -expect fun SnackAsyncImage( - imageUrl: String, - contentDescription: String?, - modifier: Modifier -) - -//@Preview -@Composable -fun SnackCardPreview() { - JetsnackTheme { - val snack = snacks.first() - HighlightSnackItem( - snack = snack, - onSnackClick = { }, - index = 0, - gradient = JetsnackTheme.colors.gradient6_1, - gradientWidth = gradientWidth, - scroll = 0 + Image( + bitmap = imageResource(image), + contentDescription = contentDescription, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() ) } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Surface.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Surface.kt index 584c446367f..e9cf13da928 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Surface.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/components/Surface.kt @@ -20,7 +20,7 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box -import androidx.compose.material.LocalContentColor +import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier @@ -37,7 +37,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme import kotlin.math.ln /** - * An alternative to [androidx.compose.material.Surface] utilizing + * An alternative to [androidx.compose.material3.Surface] utilizing * [com.example.jetsnack.ui.theme.JetsnackColors] */ @Composable @@ -51,7 +51,8 @@ fun JetsnackSurface( content: @Composable () -> Unit ) { Box( - modifier = modifier.shadow(elevation = elevation, shape = shape, clip = false) + modifier = modifier + .shadow(elevation = elevation, shape = shape, clip = false) .zIndex(elevation.value) .then(if (border != null) Modifier.border(border, shape) else Modifier) .background( @@ -67,8 +68,8 @@ fun JetsnackSurface( @Composable private fun getBackgroundColorForElevation(color: Color, elevation: Dp): Color { return if (elevation > 0.dp // && https://issuetracker.google.com/issues/161429530 - // JetsnackTheme.colors.isDark //&& - // color == JetsnackTheme.colors.uiBackground + // JetsnackTheme.colors.isDark //&& + // color == JetsnackTheme.colors.uiBackground ) { color.withElevation(elevation) } else { diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/DestinationBar.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/DestinationBar.kt index b42560861df..b7c383f5954 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/DestinationBar.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/DestinationBar.kt @@ -16,67 +16,82 @@ package com.example.jetsnack.ui.home -//import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Column -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ExpandMore +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.example.jetsnack.ExpandMore -import com.example.jetsnack.MppR -import com.example.jetsnack.label_select_delivery -import com.example.jetsnack.stringResource +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.label_select_delivery +import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope +import com.example.jetsnack.ui.LocalSharedTransitionScope import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.snackdetail.jetSnackStatusBarsPadding +import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme +import org.jetbrains.compose.resources.stringResource +@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable fun DestinationBar(modifier: Modifier = Modifier) { - Column(modifier = modifier.jetSnackStatusBarsPadding()) { - TopAppBar( - backgroundColor = JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque), - contentColor = JetsnackTheme.colors.textSecondary, - elevation = 0.dp - ) { - Text( - text = "Huidekoperstraat 26-28, 1017 ZM Amsterdam | https://kotl.in/wasm-gio23", - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .weight(1f) - .align(Alignment.CenterVertically) - ) - IconButton( - onClick = { /* todo */ }, - modifier = Modifier.align(Alignment.CenterVertically) + val sharedElementScope = + LocalSharedTransitionScope.current ?: throw IllegalStateException("No shared element scope") + val navAnimatedScope = + LocalNavAnimatedVisibilityScope.current ?: throw IllegalStateException("No nav scope") + with(sharedElementScope) { + with(navAnimatedScope) { + Column( + modifier = modifier + .renderInSharedTransitionScopeOverlay() + .animateEnterExit( + enter = slideInVertically(spatialExpressiveSpring()) { -it * 2 }, + exit = slideOutVertically(spatialExpressiveSpring()) { -it * 2 } + ) ) { - Icon( - imageVector = Icons.Outlined.ExpandMore, - tint = JetsnackTheme.colors.brand, - contentDescription = stringResource(MppR.string.label_select_delivery) + TopAppBar( + windowInsets = WindowInsets(0, 0, 0, 0), + title = { + Row { + Text( + text = "Huidekoperstraat 26-28, 1017 ZM Amsterdam | https://kotl.in/wasm-gio23", + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textSecondary, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically) + ) + IconButton( + onClick = { /* todo */ }, + modifier = Modifier.align(Alignment.CenterVertically) + ) { + Icon( + imageVector = Icons.Outlined.ExpandMore, + tint = JetsnackTheme.colors.brand, + contentDescription = stringResource(Res.string.label_select_delivery) + ) + } + } + }, + colors = TopAppBarDefaults.topAppBarColors().copy( + containerColor = JetsnackTheme.colors.uiBackground + .copy(alpha = AlphaNearOpaque), + titleContentColor = JetsnackTheme.colors.textSecondary + ), ) + JetsnackDivider() } } - JetsnackDivider() - } -} - -//@Preview -@Composable -fun PreviewDestinationBar() { - JetsnackTheme { - DestinationBar() } } diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Feed.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Feed.kt index 65669ed6509..2417a4769d6 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Feed.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Feed.kt @@ -17,19 +17,17 @@ package com.example.jetsnack.ui.home import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.expandVertically +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -37,9 +35,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.example.jetsnack.model.Filter @@ -49,12 +45,11 @@ import com.example.jetsnack.ui.components.FilterBar import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.SnackCollection -import com.example.jetsnack.ui.snackdetail.jetSnackStatusBarsPadding import com.example.jetsnack.ui.theme.JetsnackTheme @Composable fun Feed( - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { val snackCollections = remember { SnackRepo.getSnacks() } @@ -67,70 +62,77 @@ fun Feed( ) } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun Feed( snackCollections: List, filters: List, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { - JetsnackSurface(modifier = modifier.fillMaxSize()) { - Column { - DestinationBar(modifier = Modifier.jetSnackStatusBarsPadding()) - SnackCollectionList(snackCollections, filters, onSnackClick) + var filtersVisible by remember { + mutableStateOf(false) + } + SharedTransitionLayout { + Box { + SnackCollectionList( + snackCollections, + filters, + filtersVisible = filtersVisible, + onFiltersSelected = { + filtersVisible = true + }, + sharedTransitionScope = this@SharedTransitionLayout, + onSnackClick = onSnackClick + ) + DestinationBar() + AnimatedVisibility(filtersVisible, enter = fadeIn(), exit = fadeOut()) { + FilterScreen( + animatedVisibilityScope = this@AnimatedVisibility, + sharedTransitionScope = this@SharedTransitionLayout + ) { filtersVisible = false } + } + } } } } -@OptIn(ExperimentalAnimationApi::class) +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun SnackCollectionList( snackCollections: List, filters: List, - onSnackClick: (Long) -> Unit, + filtersVisible: Boolean, + onFiltersSelected: () -> Unit, + onSnackClick: (Long, String) -> Unit, + sharedTransitionScope: SharedTransitionScope, modifier: Modifier = Modifier ) { - var filtersVisible by rememberSaveable { mutableStateOf(false) } - Box(modifier) { - LazyColumn { - - item { - FilterBar(filters, onShowFilters = { filtersVisible = true }) - } - itemsIndexed(snackCollections) { index, snackCollection -> - if (index > 0) { - JetsnackDivider(thickness = 2.dp) - } - - SnackCollection( - snackCollection = snackCollection, - onSnackClick = onSnackClick, - index = index + LazyColumn(modifier = modifier) { + item { + Spacer( + Modifier.windowInsetsTopHeight( + WindowInsets.statusBars.add(WindowInsets(top = 56.dp)) ) - } + ) + FilterBar( + filters, + sharedTransitionScope = sharedTransitionScope, + filterScreenVisible = filtersVisible, + onShowFilters = onFiltersSelected + ) } - } - AnimatedVisibility( - visible = filtersVisible, - enter = slideInVertically() + expandVertically( - expandFrom = Alignment.Top - ) + fadeIn(initialAlpha = 0.3f), - exit = slideOutVertically() + shrinkVertically() + fadeOut() - ) { - FilterScreen( - onDismiss = { filtersVisible = false } - ) - } -} - -@Composable -expect fun snackCollectionListItemWindowInsets(): WindowInsets + itemsIndexed(snackCollections) { index, snackCollection -> + if (index > 0) { + JetsnackDivider(thickness = 2.dp) + } -//@Preview -@Composable -fun HomePreview() { - JetsnackTheme { - Feed(onSnackClick = { }) + SnackCollection( + snackCollection = snackCollection, + onSnackClick = onSnackClick, + index = index + ) + } } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/FilterScreen.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/FilterScreen.kt index 1c22e86f924..0eb55af96dc 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/FilterScreen.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/FilterScreen.kt @@ -16,108 +16,137 @@ package com.example.jetsnack.ui.home -import androidx.compose.animation.ExperimentalAnimationApi -//import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ContentAlpha -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.LocalContentAlpha -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Slider -import androidx.compose.material.SliderDefaults -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Done -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* +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.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.example.jetsnack.* -import com.example.jetsnack.flowlayout.FlowMainAxisAlignment -import com.example.jetsnack.flowlayout.FlowRow +import com.example.common.generated.resources.* +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.close +import com.example.common.generated.resources.label_filters +import com.example.common.generated.resources.reset import com.example.jetsnack.model.Filter import com.example.jetsnack.model.SnackRepo +import com.example.jetsnack.ui.FilterSharedElementKey import com.example.jetsnack.ui.components.FilterChip -import com.example.jetsnack.ui.components.JetsnackScaffold import com.example.jetsnack.ui.theme.JetsnackTheme +import org.jetbrains.compose.resources.stringResource -@OptIn(ExperimentalAnimationApi::class) +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun FilterScreen( + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, onDismiss: () -> Unit ) { var sortState by remember { mutableStateOf(SnackRepo.getSortDefault()) } - var maxCalories by remember { mutableStateOf(0f) } + var maxCalories by remember { mutableFloatStateOf(0f) } val defaultFilter = SnackRepo.getSortDefault() - SnackDialog(onCloseRequest = onDismiss) { + Box( + modifier = Modifier + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + // capture click + } + ) { val priceFilters = remember { SnackRepo.getPriceFilters() } val categoryFilters = remember { SnackRepo.getCategoryFilters() } val lifeStyleFilters = remember { SnackRepo.getLifeStyleFilters() } - JetsnackScaffold( - topBar = { - TopAppBar( - navigationIcon = { - IconButton(onClick = onDismiss) { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = stringResource(id = MppR.string.close) - ) - } - }, - title = { - Text( - text = stringResource(id = MppR.string.label_filters), - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.h6 - ) - }, - actions = { - var resetEnabled = sortState != defaultFilter - IconButton( - onClick = { /* TODO: Open search */ }, - enabled = resetEnabled - ) { - val alpha = if (resetEnabled) { - ContentAlpha.high - } else { - ContentAlpha.disabled - } - CompositionLocalProvider(LocalContentAlpha provides alpha) { - Text( - text = stringResource(id = MppR.string.reset), - style = MaterialTheme.typography.body2 - ) - } - } - }, - backgroundColor = JetsnackTheme.colors.uiBackground - ) - } - ) { + Spacer( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.5f)) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + onDismiss() + } + ) + with(sharedTransitionScope) { Column( Modifier - .fillMaxSize() + .padding(16.dp) + .align(Alignment.Center) + .clip(MaterialTheme.shapes.medium) + .sharedBounds( + rememberSharedContentState(FilterSharedElementKey), + animatedVisibilityScope = animatedVisibilityScope, + resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds, + clipInOverlayDuringTransition = OverlayClip(MaterialTheme.shapes.medium) + ) + .wrapContentSize() + .heightIn(max = 450.dp) .verticalScroll(rememberScrollState()) - .padding(horizontal = 24.dp, vertical = 16.dp), + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { } + .background(JetsnackTheme.colors.uiFloated) + .padding(horizontal = 24.dp, vertical = 16.dp) + .skipToLookaheadSize(), ) { + Row(modifier = Modifier.height(IntrinsicSize.Min)) { + IconButton(onClick = onDismiss) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(Res.string.close) + ) + } + Text( + text = stringResource(Res.string.label_filters), + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(top = 8.dp, end = 48.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge + ) + val resetEnabled = sortState != defaultFilter + + IconButton( + onClick = { /* TODO: Open search */ }, + enabled = resetEnabled + ) { + val fontWeight = if (resetEnabled) { + FontWeight.Bold + } else { + FontWeight.Normal + } + + Text( + text = stringResource(Res.string.reset), + style = MaterialTheme.typography.bodyMedium, + fontWeight = fontWeight, + color = JetsnackTheme.colors.uiBackground + .copy(alpha = if (!resetEnabled) 0.38f else 1f) + ) + } + } + SortFiltersSection( sortState = sortState, onFilterChange = { filter -> @@ -125,11 +154,11 @@ fun FilterScreen( } ) FilterChipSection( - title = stringResource(id = MppR.string.price), + title = stringResource(Res.string.price), filters = priceFilters ) FilterChipSection( - title = stringResource(id = MppR.string.category), + title = stringResource(Res.string.category), filters = categoryFilters ) @@ -140,7 +169,7 @@ fun FilterScreen( } ) FilterChipSection( - title = stringResource(id = MppR.string.lifestyle), + title = stringResource(Res.string.lifestyle), filters = lifeStyleFilters ) } @@ -148,15 +177,11 @@ fun FilterScreen( } } -@Composable -expect fun SnackDialog(onCloseRequest: () -> Unit, content: @Composable () -> Unit) - - +@OptIn(ExperimentalLayoutApi::class) @Composable fun FilterChipSection(title: String, filters: List) { FilterTitle(text = title) - FlowRow( - mainAxisAlignment = FlowMainAxisAlignment.Center, + androidx.compose.foundation.layout.FlowRow( modifier = Modifier .fillMaxWidth() .padding(top = 12.dp, bottom = 16.dp) @@ -173,7 +198,7 @@ fun FilterChipSection(title: String, filters: List) { @Composable fun SortFiltersSection(sortState: String, onFilterChange: (Filter) -> Unit) { - FilterTitle(text = stringResource(id = MppR.string.sort)) + FilterTitle(text = stringResource(Res.string.sort)) Column(Modifier.padding(bottom = 24.dp)) { SortFilters( sortState = sortState, @@ -201,13 +226,14 @@ fun SortFilters( } } +@OptIn(ExperimentalLayoutApi::class) @Composable fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { - FlowRow { - FilterTitle(text = stringResource(id = MppR.string.max_calories)) + androidx.compose.foundation.layout.FlowRow { + FilterTitle(text = stringResource(Res.string.max_calories)) Text( - text = stringResource(id = MppR.string.per_serving), - style = MaterialTheme.typography.body2, + text = stringResource(Res.string.per_serving), + style = MaterialTheme.typography.bodyMedium, color = JetsnackTheme.colors.brand, modifier = Modifier.padding(top = 5.dp, start = 10.dp) ) @@ -223,7 +249,8 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { .fillMaxWidth(), colors = SliderDefaults.colors( thumbColor = JetsnackTheme.colors.brand, - activeTrackColor = JetsnackTheme.colors.brand + activeTrackColor = JetsnackTheme.colors.brand, + inactiveTrackColor = JetsnackTheme.colors.iconInteractive ) ) } @@ -232,11 +259,12 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { fun FilterTitle(text: String) { Text( text = text, - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.brand, modifier = Modifier.padding(bottom = 8.dp) ) } + @Composable fun SortOption( text: String, @@ -254,7 +282,7 @@ fun SortOption( } Text( text = text, - style = MaterialTheme.typography.subtitle1, + style = MaterialTheme.typography.titleMedium, modifier = Modifier .padding(start = 10.dp) .weight(1f) @@ -267,9 +295,4 @@ fun SortOption( ) } } -} -//@Preview -@Composable -fun FilterScreenPreview() { - FilterScreen(onDismiss = {}) -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Home.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Home.kt index 98563b440a7..e44e8d5aee2 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Home.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Home.kt @@ -16,31 +16,38 @@ package com.example.jetsnack.ui.home +import androidx.annotation.FloatRange +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.animateFloatAsState -//import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.ShoppingCart +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -58,39 +65,109 @@ import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.example.jetsnack.* +import androidx.compose.ui.util.lerp +import androidx.navigation.NamedNavArgument +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDeepLink +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.example.common.generated.resources.* +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.home_cart +import com.example.common.generated.resources.home_feed +import com.example.common.generated.resources.home_search +import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope import com.example.jetsnack.ui.components.JetsnackSurface -import com.example.jetsnack.ui.snackdetail.jetSnackNavigationBarsPadding -import com.example.jetsnack.ui.snackdetail.lerp +import com.example.jetsnack.ui.home.cart.Cart +import com.example.jetsnack.ui.home.search.Search +import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring +import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.JetsnackTheme +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import kotlin.jvm.JvmSuppressWildcards -//fun NavGraphBuilder.addHomeGraph( -// onSnackSelected: (Long, NavBackStackEntry) -> Unit, -// modifier: Modifier = Modifier -//) { -// composable(HomeSections.FEED.route) { from -> -// Feed(onSnackClick = { id -> onSnackSelected(id, from) }, modifier) -// } -// composable(HomeSections.SEARCH.route) { from -> -// Search(onSnackClick = { id -> onSnackSelected(id, from) }, modifier) -// } -// composable(HomeSections.CART.route) { from -> -// Cart(onSnackClick = { id -> onSnackSelected(id, from) }, modifier) -// } -// composable(HomeSections.PROFILE.route) { -// Profile(modifier) -// } -//} +fun NavGraphBuilder.composableWithCompositionLocal( + route: String, + arguments: List = emptyList(), + deepLinks: List = emptyList(), + enterTransition: ( + @JvmSuppressWildcards + AnimatedContentTransitionScope.() -> EnterTransition? + )? = { + fadeIn(nonSpatialExpressiveSpring()) + }, + exitTransition: ( + @JvmSuppressWildcards + AnimatedContentTransitionScope.() -> ExitTransition? + )? = { + fadeOut(nonSpatialExpressiveSpring()) + }, + popEnterTransition: ( + @JvmSuppressWildcards + AnimatedContentTransitionScope.() -> EnterTransition? + )? = + enterTransition, + popExitTransition: ( + @JvmSuppressWildcards + AnimatedContentTransitionScope.() -> ExitTransition? + )? = + exitTransition, + content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit +) { + composable( + route, + arguments, + deepLinks, + enterTransition, + exitTransition, + popEnterTransition, + popExitTransition + ) { + CompositionLocalProvider( + LocalNavAnimatedVisibilityScope provides this@composable + ) { + content(it) + } + } +} + +fun NavGraphBuilder.addHomeGraph( + onSnackSelected: (Long, String, NavBackStackEntry) -> Unit, + modifier: Modifier = Modifier +) { + composable(HomeSections.FEED.route) { from -> + Feed( + onSnackClick = { id, origin -> onSnackSelected(id, origin, from) }, + modifier + ) + } + composable(HomeSections.SEARCH.route) { from -> + Search( + onSnackClick = { id, origin -> onSnackSelected(id, origin, from) }, + modifier + ) + } + composable(HomeSections.CART.route) { from -> + Cart( + onSnackClick = { id, origin -> onSnackSelected(id, origin, from) }, + modifier + ) + } + composable(HomeSections.PROFILE.route) { + Profile(modifier) + } +} enum class HomeSections( - val title: Int, // @StringRes + val title: StringResource, val icon: ImageVector, val route: String ) { - FEED(MppR.string.home_feed, Icons.Outlined.Home, "home/feed"), - SEARCH(MppR.string.home_search, Icons.Outlined.Search, "home/search"), - CART(MppR.string.home_cart, Icons.Outlined.ShoppingCart, "home/cart"), - PROFILE(MppR.string.home_profile, Icons.Outlined.AccountCircle, "home/profile") + FEED(Res.string.home_feed, Icons.Outlined.Home, "home/feed"), + SEARCH(Res.string.home_search, Icons.Outlined.Search, "home/search"), + CART(Res.string.home_cart, Icons.Outlined.ShoppingCart, "home/cart"), + PROFILE(Res.string.home_profile, Icons.Outlined.AccountCircle, "home/profile") } @Composable @@ -98,6 +175,7 @@ fun JetsnackBottomBar( tabs: Array, currentRoute: String, navigateToRoute: (String) -> Unit, + modifier: Modifier = Modifier, color: Color = JetsnackTheme.colors.iconPrimary, contentColor: Color = JetsnackTheme.colors.iconInteractive ) { @@ -105,25 +183,18 @@ fun JetsnackBottomBar( val currentSection = tabs.first { it.route == currentRoute } JetsnackSurface( + modifier = modifier, color = color, contentColor = contentColor ) { - val springSpec = SpringSpec( - // Determined experimentally - stiffness = 800f, - dampingRatio = 0.8f - ) + val springSpec = spatialExpressiveSpring() JetsnackBottomNavLayout( selectedIndex = currentSection.ordinal, itemCount = routes.size, indicator = { JetsnackBottomNavIndicator() }, animSpec = springSpec, - modifier = Modifier.jetSnackNavigationBarsPadding() + modifier = Modifier.navigationBarsPadding() ) { - // TODO: implement getting currentLocale in common source set -// val configuration = LocalConfiguration.current -// val currentLocale: Locale = -// ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() tabs.forEach { section -> val selected = section == currentSection @@ -132,11 +203,10 @@ fun JetsnackBottomBar( JetsnackTheme.colors.iconInteractive } else { JetsnackTheme.colors.iconInteractiveInactive - } + }, + label = "tint" ) - // TODO: implement uppercase using currentLocale -// val text = stringResource(section.title).uppercase(currentLocale) val text = stringResource(section.title).uppercase() JetsnackBottomNavigationItem( @@ -151,7 +221,7 @@ fun JetsnackBottomBar( Text( text = text, color = tint, - style = MaterialTheme.typography.button, + style = MaterialTheme.typography.labelLarge, maxLines = 1 ) }, @@ -253,7 +323,10 @@ fun JetsnackBottomNavigationItem( modifier: Modifier = Modifier ) { // Animate the icon/text positions within the item based on selection - val animationProgress by animateFloatAsState(if (selected) 1f else 0f, animSpec) + val animationProgress by animateFloatAsState( + if (selected) 1f else 0f, animSpec, + label = "animation progress" + ) JetsnackBottomNavItemLayout( icon = icon, text = text, @@ -268,7 +341,7 @@ fun JetsnackBottomNavigationItem( private fun JetsnackBottomNavItemLayout( icon: @Composable BoxScope.() -> Unit, text: @Composable BoxScope.() -> Unit, - animationProgress: Float, // @FloatRange(from = 0.0, to = 1.0) + @FloatRange(from = 0.0, to = 1.0) animationProgress: Float, modifier: Modifier = Modifier ) { Layout( @@ -313,7 +386,7 @@ private fun MeasureScope.placeTextAndIcon( iconPlaceable: Placeable, width: Int, height: Int, - animationProgress: Float // @FloatRange(from = 0.0, to = 1.0) + @FloatRange(from = 0.0, to = 1.0) animationProgress: Float ): MeasureResult { val iconY = (height - iconPlaceable.height) / 2 val textY = (height - textPlaceable.height) / 2 @@ -349,15 +422,3 @@ private val BottomNavHeight = 56.dp private val BottomNavLabelTransformOrigin = TransformOrigin(0f, 0.5f) private val BottomNavIndicatorShape = RoundedCornerShape(percent = 50) private val BottomNavigationItemPadding = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) - -//@Preview -@Composable -private fun JetsnackBottomNavPreview() { - JetsnackTheme { - JetsnackBottomBar( - tabs = HomeSections.values(), - currentRoute = "home/feed", - navigateToRoute = { } - ) - } -} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Profile.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Profile.kt index c55b10275dc..5f187312e49 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Profile.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/Profile.kt @@ -16,27 +16,26 @@ package com.example.jetsnack.ui.home -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -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.wrapContentSize -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.example.jetsnack.* -import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.empty_state_search +import com.example.common.generated.resources.grab_beverage +import com.example.common.generated.resources.work_in_progress +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource @Composable -fun Profile(modifier: Modifier = Modifier) { +fun Profile( + modifier: Modifier = Modifier +) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier @@ -45,30 +44,22 @@ fun Profile(modifier: Modifier = Modifier) { .padding(24.dp) ) { Image( - painterResource(MppR.drawable.empty_state_search), + painterResource(Res.drawable.empty_state_search), contentDescription = null ) Spacer(Modifier.height(24.dp)) Text( - text = stringResource(MppR.string.work_in_progress), - style = MaterialTheme.typography.subtitle1, + text = stringResource(Res.string.work_in_progress), + style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(16.dp)) Text( - text = stringResource(MppR.string.grab_beverage), - style = MaterialTheme.typography.body2, + text = stringResource(Res.string.grab_beverage), + style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) } } - -//@Preview -@Composable -fun ProfilePreview() { - JetsnackTheme { - Profile() - } -} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.kt index 6f5f694eb46..4e566d4d50c 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.kt @@ -1,20 +1,19 @@ package com.example.jetsnack.ui.home.cart -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.DeleteForever +import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -25,28 +24,36 @@ import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp -import com.example.jetsnack.* +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.common.generated.resources.* +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.cart_checkout +import com.example.common.generated.resources.label_remove +import com.example.common.generated.resources.remove_item import com.example.jetsnack.model.OrderLine import com.example.jetsnack.model.SnackCollection import com.example.jetsnack.model.SnackRepo -import com.example.jetsnack.ui.components.JetsnackButton -import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackSurface -import com.example.jetsnack.ui.components.SnackCollection +import com.example.jetsnack.ui.components.* import com.example.jetsnack.ui.home.DestinationBar +import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring +import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.utils.formatPrice - +import org.jetbrains.compose.resources.pluralStringResource +import org.jetbrains.compose.resources.stringResource +import kotlin.math.roundToInt @Composable fun Cart( - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, - viewModel: CartViewModel = provideCartViewModel() + viewModel: CartViewModel = viewModel(factory = CartViewModel.provideFactory()) ) { - val orderLines by viewModel.collectOrderLinesAsState(viewModel.orderLines) + val orderLines by viewModel.orderLines.collectAsStateWithLifecycle() val inspiredByCart = remember { SnackRepo.getInspiredByCart() } Cart( orderLines = orderLines, @@ -59,23 +66,6 @@ fun Cart( ) } -@Composable -expect fun provideCartViewModel(): CartViewModel - -/** - * Android uses ConstraintLayout which is android-only at the moment. - * So we provide an alternative implementation of `ActualCartItem` for other platforms. - */ -@Composable -expect fun ActualCartItem( - orderLine: OrderLine, - removeSnack: (Long) -> Unit, - increaseItemCount: (Long) -> Unit, - decreaseItemCount: (Long) -> Unit, - onSnackClick: (Long) -> Unit, - modifier: Modifier = Modifier -) - @Composable fun Cart( orderLines: List, @@ -83,11 +73,11 @@ fun Cart( increaseItemCount: (Long) -> Unit, decreaseItemCount: (Long) -> Unit, inspiredByCart: SnackCollection, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { JetsnackSurface(modifier = modifier.fillMaxSize()) { - Box { + Box(modifier = Modifier.fillMaxSize()) { CartContent( orderLines = orderLines, removeSnack = removeSnack, @@ -103,13 +93,6 @@ fun Cart( } } -@Composable -expect fun rememberQuantityString(res: Int, qty: Int, vararg args: Any): String - -@Composable -expect fun getCartContentInsets(): WindowInsets - -@OptIn(ExperimentalAnimationApi::class) @Composable private fun CartContent( orderLines: List, @@ -117,18 +100,24 @@ private fun CartContent( increaseItemCount: (Long) -> Unit, decreaseItemCount: (Long) -> Unit, inspiredByCart: SnackCollection, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier ) { - val snackCountFormattedString = rememberQuantityString( - MppR.plurals.cart_order_count, orderLines.size, orderLines.size + val snackCountFormattedString = pluralStringResource( + Res.plurals.cart_order_count, orderLines.size, orderLines.size ) + val itemAnimationSpecFade = nonSpatialExpressiveSpring() + val itemPlacementSpec = spatialExpressiveSpring() LazyColumn(modifier) { - item { - Spacer(Modifier.windowInsetsTopHeight(getCartContentInsets())) + item(key = "title") { + Spacer( + Modifier.windowInsetsTopHeight( + WindowInsets.statusBars.add(WindowInsets(top = 56.dp)) + ) + ) Text( - text = stringResource(MppR.string.cart_order_header, snackCountFormattedString), - style = MaterialTheme.typography.h6, + text = stringResource(Res.string.cart_order_header, snackCountFormattedString), + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.brand, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -138,87 +127,18 @@ private fun CartContent( .wrapContentHeight() ) } - items(orderLines) { orderLine -> + items(orderLines, key = { it.snack.id }) { orderLine -> SwipeDismissItem( - background = { offsetX -> - /*Background color changes from light gray to red when the - swipe to delete with exceeds 160.dp*/ - val backgroundColor = if (offsetX < -160.dp) { - JetsnackTheme.colors.error - } else { - JetsnackTheme.colors.uiFloated - } - Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(backgroundColor), - horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.Center - ) { - // Set 4.dp padding only if offset is bigger than 160.dp - val padding: Dp by animateDpAsState( - if (offsetX > -160.dp) 4.dp else 0.dp - ) - Box( - Modifier - .width(offsetX * -1) - .padding(padding) - ) { - // Height equals to width removing padding - val height = (offsetX + 8.dp) * -1 - Surface( - modifier = Modifier - .fillMaxWidth() - .height(height) - .align(Alignment.Center), - shape = CircleShape, - color = JetsnackTheme.colors.error - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - // Icon must be visible while in this width range - if (offsetX < -40.dp && offsetX > -152.dp) { - // Icon alpha decreases as it is about to disappear - val iconAlpha: Float by animateFloatAsState( - if (offsetX < -120.dp) 0.5f else 1f - ) - - Icon( - imageVector = Icons.Filled.DeleteForever, - modifier = Modifier - .size(16.dp) - .graphicsLayer(alpha = iconAlpha), - tint = JetsnackTheme.colors.uiBackground, - contentDescription = null, - ) - } - /*Text opacity increases as the text is supposed to appear in - the screen*/ - val textAlpha by animateFloatAsState( - if (offsetX > -144.dp) 0.5f else 1f - ) - if (offsetX < -120.dp) { - Text( - text = stringResource(id = MppR.string.remove_item), - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.uiBackground, - textAlign = TextAlign.Center, - modifier = Modifier - .graphicsLayer( - alpha = textAlpha - ) - ) - } - } - } - } - } + modifier = Modifier.animateItem( + fadeInSpec = itemAnimationSpecFade, + fadeOutSpec = itemAnimationSpecFade, + placementSpec = itemPlacementSpec + ), + background = { progress -> + SwipeDismissItemBackground(progress) }, ) { - ActualCartItem( + CartItem( orderLine = orderLine, removeSnack = removeSnack, increaseItemCount = increaseItemCount, @@ -227,14 +147,24 @@ private fun CartContent( ) } } - item { + item("summary") { SummaryItem( - subtotal = orderLines.map { it.snack.price * it.count }.sum(), + modifier = Modifier.animateItem( + fadeInSpec = itemAnimationSpecFade, + fadeOutSpec = itemAnimationSpecFade, + placementSpec = itemPlacementSpec + ), + subtotal = orderLines.sumOf { it.snack.price * it.count }, shippingCosts = 369 ) } - item { + item(key = "inspiredByCart") { SnackCollection( + modifier = Modifier.animateItem( + fadeInSpec = itemAnimationSpecFade, + fadeOutSpec = itemAnimationSpecFade, + placementSpec = itemPlacementSpec + ), snackCollection = inspiredByCart, onSnackClick = onSnackClick, highlight = false @@ -244,6 +174,163 @@ private fun CartContent( } } +@Composable +private fun SwipeDismissItemBackground(progress: Float) { + Column( + modifier = Modifier + .background(JetsnackTheme.colors.uiBackground) + .fillMaxWidth() + .fillMaxHeight(), + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.Center + ) { + // Set 4.dp padding only if progress is less than halfway + val padding: Dp by animateDpAsState( + if (progress < 0.5f) 4.dp else 0.dp, label = "padding" + ) + BoxWithConstraints( + Modifier + .fillMaxWidth(progress) + ) { + Surface( + modifier = Modifier + .padding(padding) + .fillMaxWidth() + .height(maxWidth) + .align(Alignment.Center), + shape = RoundedCornerShape(percent = ((1 - progress) * 100).roundToInt()), + color = JetsnackTheme.colors.error + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + // Icon must be visible while in this width range + if (progress in 0.125f..0.475f) { + // Icon alpha decreases as it is about to disappear + val iconAlpha: Float by animateFloatAsState( + if (progress > 0.4f) 0.5f else 1f, label = "icon alpha" + ) + + Icon( + imageVector = Icons.Filled.DeleteForever, + modifier = Modifier + .size(32.dp) + .graphicsLayer(alpha = iconAlpha), + tint = JetsnackTheme.colors.uiBackground, + contentDescription = null, + ) + } + /*Text opacity increases as the text is supposed to appear in + the screen*/ + val textAlpha by animateFloatAsState( + if (progress > 0.5f) 1f else 0.5f, label = "text alpha" + ) + if (progress > 0.5f) { + Text( + text = stringResource(Res.string.remove_item), + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.uiBackground, + textAlign = TextAlign.Center, + modifier = Modifier + .graphicsLayer( + alpha = textAlpha + ) + ) + } + } + } + } + } +} + +@Composable +fun CartItem( + orderLine: OrderLine, + removeSnack: (Long) -> Unit, + increaseItemCount: (Long) -> Unit, + decreaseItemCount: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, + modifier: Modifier = Modifier +) { + val snack = orderLine.snack + Column( + modifier = modifier + .fillMaxWidth() + .clickable { onSnackClick(snack.id, "cart") } + .background(JetsnackTheme.colors.uiBackground) + .padding(horizontal = 24.dp) + ) { + // Main content container + Box( + modifier = Modifier.fillMaxWidth() + ) { + // Row containing image and text content + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + ) { + // Snack Image + SnackImage( + image = snack.image, + contentDescription = null, + modifier = Modifier.size(100.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + // Text content and Quantity Selector + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = snack.name, + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textSecondary + ) + Text( + text = snack.tagline, + style = MaterialTheme.typography.bodyLarge, + color = JetsnackTheme.colors.textHelp + ) + Spacer(modifier = Modifier.height(8.dp)) + // Row for price and quantity selector + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = formatPrice(snack.price), + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textPrimary + ) + Spacer(modifier = Modifier.weight(1f)) + QuantitySelector( + count = orderLine.count, + decreaseItemCount = { decreaseItemCount(snack.id) }, + increaseItemCount = { increaseItemCount(snack.id) } + ) + } + } + } + // Remove Button positioned at the top-right corner + IconButton( + onClick = { removeSnack(snack.id) }, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 12.dp) + ) { + Icon( + imageVector = Icons.Filled.Close, + tint = JetsnackTheme.colors.iconSecondary, + contentDescription = stringResource(Res.string.label_remove) + ) + } + } + // Divider at the bottom + JetsnackDivider() + } +} + + @Composable fun SummaryItem( subtotal: Long, @@ -252,8 +339,8 @@ fun SummaryItem( ) { Column(modifier) { Text( - text = stringResource(MppR.string.cart_summary_header), - style = MaterialTheme.typography.h6, + text = stringResource(Res.string.cart_summary_header), + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.brand, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -264,8 +351,8 @@ fun SummaryItem( ) Row(modifier = Modifier.padding(horizontal = 24.dp)) { Text( - text = stringResource(MppR.string.cart_subtotal_label), - style = MaterialTheme.typography.body1, + text = stringResource(Res.string.cart_subtotal_label), + style = MaterialTheme.typography.bodyLarge, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) @@ -273,14 +360,14 @@ fun SummaryItem( ) Text( text = formatPrice(subtotal), - style = MaterialTheme.typography.body1, + style = MaterialTheme.typography.bodyLarge, modifier = Modifier.alignBy(LastBaseline) ) } Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { Text( - text = stringResource(MppR.string.cart_shipping_label), - style = MaterialTheme.typography.body1, + text = stringResource(Res.string.cart_shipping_label), + style = MaterialTheme.typography.bodyLarge, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) @@ -288,7 +375,7 @@ fun SummaryItem( ) Text( text = formatPrice(shippingCosts), - style = MaterialTheme.typography.body1, + style = MaterialTheme.typography.bodyLarge, modifier = Modifier.alignBy(LastBaseline) ) } @@ -296,8 +383,8 @@ fun SummaryItem( JetsnackDivider() Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { Text( - text = stringResource(MppR.string.cart_total_label), - style = MaterialTheme.typography.body1, + text = stringResource(Res.string.cart_total_label), + style = MaterialTheme.typography.bodyLarge, modifier = Modifier .weight(1f) .padding(end = 16.dp) @@ -306,7 +393,7 @@ fun SummaryItem( ) Text( text = formatPrice(subtotal + shippingCosts), - style = MaterialTheme.typography.subtitle1, + style = MaterialTheme.typography.titleMedium, modifier = Modifier.alignBy(LastBaseline) ) } @@ -321,6 +408,7 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque) ) ) { + JetsnackDivider() Row { Spacer(Modifier.weight(1f)) @@ -332,7 +420,7 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { .weight(1f) ) { Text( - text = stringResource(id = MppR.string.cart_checkout), + text = stringResource(Res.string.cart_checkout), modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Left, maxLines = 1 diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.kt index f4ba3066a7d..2b59ca9f818 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.kt @@ -16,14 +16,18 @@ package com.example.jetsnack.ui.home.cart -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import com.example.jetsnack.* +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.cart_decrease_error +import com.example.common.generated.resources.cart_increase_error import com.example.jetsnack.model.OrderLine import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.model.SnackbarManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlin.reflect.KClass /** * Holds the contents of the cart and allows changes to it. @@ -33,7 +37,7 @@ import kotlinx.coroutines.flow.StateFlow class CartViewModel( private val snackbarManager: SnackbarManager, snackRepository: SnackRepo -) : JetSnackCartViewModel() { +) : ViewModel() { private val _orderLines: MutableStateFlow> = MutableStateFlow(snackRepository.getCart()) @@ -48,7 +52,7 @@ class CartViewModel( val currentCount = _orderLines.value.first { it.snack.id == snackId }.count updateSnackCount(snackId, currentCount + 1) } else { - snackbarManager.showMessage(MppR.string.cart_increase_error) + snackbarManager.showMessage(Res.string.cart_increase_error) } } @@ -63,7 +67,7 @@ class CartViewModel( updateSnackCount(snackId, currentCount - 1) } } else { - snackbarManager.showMessage(MppR.string.cart_decrease_error) + snackbarManager.showMessage(Res.string.cart_decrease_error) } } @@ -81,12 +85,18 @@ class CartViewModel( } } - companion object // necessary for android (see `provideFactory` method) -} - -expect abstract class JetSnackCartViewModel() { - - @Composable - fun collectOrderLinesAsState(flow: StateFlow>): State> - + /** + * Factory for CartViewModel that takes SnackbarManager as a dependency + */ + companion object { + fun provideFactory( + snackbarManager: SnackbarManager = SnackbarManager, + snackRepository: SnackRepo = SnackRepo + ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: KClass, extras: CreationExtras): T { + return CartViewModel(snackbarManager, snackRepository) as T + } + } + } } \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt index ff5f68f43f5..dc2a7d3a189 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt @@ -19,37 +19,31 @@ package com.example.jetsnack.ui.home.cart import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically -import androidx.compose.material.DismissDirection -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.SwipeToDismiss -import androidx.compose.material.rememberDismissState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxValue +import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable -/** - * Holds the Swipe to dismiss composable, its animation and the current state - */ + /** + * Holds the Swipe to dismiss composable, its animation and the current state + */ fun SwipeDismissItem( modifier: Modifier = Modifier, - directions: Set = setOf(DismissDirection.EndToStart), enter: EnterTransition = expandVertically(), exit: ExitTransition = shrinkVertically(), - background: @Composable (offset: Dp) -> Unit, + background: @Composable (progress: Float) -> Unit, content: @Composable (isDismissed: Boolean) -> Unit, ) { // Hold the current state from the Swipe to Dismiss composable - val dismissState = rememberDismissState() + val dismissState = rememberSwipeToDismissBoxState() // Boolean value used for hiding the item if the current state is dismissed - val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart) - // Returns the swiped value in dp - val offset = with(LocalDensity.current) { dismissState.offset.value.toDp() } + val isDismissed = dismissState.currentValue == SwipeToDismissBoxValue.EndToStart AnimatedVisibility( modifier = modifier, @@ -57,12 +51,12 @@ fun SwipeDismissItem( enter = enter, exit = exit ) { - SwipeToDismiss( + SwipeToDismissBox( modifier = modifier, state = dismissState, - directions = directions, - background = { background(offset) }, - dismissContent = { content(isDismissed) } + enableDismissFromStartToEnd = false, + backgroundContent = { background(dismissState.progress) }, + content = { content(isDismissed) } ) } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Categories.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Categories.kt index 434217918b2..fb4277162a8 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Categories.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Categories.kt @@ -30,8 +30,8 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -69,7 +69,7 @@ private fun SearchCategoryCollection( Column(modifier) { Text( text = collection.name, - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.textPrimary, modifier = Modifier .heightIn(min = 56.dp) @@ -113,14 +113,14 @@ private fun SearchCategory( content = { Text( text = category.name, - style = MaterialTheme.typography.subtitle1, +// style = MaterialTheme.typography.subtitle1, color = JetsnackTheme.colors.textSecondary, modifier = Modifier .padding(4.dp) .padding(start = 8.dp) ) SnackImage( - imageUrl = category.imageUrl, + image = category.image, contentDescription = null, modifier = Modifier.fillMaxSize() ) @@ -150,17 +150,3 @@ private fun SearchCategory( } } } - -//@Preview -@Composable -private fun SearchCategoryPreview() { - JetsnackTheme { - SearchCategory( - category = SearchCategory( - name = "Desserts", - imageUrl = "" - ), - gradient = JetsnackTheme.colors.gradient3_2 - ) - } -} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Results.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Results.kt index 654dcad90ea..53098797c92 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Results.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Results.kt @@ -16,43 +16,41 @@ package com.example.jetsnack.ui.home.search -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -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.wrapContentSize +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.example.jetsnack.* -import com.example.jetsnack.model.Filter +import com.example.common.generated.resources.* import com.example.jetsnack.model.Snack -import com.example.jetsnack.model.snacks -import com.example.jetsnack.ui.components.FilterBar -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.JetsnackButton +import com.example.jetsnack.ui.components.JetsnackDivider +import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.utils.formatPrice +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource @Composable fun SearchResults( searchResults: List, - filters: List, - onSnackClick: (Long) -> Unit + onSnackClick: (Long, String) -> Unit ) { Column { - FilterBar(filters, onShowFilters = {}) Text( - text = stringResource(MppR.string.search_count, searchResults.size), - style = MaterialTheme.typography.h6, + text = stringResource(Res.string.search_count, searchResults.size), + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.textPrimary, modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp) ) @@ -67,110 +65,73 @@ fun SearchResults( @Composable private fun SearchResult( snack: Snack, - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, showDivider: Boolean, modifier: Modifier = Modifier ) { - // TODO: implement Search Result (we don't have ConstrainLayout in Compose MPP) -// ConstraintLayout( -// modifier = modifier -// .fillMaxWidth() -// .clickable { onSnackClick(snack.id) } -// .padding(horizontal = 24.dp) -// ) { -// val (divider, image, name, tag, priceSpacer, price, add) = createRefs() -// createVerticalChain(name, tag, priceSpacer, price, chainStyle = ChainStyle.Packed) -// if (showDivider) { -// JetsnackDivider( -// Modifier.constrainAs(divider) { -// linkTo(start = parent.start, end = parent.end) -// top.linkTo(parent.top) -// } -// ) -// } -// SnackImage( -// imageUrl = snack.imageUrl, -// contentDescription = null, -// modifier = Modifier -// .size(100.dp) -// .constrainAs(image) { -// linkTo( -// top = parent.top, -// topMargin = 16.dp, -// bottom = parent.bottom, -// bottomMargin = 16.dp -// ) -// start.linkTo(parent.start) -// } -// ) -// Text( -// text = snack.name, -// style = MaterialTheme.typography.subtitle1, -// color = JetsnackTheme.colors.textSecondary, -// modifier = Modifier.constrainAs(name) { -// linkTo( -// start = image.end, -// startMargin = 16.dp, -// end = add.start, -// endMargin = 16.dp, -// bias = 0f -// ) -// } -// ) -// Text( -// text = snack.tagline, -// style = MaterialTheme.typography.body1, -// color = JetsnackTheme.colors.textHelp, -// modifier = Modifier.constrainAs(tag) { -// linkTo( -// start = image.end, -// startMargin = 16.dp, -// end = add.start, -// endMargin = 16.dp, -// bias = 0f -// ) -// } -// ) -// Spacer( -// Modifier -// .height(8.dp) -// .constrainAs(priceSpacer) { -// linkTo(top = tag.bottom, bottom = price.top) -// } -// ) -// Text( -// text = formatPrice(snack.price), -// style = MaterialTheme.typography.subtitle1, -// color = JetsnackTheme.colors.textPrimary, -// modifier = Modifier.constrainAs(price) { -// linkTo( -// start = image.end, -// startMargin = 16.dp, -// end = add.start, -// endMargin = 16.dp, -// bias = 0f -// ) -// } -// ) -// JetsnackButton( -// onClick = { /* todo */ }, -// shape = CircleShape, -// contentPadding = PaddingValues(0.dp), -// modifier = Modifier -// .size(36.dp) -// .constrainAs(add) { -// linkTo(top = parent.top, bottom = parent.bottom) -// end.linkTo(parent.end) -// } -// ) { -// Icon( -// imageVector = Icons.Outlined.Add, -// contentDescription = stringResource(R.string.label_add) -// ) -// } -// } + // Note the original example uses ConstraintLayout, but it's not available in ComposeMultiplatform + Column( + modifier = modifier + .fillMaxWidth() + .clickable { onSnackClick(snack.id, "search") } + .padding(horizontal = 24.dp) + ) { + if (showDivider) { + JetsnackDivider() + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Snack Image + SnackImage( + image = snack.image, + contentDescription = null, + modifier = Modifier.size(100.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + // Text Content + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center + ) { + Text( + text = snack.name, + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textSecondary + ) + Text( + text = snack.tagline, + style = MaterialTheme.typography.bodyLarge, + color = JetsnackTheme.colors.textHelp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = formatPrice(snack.price), + style = MaterialTheme.typography.titleMedium, + color = JetsnackTheme.colors.textPrimary + ) + } + Spacer(modifier = Modifier.width(16.dp)) + // Add Button + JetsnackButton( + onClick = { /* TODO: Implement add action */ }, + shape = CircleShape, + contentPadding = PaddingValues(0.dp), + modifier = Modifier.size(36.dp) + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource(Res.string.label_add) + ) + } + } + } } + @Composable fun NoResults( query: String, @@ -184,36 +145,22 @@ fun NoResults( .padding(24.dp) ) { Image( - painterResource(MppR.drawable.empty_state_search), + painterResource(Res.drawable.empty_state_search), contentDescription = null ) Spacer(Modifier.height(24.dp)) Text( - text = stringResource(MppR.string.search_no_matches, query), - style = MaterialTheme.typography.subtitle1, + text = stringResource(Res.string.search_no_matches, query), + style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(16.dp)) Text( - text = stringResource(MppR.string.search_no_matches_retry), - style = MaterialTheme.typography.body2, + text = stringResource(Res.string.search_no_matches_retry), + style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) } -} - -//@Preview -@Composable -private fun SearchResultPreview() { - JetsnackTheme { - JetsnackSurface { - SearchResult( - snack = snacks[0], - onSnackClick = { }, - showDivider = false - ) - } - } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Search.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Search.kt index 38ce9b13259..2e991f8e59c 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Search.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Search.kt @@ -16,61 +16,37 @@ package com.example.jetsnack.ui.home.search -//import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -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.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Search -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import com.example.jetsnack.* -import com.example.jetsnack.model.Filter -import com.example.jetsnack.model.SearchCategoryCollection -import com.example.jetsnack.model.SearchRepo -import com.example.jetsnack.model.SearchSuggestionGroup -import com.example.jetsnack.model.Snack -import com.example.jetsnack.model.SnackRepo +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.label_back +import com.example.common.generated.resources.label_search +import com.example.common.generated.resources.search_jetsnack +import com.example.jetsnack.model.* import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface -import com.example.jetsnack.ui.snackdetail.jetSnackStatusBarsPadding import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.utils.mirroringBackIcon +import org.jetbrains.compose.resources.stringResource @Composable fun Search( - onSnackClick: (Long) -> Unit, + onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, state: SearchState = rememberSearchState() ) { JetsnackSurface(modifier = modifier.fillMaxSize()) { Column { - Spacer(modifier = Modifier.jetSnackStatusBarsPadding()) + Spacer(modifier = Modifier.statusBarsPadding()) SearchBar( query = state.query, onQueryChange = { state.query = it }, @@ -90,13 +66,16 @@ fun Search( SearchDisplay.Categories -> SearchCategories(state.categories) SearchDisplay.Suggestions -> SearchSuggestions( suggestions = state.suggestions, - onSuggestionSelect = { suggestion -> state.query = TextFieldValue(suggestion) } + onSuggestionSelect = { suggestion -> + state.query = TextFieldValue(suggestion) + } ) + SearchDisplay.Results -> SearchResults( state.searchResults, - state.filters, onSnackClick ) + SearchDisplay.NoResults -> NoResults(state.query.text) } } @@ -188,9 +167,9 @@ private fun SearchBar( if (searchFocused) { IconButton(onClick = onClearQuery) { Icon( - imageVector = mirroringBackIcon(), + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, tint = JetsnackTheme.colors.iconPrimary, - contentDescription = stringResource(MppR.string.label_back) + contentDescription = stringResource(Res.string.label_back) ) } } @@ -231,29 +210,12 @@ private fun SearchHint() { Icon( imageVector = Icons.Outlined.Search, tint = JetsnackTheme.colors.textHelp, - contentDescription = stringResource(MppR.string.label_search) + contentDescription = stringResource(Res.string.label_search) ) Spacer(Modifier.width(8.dp)) Text( - text = stringResource(MppR.string.search_jetsnack), + text = stringResource(Res.string.search_jetsnack), color = JetsnackTheme.colors.textHelp ) } -} - -//@Preview -@Composable -private fun SearchBarPreview() { - JetsnackTheme { - JetsnackSurface { - SearchBar( - query = TextFieldValue(""), - onQueryChange = { }, - searchFocused = false, - onSearchFocusChange = { }, - onClearQuery = { }, - searching = false - ) - } - } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Suggestions.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Suggestions.kt index 6f7562e3e20..d9887f5b43d 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Suggestions.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/home/search/Suggestions.kt @@ -16,25 +16,17 @@ package com.example.jetsnack.ui.home.search -//import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.example.jetsnack.model.SearchRepo import com.example.jetsnack.model.SearchSuggestionGroup -import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.theme.JetsnackTheme @Composable @@ -68,7 +60,7 @@ private fun SuggestionHeader( ) { Text( text = name, - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = JetsnackTheme.colors.textPrimary, modifier = modifier .heightIn(min = 56.dp) @@ -85,7 +77,7 @@ private fun Suggestion( ) { Text( text = suggestion, - style = MaterialTheme.typography.subtitle1, + style = MaterialTheme.typography.titleMedium, modifier = modifier .heightIn(min = 48.dp) .clickable { onSuggestionSelect(suggestion) } @@ -93,16 +85,3 @@ private fun Suggestion( .wrapContentSize(Alignment.CenterStart) ) } - -//@Preview -@Composable -fun PreviewSuggestions() { - JetsnackTheme { - JetsnackSurface { - SearchSuggestions( - suggestions = SearchRepo.getSuggestions(), - onSuggestionSelect = { } - ) - } - } -} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/navigation/JetsnackNavController.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/navigation/JetsnackNavController.kt new file mode 100644 index 00000000000..0321c4b66e4 --- /dev/null +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/navigation/JetsnackNavController.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetsnack.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.lifecycle.Lifecycle +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDestination +import androidx.navigation.NavGraph +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.example.jetsnack.ui.home.HomeSections + +/** + * Destinations used in the [JetsnackApp]. + */ +object MainDestinations { + const val HOME_ROUTE = "home" + const val SNACK_DETAIL_ROUTE = "snack" + const val SNACK_ID_KEY = "snackId" + const val ORIGIN = "origin" +} + +/** + * Remembers and creates an instance of [JetsnackNavController] + */ +@Composable +fun rememberJetsnackNavController( + navController: NavHostController = rememberNavController() +): JetsnackNavController = remember(navController) { + JetsnackNavController(navController) +} + +/** + * Responsible for holding UI Navigation logic. + */ +@Stable +class JetsnackNavController( + val navController: NavHostController, +) { + + // ---------------------------------------------------------- + // Navigation state source of truth + // ---------------------------------------------------------- + + fun upPress() { + navController.navigateUp() + } + + fun navigateToBottomBarRoute(route: String) { + if (route != navController.currentDestination?.route) { + navController.navigate(route) { + launchSingleTop = true + restoreState = true + // Pop up backstack to the first destination and save state. This makes going back + // to the start destination when pressing back in any other bottom tab. + popUpTo(findStartDestination(navController.graph).navigatorName) { + saveState = true + } + } + } + } + + fun navigateToSnackDetail(snackId: Long, origin: String, from: NavBackStackEntry) { + // In order to discard duplicated navigation events, we check the Lifecycle + if (from.lifecycleIsResumed()) { + navController.navigate("${MainDestinations.SNACK_DETAIL_ROUTE}/$snackId?origin=$origin") + } + } +} + +/** + * If the lifecycle is not resumed it means this NavBackStackEntry already processed a nav event. + * + * This is used to de-duplicate navigation events. + */ +private fun NavBackStackEntry.lifecycleIsResumed() = + this.lifecycle.currentState == Lifecycle.State.RESUMED + +private val NavGraph.startDestination: NavDestination? + get() = findNode(HomeSections.FEED.route) + +/** + * Copied from similar function in NavigationUI.kt + * + * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt + */ +private tailrec fun findStartDestination(graph: NavDestination): NavDestination { + return graph.parent?.findStartDestination() ?: graph +} diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/snackdetail/SnackDetail.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/snackdetail/SnackDetail.kt index 122fe679f15..0a7db3d2448 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/snackdetail/SnackDetail.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/snackdetail/SnackDetail.kt @@ -16,7 +16,24 @@ package com.example.jetsnack.ui.snackdetail -//import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.animation.BoundsTransform +import androidx.compose.animation.EnterExitState +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -29,28 +46,41 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf 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.blur +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Constraints @@ -58,24 +88,27 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import androidx.compose.ui.unit.sp -import com.example.jetsnack.* +import androidx.compose.ui.util.lerp +import com.example.common.generated.resources.* +import com.example.common.generated.resources.Res +import com.example.common.generated.resources.label_back +import com.example.common.generated.resources.see_less +import com.example.common.generated.resources.see_more import com.example.jetsnack.model.Snack import com.example.jetsnack.model.SnackCollection import com.example.jetsnack.model.SnackRepo -import com.example.jetsnack.ui.components.JetsnackButton -import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackSurface -import com.example.jetsnack.ui.components.QuantitySelector -import com.example.jetsnack.ui.components.SnackCollection -import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope +import com.example.jetsnack.ui.LocalSharedTransitionScope +import com.example.jetsnack.ui.SnackSharedElementKey +import com.example.jetsnack.ui.SnackSharedElementType +import com.example.jetsnack.ui.components.* import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.Neutral8 import com.example.jetsnack.ui.utils.formatPrice -import com.example.jetsnack.ui.utils.mirroringBackIcon +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.stringResource import kotlin.math.max import kotlin.math.min -import kotlin.math.roundToInt -import kotlin.math.roundToLong private val BottomBarHeight = 56.dp private val TitleHeight = 128.dp @@ -88,201 +121,366 @@ private val ExpandedImageSize = 300.dp private val CollapsedImageSize = 150.dp private val HzPadding = Modifier.padding(horizontal = 24.dp) +fun spatialExpressiveSpring() = spring( + dampingRatio = 0.8f, + stiffness = 380f +) + +fun nonSpatialExpressiveSpring() = spring( + dampingRatio = 1f, + stiffness = 1600f +) + +@OptIn(ExperimentalSharedTransitionApi::class) +val snackDetailBoundsTransform = BoundsTransform { _, _ -> + spatialExpressiveSpring() +} + +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun SnackDetail( snackId: Long, - upPress: () -> Unit, - onSnackClick: (Long) -> Unit, + origin: String, + upPress: () -> Unit ) { val snack = remember(snackId) { SnackRepo.getSnack(snackId) } val related = remember(snackId) { SnackRepo.getRelated(snackId) } - - Box(Modifier.fillMaxSize()) { - val scroll = rememberScrollState(0) - Header() - Body(related, scroll, onSnackClick) - Title(snack) { scroll.value } - Image(snack.imageUrl) { scroll.value } - Up(upPress) - CartBottomBar(modifier = Modifier.align(Alignment.BottomCenter)) + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalStateException("No Scope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalStateException("No Scope found") + val roundedCornerAnim by animatedVisibilityScope.transition + .animateDp(label = "rounded corner") { enterExit: EnterExitState -> + when (enterExit) { + EnterExitState.PreEnter -> 20.dp + EnterExitState.Visible -> 0.dp + EnterExitState.PostExit -> 20.dp + } + } + with(sharedTransitionScope) { + Box( + Modifier + .clip(RoundedCornerShape(roundedCornerAnim)) + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = origin, + type = SnackSharedElementType.Bounds + ) + ), + animatedVisibilityScope, + clipInOverlayDuringTransition = + OverlayClip(RoundedCornerShape(roundedCornerAnim)), + boundsTransform = snackDetailBoundsTransform, + exit = fadeOut(nonSpatialExpressiveSpring()), + enter = fadeIn(nonSpatialExpressiveSpring()), + ) + .fillMaxSize() + .background(color = JetsnackTheme.colors.uiBackground) + ) { + val scroll = rememberScrollState(0) + Header(snack.id, origin = origin) + Body(related, scroll) + Title(snack, origin) { scroll.value } + Image(snackId, origin, snack.image) { scroll.value } + Up(upPress) + CartBottomBar(modifier = Modifier.align(Alignment.BottomCenter)) + } } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable -private fun Header() { - Spacer( - modifier = Modifier - .height(280.dp) - .fillMaxWidth() - .background(Brush.horizontalGradient(JetsnackTheme.colors.tornado1)) - ) +private fun Header(snackId: Long, origin: String) { + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalArgumentException("No Scope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalArgumentException("No Scope found") + + with(sharedTransitionScope) { + val brushColors = JetsnackTheme.colors.tornado1 + + val infiniteTransition = rememberInfiniteTransition(label = "background") + val targetOffset = with(LocalDensity.current) { + 1000.dp.toPx() + } + val offset by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = targetOffset, + animationSpec = infiniteRepeatable( + tween(50000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "offset" + ) + Spacer( + modifier = Modifier + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snackId, + origin = origin, + type = SnackSharedElementType.Background + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds() + ) + .height(280.dp) + .fillMaxWidth() + .blur(40.dp) + .drawWithCache { + val brushSize = 400f + val brush = Brush.linearGradient( + colors = brushColors, + start = Offset(offset, offset), + end = Offset(offset + brushSize, offset + brushSize), + tileMode = TileMode.Mirror + ) + onDrawBehind { + drawRect(brush) + } + } + ) + } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable -private fun Up(upPress: () -> Unit) { - IconButton( - onClick = upPress, - modifier = Modifier - .jetSnackStatusBarsPadding() - .padding(horizontal = 16.dp, vertical = 10.dp) - .size(36.dp) - .background( - color = Neutral8.copy(alpha = 0.32f), - shape = CircleShape +private fun SharedTransitionScope.Up(upPress: () -> Unit) { + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalArgumentException("No Scope found") + with(animatedVisibilityScope) { + IconButton( + onClick = upPress, + modifier = Modifier + .renderInSharedTransitionScopeOverlay(zIndexInOverlay = 3f) + .statusBarsPadding() + .padding(horizontal = 16.dp, vertical = 10.dp) + .size(36.dp) + .animateEnterExit( + enter = scaleIn(tween(300, delayMillis = 300)), + exit = scaleOut(tween(20)) + ) + .background( + color = Neutral8.copy(alpha = 0.32f), + shape = CircleShape + ) + ) { + Icon( + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, + tint = JetsnackTheme.colors.iconInteractive, + contentDescription = stringResource(Res.string.label_back), ) - ) { - Icon( - imageVector = mirroringBackIcon(), - tint = JetsnackTheme.colors.iconInteractive, - contentDescription = stringResource(MppR.string.label_back) - ) + } } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun Body( related: List, - scroll: ScrollState, - onSnackClick: (Long) -> Unit, + scroll: ScrollState ) { - Column { - Spacer( - modifier = Modifier - .fillMaxWidth() - .jetSnackStatusBarsPadding() - .height(MinTitleOffset) - ) - Column( - modifier = Modifier.verticalScroll(scroll) - ) { - Spacer(Modifier.height(GradientScroll)) - JetsnackSurface(Modifier.fillMaxWidth()) { - Column { - Spacer(Modifier.height(ImageOverlap)) - Spacer(Modifier.height(TitleHeight)) - - Spacer(Modifier.height(16.dp)) - Text( - text = stringResource(MppR.string.detail_header), - style = MaterialTheme.typography.overline, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding - ) - Spacer(Modifier.height(16.dp)) - var seeMore by remember { mutableStateOf(true) } - Text( - text = stringResource(MppR.string.detail_placeholder), - style = MaterialTheme.typography.body1, - color = JetsnackTheme.colors.textHelp, - maxLines = if (seeMore) 5 else Int.MAX_VALUE, - overflow = TextOverflow.Ellipsis, - modifier = HzPadding - ) - val textButton = if (seeMore) { - stringResource(id = MppR.string.see_more) - } else { - stringResource(id = MppR.string.see_less) - } - Text( - text = textButton, - style = MaterialTheme.typography.button, - textAlign = TextAlign.Center, - color = JetsnackTheme.colors.textLink, - modifier = Modifier - .heightIn(20.dp) - .fillMaxWidth() - .padding(top = 15.dp) - .clickable { - seeMore = !seeMore - } - ) - Spacer(Modifier.height(40.dp)) - Text( - text = stringResource(MppR.string.ingredients), - style = MaterialTheme.typography.overline, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding - ) - Spacer(Modifier.height(4.dp)) - Text( - text = stringResource(MppR.string.ingredients_list), - style = MaterialTheme.typography.body1, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding - ) + val sharedTransitionScope = + LocalSharedTransitionScope.current ?: throw IllegalStateException("No scope found") + with(sharedTransitionScope) { + Column(modifier = Modifier.skipToLookaheadSize()) { + Spacer( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .height(MinTitleOffset) + ) - Spacer(Modifier.height(16.dp)) - JetsnackDivider() + Column( + modifier = Modifier.verticalScroll(scroll) + ) { + Spacer(Modifier.height(GradientScroll)) + Spacer(Modifier.height(ImageOverlap)) + JetsnackSurface( + Modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) { + Column { + Spacer(Modifier.height(TitleHeight)) + Text( + text = stringResource(Res.string.detail_header), + style = MaterialTheme.typography.labelSmall, + color = JetsnackTheme.colors.textHelp, + modifier = HzPadding + ) + Spacer(Modifier.height(16.dp)) + var seeMore by remember { mutableStateOf(true) } + with(sharedTransitionScope) { + Text( + text = stringResource(Res.string.detail_placeholder), + style = MaterialTheme.typography.bodyLarge, + color = JetsnackTheme.colors.textHelp, + maxLines = if (seeMore) 5 else Int.MAX_VALUE, + overflow = TextOverflow.Ellipsis, + modifier = HzPadding.skipToLookaheadSize() - related.forEach { snackCollection -> - key(snackCollection.id) { - SnackCollection( - snackCollection = snackCollection, - onSnackClick = onSnackClick, - highlight = false ) } - } + val textButton = if (seeMore) { + stringResource(Res.string.see_more) + } else { + stringResource(Res.string.see_less) + } - Spacer( - modifier = Modifier - .padding(bottom = BottomBarHeight) - .jetSnackNavigationBarsPadding() - .height(8.dp) - ) + Text( + text = textButton, + style = MaterialTheme.typography.labelLarge, + textAlign = TextAlign.Center, + color = JetsnackTheme.colors.textLink, + modifier = Modifier + .heightIn(20.dp) + .fillMaxWidth() + .padding(top = 15.dp) + .clickable { + seeMore = !seeMore + } + .skipToLookaheadSize() + ) + + Spacer(Modifier.height(40.dp)) + Text( + text = stringResource(Res.string.ingredients), + style = MaterialTheme.typography.labelSmall, + color = JetsnackTheme.colors.textHelp, + modifier = HzPadding + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(Res.string.ingredients_list), + style = MaterialTheme.typography.bodyLarge, + color = JetsnackTheme.colors.textHelp, + modifier = HzPadding + ) + + Spacer(Modifier.height(16.dp)) + JetsnackDivider() + + related.forEach { snackCollection -> + key(snackCollection.id) { + SnackCollection( + snackCollection = snackCollection, + onSnackClick = { _, _ -> }, + index = 0, + highlight = false + ) + } + } + + Spacer( + modifier = Modifier + .padding(bottom = BottomBarHeight) + .navigationBarsPadding() + .height(8.dp) + ) + } } } } } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable -private fun Title(snack: Snack, scrollProvider: () -> Int) { +private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { val maxOffset = with(LocalDensity.current) { MaxTitleOffset.toPx() } val minOffset = with(LocalDensity.current) { MinTitleOffset.toPx() } + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalArgumentException("No Scope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalArgumentException("No Scope found") - Column( - verticalArrangement = Arrangement.Bottom, - modifier = Modifier - .heightIn(min = TitleHeight) - .jetSnackStatusBarsPadding() - .offset { - val scroll = scrollProvider() - val offset = (maxOffset - scroll).coerceAtLeast(minOffset) - IntOffset(x = 0, y = offset.toInt()) + with(sharedTransitionScope) { + Column( + verticalArrangement = Arrangement.Bottom, + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TitleHeight) + .statusBarsPadding() + .offset { + val scroll = scrollProvider() + val offset = (maxOffset - scroll).coerceAtLeast(minOffset) + IntOffset(x = 0, y = offset.toInt()) + } + .background(JetsnackTheme.colors.uiBackground) + ) { + Spacer(Modifier.height(16.dp)) + Text( + text = snack.name, + fontStyle = FontStyle.Italic, + style = MaterialTheme.typography.headlineMedium, + color = JetsnackTheme.colors.textSecondary, + modifier = HzPadding + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = origin, + type = SnackSharedElementType.Title + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform + ) + .wrapContentWidth() + ) + Text( + text = snack.tagline, + fontStyle = FontStyle.Italic, + style = MaterialTheme.typography.titleSmall, + fontSize = 20.sp, + color = JetsnackTheme.colors.textHelp, + modifier = HzPadding + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = origin, + type = SnackSharedElementType.Tagline + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform + ) + .wrapContentWidth() + ) + Spacer(Modifier.height(4.dp)) + with(animatedVisibilityScope) { + Text( + text = formatPrice(snack.price), + style = MaterialTheme.typography.titleLarge, + color = JetsnackTheme.colors.textPrimary, + modifier = HzPadding + .animateEnterExit( + enter = fadeIn() + slideInVertically { -it / 3 }, + exit = fadeOut() + slideOutVertically { -it / 3 } + ) + .skipToLookaheadSize() + ) } - .background(color = JetsnackTheme.colors.uiBackground) - ) { - Spacer(Modifier.height(16.dp)) - Text( - text = snack.name, - style = MaterialTheme.typography.h4, - color = JetsnackTheme.colors.textSecondary, - modifier = HzPadding - ) - Text( - text = snack.tagline, - style = MaterialTheme.typography.subtitle2, - fontSize = 20.sp, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding - ) - Spacer(Modifier.height(4.dp)) - Text( - text = formatPrice(snack.price), - style = MaterialTheme.typography.h6, - color = JetsnackTheme.colors.textPrimary, - modifier = HzPadding - ) - - Spacer(Modifier.height(8.dp)) - JetsnackDivider() + Spacer(Modifier.height(8.dp)) + JetsnackDivider(modifier = Modifier) + } } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun Image( - imageUrl: String, + snackId: Long, + origin: String, + imageRes: DrawableResource, scrollProvider: () -> Int ) { val collapseRange = with(LocalDensity.current) { (MaxTitleOffset - MinTitleOffset).toPx() } @@ -292,13 +490,35 @@ private fun Image( CollapsingImageLayout( collapseFractionProvider = collapseFractionProvider, - modifier = HzPadding.then(Modifier.jetSnackStatusBarsPadding()) + modifier = HzPadding.statusBarsPadding() ) { - SnackImage( - imageUrl = imageUrl, - contentDescription = null, - modifier = Modifier.fillMaxSize() - ) + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalStateException("No sharedTransitionScope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalStateException("No animatedVisibilityScope found") + + with(sharedTransitionScope) { + SnackImage( + image = imageRes, + contentDescription = null, + modifier = Modifier + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snackId, + origin = origin, + type = SnackSharedElementType.Image + ) + ), + animatedVisibilityScope = animatedVisibilityScope, + exit = fadeOut(), + enter = fadeIn(), + boundsTransform = snackDetailBoundsTransform + ) + .fillMaxSize() + + ) + } } } @@ -336,74 +556,59 @@ private fun CollapsingImageLayout( } } -fun lerp(start: Float, stop: Float, fraction: Float): Float { - return (1 - fraction) * start + fraction * stop -} - -/** - * Linearly interpolate between [start] and [stop] with [fraction] fraction between them. - */ -fun lerp(start: Int, stop: Int, fraction: Float): Int { - return start + ((stop - start) * fraction.toDouble()).roundToInt() -} - -/** - * Linearly interpolate between [start] and [stop] with [fraction] fraction between them. - */ -fun lerp(start: Long, stop: Long, fraction: Float): Long { - return start + ((stop - start) * fraction.toDouble()).roundToLong() -} - +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun CartBottomBar(modifier: Modifier = Modifier) { - val (count, updateCount) = remember { mutableStateOf(1) } - JetsnackSurface(modifier) { - Column { - JetsnackDivider() - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .jetSnackNavigationBarsPadding() - .then(HzPadding) - .heightIn(min = BottomBarHeight) - ) { - QuantitySelector( - count = count, - decreaseItemCount = { if (count > 0) updateCount(count - 1) }, - increaseItemCount = { updateCount(count + 1) } - ) - Spacer(Modifier.width(16.dp)) - JetsnackButton( - onClick = { /* todo */ }, - modifier = Modifier.weight(1f) - ) { - Text( - text = stringResource(MppR.string.add_to_cart), - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - maxLines = 1 + val (count, updateCount) = remember { mutableIntStateOf(1) } + val sharedTransitionScope = + LocalSharedTransitionScope.current ?: throw IllegalStateException("No Shared scope") + val animatedVisibilityScope = + LocalNavAnimatedVisibilityScope.current ?: throw IllegalStateException("No Shared scope") + with(sharedTransitionScope) { + with(animatedVisibilityScope) { + JetsnackSurface( + modifier = modifier + .renderInSharedTransitionScopeOverlay(zIndexInOverlay = 4f) + .animateEnterExit( + enter = slideInVertically( + tween( + 300, + delayMillis = 300 + ) + ) { it } + fadeIn(tween(300, delayMillis = 300)), + exit = slideOutVertically(tween(50)) { it } + + fadeOut(tween(50)) ) + ) { + Column { + JetsnackDivider() + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .navigationBarsPadding() + .then(HzPadding) + .heightIn(min = BottomBarHeight) + ) { + QuantitySelector( + count = count, + decreaseItemCount = { if (count > 0) updateCount(count - 1) }, + increaseItemCount = { updateCount(count + 1) } + ) + Spacer(Modifier.width(16.dp)) + JetsnackButton( + onClick = { /* todo */ }, + modifier = Modifier.weight(1f) + ) { + Text( + text = stringResource(Res.string.add_to_cart), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + maxLines = 1 + ) + } + } } } } } -} - -@Composable -expect fun Modifier.jetSnackNavigationBarsPadding(): Modifier -@Composable -expect fun Modifier.jetSnackStatusBarsPadding(): Modifier -@Composable -expect fun Modifier.jetSnackSystemBarsPadding(): Modifier - -//@Preview -@Composable -private fun SnackDetailPreview() { - JetsnackTheme { - SnackDetail( - snackId = 1L, - upPress = { }, - onSnackClick = { } - ) - } -} +} \ No newline at end of file diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Shape.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Shape.kt index 76d6b842d49..e9887fafe94 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Shape.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Shape.kt @@ -17,7 +17,7 @@ package com.example.jetsnack.ui.theme import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes +import androidx.compose.material3.Shapes import androidx.compose.ui.unit.dp val Shapes = Shapes( diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Theme.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Theme.kt index 33032a76c83..fe6ff88cce2 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Theme.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Theme.kt @@ -17,19 +17,13 @@ package com.example.jetsnack.ui.theme import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.Colors -import androidx.compose.material.MaterialTheme +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color -//import com.google.accompanist.systemuicontroller.rememberSystemUiController private val LightColorPalette = JetsnackColors( brand = Shadow5, @@ -90,17 +84,9 @@ fun JetsnackTheme( ) { val colors = if (darkTheme) DarkColorPalette else LightColorPalette -// TODO: implement setSystemBarsColor for android! -// val sysUiController = rememberSystemUiController() -// SideEffect { -// sysUiController.setSystemBarsColor( -// color = colors.uiBackground.copy(alpha = AlphaNearOpaque) -// ) -// } - ProvideJetsnackColors(colors) { MaterialTheme( - colors = debugColors(darkTheme), + colorScheme = debugColors(darkTheme), typography = Typography, shapes = Shapes, content = content @@ -117,169 +103,44 @@ object JetsnackTheme { /** * Jetsnack custom Color Palette */ -@Stable -class JetsnackColors( - gradient6_1: List, - gradient6_2: List, - gradient3_1: List, - gradient3_2: List, - gradient2_1: List, - gradient2_2: List, - gradient2_3: List, - brand: Color, - brandSecondary: Color, - uiBackground: Color, - uiBorder: Color, - uiFloated: Color, - interactivePrimary: List = gradient2_1, - interactiveSecondary: List = gradient2_2, - interactiveMask: List = gradient6_1, - textPrimary: Color = brand, - textSecondary: Color, - textHelp: Color, - textInteractive: Color, - textLink: Color, - tornado1: List, - iconPrimary: Color = brand, - iconSecondary: Color, - iconInteractive: Color, - iconInteractiveInactive: Color, - error: Color, - notificationBadge: Color = error, - isDark: Boolean -) { - var gradient6_1 by mutableStateOf(gradient6_1) - private set - var gradient6_2 by mutableStateOf(gradient6_2) - private set - var gradient3_1 by mutableStateOf(gradient3_1) - private set - var gradient3_2 by mutableStateOf(gradient3_2) - private set - var gradient2_1 by mutableStateOf(gradient2_1) - private set - var gradient2_2 by mutableStateOf(gradient2_2) - private set - var gradient2_3 by mutableStateOf(gradient2_3) - private set - var brand by mutableStateOf(brand) - private set - var brandSecondary by mutableStateOf(brandSecondary) - private set - var uiBackground by mutableStateOf(uiBackground) - private set - var uiBorder by mutableStateOf(uiBorder) - private set - var uiFloated by mutableStateOf(uiFloated) - private set - var interactivePrimary by mutableStateOf(interactivePrimary) - private set - var interactiveSecondary by mutableStateOf(interactiveSecondary) - private set - var interactiveMask by mutableStateOf(interactiveMask) - private set - var textPrimary by mutableStateOf(textPrimary) - private set - var textSecondary by mutableStateOf(textSecondary) - private set - var textHelp by mutableStateOf(textHelp) - private set - var textInteractive by mutableStateOf(textInteractive) - private set - var tornado1 by mutableStateOf(tornado1) - private set - var textLink by mutableStateOf(textLink) - private set - var iconPrimary by mutableStateOf(iconPrimary) - private set - var iconSecondary by mutableStateOf(iconSecondary) - private set - var iconInteractive by mutableStateOf(iconInteractive) - private set - var iconInteractiveInactive by mutableStateOf(iconInteractiveInactive) - private set - var error by mutableStateOf(error) - private set - var notificationBadge by mutableStateOf(notificationBadge) - private set - var isDark by mutableStateOf(isDark) - private set - - fun update(other: JetsnackColors) { - gradient6_1 = other.gradient6_1 - gradient6_2 = other.gradient6_2 - gradient3_1 = other.gradient3_1 - gradient3_2 = other.gradient3_2 - gradient2_1 = other.gradient2_1 - gradient2_2 = other.gradient2_2 - gradient2_3 = other.gradient2_3 - brand = other.brand - brandSecondary = other.brandSecondary - uiBackground = other.uiBackground - uiBorder = other.uiBorder - uiFloated = other.uiFloated - interactivePrimary = other.interactivePrimary - interactiveSecondary = other.interactiveSecondary - interactiveMask = other.interactiveMask - textPrimary = other.textPrimary - textSecondary = other.textSecondary - textHelp = other.textHelp - textInteractive = other.textInteractive - textLink = other.textLink - tornado1 = other.tornado1 - iconPrimary = other.iconPrimary - iconSecondary = other.iconSecondary - iconInteractive = other.iconInteractive - iconInteractiveInactive = other.iconInteractiveInactive - error = other.error - notificationBadge = other.notificationBadge - isDark = other.isDark - } - - fun copy(): JetsnackColors = JetsnackColors( - gradient6_1 = gradient6_1, - gradient6_2 = gradient6_2, - gradient3_1 = gradient3_1, - gradient3_2 = gradient3_2, - gradient2_1 = gradient2_1, - gradient2_2 = gradient2_2, - gradient2_3 = gradient2_3, - brand = brand, - brandSecondary = brandSecondary, - uiBackground = uiBackground, - uiBorder = uiBorder, - uiFloated = uiFloated, - interactivePrimary = interactivePrimary, - interactiveSecondary = interactiveSecondary, - interactiveMask = interactiveMask, - textPrimary = textPrimary, - textSecondary = textSecondary, - textHelp = textHelp, - textInteractive = textInteractive, - textLink = textLink, - tornado1 = tornado1, - iconPrimary = iconPrimary, - iconSecondary = iconSecondary, - iconInteractive = iconInteractive, - iconInteractiveInactive = iconInteractiveInactive, - error = error, - notificationBadge = notificationBadge, - isDark = isDark, - ) -} +@Immutable +data class JetsnackColors( + val gradient6_1: List, + val gradient6_2: List, + val gradient3_1: List, + val gradient3_2: List, + val gradient2_1: List, + val gradient2_2: List, + val gradient2_3: List, + val brand: Color, + val brandSecondary: Color, + val uiBackground: Color, + val uiBorder: Color, + val uiFloated: Color, + val interactivePrimary: List = gradient2_1, + val interactiveSecondary: List = gradient2_2, + val interactiveMask: List = gradient6_1, + val textPrimary: Color = brand, + val textSecondary: Color, + val textHelp: Color, + val textInteractive: Color, + val textLink: Color, + val tornado1: List, + val iconPrimary: Color = brand, + val iconSecondary: Color, + val iconInteractive: Color, + val iconInteractiveInactive: Color, + val error: Color, + val notificationBadge: Color = error, + val isDark: Boolean +) @Composable fun ProvideJetsnackColors( colors: JetsnackColors, content: @Composable () -> Unit ) { - val colorPalette = remember { - // Explicitly creating a new object here so we don't mutate the initial [colors] - // provided, and overwrite the values set in it. - colors.copy() - } - colorPalette.update(colors) - CompositionLocalProvider(LocalJetsnackColors provides colorPalette, content = content) + CompositionLocalProvider(LocalJetsnackColors provides colors, content = content) } private val LocalJetsnackColors = staticCompositionLocalOf { @@ -288,23 +149,46 @@ private val LocalJetsnackColors = staticCompositionLocalOf { /** * A Material [Colors] implementation which sets all colors to [debugColor] to discourage usage of - * [MaterialTheme.colors] in preference to [JetsnackTheme.colors]. + * [MaterialTheme.colorScheme] in preference to [JetsnackTheme.colors]. */ fun debugColors( darkTheme: Boolean, debugColor: Color = Color.Magenta -) = Colors( +) = ColorScheme( primary = debugColor, - primaryVariant = debugColor, - secondary = debugColor, - secondaryVariant = debugColor, - background = debugColor, - surface = debugColor, - error = debugColor, onPrimary = debugColor, + primaryContainer = debugColor, + onPrimaryContainer = debugColor, + inversePrimary = debugColor, + secondary = debugColor, onSecondary = debugColor, + secondaryContainer = debugColor, + onSecondaryContainer = debugColor, + tertiary = debugColor, + onTertiary = debugColor, + tertiaryContainer = debugColor, + onTertiaryContainer = debugColor, + background = debugColor, onBackground = debugColor, + surface = debugColor, onSurface = debugColor, + surfaceVariant = debugColor, + onSurfaceVariant = debugColor, + surfaceTint = debugColor, + inverseSurface = debugColor, + inverseOnSurface = debugColor, + error = debugColor, onError = debugColor, - isLight = !darkTheme + errorContainer = debugColor, + onErrorContainer = debugColor, + outline = debugColor, + outlineVariant = debugColor, + scrim = debugColor, + surfaceBright = debugColor, + surfaceDim = debugColor, + surfaceContainer = debugColor, + surfaceContainerHigh = debugColor, + surfaceContainerHighest = debugColor, + surfaceContainerLow = debugColor, + surfaceContainerLowest = debugColor, ) diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Type.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Type.kt index 195efcff4fb..69ab53cab7a 100644 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Type.kt +++ b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/theme/Type.kt @@ -16,7 +16,7 @@ package com.example.jetsnack.ui.theme -import androidx.compose.material.Typography +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -25,94 +25,92 @@ import androidx.compose.ui.unit.sp var Montserrat: FontFamily? = null // init in platform code var Karla: FontFamily? = null // init in platform code -val Typography by lazy { - Typography( - h1 = TextStyle( - fontFamily = Montserrat, - fontSize = 96.sp, - fontWeight = FontWeight.Light, - lineHeight = 117.sp, - letterSpacing = (-1.5).sp, - ), - h2 = TextStyle( - fontFamily = Montserrat, - fontSize = 60.sp, - fontWeight = FontWeight.Light, - lineHeight = 73.sp, - letterSpacing = (-0.5).sp - ), - h3 = TextStyle( - fontFamily = Montserrat, - fontSize = 48.sp, - fontWeight = FontWeight.Normal, - lineHeight = 59.sp - ), - h4 = TextStyle( - fontFamily = Montserrat, - fontSize = 30.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 37.sp - ), - h5 = TextStyle( - fontFamily = Montserrat, - fontSize = 24.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 29.sp - ), - h6 = TextStyle( - fontFamily = Montserrat, - fontSize = 20.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 24.sp - ), - subtitle1 = TextStyle( - fontFamily = Montserrat, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 24.sp, - letterSpacing = 0.15.sp - ), - subtitle2 = TextStyle( - fontFamily = Karla, - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - lineHeight = 24.sp, - letterSpacing = 0.1.sp - ), - body1 = TextStyle( - fontFamily = Karla, - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - lineHeight = 28.sp, - letterSpacing = 0.15.sp - ), - body2 = TextStyle( - fontFamily = Montserrat, - fontSize = 14.sp, - fontWeight = FontWeight.Medium, - lineHeight = 20.sp, - letterSpacing = 0.25.sp - ), - button = TextStyle( - fontFamily = Montserrat, - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 16.sp, - letterSpacing = 1.25.sp - ), - caption = TextStyle( - fontFamily = Karla, - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - lineHeight = 16.sp, - letterSpacing = 0.4.sp - ), - overline = TextStyle( - fontFamily = Montserrat, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 16.sp, - letterSpacing = 1.sp - ) +val Typography = Typography( + displayLarge = TextStyle( + fontFamily = Montserrat, + fontSize = 96.sp, + fontWeight = FontWeight.Light, + lineHeight = 117.sp, + letterSpacing = (-1.5).sp + ), + displayMedium = TextStyle( + fontFamily = Montserrat, + fontSize = 60.sp, + fontWeight = FontWeight.Light, + lineHeight = 73.sp, + letterSpacing = (-0.5).sp + ), + displaySmall = TextStyle( + fontFamily = Montserrat, + fontSize = 48.sp, + fontWeight = FontWeight.Normal, + lineHeight = 59.sp + ), + headlineMedium = TextStyle( + fontFamily = Montserrat, + fontSize = 30.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 37.sp + ), + headlineSmall = TextStyle( + fontFamily = Montserrat, + fontSize = 24.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 29.sp + ), + titleLarge = TextStyle( + fontFamily = Montserrat, + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 24.sp + ), + titleMedium = TextStyle( + fontFamily = Montserrat, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 24.sp, + letterSpacing = 0.15.sp + ), + titleSmall = TextStyle( + fontFamily = Karla, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + lineHeight = 24.sp, + letterSpacing = 0.1.sp + ), + bodyLarge = TextStyle( + fontFamily = Karla, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + lineHeight = 28.sp, + letterSpacing = 0.15.sp + ), + bodyMedium = TextStyle( + fontFamily = Montserrat, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + lineHeight = 20.sp, + letterSpacing = 0.25.sp + ), + labelLarge = TextStyle( + fontFamily = Montserrat, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp, + letterSpacing = 1.25.sp + ), + bodySmall = TextStyle( + fontFamily = Karla, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + lineHeight = 16.sp, + letterSpacing = 0.4.sp + ), + labelSmall = TextStyle( + fontFamily = Montserrat, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp, + letterSpacing = 1.sp ) -} +) diff --git a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/utils/Rtl.kt b/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/utils/Rtl.kt deleted file mode 100644 index 0bff71cd0a6..00000000000 --- a/examples/jetsnack/common/src/commonMain/kotlin/com/example/jetsnack/ui/utils/Rtl.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetsnack.ui.utils - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material.icons.outlined.ArrowForward -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.LayoutDirection - -/** - * Returns the correct icon based on the current layout direction. - */ -@Composable -fun mirroringIcon(ltrIcon: ImageVector, rtlIcon: ImageVector): ImageVector = - if (LocalLayoutDirection.current == LayoutDirection.Ltr) ltrIcon else rtlIcon - -/** - * Returns the correct back navigation icon based on the current layout direction. - */ -@Composable -fun mirroringBackIcon() = mirroringIcon( - ltrIcon = Icons.Outlined.ArrowBack, rtlIcon = Icons.Outlined.ArrowForward -) diff --git a/examples/jetsnack/common/src/desktopMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt b/examples/jetsnack/common/src/desktopMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt deleted file mode 100644 index cb465b829a8..00000000000 --- a/examples/jetsnack/common/src/desktopMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.example.jetsnack.ui.components - -import androidx.compose.animation.* -import androidx.compose.animation.core.TweenSpec -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.toComposeImageBitmap -import androidx.compose.ui.layout.ContentScale -import com.example.common.generated.resources.Res -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.jetbrains.compose.resources.ExperimentalResourceApi -import java.net.URL -import javax.imageio.ImageIO - - -private val imagesCache = mutableMapOf() - -@OptIn(ExperimentalAnimationApi::class, ExperimentalResourceApi::class) -@Composable -actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifier: Modifier) { - var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) } - - - AnimatedContent(img, transitionSpec = { - fadeIn(TweenSpec()) with fadeOut(TweenSpec()) - }) { - if (img != null) { - Image(img!!, contentDescription = contentDescription, modifier = modifier, contentScale = ContentScale.Crop) - } else { - Box(modifier = modifier) - } - } - - LaunchedEffect(imageUrl) { - if (imagesCache.contains(imageUrl)) { - img = imagesCache[imageUrl] - } else { - withContext(Dispatchers.IO) { - img = try { - org.jetbrains.skia.Image.makeFromEncoded(Res.readBytes(imageUrl)).toComposeImageBitmap().also { - imagesCache[imageUrl] = it - img = it - } - } catch (e: Throwable) { - e.printStackTrace() - null - } - } - } - } -} diff --git a/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.ios.kt b/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.ios.kt deleted file mode 100644 index fe33db7749d..00000000000 --- a/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.ios.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.example.jetsnack.ui.components - -import androidx.compose.animation.* -import androidx.compose.animation.core.TweenSpec -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.toComposeImageBitmap -import androidx.compose.ui.layout.ContentScale -import com.example.common.generated.resources.Res -import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.addressOf -import kotlinx.cinterop.usePinned -import kotlinx.coroutines.* -import org.jetbrains.compose.resources.ExperimentalResourceApi -import org.jetbrains.skia.Image -import platform.Foundation.* -import platform.posix.memcpy -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -private val imagesCache = mutableMapOf() - -@OptIn(ExperimentalAnimationApi::class, ExperimentalResourceApi::class) -@Composable -actual fun SnackAsyncImage(imageUrl: String, contentDescription: String?, modifier: Modifier) { - var img: ImageBitmap? by remember(imageUrl) { mutableStateOf(null) } - - - AnimatedContent(img, transitionSpec = { - fadeIn(TweenSpec()) with fadeOut(TweenSpec()) - }) { - if (img != null) { - Image(img!!, contentDescription = contentDescription, modifier = modifier, contentScale = ContentScale.Crop) - } else { - Box(modifier = modifier) - } - } - - LaunchedEffect(imageUrl) { - if (imagesCache.contains(imageUrl)) { - img = imagesCache[imageUrl] - } else { - withContext(Dispatchers.IO) { - img = try { - Image.makeFromEncoded(Res.readBytes(imageUrl)).toComposeImageBitmap().also { - imagesCache[imageUrl] = it - img = it - } - } catch (e: Throwable) { - e.printStackTrace() - null - } - } - } - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/main.ios.kt b/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/main.ios.kt index 703480ffa1a..a22d5a0ba66 100644 --- a/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/main.ios.kt +++ b/examples/jetsnack/common/src/iosMain/kotlin/com/example/jetsnack/ui/main.ios.kt @@ -1,10 +1,9 @@ package com.example.jetsnack.ui import androidx.compose.ui.window.ComposeUIViewController -import com.example.jetsnack.JetSnackAppEntryPoint import platform.UIKit.UIViewController fun MainViewController(): UIViewController = ComposeUIViewController { - JetSnackAppEntryPoint() + JetsnackApp() } \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/DesktopApp.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/DesktopApp.kt deleted file mode 100644 index ea6e5518e77..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/DesktopApp.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.jetsnack - -//import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.runtime.Composable -import com.example.jetsnack.ui.JetsnackApp - -//@Preview -@Composable -fun AppPreview() { - JetsnackApp() -// App() -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/JetsnakAppEntryPoint.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/JetsnakAppEntryPoint.kt deleted file mode 100644 index 46521120c33..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/JetsnakAppEntryPoint.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.jetsnack - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.example.jetsnack.ui.JetsnackApp - -@Composable -fun JetSnackAppEntryPoint() { - CompositionLocalProvider( - strsLocal provides buildStingsResources(), - pluralsLocal provides buildPluralResources() - ) { - JetsnackApp() - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/drawableResources.desktop.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/drawableResources.desktop.kt deleted file mode 100644 index 43247412f2f..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/drawableResources.desktop.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.jetsnack - -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.VectorPainter -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import com.example.jetsnack.MppR -import com.example.jetsnack.ui.myiconpack.EmptyStateSearch -import org.jetbrains.skiko.currentNanoTime - -@Composable -actual fun painterResource(id: Int): Painter { - return when(id) { - MppR.drawable.empty_state_search -> rememberVectorPainter(EmptyStateSearch) - else -> TODO() - } -} - -private var lastId = currentNanoTime().toInt() - - -private val _empty_state_search = lastId++ -actual val MppR.drawable.empty_state_search: Int get() = _empty_state_search \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/initStringResource.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/initStringResource.kt deleted file mode 100644 index 2600f190a67..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/initStringResource.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.example.jetsnack - -fun buildStingsResources(): Map { - val strs = mutableMapOf() - val rs = MppR.string - - strs[rs.label_filters] = "Filters" - strs[rs.quantity] = "Qty" - strs[rs.label_decrease] = "Decrease" - strs[rs.label_increase] = "Increase" - - strs[rs.label_back] = "Back" - strs[rs.detail_header] = "Details" - strs[rs.detail_placeholder] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut tempus, sem vitae convallis imperdiet, lectus nunc pharetra diam, ac rhoncus quam eros eu risus. Nulla pulvinar condimentum erat, pulvinar tempus turpis blandit ut. Etiam sed ipsum sed lacus eleifend hendrerit eu quis quam. Etiam ligula eros, finibus vestibulum tortor ac, ultrices accumsan dolor. Vivamus vel nisl a libero lobortis posuere. Aenean facilisis nibh vel ultrices bibendum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse ac est vitae lacus commodo efficitur at ut massa. Etiam vestibulum sit amet sapien sed varius. Aliquam non ipsum imperdiet, pulvinar enim nec, mollis risus. Fusce id tincidunt nisl." - strs[rs.see_more] = "SEE MORE" - strs[rs.see_less] = "SEE LESS" - strs[rs.ingredients] = "Ingredients" - strs[rs.ingredients_list] = "Vanilla, Almond Flour, Eggs, Butter, Cream, Sugar" - strs[rs.add_to_cart] = "ADD TO CART" - strs[rs.label_select_delivery] = "Select delivery address" - - strs[rs.max_calories] = "Max Calories" - strs[rs.per_serving] = "per serving" - strs[rs.sort] = "Sort" - strs[rs.lifestyle] = "Lifestyle" - strs[rs.category] = "Category" - strs[rs.price] = "Price" - strs[rs.reset] = "Reset" - strs[rs.close] = "Close" - - - strs[rs.work_in_progress] = "This is currently work in progress" - strs[rs.grab_beverage] = "Grab a beverage and check back later!" - - strs[rs.home_feed] = "Home" - strs[rs.home_search] = "Search" - strs[rs.home_cart] = "My Cart" - strs[rs.home_profile] = "Profile" - - - strs[rs.search_no_matches] = "No matches for ā€œ%1sā€" - strs[rs.search_no_matches_retry] = "Try broadening your search" - strs[rs.label_add] = "Add to cart" - strs[rs.search_count] = "%1d items" - strs[rs.label_search] = "Perform search" - strs[rs.search_jetsnack] = "Search Jetsnack" - strs[rs.cart_increase_error] = "There was an error and the quantity couldn\\'t be increased. Please try again." - strs[rs.cart_increase_error] = "There was an error and the quantity couldn\\'t be decreased. Please try again." - - // Cart - strs[rs.cart_order_header] = "Order (%1s)" - strs[rs.remove_item] = "Remove Item" - strs[rs.cart_summary_header] = "Summary" - strs[rs.cart_subtotal_label] = "Subtotal" - strs[rs.cart_shipping_label] = "Shipping & Handling" - strs[rs.cart_total_label] = "Total" - strs[rs.cart_checkout] = "Checkout" - strs[rs.label_remove] = "Remove item" - - return strs -} - -class PluralResource(val items: Map) { - - // TODO: this is very dumb implementation, which works only for `one` or `other` - fun forQuantity(qty: Int): String { - return when (qty) { - 1 -> items["one"] ?: "?????" - else -> items["other"] ?: "?????" - } - } -} - -fun buildPluralResources(): Map { - val plurals = mutableMapOf() - val ps = MppR.plurals - - plurals[ps.cart_order_count] = PluralResource(buildMap { - this["one"] = "%1d item" - this["other"] = "%1d items" - }) - - return plurals -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/stringResource.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/stringResource.kt deleted file mode 100644 index fe94abce163..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/stringResource.kt +++ /dev/null @@ -1,176 +0,0 @@ -@file:Suppress("PrivatePropertyName") - -package com.example.jetsnack - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocal -import androidx.compose.runtime.compositionLocalOf -import org.jetbrains.skiko.currentNanoTime - - -val strsLocal = compositionLocalOf { emptyMap() } // intId to String -val pluralsLocal = compositionLocalOf { emptyMap() } - -@Composable -actual fun stringResource(id: Int): String { - return strsLocal.current[id] ?: "TODO" -} - -@Composable -actual fun stringResource(id: Int, part: String): String { - return strsLocal.current[id]?.replace("%1s", part) ?: "TODO" -} - -@Composable -actual fun stringResource(id: Int, count: Int): String { - return strsLocal.current[id]?.replace("%1d", count.toString()) ?: "TODO" -} - -private var lastId = currentNanoTime().toInt() - -// Filters -private var _label_filters = lastId++ -actual val MppR.string.label_filters: Int get() = _label_filters - -// Qty -private var _quantity = lastId++ -actual val MppR.string.quantity: Int get() = _quantity - -private val _label_decrease = lastId++ -actual val MppR.string.label_decrease: Int get() = _label_decrease - -private val _label_increase = lastId++ -actual val MppR.string.label_increase: Int get() = _label_increase - - -// Snack detail -private val _label_back = lastId++ -actual val MppR.string.label_back: Int get() = _label_back - -private val _detail_header = lastId++ -actual val MppR.string.detail_header: Int get() = _detail_header - -private val _detail_placeholder = lastId++ -actual val MppR.string.detail_placeholder: Int get() = _detail_placeholder - -private val _see_more = lastId++ -actual val MppR.string.see_more: Int get() = _see_more - -private val _see_less = lastId++ -actual val MppR.string.see_less: Int get() = _see_less - -private val _ingredients = lastId++ -actual val MppR.string.ingredients: Int get() = _ingredients - -private val _ingredients_list = lastId++ -actual val MppR.string.ingredients_list: Int get() = _ingredients_list - -private val _add_to_cart = lastId++ -actual val MppR.string.add_to_cart: Int get() = _add_to_cart - -// Home -private val _label_select_delivery = lastId++ -actual val MppR.string.label_select_delivery: Int get() = _label_select_delivery - - -// Filter -private val _max_calories = lastId++ -actual val MppR.string.max_calories: Int get() = _max_calories - -private val _per_serving = lastId++ -actual val MppR.string.per_serving: Int get() = _per_serving - -private val _sort = lastId++ -actual val MppR.string.sort: Int get() = _sort - -private val _lifestyle = lastId++ -actual val MppR.string.lifestyle: Int get() = _lifestyle - -private val _category = lastId++ -actual val MppR.string.category: Int get() = _category - -private val _price = lastId++ -actual val MppR.string.price: Int get() = _price - -private val _reset = lastId++ -actual val MppR.string.reset: Int get() = _reset - -private val _close = lastId++ -actual val MppR.string.close: Int get() = _close - -// Profile - -private val _work_in_progress = lastId++ -actual val MppR.string.work_in_progress: Int get() = _work_in_progress - -private val _grab_beverage = lastId++ -actual val MppR.string.grab_beverage: Int get() = _grab_beverage - -// Home -private val _home_feed = lastId++ -actual val MppR.string.home_feed: Int get() = _home_feed - -private val _home_search = lastId++ -actual val MppR.string.home_search: Int get() = _home_search - -private val _home_cart = lastId++ -actual val MppR.string.home_cart: Int get() = _home_cart - -private val _home_profile = lastId++ -actual val MppR.string.home_profile: Int get() = _home_profile - - -// Search -private val _search_no_matches = lastId++ -actual val MppR.string.search_no_matches: Int get() = _search_no_matches - -private val _search_no_matches_retry = lastId++ -actual val MppR.string.search_no_matches_retry: Int get() = _search_no_matches_retry - -private val _label_add = lastId++ -actual val MppR.string.label_add: Int get() = _label_add - -private val _search_count = lastId++ -actual val MppR.string.search_count: Int get() = _search_count - -private val _label_search = lastId++ -actual val MppR.string.label_search: Int get() = _label_search - -private val _search_jetsnack = lastId++ -actual val MppR.string.search_jetsnack: Int get() = _search_jetsnack - -private val _cart_increase_error = lastId++ -actual val MppR.string.cart_increase_error: Int get() = _cart_increase_error - -private val _cart_decrease_error = lastId++ -actual val MppR.string.cart_decrease_error: Int get() = _cart_decrease_error - - -// Cart - -private val _cart_order_count = lastId++ -actual val MppR.plurals.cart_order_count: Int get() = _cart_order_count - -private val _cart_order_header = lastId++ -actual val MppR.string.cart_order_header: Int get() = _cart_order_header - -private val _remove_item = lastId++ -actual val MppR.string.remove_item: Int get() = _remove_item - -private val _cart_summary_header = lastId++ -actual val MppR.string.cart_summary_header: Int get() = _cart_summary_header - -private val _cart_subtotal_label = lastId++ -actual val MppR.string.cart_subtotal_label: Int get() = _cart_subtotal_label - -private val _cart_shipping_label = lastId++ -actual val MppR.string.cart_shipping_label: Int get() = _cart_shipping_label - -private val _cart_total_label = lastId++ -actual val MppR.string.cart_total_label: Int get() = _cart_total_label - -private val _cart_checkout = lastId++ -actual val MppR.string.cart_checkout: Int get() = _cart_checkout - -private val _label_remove = lastId++ -actual val MppR.string.label_remove: Int get() = _label_remove \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/JetsnackScaffoldContent.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/JetsnackScaffoldContent.kt deleted file mode 100644 index c2d552a70a2..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/JetsnackScaffoldContent.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.example.jetsnack.ui - -import androidx.compose.animation.* -import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.Snapshot -import androidx.compose.ui.Modifier -import com.example.jetsnack.ui.home.CartTodo -import com.example.jetsnack.ui.home.Feed -import com.example.jetsnack.ui.home.HomeSections -import com.example.jetsnack.ui.home.Profile -import com.example.jetsnack.ui.home.cart.Cart -import com.example.jetsnack.ui.home.search.Search -import com.example.jetsnack.ui.snackdetail.SnackDetail - -@OptIn(ExperimentalAnimationApi::class) -@Composable -actual fun JetsnackScaffoldContent( - innerPaddingModifier: PaddingValues, - appState: MppJetsnackAppState -) { - - when (appState.currentRoute) { - HomeSections.FEED.route -> { - Feed( - onSnackClick = appState::navigateToSnackDetail, - modifier = Modifier.padding(innerPaddingModifier) - ) - } - - HomeSections.SEARCH.route -> { - Search( - onSnackClick = appState::navigateToSnackDetail, - modifier = Modifier.padding(innerPaddingModifier) - ) - } - - HomeSections.CART.route -> { - Cart( - onSnackClick = appState::navigateToSnackDetail, - modifier = Modifier.padding(innerPaddingModifier) - ) - } - - HomeSections.PROFILE.route -> { - Profile(modifier = Modifier.padding(innerPaddingModifier)) - } - - else -> { - val snackId = appState.currentRoute?.takeIf { - it.startsWith(MainDestinations.SNACK_DETAIL_ROUTE + "/") - }?.let { - it.split("/")[1].toLongOrNull() - } - if (snackId != null) { - SnackDetail(snackId, appState::upPress, appState::navigateToSnackDetail) - } - } - } -} - -class NavigationStack(initial: T) { - private val stack = mutableStateListOf(initial) - fun push(t: T) { - stack.add(t) - } - - fun replaceBy(t: T) { - stack.removeLast() - stack.add(t) - } - - fun back() { - if(stack.size > 1) { - // Always keep one element on the view stack - stack.removeLast() - } - } - - fun lastWithIndex() = stack.withIndex().last() -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/MppJetsnackAppState.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/MppJetsnackAppState.kt deleted file mode 100644 index ea75f3ab793..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/MppJetsnackAppState.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.example.jetsnack.ui - -import androidx.compose.material.ScaffoldState -import androidx.compose.material.rememberScaffoldState -import androidx.compose.runtime.* -import com.example.jetsnack.model.SnackbarManager -import com.example.jetsnack.ui.home.HomeSections -import kotlinx.coroutines.CoroutineScope -import kotlin.native.HiddenFromObjC - -@Stable -@OptIn(kotlin.experimental.ExperimentalObjCRefinement::class) -@HiddenFromObjC // Remove after the bug is fixed: https://github.com/JetBrains/compose-multiplatform/issues/4848 -actual class MppJetsnackAppState( - actual val scaffoldState: ScaffoldState, - actual val snackbarManager: SnackbarManager, - actual val coroutineScope: CoroutineScope, -) { - actual val bottomBarTabs: Array - get() = HomeSections.values() - - private val navigationStack = NavigationStack(HomeSections.FEED.route) - - actual val currentRoute: String? - get() = navigationStack.lastWithIndex().value - - - @Composable - actual fun shouldShowBottomBar(): Boolean { - return currentRoute?.startsWith(MainDestinations.SNACK_DETAIL_ROUTE) != true - } - - actual fun navigateToBottomBarRoute(route: String) { - navigationStack.replaceBy(route) - } - - fun navigateToSnackDetail(snackId: Long) { - navigationStack.push("${MainDestinations.SNACK_DETAIL_ROUTE}/$snackId") - } - - fun upPress() { - navigationStack.back() - } -} - -@Composable -actual fun rememberMppJetsnackAppState(): MppJetsnackAppState { - val scaffoldState = rememberScaffoldState() - val snackbarManager = SnackbarManager - val coroutineScope = rememberCoroutineScope() - - return remember(scaffoldState, snackbarManager, coroutineScope) { - MppJetsnackAppState(scaffoldState, snackbarManager, coroutineScope) - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/CartTodo.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/CartTodo.kt deleted file mode 100644 index 8bd0c44087d..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/CartTodo.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.jetsnack.ui.home - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.example.jetsnack.* - -@Composable -fun CartTodo(modifier: Modifier = Modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .fillMaxSize() - .wrapContentSize() - .padding(24.dp) - ) { - Image( - painterResource(MppR.drawable.empty_state_search), - contentDescription = null - ) - Spacer(Modifier.height(24.dp)) - Text( - text = stringResource(MppR.string.work_in_progress), - style = MaterialTheme.typography.subtitle1, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.height(16.dp)) - Text( - text = stringResource(MppR.string.grab_beverage), - style = MaterialTheme.typography.body2, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/SnackDialog.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/SnackDialog.kt deleted file mode 100644 index 9d9255cfbc4..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/SnackDialog.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.jetsnack.ui.home - -import androidx.compose.runtime.Composable -import androidx.compose.ui.window.Popup - -@Composable -actual fun SnackDialog(onCloseRequest: () -> Unit, content: @Composable () -> Unit) { - Popup(onDismissRequest = onCloseRequest, content = { content() }) -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.nonAndroid.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.nonAndroid.kt deleted file mode 100644 index 83b50732b9a..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/cart/Cart.nonAndroid.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.example.jetsnack.ui.home.cart - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -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 com.example.jetsnack.MppR -import com.example.jetsnack.label_remove -import com.example.jetsnack.model.OrderLine -import com.example.jetsnack.model.SnackRepo -import com.example.jetsnack.model.SnackbarManager -import com.example.jetsnack.pluralsLocal -import com.example.jetsnack.stringResource -import com.example.jetsnack.ui.components.QuantitySelector -import com.example.jetsnack.ui.components.SnackImage -import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.utils.formatPrice - -@Composable -actual fun rememberQuantityString(res: Int, qty: Int, vararg args: Any): String { - val plurals = pluralsLocal.current - - return remember(res, qty, plurals) { - var str = plurals[res]?.forQuantity(qty) ?: "" - args.forEachIndexed { index, any -> - str = str.replace("%${index + 1}d", any.toString()) - } - str - } -} - -@Composable -actual fun ActualCartItem( - orderLine: OrderLine, - removeSnack: (Long) -> Unit, - increaseItemCount: (Long) -> Unit, - decreaseItemCount: (Long) -> Unit, - onSnackClick: (Long) -> Unit, - modifier: Modifier -) { - val snack = orderLine.snack - - Row(modifier = modifier - .fillMaxWidth() - .clickable { onSnackClick(snack.id) } - .background(JetsnackTheme.colors.uiBackground) - .padding(horizontal = 24.dp) - ) { - SnackImage( - imageUrl = snack.imageUrl, - contentDescription = null, - modifier = Modifier.padding(top = 4.dp).size(100.dp) - ) - Column(modifier = Modifier.padding(12.dp).weight(1f)) { - Text( - text = snack.name, - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textSecondary, - ) - Text( - text = snack.tagline, - style = MaterialTheme.typography.body1, - color = JetsnackTheme.colors.textHelp, - ) - Text( - text = formatPrice(snack.price), - style = MaterialTheme.typography.subtitle1, - color = JetsnackTheme.colors.textPrimary, - modifier = Modifier.padding(top = 8.dp) - ) - } - Column(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.End) { - IconButton( - onClick = { removeSnack(snack.id) }, - modifier = Modifier.padding(top = 12.dp) - ) { - Icon( - imageVector = Icons.Filled.Close, - tint = JetsnackTheme.colors.iconSecondary, - contentDescription = stringResource(MppR.string.label_remove) - ) - } - QuantitySelector( - count = orderLine.count, - decreaseItemCount = { decreaseItemCount(snack.id) }, - increaseItemCount = { increaseItemCount(snack.id) }, - modifier = Modifier.padding(top = 12.dp) - ) - } - } - -} - -@Composable -actual fun getCartContentInsets(): WindowInsets { - return WindowInsets(top = 56.dp) -} - -@Composable -actual fun provideCartViewModel(): CartViewModel { - return remember { CartViewModel(SnackbarManager, SnackRepo) } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.nonAndroid.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.nonAndroid.kt deleted file mode 100644 index 8866dba8377..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/cart/CartViewModel.nonAndroid.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.jetsnack.ui.home.cart - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import com.example.jetsnack.model.OrderLine -import kotlinx.coroutines.flow.StateFlow -import kotlin.native.HiddenFromObjC - -@OptIn(kotlin.experimental.ExperimentalObjCRefinement::class) -@HiddenFromObjC // Remove after the bug is fixed: https://github.com/JetBrains/compose-multiplatform/issues/4848 -actual abstract class JetSnackCartViewModel actual constructor() { - - @Composable - actual fun collectOrderLinesAsState(flow: StateFlow>): State> { - return flow.collectAsState() - } -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/snackCollectionListItemWindowInsets.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/snackCollectionListItemWindowInsets.kt deleted file mode 100644 index ea94ef244e5..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/home/snackCollectionListItemWindowInsets.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.jetsnack.ui.home - -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.add -import androidx.compose.foundation.layout.statusBars -import androidx.compose.runtime.Composable -import androidx.compose.ui.unit.dp - -@Composable -actual fun snackCollectionListItemWindowInsets(): WindowInsets { - return WindowInsets.statusBars.add(WindowInsets(top = 56.dp)) -} \ No newline at end of file diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/myiconpack/EmptyStateSearch.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/myiconpack/EmptyStateSearch.kt deleted file mode 100644 index b769061405a..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/myiconpack/EmptyStateSearch.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.example.jetsnack.ui.myiconpack - -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd -import androidx.compose.ui.graphics.PathFillType.Companion.NonZero -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.StrokeCap.Companion.Butt -import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.ImageVector.Builder -import androidx.compose.ui.graphics.vector.path -import androidx.compose.ui.unit.dp - -public val EmptyStateSearch: ImageVector - get() { - if (_emptyStateSearch != null) { - return _emptyStateSearch!! - } - _emptyStateSearch = Builder(name = "EmptyStateSearch", defaultWidth = 341.0.dp, - defaultHeight = 179.0.dp, viewportWidth = 341.0f, viewportHeight = 179.0f).apply { - path(fill = SolidColor(Color(0xFFDDE3E8)), stroke = null, strokeLineWidth = 0.0f, - strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, - pathFillType = NonZero) { - moveTo(302.676f, 111.056f) - lineTo(244.424f, 65.728f) - curveTo(234.123f, 57.654f, 224.238f, 49.061f, 214.807f, 39.98f) - curveTo(198.202f, 24.102f, 175.659f, 11.407f, 149.414f, 4.648f) - curveTo(85.649f, -11.772f, 35.135f, 17.344f, 12.16f, 60.096f) - curveTo(-22.949f, 125.426f, 20.921f, 195.341f, 105.817f, 175.009f) - curveTo(145.621f, 169.5f, 174.324f, 161.356f, 200.455f, 154.855f) - lineTo(295.072f, 135.285f) - lineTo(302.676f, 111.056f) - close() - } - path(fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f, - strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, - pathFillType = NonZero) { - moveTo(288.225f, 120.035f) - arcToRelative(12.46f, 10.541f, 105.0f, true, false, 20.363f, 5.456f) - arcToRelative(12.46f, 10.541f, 105.0f, true, false, -20.363f, -5.456f) - close() - } - path(fill = SolidColor(Color(0xFF3C4043)), stroke = null, strokeLineWidth = 0.0f, - strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, - pathFillType = NonZero) { - moveTo(299.659f, 110.277f) - curveTo(304.701f, 111.618f, 309.064f, 114.797f, 311.893f, 119.193f) - lineTo(313.356f, 121.465f) - lineTo(313.43f, 121.559f) - lineTo(339.097f, 129.093f) - curveTo(339.567f, 129.232f, 339.965f, 129.549f, 340.204f, 129.979f) - curveTo(340.444f, 130.408f, 340.505f, 130.914f, 340.376f, 131.389f) - lineTo(338.384f, 138.718f) - curveTo(338.319f, 138.957f, 338.208f, 139.18f, 338.056f, 139.376f) - curveTo(337.905f, 139.571f, 337.716f, 139.734f, 337.502f, 139.856f) - curveTo(337.287f, 139.979f, 337.051f, 140.057f, 336.806f, 140.087f) - curveTo(336.561f, 140.117f, 336.313f, 140.098f, 336.075f, 140.032f) - lineTo(310.402f, 132.833f) - lineTo(310.401f, 132.834f) - lineTo(307.823f, 133.812f) - curveTo(303.075f, 135.612f, 297.867f, 135.79f, 293.008f, 134.317f) - verticalLineTo(134.317f) - lineTo(299.659f, 110.277f) - close() - } - path(fill = SolidColor(Color(0xFF3C4043)), stroke = null, strokeLineWidth = 0.0f, - strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, - pathFillType = EvenOdd) { - moveTo(161.472f, 52.165f) - lineTo(151.381f, 69.821f) - verticalLineTo(69.849f) - curveTo(160.101f, 74.643f, 167.496f, 81.558f, 172.896f, 89.966f) - curveTo(178.297f, 98.374f, 181.531f, 108.01f, 182.306f, 118.0f) - horizontalLineTo(61.0f) - curveTo(61.765f, 108.002f, 64.996f, 98.356f, 70.397f, 89.939f) - curveTo(75.798f, 81.523f, 83.198f, 74.602f, 91.925f, 69.807f) - lineTo(81.827f, 52.165f) - curveTo(81.551f, 51.678f, 81.478f, 51.101f, 81.624f, 50.56f) - curveTo(81.77f, 50.019f, 82.122f, 49.558f, 82.605f, 49.279f) - curveTo(83.087f, 49.001f, 83.659f, 48.927f, 84.195f, 49.074f) - curveTo(84.731f, 49.221f, 85.188f, 49.577f, 85.464f, 50.064f) - lineTo(95.687f, 67.93f) - curveTo(103.852f, 64.232f, 112.7f, 62.321f, 121.65f, 62.321f) - curveTo(130.599f, 62.321f, 139.448f, 64.232f, 147.613f, 67.93f) - lineTo(157.836f, 50.064f) - curveTo(158.112f, 49.577f, 158.568f, 49.221f, 159.104f, 49.074f) - curveTo(159.64f, 48.927f, 160.213f, 49.001f, 160.695f, 49.279f) - curveTo(161.177f, 49.558f, 161.53f, 50.019f, 161.676f, 50.56f) - curveTo(161.822f, 51.101f, 161.748f, 51.678f, 161.472f, 52.165f) - close() - moveTo(133.338f, 84.859f) - curveTo(133.338f, 79.463f, 128.696f, 75.709f, 121.95f, 75.709f) - curveTo(116.815f, 75.709f, 113.167f, 77.774f, 111.438f, 81.052f) - curveTo(110.345f, 83.124f, 111.889f, 85.617f, 114.226f, 85.617f) - curveTo(114.833f, 85.623f, 115.428f, 85.455f, 115.943f, 85.133f) - curveTo(116.457f, 84.81f, 116.869f, 84.346f, 117.129f, 83.797f) - curveTo(117.868f, 82.172f, 119.481f, 81.177f, 121.518f, 81.177f) - curveTo(124.199f, 81.177f, 126.358f, 82.82f, 126.358f, 85.058f) - curveTo(126.358f, 87.296f, 125.08f, 88.451f, 122.04f, 90.273f) - curveTo(118.783f, 92.186f, 117.488f, 94.496f, 117.794f, 98.214f) - lineTo(117.8f, 98.39f) - curveTo(117.813f, 98.76f, 117.968f, 99.109f, 118.233f, 99.366f) - curveTo(118.498f, 99.623f, 118.852f, 99.766f, 119.22f, 99.766f) - horizontalLineTo(122.669f) - curveTo(122.856f, 99.766f, 123.041f, 99.729f, 123.213f, 99.658f) - curveTo(123.386f, 99.586f, 123.543f, 99.481f, 123.675f, 99.349f) - curveTo(123.806f, 99.216f, 123.911f, 99.059f, 123.983f, 98.886f) - curveTo(124.054f, 98.713f, 124.091f, 98.528f, 124.091f, 98.341f) - curveTo(124.091f, 96.031f, 125.152f, 94.695f, 128.283f, 92.872f) - curveTo(131.611f, 90.905f, 133.338f, 88.433f, 133.338f, 84.859f) - close() - moveTo(121.068f, 102.925f) - curveTo(118.945f, 102.925f, 117.218f, 104.567f, 117.218f, 106.642f) - curveTo(117.218f, 108.736f, 118.927f, 110.36f, 121.068f, 110.36f) - curveTo(123.209f, 110.36f, 124.936f, 108.736f, 124.936f, 106.642f) - curveTo(124.936f, 104.549f, 123.209f, 102.925f, 121.068f, 102.925f) - close() - } - } - .build() - return _emptyStateSearch!! - } - -private var _emptyStateSearch: ImageVector? = null diff --git a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/snackdetail/jetSnackNavigationBarsPadding.kt b/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/snackdetail/jetSnackNavigationBarsPadding.kt deleted file mode 100644 index 62e5979e2de..00000000000 --- a/examples/jetsnack/common/src/nonAndroidMain/kotlin/com/example/jetsnack/ui/snackdetail/jetSnackNavigationBarsPadding.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.jetsnack.ui.snackdetail - -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -actual fun Modifier.jetSnackNavigationBarsPadding(): Modifier = - this.windowInsetsPadding(WindowInsets.navigationBars) - -@Composable -actual fun Modifier.jetSnackStatusBarsPadding(): Modifier = - this.windowInsetsPadding(WindowInsets.statusBars) - -@Composable -actual fun Modifier.jetSnackSystemBarsPadding(): Modifier = - this.windowInsetsPadding(WindowInsets.systemBars) \ No newline at end of file diff --git a/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/ImageLoader.kt b/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/ImageLoader.kt index 58d5112d688..48f5eac9f9c 100644 --- a/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/ImageLoader.kt +++ b/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/ImageLoader.kt @@ -1,9 +1,12 @@ package com.example.jetsnack.ui.components +import kotlinx.browser.window +import kotlinx.coroutines.await import org.jetbrains.skia.ExternalSymbolName import org.jetbrains.skia.impl.NativePointer import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.Int8Array +import org.w3c.fetch.Response import org.w3c.xhr.XMLHttpRequest import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -14,21 +17,7 @@ import kotlin.wasm.unsafe.withScopedMemoryAllocator private class MissingResourceException(url: String): Exception("GET $url failed") suspend fun loadImage(url: String): ArrayBuffer { - return suspendCoroutine { continuation -> - val req = XMLHttpRequest() - req.open("GET", url, true) - req.responseType = "arraybuffer".toJsString().unsafeCast() - - req.onload = { _ -> - val arrayBuffer = req.response - if (arrayBuffer is ArrayBuffer) { - continuation.resume(arrayBuffer) - } else { - continuation.resumeWithException(MissingResourceException(url)) - } - } - req.send("") - } + return window.fetch(url).await().arrayBuffer().await() } fun ArrayBuffer.toByteArray(): ByteArray { diff --git a/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt b/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt deleted file mode 100644 index 4204802ced2..00000000000 --- a/examples/jetsnack/common/src/wasmJsMain/kotlin/com/example/jetsnack/ui/components/SnackAsyncImage.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.example.jetsnack.ui.components - -import androidx.compose.ui.Modifier -import androidx.compose.runtime.* -import androidx.compose.foundation.Image -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.toComposeImageBitmap -import androidx.compose.ui.layout.ContentScale -import com.example.common.generated.resources.Res -import kotlinx.coroutines.* -import com.example.jetsnack.model.snacks -import org.jetbrains.compose.resources.ExperimentalResourceApi - -val imagesCache = mutableMapOf() - -@OptIn(ExperimentalResourceApi::class) -@Composable -actual fun SnackAsyncImage( - imageUrl: String, - contentDescription: String?, - modifier: Modifier -) { - - var bitmap: ImageBitmap? by remember { mutableStateOf(null) } - - if (bitmap != null) { - Image(bitmap!!, contentDescription = contentDescription, modifier = modifier, contentScale = ContentScale.Crop) - } - - LaunchedEffect(imageUrl) { - if (imagesCache.contains(imageUrl)) { - bitmap = imagesCache[imageUrl]!! - } else { - imagesCache[imageUrl] = org.jetbrains.skia.Image.makeFromEncoded( - Res.readBytes(imageUrl) - ).toComposeImageBitmap() - bitmap = imagesCache[imageUrl] - } - } -} -@OptIn(ExperimentalResourceApi::class) -suspend fun CoroutineScope.prepareImagesCache() { - val jobs = mutableListOf() - // We have not many images, so we can prepare and cache them upfront - snacks.forEach { - val j = launch { - imagesCache[it.imageUrl] = org.jetbrains.skia.Image.makeFromEncoded( - Res.readBytes(it.imageUrl) - ).toComposeImageBitmap() - } - jobs.add(j) - } - joinAll(*jobs.toTypedArray()) -} \ No newline at end of file diff --git a/examples/jetsnack/desktop/build.gradle.kts b/examples/jetsnack/desktop/build.gradle.kts index 4b23069ade5..da0b0610dec 100644 --- a/examples/jetsnack/desktop/build.gradle.kts +++ b/examples/jetsnack/desktop/build.gradle.kts @@ -1,6 +1,4 @@ -import org.jetbrains.compose.compose import org.jetbrains.compose.desktop.application.dsl.TargetFormat -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("multiplatform") diff --git a/examples/jetsnack/desktop/src/jvmMain/kotlin/Main.kt b/examples/jetsnack/desktop/src/jvmMain/kotlin/Main.kt index 64d88fca98f..bde1259e0c3 100644 --- a/examples/jetsnack/desktop/src/jvmMain/kotlin/Main.kt +++ b/examples/jetsnack/desktop/src/jvmMain/kotlin/Main.kt @@ -2,14 +2,13 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState -import com.example.jetsnack.JetSnackAppEntryPoint - +import com.example.jetsnack.ui.JetsnackApp fun main() = application { Window( onCloseRequest = ::exitApplication, state = rememberWindowState(width = 1200.dp, height = 960.dp) ) { - JetSnackAppEntryPoint() + JetsnackApp() } } diff --git a/examples/jetsnack/gradle.properties b/examples/jetsnack/gradle.properties index 5527a505a8b..dc0260c2901 100644 --- a/examples/jetsnack/gradle.properties +++ b/examples/jetsnack/gradle.properties @@ -2,5 +2,5 @@ org.gradle.jvmargs=-Xmx8g kotlin.code.style=official android.useAndroidX=true agp.version=8.0.2 -kotlin.version=2.0.0 -compose.version=1.6.10 +kotlin.version=2.0.20 +compose.version=1.7.0 diff --git a/examples/jetsnack/gradle/libs.versions.toml b/examples/jetsnack/gradle/libs.versions.toml index 7c1822e001a..8e5af9c1025 100644 --- a/examples/jetsnack/gradle/libs.versions.toml +++ b/examples/jetsnack/gradle/libs.versions.toml @@ -12,9 +12,12 @@ androidx-benchmark-junit4 = "1.1.0-beta04" androidx-compose-bom = "2023.03.00" androidx-constraintlayout = "1.0.1" androidx-corektx = "1.9.0" -androidx-lifecycle-compose = "2.6.1" -androidx-lifecycle-runtime-compose = "2.6.1" -androidx-navigation = "2.5.3" +androidx-lifecycle-compose-jb = "2.8.2+build1807-release-1.7.0-beta02" +androidx-lifecycle-compose = "2.8.2" +androidx-lifecycle-runtime-compose-jb = "2.8.2+build1807-release-1.7.0-beta02" +androidx-lifecycle-runtime-compose = "2.8.2" +androidx-navigation-jb = "2.8.0-alpha10+build1807-release-1.7.0-beta02" +androidx-navigation = "2.8.0" androidx-palette = "1.0.0" androidx-test = "1.5.0" androidx-test-espresso = "3.5.1" @@ -86,11 +89,12 @@ androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" } androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle-compose" } androidx-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.0-alpha04" -androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-runtime-compose" } -androidx-lifecycle-viewModelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle-compose" } +androidx-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-runtime-compose-jb" } +androidx-lifecycle-viewModelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle-compose-jb" } +androidx-lifecycle-viewModel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle-compose-jb" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle-compose" } -androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidx-lifecycle-compose" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } +androidx-lifecycle-viewmodel-savedstate = { module = "org.androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidx-lifecycle-compose-jb" } +androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation-jb" } androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" } androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" } androidx-palette = { module = "androidx.palette:palette", version.ref = "androidx-palette" } @@ -117,6 +121,7 @@ hilt-ext-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hil junit = { module = "junit:junit", version.ref = "junit" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } diff --git a/examples/jetsnack/kotlin-js-store/yarn.lock b/examples/jetsnack/kotlin-js-store/yarn.lock index e962c64c5ed..dfb1940cd34 100644 --- a/examples/jetsnack/kotlin-js-store/yarn.lock +++ b/examples/jetsnack/kotlin-js-store/yarn.lock @@ -12,18 +12,6 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@jridgewell/gen-mapping@^0.3.0": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -82,36 +70,16 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@jsonjoy.com/base64@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" - integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== - -"@jsonjoy.com/json-pack@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz#ab59c642a2e5368e8bcfd815d817143d4f3035d0" - integrity sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg== - dependencies: - "@jsonjoy.com/base64" "^1.1.1" - "@jsonjoy.com/util" "^1.1.2" - hyperdyperid "^1.2.0" - thingies "^1.20.0" - -"@jsonjoy.com/util@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.1.3.tgz#75b1c3cf21b70e665789d1ad3eabeff8b7fd1429" - integrity sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg== +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" @@ -125,14 +93,14 @@ "@types/connect" "*" "@types/node" "*" -"@types/bonjour@^3.5.13": +"@types/bonjour@^3.5.9": version "3.5.13" resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== dependencies: "@types/node" "*" -"@types/connect-history-api-fallback@^1.5.4": +"@types/connect-history-api-fallback@^1.3.5": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== @@ -195,7 +163,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@*", "@types/express@^4.17.21": +"@types/express@*", "@types/express@^4.17.13": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -259,10 +227,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/retry@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" - integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/send@*": version "0.17.4" @@ -272,7 +240,7 @@ "@types/mime" "^1" "@types/node" "*" -"@types/serve-index@^1.9.4": +"@types/serve-index@^1.9.1": version "1.9.4" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== @@ -288,7 +256,7 @@ "@types/mime" "*" "@types/node" "*" -"@types/serve-static@^1.15.5": +"@types/serve-static@^1.13.10": version "1.15.7" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== @@ -297,17 +265,17 @@ "@types/node" "*" "@types/send" "*" -"@types/sockjs@^0.3.36": +"@types/sockjs@^0.3.33": version "0.3.36" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== dependencies: "@types/node" "*" -"@types/ws@^8.5.10": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== +"@types/ws@^8.5.5": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== dependencies: "@types/node" "*" @@ -465,10 +433,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn@^8.7.1: version "8.8.2" @@ -519,10 +487,10 @@ ajv@^8.0.0, ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-html-community@^0.0.8: version "0.0.8" @@ -534,11 +502,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -546,11 +509,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -607,7 +565,7 @@ body-parser@1.20.2, body-parser@^1.19.0: type-is "~1.6.18" unpipe "1.0.0" -bonjour-service@^1.2.1: +bonjour-service@^1.0.11: version "1.2.1" resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== @@ -637,7 +595,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== @@ -657,13 +615,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bundle-name@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" - integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - dependencies: - run-applescript "^7.0.0" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -700,7 +651,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3, chokidar@^3.5.1: +chokidar@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -715,7 +666,7 @@ chokidar@3.5.3, chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" -chokidar@^3.6.0: +chokidar@^3.5.3: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -860,7 +811,7 @@ cors@~2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -886,31 +837,25 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.3.4, debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== - -default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== - dependencies: - bundle-name "^4.1.0" - default-browser-id "^5.0.0" - default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" @@ -918,10 +863,10 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== depd@2.0.0: version "2.0.0" @@ -948,10 +893,10 @@ di@^0.0.1: resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== dns-packet@^5.2.2: version "5.6.1" @@ -970,11 +915,6 @@ dom-serialize@^2.2.1: extend "^3.0.0" void-elements "^2.0.0" -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -990,11 +930,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1021,10 +956,10 @@ engine.io@~6.5.2: engine.io-parser "~5.2.1" ws "~8.11.0" -enhanced-resolve@^5.16.0: - version "5.16.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567" - integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw== +enhanced-resolve@^5.17.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -1059,7 +994,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== @@ -1216,14 +1151,6 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1232,6 +1159,14 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -1247,14 +1182,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - format-util@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" @@ -1279,6 +1206,11 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1325,28 +1257,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^10.3.7: - version "10.3.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.15.tgz#e72bc61bc3038c90605f5dd48543dc67aaf3b50d" - integrity sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.6" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.11.0" - glob@^7.1.3, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1359,6 +1269,17 @@ glob@^7.1.3, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -1386,7 +1307,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -he@1.2.0: +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -1401,7 +1322,7 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -html-entities@^2.4.0: +html-entities@^2.3.2: version "2.5.2" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== @@ -1462,11 +1383,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -hyperdyperid@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" - integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1517,7 +1433,7 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.1.0: +ipaddr.js@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== @@ -1536,10 +1452,10 @@ is-core-module@^2.11.0: dependencies: has "^1.0.3" -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extglob@^2.1.1: version "2.1.1" @@ -1558,18 +1474,6 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-network-error@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" - integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1602,12 +1506,12 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: - is-inside-container "^1.0.0" + is-docker "^2.0.0" isarray@~1.0.0: version "1.0.0" @@ -1629,15 +1533,6 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -jackspeak@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -1647,7 +1542,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -js-yaml@4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -1741,10 +1636,10 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -launch-editor@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== +launch-editor@^2.6.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047" + integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w== dependencies: picocolors "^1.0.0" shell-quote "^1.8.1" @@ -1773,7 +1668,7 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -1792,25 +1687,17 @@ log4js@^6.4.1: rfdc "^1.3.0" streamroller "^3.1.5" -lru-cache@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" - integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memfs@^4.6.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.9.2.tgz#42e7b48207268dad8c9c48ea5d4952c5d3840433" - integrity sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ== +memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: - "@jsonjoy.com/json-pack" "^1.0.3" - "@jsonjoy.com/util" "^1.1.2" - sonic-forest "^1.0.0" - tslib "^2.0.0" + fs-monkey "^1.0.4" merge-descriptors@1.0.1: version "1.0.1" @@ -1867,13 +1754,6 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1881,14 +1761,14 @@ minimatch@^3.0.4, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: +minimatch@^5.0.1, minimatch@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1, minimatch@^9.0.3: +minimatch@^9.0.3: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== @@ -1900,11 +1780,6 @@ minimist@^1.2.3, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: - version "7.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481" - integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA== - mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -1912,31 +1787,31 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mocha@10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" - integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "8.1.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" +mocha@10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.0.tgz#9e5cbed8fa9b37537a25bd1f7fb4f6fc45458b9a" + integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" ms@2.0.0: version "2.0.0" @@ -1948,7 +1823,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2008,7 +1883,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@^2.4.1: +on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -2041,15 +1916,14 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^10.0.3: - version "10.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1" - integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw== +open@^8.0.9: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== dependencies: - default-browser "^5.2.1" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^3.1.0" + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" p-limit@^2.2.0: version "2.3.0" @@ -2079,13 +1953,12 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-retry@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd" - integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA== +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== dependencies: - "@types/retry" "0.12.2" - is-network-error "^1.0.0" + "@types/retry" "0.12.0" retry "^0.13.1" p-try@^2.0.0: @@ -2118,14 +1991,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.0: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -2294,18 +2159,6 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.5: - version "5.0.7" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.7.tgz#27bddf202e7d89cb2e0381656380d1734a854a74" - integrity sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg== - dependencies: - glob "^10.3.7" - -run-applescript@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" - integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -2339,7 +2192,7 @@ schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0, schema-utils@^4.2.0: +schema-utils@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -2354,7 +2207,7 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selfsigned@^2.4.1: +selfsigned@^2.1.1: version "2.4.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== @@ -2381,13 +2234,6 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" @@ -2395,6 +2241,13 @@ serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -2466,11 +2319,6 @@ signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - socket.io-adapter@~2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" @@ -2508,13 +2356,6 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" -sonic-forest@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sonic-forest/-/sonic-forest-1.0.3.tgz#81363af60017daba39b794fce24627dc412563cb" - integrity sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ== - dependencies: - tree-dump "^1.0.0" - source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -2583,7 +2424,7 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2592,15 +2433,6 @@ streamroller@^3.1.5: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2615,37 +2447,23 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2653,6 +2471,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -2684,11 +2509,6 @@ terser@^5.26.0: commander "^2.20.0" source-map-support "~0.5.20" -thingies@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" - integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== - thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -2713,16 +2533,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tree-dump@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.1.tgz#b448758da7495580e6b7830d6b7834fca4c45b96" - integrity sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA== - -tslib@^2.0.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -2731,10 +2541,10 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" - integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +typescript@5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== ua-parser-js@^0.7.30: version "0.7.35" @@ -2825,53 +2635,52 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^7.1.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz#2af00538b6e4eda05f5afdd5d711dbebc05958f7" - integrity sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA== +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" - memfs "^4.6.0" + memfs "^3.4.3" mime-types "^2.1.31" - on-finished "^2.4.1" range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8" - integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA== - dependencies: - "@types/bonjour" "^3.5.13" - "@types/connect-history-api-fallback" "^1.5.4" - "@types/express" "^4.17.21" - "@types/serve-index" "^1.9.4" - "@types/serve-static" "^1.15.5" - "@types/sockjs" "^0.3.36" - "@types/ws" "^8.5.10" +webpack-dev-server@4.15.2: + version "4.15.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" + integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" ansi-html-community "^0.0.8" - bonjour-service "^1.2.1" - chokidar "^3.6.0" + bonjour-service "^1.0.11" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^2.0.0" default-gateway "^6.0.3" express "^4.17.3" graceful-fs "^4.2.6" - html-entities "^2.4.0" + html-entities "^2.3.2" http-proxy-middleware "^2.0.3" - ipaddr.js "^2.1.0" - launch-editor "^2.6.1" - open "^10.0.3" - p-retry "^6.2.0" - rimraf "^5.0.5" - schema-utils "^4.2.0" - selfsigned "^2.4.1" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" serve-index "^1.9.1" sockjs "^0.3.24" spdy "^4.0.2" - webpack-dev-middleware "^7.1.0" - ws "^8.16.0" + webpack-dev-middleware "^5.3.4" + ws "^8.13.0" webpack-merge@^4.1.5: version "4.2.2" @@ -2893,10 +2702,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.91.0: - version "5.91.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" - integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== +webpack@5.93.0: + version "5.93.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5" + integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" @@ -2904,10 +2713,10 @@ webpack@5.91.0: "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.16.0" + enhanced-resolve "^5.17.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -2956,12 +2765,12 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -2970,24 +2779,15 @@ workerpool@6.2.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^8.16.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== +ws@^8.13.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@~8.11.0: version "8.11.0" @@ -2999,17 +2799,12 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== @@ -3019,7 +2814,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0, yargs@^16.1.1: +yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== diff --git a/examples/jetsnack/web/src/wasmJsMain/kotlin/Main.kt b/examples/jetsnack/web/src/wasmJsMain/kotlin/Main.kt index cc02636306d..297b2f5a371 100644 --- a/examples/jetsnack/web/src/wasmJsMain/kotlin/Main.kt +++ b/examples/jetsnack/web/src/wasmJsMain/kotlin/Main.kt @@ -1,6 +1,5 @@ -import com.example.jetsnack.ui.components.prepareImagesCache import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -8,7 +7,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.platform.Font import androidx.compose.ui.window.CanvasBasedWindow -import com.example.jetsnack.JetSnackAppEntryPoint +import com.example.jetsnack.ui.JetsnackApp import com.example.jetsnack.ui.components.loadImage import com.example.jetsnack.ui.components.toByteArray import com.example.jetsnack.ui.theme.Karla @@ -29,7 +28,7 @@ fun main() { if (loading) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } else { - JetSnackAppEntryPoint() + JetsnackApp() } LaunchedEffect(Unit) { @@ -39,10 +38,7 @@ fun main() { val j2 = launch { loadKarlaFont() } - val j3 = launch { - prepareImagesCache() - } - joinAll(j1, j2, j3) + joinAll(j1, j2) loading = false } }