After Width: | Height: | Size: 332 KiB |
After Width: | Height: | Size: 228 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 248 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 201 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 382 KiB |
After Width: | Height: | Size: 653 KiB |
After Width: | Height: | Size: 496 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 380 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 250 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 300 KiB |
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 217 KiB |
After Width: | Height: | Size: 218 KiB |
After Width: | Height: | Size: 135 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 187 KiB |
@ -0,0 +1,65 @@
|
||||
<resources> |
||||
<string name="app_name">Jetsnack</string> |
||||
<string name="label_back">Back</string> |
||||
|
||||
<!-- Home Tabs --> |
||||
<string name="home_feed">Home</string> |
||||
<string name="home_search">Search</string> |
||||
<string name="home_cart">My Cart</string> |
||||
<string name="home_profile">Profile</string> |
||||
|
||||
<!-- Home --> |
||||
<string name="label_filters">Filters</string> |
||||
<string name="label_select_delivery">Select delivery address</string> |
||||
|
||||
<!-- Search --> |
||||
<string name="search_jetsnack">Search Jetsnack</string> |
||||
<string name="search_no_matches">No matches for “%1$s”</string> |
||||
<string name="search_no_matches_retry">Try broadening your search</string> |
||||
<string name="search_count">%1$d items</string> |
||||
<string name="label_add">Add to cart</string> |
||||
<string name="label_search">Perform search</string> |
||||
|
||||
<!-- Snack Detail --> |
||||
<string name="detail_header">Details</string> |
||||
<string name="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.</string> |
||||
<string name="ingredients">Ingredients</string> |
||||
<string name="ingredients_list">Vanilla, Almond Flour, Eggs, Butter, Cream, Sugar</string> |
||||
<string name="quantity">Qty</string> |
||||
<string name="add_to_cart">ADD TO CART</string> |
||||
|
||||
<!-- Cart --> |
||||
<string name="cart_order_header">Order (%1$s)</string> |
||||
<plurals name="cart_order_count"> |
||||
<item quantity="one">%1$d item</item> |
||||
<item quantity="other">%1$d items</item> |
||||
</plurals> |
||||
<string name="cart_summary_header">Summary</string> |
||||
<string name="cart_subtotal_label">Subtotal</string> |
||||
<string name="cart_shipping_label">Shipping & Handling</string> |
||||
<string name="cart_total_label">Total</string> |
||||
<string name="cart_checkout">Checkout</string> |
||||
<string name="cart_increase_error">There was an error and the quantity couldn\'t be increased. Please try again.</string> |
||||
<string name="cart_decrease_error">There was an error and the quantity couldn\'t be decreased. Please try again.</string> |
||||
<string name="label_remove">Remove item</string> |
||||
|
||||
<!-- Quantity Selector --> |
||||
<string name="label_increase">Increase</string> |
||||
<string name="label_decrease">Decrease</string> |
||||
<string name="work_in_progress">This is currently work in progress</string> |
||||
<string name="grab_beverage">Grab a beverage and check back later!</string> |
||||
<string name="see_more">SEE MORE</string> |
||||
<string name="see_less">SEE LESS</string> |
||||
<string name="remove_item">Remove Item</string> |
||||
<string name="reset">Reset</string> |
||||
<string name="sort">Sort</string> |
||||
<string name="price">Price</string> |
||||
<string name="category">Category</string> |
||||
<string name="max_calories">Max Calories</string> |
||||
<string name="lifestyle">LifeStyle</string> |
||||
<string name="per_serving">per serving</string> |
||||
<string name="android_favorites">Android\'s Favorite (default)</string> |
||||
<string name="rating">Rating</string> |
||||
<string name="alphabetical">Alphabetical</string> |
||||
<string name="close">Close</string> |
||||
</resources> |
@ -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 |
@ -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 |
@ -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 |
@ -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<HomeSections> |
||||
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 |
||||
//} |
@ -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 |
||||
} |
@ -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 |
||||
) |
@ -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<String, ImageBitmap>() |
||||
|
||||
@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 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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<String, ImageBitmap>() |
||||
|
||||
@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 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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() |
||||
} |
@ -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() |
||||
} |
||||
} |
@ -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 |
@ -1,84 +0,0 @@
|
||||
package com.example.jetsnack |
||||
|
||||
fun buildStingsResources(): Map<Int, String> { |
||||
val strs = mutableMapOf<Int, String>() |
||||
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<String, String>) { |
||||
|
||||
// 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<Int, PluralResource> { |
||||
val plurals = mutableMapOf<Int, PluralResource>() |
||||
val ps = MppR.plurals |
||||
|
||||
plurals[ps.cart_order_count] = PluralResource(buildMap { |
||||
this["one"] = "%1d item" |
||||
this["other"] = "%1d items" |
||||
}) |
||||
|
||||
return plurals |
||||
} |
@ -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<Int, String>() } // intId to String |
||||
val pluralsLocal = compositionLocalOf { emptyMap<Int, PluralResource>() } |
||||
|
||||
@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 |
@ -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<T>(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() |
||||
} |
@ -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<HomeSections> |
||||
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) |
||||
} |
||||
} |
@ -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() |
||||
) |
||||
} |
||||
} |
@ -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() }) |
||||
} |
@ -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) } |
||||
} |
@ -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<List<OrderLine>>): State<List<OrderLine>> { |
||||
return flow.collectAsState() |
||||
} |
||||
} |
@ -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,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 |
@ -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) |
@ -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<String, ImageBitmap>() |
||||
|
||||
@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<Job>() |
||||
// 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()) |
||||
} |