@ -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 |
|
||||||
|
|
@ -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 |
|
@ -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) |
|
||||||
} |
|
||||||
} |
|
@ -1,123 +0,0 @@ |
|||||||
package com.example.jetsnack.ui |
|
||||||
|
|
||||||
import android.content.res.Resources |
|
||||||
import androidx.compose.material3.ScaffoldState |
|
||||||
import androidx.compose.material3.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<HomeSections> |
|
||||||
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 |
|
||||||
} |
|
@ -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<String, ImageBitmap>() |
|
||||||
|
|
||||||
@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) |
|
||||||
} |
|
@ -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) |
|
||||||
} |
|
@ -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.material3.Icon |
|
||||||
import androidx.compose.material3.IconButton |
|
||||||
import androidx.compose.material3.MaterialTheme |
|
||||||
import androidx.compose.material3.Text |
|
||||||
import androidx.compose.material3.icons.Icons |
|
||||||
import androidx.compose.material3.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)) |
|
||||||
} |
|
@ -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 <T : ViewModel> create(modelClass: Class<T>): T { |
|
||||||
return CartViewModel(snackbarManager, snackRepository) as T |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@OptIn(kotlin.ExperimentalMultiplatform::class) |
|
||||||
actual abstract class JetSnackCartViewModel actual constructor() : ViewModel() { |
|
||||||
@Composable |
|
||||||
actual fun collectOrderLinesAsState(flow: StateFlow<List<OrderLine>>): State<List<OrderLine>> { |
|
||||||
return flow.collectAsStateWithLifecycle() |
|
||||||
} |
|
||||||
} |
|
@ -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)) |
|
||||||
} |
|
@ -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() |
|
Before Width: | Height: | Size: 332 KiB |
Before Width: | Height: | Size: 228 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 199 KiB |
Before Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 201 KiB |
Before Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 382 KiB |
Before Width: | Height: | Size: 653 KiB |
Before Width: | Height: | Size: 496 KiB |
Before Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 250 KiB |
Before Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 137 KiB |
Before Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 300 KiB |
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 217 KiB |
Before Width: | Height: | Size: 218 KiB |
Before Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 187 KiB |
@ -1,10 +1,9 @@ |
|||||||
package com.example.jetsnack.ui |
package com.example.jetsnack.ui |
||||||
|
|
||||||
import androidx.compose.ui.window.ComposeUIViewController |
import androidx.compose.ui.window.ComposeUIViewController |
||||||
import com.example.jetsnack.JetSnackAppEntryPoint |
|
||||||
import platform.UIKit.UIViewController |
import platform.UIKit.UIViewController |
||||||
|
|
||||||
fun MainViewController(): UIViewController = |
fun MainViewController(): UIViewController = |
||||||
ComposeUIViewController { |
ComposeUIViewController { |
||||||
JetSnackAppEntryPoint() |
JetsnackApp() |
||||||
} |
} |