Added Bookshelf page for easier sorting

Renamed App
Few cosmetic tweaks for mobile so it looks nicer
Changed App Icon, Using Ophelia for placeholder. Maybe I'll just put her in a hat later.
This commit is contained in:
Azea 2025-02-25 16:13:52 -05:00
parent d8bbce38d7
commit 309304b51a
18 changed files with 151 additions and 76 deletions

View file

@ -1,42 +0,0 @@
## Recipe App Compose Multiplatform
Inspired by [Roaa Kadam](https://github.com/Roaa94) flutter [app](https://github.com/Roaa94/recipes_ui_app/), I wanted to do the same in Compose Multiplatform. There is a lot to explore in Compose Multiplatform from the aspect of this app like Heor Animation, Collapsable Toolbar, Staggered Animations, Gyroscope detection etc.
> **Note**
> It is still a work in progress
### Live
You can find it live [here](https://seabdulbasit.github.io/recipe-app/)
## Supported Platforms
- Android
- iOS
- Desktop
- Web
- Android TV
![Screenshot 2023-10-08 at 1 23 46PM](https://github.com/SEAbdulbasit/recipe-app/assets/33172684/bf0c9376-fb57-4498-80f6-4a72300cb8e9)
![Screenshot 2024-06-25 at 11 53 05AM](https://github.com/Atif-09/recipe-app/assets/55842938/16e66d0b-48de-4403-bb74-e2788c756cc3)
## Demo
### iOS Demo
https://youtu.be/MZDgPtjTiIs
### Web Demo
https://www.youtube.com/watch?v=MZDgPtjTiIs&ab_channel=AbdulBasit
### Desktop Demo
https://www.youtube.com/watch?v=6mWrp_-MxW8&ab_channel=AbdulBasit
<img width="615" alt="Screenshot 2023-06-22 at 11 49 28 AM" src="https://github.com/SEAbdulbasit/recipe-app/assets/33172684/ac19c301-8263-4d2c-8cfc-58f27d1acdb3">
## Video Demo
You can watch the video demo [here](https://www.youtube.com/watch?v=99i21nB4sI0&ab_channel=AbdulBasit)

View file

@ -15,7 +15,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, true)
setContent { setContent {
val view = LocalView.current val view = LocalView.current

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ophelia_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ophelia" />
</adaptive-icon> </adaptive-icon>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ophelia_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ophelia" />
</adaptive-icon> </adaptive-icon>

View file

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Recipe App</string> <string name="app_name">Baker\'s Menagerie</string>
</resources> </resources>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#7dae7d"
android:pathData="M0,0h108v108h-108z" />
</vector>

View file

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Recipe App</string> <string name="app_name">Baker\'s Menagerie</string>
</resources> </resources>

View file

@ -2,7 +2,7 @@ import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application import androidx.compose.ui.window.application
fun main() = application { fun main() = application {
Window(title = "Recipe App", onCloseRequest = ::exitApplication) { Window(title = "Baker's Menagerie", onCloseRequest = ::exitApplication) {
MainView() MainView()
} }
} }

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="manifest" href="manifest.json"/> <link rel="manifest" href="manifest.json"/>
<title>Recipe App KMP</title> <title>Baker's Menagerie KMP</title>
<script type="application/javascript" src="skiko.js"></script> <script type="application/javascript" src="skiko.js"></script>
<script type="application/javascript" src="webApp.js"></script> <script type="application/javascript" src="webApp.js"></script>
</head> </head>

View file

@ -1,5 +1,5 @@
{ {
"name": "Recipe App", "name": "Baker's Menagerie",
"icons": [ "icons": [
{ {
"src": "images/logo.png", "src": "images/logo.png",

View file

@ -60,6 +60,11 @@
"@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14" "@jridgewell/sourcemap-codec" "^1.4.14"
"@js-joda/core@3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273"
integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==
"@leichtgewicht/ip-codec@^2.0.1": "@leichtgewicht/ip-codec@^2.0.1":
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"

View file

@ -1,10 +1,21 @@
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.Text import androidx.compose.material.Text
@ -14,11 +25,15 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import details.RecipeDetails import details.RecipeDetails
import recipeslist.RecipesListScreen import recipeslist.RecipesListScreen
import sensor.SensorManager import sensor.SensorManager
@ -67,6 +82,7 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
var search by remember { mutableStateOf("") } var search by remember { mutableStateOf("") }
val tags = remember { mutableStateListOf<String>() } val tags = remember { mutableStateListOf<String>() }
val recipeTags by remember { mutableStateOf(mutableListOf<String>()) } val recipeTags by remember { mutableStateOf(mutableListOf<String>()) }
var book by remember { mutableStateOf("") }
for (recipe in getRecipeList()) { for (recipe in getRecipeList()) {
for (tag in recipe.tags) { for (tag in recipe.tags) {
@ -83,7 +99,7 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
} }
} }
val filteredItems = getFilteredRecipeList(tags, search) val filteredItems = getFilteredRecipeList(tags, search, book)
var currentRecipe = getRecipeList().first() var currentRecipe = getRecipeList().first()
if (filteredItems.isNotEmpty()) if (filteredItems.isNotEmpty())
@ -93,13 +109,6 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
Column { Column {
if (searchBar) { if (searchBar) {
Row { Row {
// Button(
// onClick = {
// onThemeToggle.invoke()
// }
// ) {
// Text(text = theme.name)
// }
Button( Button(
onClick = { onClick = {
show = true show = true
@ -119,7 +128,6 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
} }
SharedTransitionLayout { SharedTransitionLayout {
val sharedTransitionScope = this
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = "Home", startDestination = "Home",
@ -127,13 +135,30 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
) { ) {
composable(route = "Home") composable(route = "Home")
{ {
HomeScreen { HomeScreen(
searchBar = true isLarge = isLarge,
navController.navigate(RecipeAppScreen.List.name) onClick = {
searchBar = false
} navController.navigate("BookShelf")
}
)
} }
composable(route = RecipeAppScreen.List.name) { composable(route = "BookShelf")
{
BookShelf(
isLarge = isLarge,
tags = recipeTags,
onClick = { lockedTag ->
book = lockedTag
searchBar = true
navController.navigate(RecipeAppScreen.List.name)
}
)
}
composable(
route = RecipeAppScreen.List.name
) {
RecipesListScreen( RecipesListScreen(
isLarge = isLarge, isLarge = isLarge,
items = filteredItems, items = filteredItems,
@ -158,4 +183,54 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
} }
} }
} }
} }
@Composable
fun BookShelf(
onClick: (String) -> Unit,
isLarge: Boolean,
tags: List<String>
) {
Box(
modifier = Modifier
.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp)
.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(text = "What Do You Want To Cook Today?", textAlign = TextAlign.Center)
Button(onClick = { onClick("") }) {
Text("ANYTHING!")
}
val listState = rememberLazyGridState()
LazyVerticalGrid(
columns = GridCells.Fixed(if(isLarge) 4 else 2),
state = listState
)
{
if(isLarge.not()) {
item {
Spacer(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars))
}
items(tags.size) {item ->
val tag = tags[item]
Button(onClick = {onClick(tag) }) {
Text(tag)
}
}
}
}
}
}
}

View file

@ -16,15 +16,21 @@ fun List<String>.containsPartial(text:String) : Boolean {
fun getFilteredRecipeList( fun getFilteredRecipeList(
tags : List<String>, tags : List<String>,
search : String, search : String,
lockTag : String,
) : List<Recipe> { ) : List<Recipe> {
val items = getRecipeList() val items = getRecipeList()
var recipes = items.filter { it.tags.containsAll(tags)} var recipes = items.filter { it.tags.containsAll(tags) }
recipes = recipes.filter { recipes = recipes.filter {
it.title.contains(search) || it.ingredients.containsPartial(search) it.title.contains(search) || it.ingredients.containsPartial(search)
} }
if (lockTag != "")
{
recipes = recipes.filter { it.tags.contains(lockTag)}
}
return recipes return recipes
} }

View file

@ -11,21 +11,29 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun HomeScreen( fun HomeScreen(
onClick: () -> Unit, onClick: () -> Unit,
isLarge: Boolean,
) { ) {
val text = "Recipes" val text = "Recipes"
HeaderText(text, onClick) HeaderText(text, onClick, isLarge)
} }
@Composable @Composable
fun HeaderText( fun HeaderText(
text: String, text: String,
onClick: () -> Unit onClick: () -> Unit,
isLarge: Boolean
) { ) {
val header = if(isLarge) MaterialTheme.typography.h1 else MaterialTheme.typography.h2
val lowHead = if(isLarge) MaterialTheme.typography.h3 else MaterialTheme.typography.h4
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -41,10 +49,13 @@ fun HeaderText(
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
androidx.compose.material3.Text ( androidx.compose.material3.Text (
text = "Baker's Menagerie", text = "Baker's Menagerie",
style = MaterialTheme.typography.h1 style = header,
textAlign = TextAlign.Center
) )
androidx.compose.material3.Text ( androidx.compose.material3.Text (
text = "An Allbright Family Cookbook" text = "An Allbright Family Cookbook",
style = lowHead,
textAlign = TextAlign.Center
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
androidx.compose.material3.Button( androidx.compose.material3.Button(

View file

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Recipe App</string> <string name="app_name">Baker\'s Menagerie</string>
</resources> </resources>

View file

@ -5,7 +5,7 @@
<link rel="manifest" href="manifest.json"/> <link rel="manifest" href="manifest.json"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link type="text/css" rel="stylesheet" href="styles.css"> <link type="text/css" rel="stylesheet" href="styles.css">
<title>Recipe App KMP</title> <title>Baker's Menagerie KMP</title>
<script type="application/javascript" src="webApp.js"></script> <script type="application/javascript" src="webApp.js"></script>
</head> </head>
<body> <body>

View file

@ -1,5 +1,5 @@
{ {
"name": "Recipe App", "name": "Baker's Menagerie",
"icons": [ "icons": [
{ {
"src": "images/logo.png", "src": "images/logo.png",