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:
parent
d8bbce38d7
commit
309304b51a
18 changed files with 151 additions and 76 deletions
42
README.md
42
README.md
|
@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,3 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Recipe App</string>
|
<string name="app_name">Baker\'s Menagerie</string>
|
||||||
</resources>
|
</resources>
|
10
androidApp/src/main/res/drawable-v24/ophelia.xml
Normal file
10
androidApp/src/main/res/drawable-v24/ophelia.xml
Normal file
File diff suppressed because one or more lines are too long
10
androidApp/src/main/res/drawable-v24/ophelia_background.xml
Normal file
10
androidApp/src/main/res/drawable-v24/ophelia_background.xml
Normal 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>
|
|
@ -1,3 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Recipe App</string>
|
<string name="app_name">Baker\'s Menagerie</string>
|
||||||
</resources>
|
</resources>
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "Recipe App",
|
"name": "Baker's Menagerie",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "images/logo.png",
|
"src": "images/logo.png",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -159,3 +184,53 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Recipe App</string>
|
<string name="app_name">Baker\'s Menagerie</string>
|
||||||
</resources>
|
</resources>
|
|
@ -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>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "Recipe App",
|
"name": "Baker's Menagerie",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "images/logo.png",
|
"src": "images/logo.png",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue