app integraded :)😈

This commit is contained in:
2021-11-02 21:38:46 +01:00
parent ad391b69f6
commit e202b7a160
29 changed files with 307 additions and 228 deletions

View File

@@ -15,13 +15,14 @@
"@react-navigation/stack": "^6.0.11",
"@reduxjs/toolkit": "^1.6.2",
"expo": "~43.0.0",
"expo-image-picker": "^11.0.3",
"expo-status-bar": "~1.1.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.2",
"react-native-animatable": "^1.3.3",
"react-native-gesture-handler": "~1.10.2",
"react-native-modal": "^13.0.0",
"react-native-reanimated": "~2.2.0",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.8.0",
"react-native-vector-icons": "^9.0.0",

View File

@@ -54,6 +54,7 @@ function App() {
<Stack.Screen name="Groceries" component={GroceryListPage} />
<Stack.Screen name="Recipes" component={RecipePage} />
<Stack.Screen name="Recipe" component={Recipe} />
<Stack.Screen name="AddRecipe" component={AddRecipe} />
</Stack.Navigator>
</NavigationContainer>
);

View File

@@ -48,7 +48,7 @@ const Dropdown = (props) => {
)
})
return (
<WrapperDropdown keyboardShouldPersistTaps={'always'}>
<WrapperDropdown keyboardShouldPersistTaps={'always'} nestedScrollEnabled = {true}>
{dropdownList}
</WrapperDropdown>
)

View File

@@ -88,7 +88,7 @@ export default React.memo((props) => {
< WrapperList listLength={items.length} onLayout={(event) => { HandleAnimation(event) }}>
{list.open &&
<FlatList style={{ width: '100%' }}
initialNumToRender={5}
initialNumToRender={10}
maxToRenderPerBatch={5}
ListFooterComponent={filteredItems.length === 0 && <ListSubtitle style={{ fontSize: 22, width: '100%', height: 30, alignSelf: 'center' }} >Add a grocery</ListSubtitle>}
data={filteredItems}

View File

@@ -7,18 +7,19 @@ import { CheckButton } from '../../../styles/componentBlueprints';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { selectItemById, checkToggle, modalToggle } from '../../../redux/slices/groceryList/itemsSlice'
import { findTag } from '../../../redux/slices/groceryList/tagsSlice'
import { toggleOffAnim, toggleOnAnim } from '../../../styles/animations';
const ListedItem = React.memo((props) => {
const dispatch = useDispatch()
const item = useSelector(state => selectItemById(state, props.item._id), shallowEqual)
const tag = useSelector(state => findTag(state, item.tag))
return (
<Wrapper>
<DarkLayer>
<Wrapper >
<DarkLayer animation={item.checked ? toggleOnAnim: toggleOffAnim}>
<WrapperItem person={item.person} color={tag.color} checked={item.checked} onPress={() => { props.setVisible(true); return dispatch(modalToggle(item._id)) }}>
<TextProductName >{item.productName}</TextProductName>
{item.details != "" && <TextDetails>{item.details}</TextDetails>}
<TextAmount>{item.amount.am}{item.amount.qt && " " + item.amount.qt}</TextAmount>
<TextAmount>{item.amount.am}{item.amount.qt && " x (" + item.amount.qt + ")"}</TextAmount>
{item.person != "" && <TextPerson>{item.person}</TextPerson>}
</WrapperItem>
</DarkLayer>

View File

@@ -1,4 +1,7 @@
import * as Animatable from 'react-native-animatable';
import { Text, View, Image, TouchableOpacity} from 'react-native'
import styled, { css } from 'styled-components'
export const Wrapper = styled(View)`
@@ -8,7 +11,7 @@ export const Wrapper = styled(View)`
justify-content: flex-start;
width: 100%;
`
export const DarkLayer = styled(View)`
export const DarkLayer = styled(Animatable.View)`
background-color: ${({ theme }) => theme.colors.dp00};
display: flex;
flex: 1;
@@ -31,7 +34,7 @@ export const WrapperItem = styled(TouchableOpacity)`
background-color: ${props => props.checked ? props.theme.colors.itemSelected : props.color + '66'};
`
export const WrapperButton = styled(TouchableOpacity)`
height: 30px;
min-height: 40px;
width: 40px;
`
export const TextProductName = styled(Text)`

View File

@@ -1,4 +1,5 @@
import React from 'react'
import * as Animatable from 'react-native-animatable';
//components
//styling
import { Wrapper, WrapperProduct, WrapperButton, WrapperText, TextProductName, TextTag, TextPrice, StyledImage, IconCheck } from './styles/listedProduct'
@@ -7,6 +8,7 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { selectProductById, checkToggle, modalToggle } from '../../../redux/slices/groceryList/productsSlice'
import { findTag } from '../../../redux/slices/groceryList/tagsSlice'
import { CheckButton } from '../../../styles/componentBlueprints';
import { toggleOffAnim, toggleOnAnim } from '../../../styles/animations';
const ListedProduct = React.memo((props) => {
const dispatch = useDispatch()
@@ -14,6 +16,7 @@ const ListedProduct = React.memo((props) => {
const tag = useSelector(state => findTag(state, product.tag))
return (
<Wrapper>
<Animatable.View style={{ flex: 1 }} animation={product.checked ? toggleOnAnim : toggleOffAnim} >
<WrapperProduct color={tag.color} checked={product.checked} onPress={() => { props.setVisible(true); return dispatch(modalToggle(product._id)) }}>
<WrapperText>
<TextProductName >{product.productName}</TextProductName>
@@ -22,6 +25,7 @@ const ListedProduct = React.memo((props) => {
</WrapperText>
{product.image != "" && <StyledImage source={{ uri: product.image }} />}
</WrapperProduct>
</Animatable.View>
<WrapperButton onPress={() => dispatch(checkToggle(product._id))} >
<CheckButton checked={product.checked}>
<IconCheck checked={product.checked} />

View File

@@ -17,8 +17,7 @@ export const Wrapper = styled(View)`
export const WrapperProduct = styled(TouchableOpacity)`
box-shadow: ${({theme})=> theme.colors.shadow};
display: flex;
flex: 1;
width: 100%;
width: 98%;
min-height: 70px;
position: relative;
flex-direction: row;
@@ -30,7 +29,7 @@ export const WrapperProduct = styled(TouchableOpacity)`
background-color: ${props => props.checked ? props.theme.colors.itemSelected : props.color + '66'};
`
export const WrapperButton = styled(TouchableOpacity)`
height: 60px;
height: 70px;
width: 40px;
`

View File

@@ -5,7 +5,7 @@ import { useHeaderHeight } from '@react-navigation/elements';
const HeaderPadding = (props) => {
const headerHeight = useHeaderHeight();
return (
<View style={{ height: headerHeight + 10 }} />
<View style={{ opacity: 0, height: headerHeight + 10 }} />
)
}
export default HeaderPadding;

View File

@@ -61,7 +61,7 @@ const ModalAddItem = (props) => {
let product = products.find((product) => product.productName === pName);
setProductName(product.productName)
setTag(product.tag)
setPrice(product.price)
setPrice(product.price && product.price.toString())
}
return (
<StyledModal isVisible={props.visible} animationIn={'slideInUp'} animationOut={'slideOutDown'}

View File

@@ -81,7 +81,7 @@ const ModalEditProduct = (props) => {
</WrapperDropdown>}
<WrapperInput>
<IconDollar />
<Input type={"number-pad"}
<Input keyboardType={"number-pad"}
value={price}
onChangeText={(text) => setPrice(text)}
placeholder="Price" />

View File

@@ -84,7 +84,7 @@ const ModalAddIngredients = (props) => {
<IconMeal />
<Input
style={{ fontSize: 20, width: 100 }}
type="number-pad"
keyboardType={"number-pad"}
value={servings}
onChangeText={(text) => setServings(text)}
min={amountOfServings}

View File

@@ -22,7 +22,7 @@ export const WrapperInput = styled(View)`
margin-bottom:5px;
`
export const WrapperDropdown = styled(View)`
margin-left: 16px;
margin-left: 42px;
margin-top: -5px;
margin-bottom: 5px;
`
@@ -68,7 +68,7 @@ export const Button = styled(TouchableOpacity)`
export const ButtonText = styled(Text)`
color: ${props => props.theme.colors.primary};
font-weight: bold;
font-size: ${({ theme }) => theme.fontSizes.fontS}px;
font-size: ${({ theme }) => theme.fontSizes.fontM}px;
`
export const ModalHeader = styled(Text)`

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native'
import { TouchableOpacity, View } from 'react-native'
import styled from "styled-components"
import { Ingredient } from "./addRecipe/Ingredient"
@@ -14,7 +14,7 @@ export const Wrapper = styled(View)`
border-bottom-color: ${props => props.theme.colors.dp01};
border-bottom-width: 1px;
border-radius: 20px;
${props => props.checked === true && css`background-color: ` + props.theme.colors.selected + '33'};
${props => props.checked === true && "background-color:" + props.theme.colors.selected + '33'};
`
const IngredientButton = (props) => {
@@ -23,9 +23,9 @@ const IngredientButton = (props) => {
return (
<Wrapper checked={ingredient.checked}>
<Ingredient ingredient={ingredient} multiplier={props.multiplier}/>
<View onClick={() => dispatch(ingredientCheckToggle({ id: props.recipeId, productName: ingredient.productName }))} >
<TouchableOpacity onPress={() => dispatch(ingredientCheckToggle({ id: props.recipeId, productName: ingredient.productName }))} >
<CheckButton checked={ingredient.checked} />
</View>
</TouchableOpacity>
</Wrapper>
)
}

View File

@@ -11,22 +11,22 @@ import { removeRecipe } from "../../../redux/slices/recipesSlice"
import { PlusIcon, WrapperAddItem } from "../../GroceryList/groceries/styles/buttons"
import ModalAddIngredients from "../../modals/recipes/ModalAddIngredients"
import { Alert } from "react-native"
import { useNavigation } from "@react-navigation/core";
import { TouchableOpacity } from "react-native-gesture-handler";
export const AddRecipeButton = () => {
let navigation = useNavigation()
return (
// < Link to="/recipes/addRecipe">
<WrapperAddRecipe>
<WrapperAddRecipe onPress={() => navigation.navigate('AddRecipe', {id: ""})}>
<IconPlus />
</WrapperAddRecipe>
// </Link>
)
}
export const AddIngredientsButton = (props) => {
const [visible, setVisible] = useState(false)
return (<>
<WrapperAddItem style={{ bottom: 10 }} onPress={()=>setVisible(true)}>
<WrapperAddItem style={{ bottom: 10 }} onPress={() => setVisible(true)}>
<PlusIcon />
</WrapperAddItem>
<ModalAddIngredients id={props.id} visible={visible} closeModal={() => setVisible(false)} />
@@ -37,6 +37,7 @@ export const AddIngredientsButton = (props) => {
export const OptionsButtonRecipe = (props) => {
const dispatch = useDispatch()
const [toggled, setToggled] = useState(false)
const navigation = useNavigation()
const handleRemove = () => {
Alert.alert(
"Warning",
@@ -45,6 +46,7 @@ export const OptionsButtonRecipe = (props) => {
{ text: "Cancel", },
{
text: "Remove", onPress: () => {
navigation.goBack()
dispatch(removeRecipe(props.id))
}
}
@@ -54,11 +56,12 @@ export const OptionsButtonRecipe = (props) => {
}
return (
<WrapperOptions toggled={toggled} >
<IconOptions toggled={toggled} onClick={() => setToggled(!toggled)} />
<WrapperOptionButtons toggled={toggled}>
<IconRemove onClick={() => handleRemove()} />
<IconEdit onClick={() => history.push("/recipes/addRecipe/" + props.id)} />
</WrapperOptionButtons>
<TouchableOpacity onPress={() => handleRemove()} >
<IconRemove />
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate("AddRecipe", { id: props.id })} >
<IconEdit />
</TouchableOpacity>
</WrapperOptions>
)
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { View } from "react-native"
import { TouchableOpacity, View } from "react-native"
import { useSelector } from "react-redux"
import { WrapperIngredient, WrapperIngredientLeft, TagCircle, IngredientName, IngredientPortion, IngredientAmount, IconEdit, IconRemove } from './styles/ingredient'
@@ -12,12 +12,11 @@ export const Ingredient = (props) => {
<WrapperIngredient>
<WrapperIngredientLeft>
<TagCircle tagColor={tag.color} />
<IngredientPortion>{ingredient.portion}</IngredientPortion>
<IngredientPortion>{ingredient.amount * multiplier} x ({ingredient.portion})</IngredientPortion>
<IngredientName>{ingredient.productName}</IngredientName>
</WrapperIngredientLeft>
<IngredientAmount>{ingredient.amount*multiplier}</IngredientAmount>
{props.EditIngredient && <IconEdit onClick={() => props.EditIngredient(props.index)} />}
{props.RemoveIngredient && <IconRemove onClick={() => props.RemoveIngredient(props.index)} />}
{props.EditIngredient && <TouchableOpacity onPress={() => props.EditIngredient(props.index)} ><IconEdit /></TouchableOpacity>}
{props.RemoveIngredient && <TouchableOpacity onPress={() => props.RemoveIngredient(props.index)} ><IconRemove /></TouchableOpacity>}
</WrapperIngredient>
)
}

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { useState } from "react"
import { View } from "react-native"
import { Text, View } from "react-native"
import { useSelector } from "react-redux"
import { selectAllProducts } from "../../../redux/slices/groceryList/productsSlice"
import Dropdown from "../../Dropdown"
import { Input } from "../../modals/styles/modal"
import { ButtonText, Input } from "../../modals/styles/modal"
import { Ingredient } from "./Ingredient"
import {
WrapperIngredients, InputIngredientName, InputAmount, InputPortion, Button
WrapperIngredients, InputIngredientName, InputAmount, InputPortion, Button, Row, TitleIngredients, WrapperDropdown
} from "./styles/inputs"
export const Ingredients = (props) => {
let ingredients = props.ingredients
@@ -67,54 +67,56 @@ export const Ingredients = (props) => {
let product = products.find((product) => product.productName === pName);
setIngredientName(product.productName)
setTag(product.tag)
setPrice(product.price)
setPrice(product.price && product.price.toString())
}
return (
<WrapperIngredients>
<h2>Ingredients</h2>
<View id="row">
<TitleIngredients>Ingredients</TitleIngredients>
<Row>
<View style={{ width: '100%' }}>
{IngredientList}
<InputIngredientName onFocus={() => setFocused(true)} onBlur={() => { setTimeout(() => { setFocused(false) }, 100) }}
type="text"
<InputIngredientName onFocus={() => setFocused(true)} onEndEditing={() => setFocused(false)}
value={ingredientName}
onChange={(text) => setIngredientName(text.target.value)}
onChangeText={(text) => setIngredientName(text)}
placeholder="Name ingredient" />
<WrapperDropdown>
{focused &&
<Dropdown array={products.map(product => product.productName)} text={ingredientName} setElement={handleDropdownPress} />}
<View id="row">
<View>
</WrapperDropdown>
<Row>
<Input
onFocus={() => setFocusedTag(true)} onBlur={() => { setTimeout(() => { setFocusedTag(false) }, 100) }}
onFocus={() => setFocusedTag(true)} onEndEditing={() => setFocusedTag(false)}
style={{ marginLeft: 0 }}
type="text"
value={tag}
onChange={(text) => setTag(text.target.value)}
onChangeText={(text) => setTag(text)}
placeholder="tag" />
<Input
keyboardType={"number-pad"}
value={price}
onChangeText={(text) => setPrice(text)}
placeholder="Price" />
</Row>
<View style={{marginTop: 0}}>
{focusedTag &&
<Dropdown array={tags.map(t => t.tagName)} text={tag} setElement={setTag} />}
</View>
<Input
type="number"
value={price}
onChange={(text) => setPrice(text.target.value)}
placeholder="Price" />
</View>
<View id="row">
<Row>
<InputAmount
type="number"
keyboardType={"number-pad"}
value={amount}
onChange={(text) => setAmount(text.target.value)}
onChangeText={(text) => setAmount(text)}
placeholder="amt." />
<InputPortion
type="text"
value={portion}
onChange={(text) => setPortion(text.target.value)}
onChangeText={(text) => setPortion(text)}
placeholder="Portion (1/2 teaspoon, ...)" />
<Button onClick={submitIngredient} >{index === -1 ? 'Add' : 'Edit'}</Button>
<Button onPress={submitIngredient} ><ButtonText>{index === -1 ? 'Add' : 'Edit'}</ButtonText></Button>
</Row>
</View>
</View>
</View>
</WrapperIngredients>
</Row >
</WrapperIngredients >
)
}

View File

@@ -15,30 +15,8 @@ export const WrapperAddRecipe = styled(Button)`
export const IconPlus = () => <MaterialCommunityIcons name="plus" color={theme.colors.textB2} size={60} />
export const WrapperOptions = styled(View)`
position: absolute;
top: 0px;
right: 0px;
`
export const WrapperOptionButtons = styled(View)`
display: flex;
flex-direction: column;
position: absolute;
align-items: center;
top: 60px;
width: 55px;
right: 0px;
height: ${props => props.toggled ? css`100px` : css`0px` };
background-color: ${({theme}) => theme.colors.dp00 +'bb'};
border-bottom-left-radius: 8px;
overflow: hidden;
`
const Options = () => <MaterialCommunityIcons name="cog-outline" color={theme.colors.textW2} size={40} />
export const IconOptions = styled(Options)`
margin-right: 5px;
margin-top: 10px;
transform: rotate(${props => props.toggled ? css`135deg` : css`0deg` });
flex-direction: row;
`
const Remove = () => <MaterialCommunityIcons name="trash-can-outline" color={theme.colors.error} size={40} />

View File

@@ -1,3 +1,4 @@
import React from 'react';
import { Text, View } from "react-native"
import styled from 'styled-components'
@@ -9,7 +10,7 @@ export const WrapperIngredient = styled(View)`
display: flex;
flex: 1;
flex-direction: row;
justify-content: center;
justify-content: space-between;
align-items: center;
`
export const WrapperIngredientLeft = styled(View)`
@@ -29,32 +30,25 @@ export const TagCircle = styled(View)`
export const IngredientName = styled(Text)`
margin-left: 10px;
width: 60%;
font-size: ${({ theme }) => theme.fontSizes.fontS}px;
font-size: 18px;
/* overflow-wrap: break-word; */
color: ${props => props.theme.colors.textW4};
`
export const IngredientPortion = styled(Text)`
width: 35%;
font-size: ${({ theme }) => theme.fontSizes.fontS}px;
font-size: 18px;
font-weight: 900;
/* word-break: break-all; */
/* overflow-wrap: break-word; */
color: ${props => props.theme.colors.textW5};
`
export const IngredientAmount = styled(Text)`
max-width: 12%;
margin-right: 48px;
font-size: ${({theme}) => theme.fontSizes.fontS}px;
/* overflow-wrap: break-word; */
color: ${props => props.theme.colors.textW5};
`
const Remove = () => <MaterialCommunityIcons name="trash-can-outline" color={theme.colors.error + 'aa'} size={theme.fontSizes.fontS} />
const Remove = () => <MaterialCommunityIcons name="trash-can-outline" color={theme.colors.error + 'aa'} size={theme.fontSizes.fontM} />
export const IconRemove = styled(Remove)`
position:absolute;
right: 0px;
`
const Edit = () => <MaterialCommunityIcons name="pencil-outline" color={theme.colors.textW5} size={theme.fontSizes.fontS} />
const Edit = () => <MaterialCommunityIcons name="pencil-outline" color={theme.colors.textW5} size={theme.fontSizes.fontM} />
export const IconEdit = styled(Edit)`
position:absolute;

View File

@@ -1,34 +1,49 @@
import { Text, TextInput, View } from "react-native"
import styled from "styled-components"
import { Input } from "../../../modals/styles/modal"
export const WrapperIngredients = styled(View)`
margin-top: 10px;
width:100%;
border-left: solid ${props => props.theme.colors.primary + '55'} 1px;
border-left-color: ${props => props.theme.colors.primary + '55'} ;
border-left-width: 1px;
padding-left: 5px;
/* #row{
`
export const Row = styled(View)`
position: relative;
display: flex;
flex-direction: row;
flex:1;
width:100%;
} */
`
export const InputIngredientName = styled(TextInput)`
margin-top: 10px;
font-size: ${({theme}) => theme.fontSizes.fontS}px;
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
export const WrapperDropdown = styled(View)`
margin-top: -10px;
margin-bottom: 10px;
`
export const TitleIngredients = styled(Text)`
font-size: 30px;
font-weight: bold;
color: ${props => props.theme.colors.textW2};
`
export const InputIngredientName = styled(Input)`
margin-left: 0px;
padding-right: 0px;
margin: 10px 0px;
border-bottom-width: 1px;
width:100%;
`
export const InputAmount = styled(TextInput)`
font-size: ${({theme}) => theme.fontSizes.fontS}px;
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
export const InputAmount = styled(Input)`
margin-left: 0px;
padding-right: 0px;
margin-top: 10px;
border-bottom-width: 1px;
width: 50px;
`
export const InputPortion = styled(TextInput)`
font-size: ${({theme}) => theme.fontSizes.fontS}px;
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
export const InputPortion = styled(Input)`
margin-left: 0px;
padding-right: 0px;
margin-top: 10px;
border-bottom-width: 1px;
width: 60%;
margin-left: 10px;
@@ -39,5 +54,5 @@ export const Button = styled(Text)`
bottom: -8px;
color: ${props => props.theme.colors.primary + 'bb'};
font-weight: bold;
font-size: ${({theme}) => theme.fontSizes.fontS}px;
font-size: ${({ theme }) => theme.fontSizes.fontS}px;
`

View File

@@ -13,6 +13,7 @@ export const WrapperRecipeCard = styled(TouchableOpacity)`
width: 48%;
background-color: ${props => props.theme.colors.dp01 + 'dd'};
border-radius: 20px;
margin-bottom: 4px;
`
export const RecipeName = styled(Text)`

View File

@@ -1,57 +1,66 @@
import React from 'react';
import { Text, View } from 'react-native'
import { Button, ScrollView, Text, View } from 'react-native'
import * as ImagePicker from 'expo-image-picker';
//components
import Header from "../../components/Header"
import { ArrowBack } from "../styles/page";
import { Ingredients } from "../../components/recipes/addRecipe/Inputs";
//styling
import { Wrapper, WrapperRecipe, Input, InputInstructions, IconRecipe, IconPot, IconMeal, IconImage, IconCheck } from "./styles/addRecipe"
import { Wrapper, WrapperRecipe, Input, InputInstructions, IconRecipe, IconPot, IconMeal, IconImage, IconCheck, AddImageButton, ButtonText, SubmitButton } from "./styles/addRecipe"
import { useState } from "react";
import { addRecipe, findRecipeById, updateRecipe } from "../../redux/slices/recipesSlice";
import { useDispatch, useSelector } from "react-redux";
import { StyledImage } from "./styles/recipe";
import HeaderPadding from '../../components/Header';
import { Row } from '../../components/recipes/addRecipe/styles/inputs';
import { useNavigation } from '@react-navigation/core';
const AddRecipe = (props) => {
const id = props.route.params.id
const navigation = useNavigation()
const AddRecipe = () => {
let { id } = useParams()
const foundRecipe = useSelector(state => findRecipeById(state, id))
let recipe = foundRecipe ? foundRecipe : { name: '', prepTime: '', servings: '', image: '', ingredients: [], instructions: '' }
const dispatch = useDispatch()
const [name, setName] = useState(recipe.name)
const [prepTime, setPrepTime] = useState(recipe.prepTime)
const [servings, setServings] = useState(recipe.servings)
const [prepTime, setPrepTime] = useState(recipe.prepTime && recipe.prepTime.toString())
const [servings, setServings] = useState(recipe.servings && recipe.servings.toString())
const [image, setImage] = useState(recipe.image)
const [ingredients, setIngredients] = useState(recipe.ingredients)
const [instructions, setInstructions] = useState(recipe.instructions)
const handleImageInput = (input) => {
if (input.target.files && input.target.files[0] && input.target.files[0].size < 1000000) {
let reader = new FileReader()
reader.onload = function (e) {
setImage(e.target.result)
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
console.log(result);
if (!result.cancelled) {
setImage(result.uri);
}
};
reader.readAsDataURL(input.target.files[0])
}
else {
alert("That file is too big! Choose an image with a size lower than 1MB")
}
}
const submitRecipe = () => {
if (name && ingredients && instructions) {
foundRecipe ? dispatch(updateRecipe({ _id: foundRecipe._id, name, prepTime: Number(prepTime), servings: Number(servings), image, ingredients, instructions }))
: dispatch(addRecipe({ name, prepTime: Number(prepTime), servings: Number(servings), image, ingredients, instructions }))
console.log(typeof (image))
navigation.goBack()
}
else {
alert("You should add a name, at least one ingredient and the instructions")
}
}
const handleInput = (event) => {
let lastChar = event.target.value.substr(-1).charCodeAt(0);
let penultimateChar = event.target.value.substr(-2).charCodeAt(0);
const handleInput = (text) => {
let lastChar = text.substr(-1).charCodeAt(0);
let penultimateChar = text.substr(-2).charCodeAt(0);
if (instructions === '') {
setInstructions("- " + event.target.value)
setInstructions("- " + text)
return
}
else if (penultimateChar === 10 && lastChar === 45) {
@@ -59,54 +68,55 @@ const AddRecipe = () => {
return
}
else if (lastChar === 10) {
setInstructions(event.target.value + "- ")
setInstructions(text + "- ")
return
}
setInstructions(event.target.value)
setInstructions(text)
}
return (
<>
<HeaderPadding/>
return (<>
<ScrollView nestedScrollEnabled={true} keyboardShouldPersistTaps={'always'}>
<HeaderPadding />
{image != "" && <StyledImage source={{ uri: image }} alt="Recipe" />}
<Wrapper >
{image && <StyledImage source={image} alt="Recipe" />}
<WrapperRecipe>
<View id="row">
<Row>
<IconRecipe />
<Input
type="text"
value={name}
onChange={(text) => setName(text.target.value)}
onChangeText={(text) => setName(text)}
placeholder="Recipe name" />
</View>
<View id="row">
</Row>
<Row>
<IconPot />
<Input
type="number"
keyboardType={"number-pad"}
value={prepTime}
onChange={(text) => setPrepTime(text.target.value)}
onChangeText={(text) => setPrepTime(text)}
placeholder="Prep time (min)" />
<IconMeal />
<Input
type="number"
keyboardType={"number-pad"}
value={servings}
onChange={(text) => setServings(text.target.value)}
onChangeText={(text) => setServings(text)}
placeholder="Servings" />
</View>
<View id="row">
<IconImage />
<Input
style={{ borderBottom: 'none' }}
type="file" accept="image/*"
onChange={input => handleImageInput(input)} />
</View>
</Row>
<AddImageButton onPress={pickImage}
style={{ borderBottom: 'none' }} >
<ButtonText>Add Image</ButtonText>
</AddImageButton>
<Ingredients ingredients={ingredients} setIngredients={setIngredients} />
<InputInstructions
type="text"
minHeight={40}
multiline={true}
value={instructions}
onChange={(text) => handleInput(text)}
onChangeText={(text) => handleInput(text)}
placeholder='Instructions' />
</WrapperRecipe>
</Wrapper>
</ScrollView>
{name != "" && ingredients != "" && instructions != "" && <SubmitButton onPress={submitRecipe} >
<IconCheck />
</SubmitButton>}
</>
)
}

View File

@@ -10,8 +10,8 @@ import { OptionsButtonRecipe, AddIngredientsButton } from "../../components/reci
import { ScrollView, Text, View } from "react-native"
import HeaderPadding from "../../components/Header"
const Recipe = (props) => {
const id = props.route.params.id
const Recipe = ({route, navigation}) => {
const id = route.params.id
const recipe = useSelector(state => findRecipeById(state, id))
const IngredientList = recipe.ingredients.map((ingredient, index) => {
return (<Ingredient ingredient={ingredient} index={index} key={index} />)
@@ -25,12 +25,21 @@ const Recipe = (props) => {
<InstructionText>{instruction}</InstructionText>
</InstructionWrapper>)
})
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<OptionsButtonRecipe id={id} />
),
});
}, [navigation]);
return (
<>
<ScrollView>
<Wrapper >
<HeaderPadding/>
{recipe.image != "" && <StyledImage source={{ uri: recipe.image }} />}
<ScrollView>
<Wrapper >
<WrapperRecipe>
<Title>{recipe.name}</Title>
<Hr />

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { Image, Text, TextInput, View } from "react-native"
import { Image, Text, TextInput, TouchableOpacity, View } from "react-native"
import styled from 'styled-components'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import theme from "../../../styles/theme";
import { Button } from '../../../styles/componentBlueprints';
export const Wrapper = styled(View)`
display: flex;
@@ -38,35 +39,49 @@ export const Input = styled(TextInput)`
color: ${props => props.theme.colors.textW1};
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
border-bottom-width: 1px;
font-size: ${({theme}) => theme.fontSizes.fontS}px;
font-size: ${({theme}) => theme.fontSizes.fontM}px;
width: 100%;
`
export const InputInstructions = styled(Text)`
export const InputInstructions = styled(TextInput)`
margin-top: 30px;
padding: 5px;
color: ${props => props.theme.colors.textW1};
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
border-bottom-width: 1px;
font-size: ${({theme}) => theme.fontSizes.fontS}px;
min-height: 600px;
font-size: ${({theme}) => theme.fontSizes.fontM}px;
width: 100%;
`
export const StyledImage = styled(Image)`
width: 100px;
height: 100px;
export const AddImageButton = styled(TouchableOpacity)`
justify-content: center;
align-items: center;
align-self: center;
margin: 10px;
width: 140px;
height: 40px;
background-color:${props => props.theme.colors.primary};
border-radius: 15px;
`
export const IconRecipe = () => <MaterialCommunityIcons name="pasta" color={theme.colors.primaryVar} size={theme.fontSizes.fontM} />
export const ButtonText = styled(Text)`
color: ${props => props.theme.colors.textB1};
font-weight: bold;
font-size: 20px;
`
export const SubmitButton = styled(Button)`
position: absolute;
right: 10px;
bottom: 10px;
background-color: ${({theme}) => theme.colors.primary};
`
export const IconRecipe = () => <MaterialCommunityIcons name="pasta" color={theme.colors.primaryVar} size={theme.fontSizes.fontL} />
export const IconPot = () => <MaterialCommunityIcons name="pot-steam" color={theme.colors.primaryVar} size={theme.fontSizes.fontM} />
export const IconPot = () => <MaterialCommunityIcons name="pot-steam" color={theme.colors.primaryVar} size={theme.fontSizes.fontL} />
export const IconMeal = () => <MaterialCommunityIcons name="pot-steam" color={theme.colors.primaryVar} size={theme.fontSizes.fontM} />
export const IconMeal = () => <MaterialCommunityIcons name="pot-steam" color={theme.colors.primaryVar} size={theme.fontSizes.fontL} />
export const IconImage = () => <MaterialCommunityIcons name="food" color={theme.colors.primaryVar} size={theme.fontSizes.fontM} />
const Check = () => <MaterialCommunityIcons name="check" color={theme.colors.textB3} size={theme.fontSizes.fontL} />
const Check = () => <MaterialCommunityIcons name="check" color={theme.colors.textB3} size={50} />
export const IconCheck = styled(Check)`
margin-right: 10px;

View File

@@ -47,6 +47,7 @@ export const InstructionNumber = styled(Text)`
`
export const StyledImage = styled(Image)`
margin-top: -110px;
height: 200px;
width: 100%;
`

View File

@@ -14,8 +14,8 @@ export const fetchRecipes = createAsyncThunk('recipes/fetchRecipes', async () =>
})
export const addRecipe = createAsyncThunk('recipes/addRecipe', async (payload) => {
const size = new TextEncoder().encode(JSON.stringify(payload)).length
console.log(size)
// const size = new TextEncoder().encode(JSON.stringify(payload)).length
// console.log(size)
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },

40
src/styles/animations.js Normal file
View File

@@ -0,0 +1,40 @@
export const toggleOnAnim = {
0.0: {
scale: 1
},
0.2: {
scale: 0.94
},
0.4: {
scale: 1.02
},
0.6: {
scale: 0.99
},
0.8: {
scale: 1.005
},
1: {
scale: 1
}
};
export const toggleOffAnim = {
0.0: {
scale: 1
},
0.2: {
scale: 0.94
},
0.4: {
scale: 1.02
},
0.6: {
scale: 0.99
},
0.8: {
scale: 1.0051
},
1: {
scale: 1
}
};

View File

@@ -5,6 +5,8 @@ import styled, { css } from 'styled-components'
import LightenDarken from '../functions'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import theme from "./theme";
import * as Animatable from 'react-native-animatable'
import { toggleOffAnim, toggleOnAnim } from "./animations";
//standard button layout
export const Button = styled(TouchableOpacity)`
box-shadow: ${({ theme }) => theme.colors.shadow};
@@ -15,7 +17,7 @@ export const Button = styled(TouchableOpacity)`
width: 65px;
border-radius: 35px;
`
const CheckButtonWrapper = styled(View)`
const CheckButtonWrapper = styled(Animatable.View)`
display:flex;
justify-content: center;
align-items: center;
@@ -30,7 +32,7 @@ const CheckButtonWrapper = styled(View)`
export const CheckButton = (props) => {
return (
<CheckButtonWrapper checked={props.checked} >
<CheckButtonWrapper animation={props.checked ? toggleOnAnim : toggleOffAnim} checked={props.checked} >
<MaterialCommunityIcons name="check" color={theme.colors.dp00} size={25} />
</CheckButtonWrapper>
)

View File

@@ -719,7 +719,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
"@babel/plugin-transform-object-assign@^7.0.0", "@babel/plugin-transform-object-assign@^7.10.4":
"@babel/plugin-transform-object-assign@^7.0.0":
version "7.14.5"
resolved "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.14.5.tgz"
integrity sha512-lvhjk4UN9xJJYB1mI5KC0/o1D5EcJXdbhVe+4fSk08D6ZN+iuAIs7LJC+71h8av9Ew4+uRq9452v9R93SFmQlQ==
@@ -2605,6 +2605,15 @@ expo-font@~10.0.3:
expo-modules-core "~0.4.4"
fontfaceobserver "^2.1.0"
expo-image-picker@^11.0.3:
version "11.0.3"
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-11.0.3.tgz#c0b6cb9b5fa027f1bd1879a879fe13157f31ac94"
integrity sha512-s7nXB+hop5htcETlSvtPhEGE6RM4BX0G2e6mhr2SjJdJPAFn+hY7R3vCGBFMtxV11Gjzr0D1w2y9gDqhOj0GKg==
dependencies:
"@expo/config-plugins" "^4.0.2"
expo-modules-core "~0.4.4"
uuid "7.0.2"
expo-keep-awake@~10.0.0:
version "10.0.0"
resolved "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-10.0.0.tgz"
@@ -4147,11 +4156,6 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
mockdate@^3.0.2:
version "3.0.5"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
@@ -4662,6 +4666,13 @@ react-native-animatable@1.3.3:
dependencies:
prop-types "^15.7.2"
react-native-animatable@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/react-native-animatable/-/react-native-animatable-1.3.3.tgz#a13a4af8258e3bb14d0a9d839917e9bb9274ec8a"
integrity sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==
dependencies:
prop-types "^15.7.2"
react-native-codegen@^0.0.6:
version "0.0.6"
resolved "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.0.6.tgz"
@@ -4690,16 +4701,6 @@ react-native-modal@^13.0.0:
prop-types "^15.6.2"
react-native-animatable "1.3.3"
react-native-reanimated@~2.2.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.3.tgz#edecfe477ad9efac6f006f7e1194e8c9aa4fc6d5"
integrity sha512-d+BV39Jp4Om0ZkgVjop672/004ytlTfDT01EloO3HFZs9wR2QTuCjekq8yi3xl0G2xGZKd4DXhvqabIa7OnMYA==
dependencies:
"@babel/plugin-transform-object-assign" "^7.10.4"
fbjs "^3.0.0"
mockdate "^3.0.2"
string-hash-64 "^1.0.3"
react-native-safe-area-context@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz#9549a2ce580f2374edb05e49d661258d1b8bcaed"
@@ -5357,11 +5358,6 @@ strict-uri-encode@^2.0.0:
resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz"
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
string-hash-64@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@@ -5667,6 +5663,11 @@ utils-merge@1.0.1:
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.2.tgz#7ff5c203467e91f5e0d85cfcbaaf7d2ebbca9be6"
integrity sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==
uuid@^3.3.2, uuid@^3.4.0:
version "3.4.0"
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"