diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b590732..19952fa 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" + android:screenOrientation="portrait" android:launchMode="singleTask" android:windowSoftInputMode="adjustPan"> diff --git a/src/components/groceryComponents/GroceryListButtons.js b/src/components/groceryComponents/GroceryListButtons.js index 7268ae7..862bca0 100644 --- a/src/components/groceryComponents/GroceryListButtons.js +++ b/src/components/groceryComponents/GroceryListButtons.js @@ -11,6 +11,7 @@ import AddItemModal from './modals/AddItemModal'; import { useDispatch, useSelector } from 'react-redux'; import { toggleVisibility } from '../../redux/slices/groceryList/toggleSlice'; import { removeCheckedItems } from '../../redux/slices/groceryList/itemsSlice'; +import { pushItems } from '../../redux/slices/groceryList/storageSlice'; export const AddItemButton = () => { @@ -27,24 +28,32 @@ export const AddItemButton = () => { } export const AddNewRecipeButton = () => { return ( - alert('hello')}> ); } export const PushItemsToStorageButton = () => { + const dispatch = useDispatch(); + const items = useSelector(state => state.items) + const products = useSelector(state => state.products) + const checkedItems = items.filter((item)=>item.checked && products.find((product)=>product.id === item.productId) !== undefined) + const dispatchAll = () =>{ + dispatch(pushItems(checkedItems)) + dispatch(removeCheckedItems()) + } return ( - alert('lol')}> - + dispatchAll()}> + ); } export const RemoveItemsButton = () => { const dispatch = useDispatch(); return ( - dispatch(removeCheckedItems())}> @@ -63,14 +72,14 @@ const styles = StyleSheet.create({ addItem: { backgroundColor: COLORS.primary, }, - AddNewProduct: { + AddNewRecipeButton: { backgroundColor: COLORS.primary, }, - AddNewRecipe: { + PushItemsToStorageButton: { backgroundColor: COLORS.primary, }, - downloadList: { + RemoveItemsButton: { backgroundColor: COLORS.primary, }, diff --git a/src/components/groceryComponents/ListedItem.js b/src/components/groceryComponents/ListedItem.js index ad22532..c08cc87 100644 --- a/src/components/groceryComponents/ListedItem.js +++ b/src/components/groceryComponents/ListedItem.js @@ -1,42 +1,31 @@ -import React, { useRef, useEffect, useState } from 'react'; +import React, { useRef, useEffect, useState, Suspense } from 'react'; import { StyleSheet, Text, TouchableOpacity, View, Animated } from 'react-native'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; //themes import { COLORS } from '../../themes/Colors'; + +//components +import EditItemModal from './modals/EditItemModal' //redux import { useDispatch, useSelector } from 'react-redux'; -import { checkToggle } from '../.././redux/slices/groceryList/itemsSlice'; +import { checkToggle, modalToggle } from '../.././redux/slices/groceryList/itemsSlice'; - -export const ListedItem = (props) => { +const ListedItem = (props) => { //redux const dispatch = useDispatch() - const products = useSelector(state => state.products) - const items = useSelector(state => state.items) - const tags = useSelector(state => state.tags) - - - let product; - const item = items.find((item) => item.productId === props.id); - - if (products.find((product) => product.id === item.productId)) { - product = products.find((product) => product.id === item.productId); - } - else { + const item = useSelector(state => state.items.find((item) => item.productId === props.id)) + let product = useSelector(state => state.products.find((product) => product.id === item.productId)) + if (!product) { product = { productName: props.id, - tag:'no-tag', + tag: 'uncathegorized', } - } - + const tag = useSelector(state => state.tags.find((tag) => tag.tagName === product.tag)) const dispatchAnim = () => { dispatch(checkToggle(item.productId)); } - - const tag = tags.find((tag)=>tag.tagName === product.tag); - const scaleAnimValue = useRef(new Animated.Value(0)).current; // animation start value const colorAnimValue = useRef(new Animated.Value(0)).current; // animation start value const checkAnimValue = useRef(new Animated.Value(0)).current; // animation start value @@ -106,33 +95,9 @@ export const ListedItem = (props) => { return ( - - - console.log(item.checked)} - onLongPress={() => dispatchAnim()}> - {product.productName} - - Amount: {item.amount} - For: {item.person} - - - {item.details ? Details: {item.details} : } - - - dispatchAnim()}> - Loading}> + + { scaleY: scaleAnimValue } ] - },]} > - + dispatch(modalToggle(props.id))} + onLongPress={() => dispatchAnim()}> + + {item.person ? {item.person} : } + + {product.productName} + {item.amount} + {item.details ? {item.details} : } + + + + dispatchAnim()}> + - - + },]} > + + + - - - + + + + ); } +export default React.memo(ListedItem); + const height = 30; const styles = StyleSheet.create({ listedItemStyle: { width: '90%', + minHeight: height + 30, marginBottom: height / 7, borderRadius: height / 2, - padding: 2, - paddingBottom: 3, backgroundColor: COLORS.dp03, }, listedItemChecked: { backgroundColor: '#253f34', }, textItem: { + width: '80%', flex: 1, - left: 8, + marginLeft: 10, + marginTop: 5, fontFamily: 'Roboto', fontSize: height * 0.65, color: COLORS.textW0 + 'cc', }, - textAmount: { - flex: 1, - left: 30, - fontFamily: 'Roboto', - fontSize: height * 0.5, - color: COLORS.textW1, - }, - textPerson: { - flex: 1, - left: 25, - fontFamily: 'Roboto', - fontSize: height * 0.5, - color: COLORS.textW1, - }, textDetails: { flex: 1, + left: 10, + marginBottom: 3, - left: 8, fontFamily: 'Roboto', fontSize: height * 0.5, color: COLORS.primary + 'aa', }, + nameTag: { + backgroundColor: '#000000' + '33', + minWidth: '30%', + position: 'absolute', + alignItems: 'center', + justifyContent: 'center', + + right: 0, + bottom: 0, + + borderTopLeftRadius: 10, + borderBottomRightRadius: height / 2, + }, + textPerson: { + fontFamily: 'Roboto', + fontSize: height * 0.45, + color: COLORS.textW0 + 'cc', + padding: 5, + maxHeight: 40, + }, + textAmount: { + position: 'absolute', + backgroundColor: '#000' + '3', + right: 5, + top: 5, + minWidth: 50, + borderTopRightRadius: height / 2, + borderRadius: height / 3, + + paddingLeft: 5, + paddingRight: 10, + fontFamily: 'Roboto', + fontSize: height * 0.55, + color: COLORS.textW0 + 'bb', + }, checkmark: { height: 30, width: 30, diff --git a/src/components/groceryComponents/ListedProduct.js b/src/components/groceryComponents/ListedProduct.js index 2dba0e9..6493f3f 100644 --- a/src/components/groceryComponents/ListedProduct.js +++ b/src/components/groceryComponents/ListedProduct.js @@ -1,21 +1,25 @@ import React, { useRef, useEffect } from 'react'; import { Image, StyleSheet, Text, TouchableOpacity, View, Animated } from 'react-native'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +//components +import EditProductModal from './modals/EditProductModal' + //themes import { COLORS } from '../../themes/Colors'; //redux import { useDispatch, useSelector } from 'react-redux'; -import { checkToggle } from '../.././redux/slices/groceryList/productsSlice'; +import { checkToggle, modalToggle } from '../.././redux/slices/groceryList/productsSlice'; -export const ListedProduct = (props) => { +const ListedProduct = (props) => { - const products = useSelector(state => state.products) - const product = products.find((product) => product.id === props.id); + const product = useSelector(state => state.products.find((product) => product.id === props.id)); const tags = useSelector(state => state.tags) const tag = tags.find((tag) => tag.tagName === product.tag); + //redux + const dispatch = useDispatch() const scaleAnimValue = useRef(new Animated.Value(0)).current; // animation start value const colorAnimValue = useRef(new Animated.Value(0)).current; // animation start value @@ -78,12 +82,6 @@ export const ListedProduct = (props) => { ]).start() }, [product.checked]) - //redux - const dispatch = useDispatch() - - const dispatchAnim = () => { - dispatch(checkToggle(product.id)); - } const interpolateColor = colorAnimValue.interpolate({ inputRange: [0, 1], outputRange: ['rgb(22, 26, 30)', 'rgb(62, 131, 98)'] @@ -99,24 +97,24 @@ export const ListedProduct = (props) => { } ] }]} > + alert('Not ready yet!!')} - onLongPress={() => dispatchAnim()}> + onPress={() => dispatch(modalToggle(product.id))} + onLongPress={() => dispatch(checkToggle(product.id))}> + {product.productName} - - Tag: - {tag.tagName} - - - {product.price ? Price: : } - {product.price ? €{product.price} : } - + {tag.tagName} + {product.price ? €{product.price} : } + + + - dispatchAnim()}> + + dispatch(checkToggle(product.id))}> { ); } +export default React.memo(ListedProduct); const height = 30; const styles = StyleSheet.create({ @@ -192,7 +191,7 @@ const styles = StyleSheet.create({ position: 'absolute', right: 0, top: 0, - bottom:0, + bottom: 0, width: 80, borderRadius: 15, opacity: 0.8, diff --git a/src/components/groceryComponents/ListedStorageItem.js b/src/components/groceryComponents/ListedStorageItem.js new file mode 100644 index 0000000..6a8949c --- /dev/null +++ b/src/components/groceryComponents/ListedStorageItem.js @@ -0,0 +1,112 @@ +import React from 'react'; +import { StyleSheet, Text, TouchableOpacity, View, Animated } from 'react-native'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +//themes +import { COLORS } from '../../themes/Colors'; +//redux +import { useDispatch } from 'react-redux'; +import { changeAmount, removeItem } from '../../redux/slices/groceryList/storageSlice'; + + +const Hr = (props) => { + return ( + + ) +} +let ListedStorageItem = ({product, storageItem, tag}) => { + const dispatch = useDispatch(); + return ( + + {/* delete button */} + {!storageItem.amount ? dispatch(removeItem(storageItem.id))}> + + : } + + + + {product.productName} + {storageItem.amount} + + + + {/* button increment amount */} + dispatch(changeAmount(storageItem.id, 1))}> + + + + +
+ {/* button decrement amount */} + storageItem.amount > 0 ? dispatch(changeAmount(product.id, -1)) : 0} + onLongPress={() => dispatch(changeAmount(product.id, -storageItem.amount))}> + + + +
+
+
) + +} +ListedStorageItem = React.memo(ListedStorageItem); +export default ListedStorageItem; +const height = 30; +const styles = StyleSheet.create({ + listedItem: { + borderTopLeftRadius: 15, + borderBottomLeftRadius: 15, + width: '90%', + paddingLeft: 8, + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'space-between', + marginTop: 2, + marginBottom: 2, + }, + textItem: { + flex: 1, + left: 8, + marginTop: 2, + fontFamily: 'Roboto', + fontSize: height * 0.65, + color: COLORS.textW0 + 'bb', + }, + textAmount: { + width: '10%', + backgroundColor: '#000000' + '40', + paddingLeft: 3, + borderRadius: 6, + marginTop: 3, + marginBottom: 3, + + marginRight: 3, + fontFamily: 'Roboto', + fontSize: height * 0.6, + color: COLORS.textW1, + }, + amountButtons: { + flexDirection: 'row', + marginTop: 3, + marginBottom: 3, + borderTopLeftRadius: 10, + borderBottomLeftRadius: 10, + height: 25, + width: 70, + alignItems: 'center', + justifyContent: 'space-evenly', + + backgroundColor: '#ffffff' + '10', + }, + amountIncrement: { + }, + amountDecrement: { + }, + deleteItem: { + height: 27, + width: 27, + backgroundColor: '#ffffff' + '10', + borderRadius: 12.5, + marginLeft: 5, + justifyContent: 'center', + alignItems: 'center', + } +}) \ No newline at end of file diff --git a/src/components/groceryComponents/StorageList.js b/src/components/groceryComponents/StorageList.js new file mode 100644 index 0000000..5b56b44 --- /dev/null +++ b/src/components/groceryComponents/StorageList.js @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { StyleSheet, TouchableOpacity, View, Text } from 'react-native'; + +//components +import ListedStorageItem from './ListedStorageItem'; +//themes +import { COLORS } from '../../themes/Colors'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +//redux +import { useSelector, useDispatch } from 'react-redux'; +export default StorageList = (props) => { + const products = useSelector(state => state.products) + const storageFiltered = useSelector(state => state.storage.filter((item) => products.find((product) => product.id === item.id).tag === props.tag)) + const tag = useSelector(state => state.tags.find((tag) => tag.tagName === props.tag)) + + const [visible, setVisible] = useState(false); + + if (storageFiltered.length !== 0) { + return( + + setVisible(!visible)}> + {visible ? + + : + } + {props.tag} + + + {visible ? storageFiltered.map((storageItem, index) => { + const product = products.find((product) => product.id === storageItem.id) + return + }) : } + + + ) + } + else { + return + } +} +const height = 30; +const styles = StyleSheet.create({ + container: { + marginBottom: 5, + }, + list: { + alignItems: 'flex-end', + }, + categoryButton: { + flex: 1, + paddingLeft: 0, + paddingRight: 8, + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'space-between', + }, + listedItem: { + borderTopLeftRadius: 15, + borderBottomLeftRadius: 15, + width: '90%', + paddingLeft: 8, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 2, + marginBottom: 2, + }, + textItem: { + flex: 1, + left: 8, + fontFamily: 'Roboto', + fontSize: height * 0.65, + color: COLORS.textW0 + 'bb', + }, + textAmount: { + width: '10%', + backgroundColor: '#ffffff' + '10', + paddingLeft: 3, + borderRadius: 6, + + marginRight: 3, + fontFamily: 'Roboto', + fontSize: height * 0.6, + color: COLORS.textW1, + }, + amountButtons: { + flexDirection: 'row', + marginTop: 3, + marginBottom: 3, + borderTopLeftRadius: 10, + borderBottomLeftRadius: 10, + height: 25, + width: 70, + alignItems: 'center', + alignSelf: 'flex-start', + justifyContent: 'space-evenly', + + backgroundColor: '#ffffff' + '10', + }, + amountIncrement: { + }, + amountDecrement: { + }, + deleteItem: { + height: 27, + width: 27, + backgroundColor: '#ffffff' + '10', + borderRadius: 12.5, + marginLeft: 5, + justifyContent: 'center', + alignItems: 'center', + } +}) \ No newline at end of file diff --git a/src/components/groceryComponents/modals/AddItemModal.js b/src/components/groceryComponents/modals/AddItemModal.js index 4a357c2..4a73702 100644 --- a/src/components/groceryComponents/modals/AddItemModal.js +++ b/src/components/groceryComponents/modals/AddItemModal.js @@ -1,32 +1,32 @@ import React, { useState } from 'react'; -import { StyleSheet, TouchableOpacity, View, Text, TextInput, Image, ScrollView, TouchableOpacityComponent } from 'react-native'; +import { StyleSheet, TouchableOpacity, View, Text, TextInput, ScrollView , Image } from 'react-native'; import Modal from 'react-native-modal'; //themes import { COLORS } from '../../../themes/Colors'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; //redux import { useDispatch, useSelector } from 'react-redux'; import { itemAdded, amountUp } from '../../../redux/slices/groceryList/itemsSlice'; import { toggleVisibility } from '../../../redux/slices/groceryList/toggleSlice'; + export default AddItemModal = () => { //redux const modalVisible = useSelector(state => state.toggle.addItemModalVisible); const products = useSelector(state => state.products); - const items = useSelector(state => state.items); - - const dispatch = useDispatch(); + const dispatch = useDispatch(); + let newProductsList = []; for (let i = 0; i < products.length; i++) { newProductsList.push(products[i].productName); } - const [item, setItem] = useState(''); const [focussed, setFocussed] = useState(false); - const [amount, setAmount] = useState(''); + const [amount, setAmount] = useState(); const [person, setPerson] = useState(''); const [details, setDetails] = useState(''); @@ -47,39 +47,29 @@ export default AddItemModal = () => { setItem(text); }; const toggleModal = () => { - if (modalVisible && item !== '' && amount !== '') { - if (products.find((product) => product.productName === item)) { //find if item is in products , else create unregistered item - const productId = products.find((product) => product.productName === item).id; - if(!items.find((itemObj) => itemObj.productId === productId)){ //check if amount up needed - dispatch(itemAdded(productId, amount, person, details)); - } - else{ - } - } - else if (!items.find((itemObj) => itemObj.productId === item)) //unregistered item check for amount up - { - dispatch(itemAdded(item, amount, person, details)); - } - else{ - dispatch(amountUp(item,amount)); - } - setItem(''); - setAmount(); - setPerson(''); - setDetails(''); - setProductsList(products.map((product) => product.productName)); + if (modalVisible && item !== '' && 0 < amount) { + if (products.find((product) => product.productName === item)) { //find if item is in products , else create unregistered item + const productId = products.find((product) => product.productName === item).id; + dispatch(itemAdded(productId, amount, person, details)); } + else { //unregistered + dispatch(itemAdded(item, amount, person, details)); + } + setItem(''); + setAmount(); + setPerson(''); + setDetails(''); + setFocussed(false) + setProductsList(products.map((product) => product.productName)); + } - else { - alert('You should give both a product and an amount'); - } + else { + alert('You should give both a product and an amount'); + } dispatch(toggleVisibility('addItemModalVisible')); }; const closeModal = () => { - setItem(''); - setAmount(); - setPerson(''); - setDetails(''); + setFocussed(false) dispatch(toggleVisibility('addItemModalVisible')); setProductsList(products.map((product) => product.productName)); }; @@ -96,19 +86,22 @@ export default AddItemModal = () => { backgroundColor: COLORS.dp01, }}> + product.productName === item) ? products.find((product) => product.productName === item).image : 'https://icons-for-free.com/iconfiles/png/512/linecon+products+round+icon-1320165923260225670.png'}} + style={styles.image} /> + Add item - + + setProductsList([])} onChangeText={text => filterProductsList(text)} value={item} onFocus={() => setFocussed(true)} onEndEditing={() => setFocussed(false)} /> - + {item ? : } {focussed ? productsList.map((product, index) => @@ -119,7 +112,8 @@ export default AddItemModal = () => { - + + { value={amount} keyboardType={'number-pad'} /> + {amount ? : } - + + { onChangeText={text => setPerson(text)} value={person} /> + {person ? : } - + + { onChangeText={text => setDetails(text)} value={details} /> + {details ? : } - + Cancel { height: '80%', }} /> - + Add item @@ -173,6 +172,18 @@ const styles = StyleSheet.create({ modalAddItem: { margin: 0, }, + image: { + position: 'absolute', + right:0, + top: 0, + bottom: 0, + opacity: 0.04, + width: 200, + + + borderTopLeftRadius: 150, + borderBottomLeftRadius: 150, + }, modalHeaderText: { opacity: 0.9, fontSize: 23, @@ -187,20 +198,20 @@ const styles = StyleSheet.create({ }, modalAddButtonText: { opacity: 0.8, - fontSize: 16, + fontSize: 18, color: COLORS.primary, fontWeight: 'bold', }, modalCloseButtonText: { opacity: 0.8, - fontSize: 16, + fontSize: 18, color: COLORS.primary, fontWeight: 'bold', }, textInput: { height: 25, - width: '80%', + width: '70%', left: 10, borderBottomColor: COLORS.primary + 'aa', @@ -209,12 +220,18 @@ const styles = StyleSheet.create({ marginTop: 10, padding: 0, paddingLeft: 5, - paddingRight: 5, + paddingRight: 25, color: COLORS.textW0, fontSize: 18 }, + inputRow: { + flexDirection: 'row', + alignItems: 'flex-end', + justifyContent: 'flex-start' + }, + dropdown: { zIndex: 1, @@ -232,8 +249,8 @@ const styles = StyleSheet.create({ zIndex: 1, width: 150, minHeight: 20, - borderBottomWidth: 1.2, - borderColor: '#666' + 'e', + borderBottomWidth: 1.0, + borderColor: '#666' + '5', }, dropdownText: { diff --git a/src/components/groceryComponents/modals/AddProductModal.js b/src/components/groceryComponents/modals/AddProductModal.js index e78d5ab..08b8ac2 100644 --- a/src/components/groceryComponents/modals/AddProductModal.js +++ b/src/components/groceryComponents/modals/AddProductModal.js @@ -1,10 +1,13 @@ import React, { useState } from 'react'; import { StyleSheet, TouchableOpacity, View, Text, TextInput, Image, ScrollView } from 'react-native'; + +//dependencies import Modal from 'react-native-modal'; //themes import { COLORS } from '../../../themes/Colors'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; //redux import { useDispatch, useSelector } from 'react-redux'; @@ -23,14 +26,10 @@ export default AddProductModal = () => { //tags const tags = useSelector(state => state.tags) - let newTagsList = []; - for (let i = 0; i < tags.length; i++) { - newTagsList.push(tags[i].tagName); - } - const [tagsList, setTagsList] = useState(newTagsList); - + const [tagsList, setTagsList] = useState(tags.map((listTag) => listTag.tagName)); const [focussed, setFocussed] = useState(false); + const filterTagsList = (text) => { let regex = new RegExp(text); @@ -61,6 +60,7 @@ export default AddProductModal = () => { setProduct(''); setTag(''); setPrice(); + setFocussed(false) setImage('https://icons-for-free.com/iconfiles/png/512/linecon+products+round+icon-1320165923260225670.png'); }; @@ -70,7 +70,9 @@ export default AddProductModal = () => { setTag(); setPrice(); setImage('https://icons-for-free.com/iconfiles/png/512/linecon+products+round+icon-1320165923260225670.png'); + setFocussed(false) dispatch(toggleVisibility('addProductModalVisible')); + }; return ( @@ -83,7 +85,6 @@ export default AddProductModal = () => { borderRadius: 10, padding: 5, backgroundColor: COLORS.dp01, - }}> Add product @@ -108,7 +109,6 @@ export default AddProductModal = () => { placeholderTextColor={'grey'} style={[styles.textInput]} value={tag} - offFocus={() => setTagsList([])} onChangeText={text => filterTagsList(text)} onFocus={() => setFocussed(true)} onEndEditing={() => setFocussed(false)} @@ -136,7 +136,7 @@ export default AddProductModal = () => { placeholder={'Image url'} placeholderTextColor={'grey'} style={styles.textInput} - onChangeText={text => { if (text !== '') { return setImage(text) } }} + onChangeText={text => { if (text !== '') { return setImage(`https://www.googleapis.com/customsearch/v1?key=${product}&cx=017576662512468239146:omuauf_lfve&q=lectures`) } }} /> @@ -168,7 +168,7 @@ export default AddProductModal = () => { const styles = StyleSheet.create({ //modals - modalAddItem: { + modalAddProduct: { margin: 0, }, modalHeaderText: { @@ -236,7 +236,9 @@ const styles = StyleSheet.create({ zIndex: 1, width: 150, - minHeight: 20 + minHeight: 20, + borderBottomWidth: 1.0, + borderColor: '#666' + '5', }, dropdownText: { diff --git a/src/components/groceryComponents/modals/EditItemModal.js b/src/components/groceryComponents/modals/EditItemModal.js new file mode 100644 index 0000000..9eafa9f --- /dev/null +++ b/src/components/groceryComponents/modals/EditItemModal.js @@ -0,0 +1,183 @@ +import React, { useState } from 'react'; +import { StyleSheet, TouchableOpacity, View, Text, TextInput, Image, FlatList, ScrollView } from 'react-native'; +//dependencies +import Modal from 'react-native-modal'; + +//themes +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { COLORS } from '../../../themes/Colors'; + +//redux +import { useDispatch , useSelector} from 'react-redux'; +import { modalToggle, editItem } from '../../../redux/slices/groceryList/itemsSlice'; + +export default EditItemModal = (props) => { + + const item = useSelector(state => state.items.find((item) => item.modalVisible === true)); + let product = useSelector(state => state.products.find((product) => product.id === item.productId)) + if (!product) { + product = { + productName: props.id, + tag: 'uncathegorized', + } + } + + const tags = useSelector(state => state.tags) + let tag = tags.find(tag => tag.tagName === product.tag) + + const dispatch = useDispatch(); + + const [amount, setAmount] = useState(item.amount.toString()); + const [person, setPerson] = useState(item.person); + const [details, setDetails] = useState(item.details); + + const saveModal = () => { + dispatch(modalToggle(item.productId)) + dispatch(editItem(item.productId, amount, person, details)); + } + return ( + dispatch(modalToggle(item.productId))} + swipeDirection={'down'} + onSwipeComplete={saveModal} + useNativeDriverForBackdrop + style={styles.modalEditItem} > + + + + {product.price ? €{product.price} : } + + {tag.tagName} + + {product.productName} + + + + setAmount(text)} + value={amount} + keyboardType={'number-pad'} + /> + + + + setPerson(text)} + value={person} + /> + + + + setDetails(text)} + value={details} + /> + + + Save + + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + borderTopLeftRadius: 10, + borderTopRightRadius: 10, + minHeight: '60%', + backgroundColor: COLORS.dp01, + alignItems: 'center', + }, + modalEditItem: { + justifyContent: 'flex-end', + margin: 0, + padding: 0, + width: '100%', + }, + image: { + marginTop: -70, + width: '50%', + height: 180, + alignSelf: 'flex-start', + borderTopRightRadius: 20, + }, + + textInput: { + + height: 30, + width: '85%', + borderBottomColor: COLORS.primary + 'aa', + borderBottomWidth: 1, + borderRadius: 3, + marginBottom: 10, + padding: 0, + paddingLeft: 5, + paddingRight: 5, + + color: COLORS.textW0 + 'dd', + fontSize: 20 + + }, + inputRow: { + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'flex-start', + width: '100%', + marginLeft: 5, + }, + textItem: { + width: '100%', + alignSelf: 'flex-start', + padding: 5, + paddingLeft: 15, + + opacity: 0.9, + fontSize: 24, + color: COLORS.textW0 + 'ff', + backgroundColor: '#000' + '3', + }, + textPrice: { + paddingLeft: '52%', + position: 'absolute', + fontSize: 18, + color: COLORS.textW1, + backgroundColor: '#000' + '5', + padding: 5, + width: '100%', + borderTopRightRadius: 10, + }, + textTags: { + left: 8, + marginTop: 35, + fontFamily: 'Roboto', + fontSize: 18, + color: COLORS.textW1, + }, + + modalSaveButtonText: { + opacity: 0.8, + fontSize: 20, + color: COLORS.primary, + fontWeight: 'bold', + }, + modalSaveButton:{ + marginTop: 10, + marginBottom: 10, + } + + +}) \ No newline at end of file diff --git a/src/components/groceryComponents/modals/EditProductModal.js b/src/components/groceryComponents/modals/EditProductModal.js new file mode 100644 index 0000000..3309f1c --- /dev/null +++ b/src/components/groceryComponents/modals/EditProductModal.js @@ -0,0 +1,171 @@ +import React, { useState } from 'react'; +import { StyleSheet, TouchableOpacity, View, Text, TextInput, Image, FlatList, ScrollView } from 'react-native'; +//dependencies +import Modal from 'react-native-modal'; + +//themes +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { COLORS } from '../../../themes/Colors'; + +//redux +import { useDispatch, useSelector } from 'react-redux'; +import { modalToggle, editProduct } from '../../../redux/slices/groceryList/productsSlice'; + +export default EditProductModal = () => { + + const product = useSelector(state => state.products.find((product) => product.modalVisible === true)); + const dispatch = useDispatch(); + const tags = useSelector(state => state.tags) + let tag = tags.find(tag => tag.tagName === product.tag) + + const [productName, setProductName] = useState(product.productName); + const [tagName, setTagName] = useState(product.tag); + const [price, setPrice] = useState(product.price.toString()); + const [imageURL, setImageURL] = useState(product.image); + + const saveModal = () => { + dispatch(editProduct(product.id, productName, tagName, price, imageURL)); + dispatch(modalToggle(product.id)) + } + return ( + dispatch(modalToggle(product.id))} + swipeDirection={'down'} + onSwipeComplete={saveModal} + useNativeDriverForBackdrop + style={styles.modalEditItem} > + + + + + + setProductName(text)} + value={productName} + /> + + + + + setTagName(text)} + value={tagName} + /> + + + + setPrice(text)} + keyboardType={'number-pad'} + value={price} + /> + + + Save + + + + + + ) +} + +const styles = StyleSheet.create({ + container: { + borderTopLeftRadius: 10, + borderTopRightRadius: 10, + minHeight: '60%', + backgroundColor: COLORS.dp01, + alignItems: 'center', + }, + modalEditItem: { + justifyContent: 'flex-end', + margin: 0, + padding: 0, + width: '100%', + }, + image: { + marginTop: -70, + width: '50%', + height: 180, + alignSelf: 'flex-start', + borderTopRightRadius: 20, + }, + + textInput: { + + height: 30, + width: '85%', + borderBottomColor: COLORS.primary + 'aa', + borderBottomWidth: 1, + borderRadius: 3, + marginBottom: 10, + padding: 0, + paddingLeft: 5, + paddingRight: 5, + + color: COLORS.textW0 + 'dd', + fontSize: 20 + + }, + inputRow: { + flexDirection: 'row', + alignItems: 'flex-start', + justifyContent: 'flex-start', + width: '100%', + marginLeft: 5, + }, + textItem: { + width: '100%', + height: 40, + alignSelf: 'flex-start', + padding: 5, + paddingLeft: 15, + + opacity: 0.9, + fontSize: 24, + color: COLORS.textW0 + 'ff', + backgroundColor: '#000' + '3', + }, + textPrice: { + paddingLeft: '52%', + position: 'absolute', + fontSize: 18, + color: COLORS.textW1, + backgroundColor: '#000' + '5', + padding: 5, + width: '100%', + borderTopRightRadius: 10, + }, + textTags: { + left: 8, + marginTop: 35, + fontFamily: 'Roboto', + fontSize: 18, + color: COLORS.textW1, + }, + + modalSaveButtonText: { + opacity: 0.8, + fontSize: 20, + color: COLORS.primary, + fontWeight: 'bold', + }, + modalSaveButton: { + marginTop: 10, + marginBottom: 10, + } + + +}) \ No newline at end of file diff --git a/src/redux/slices/groceryList/itemsSlice.js b/src/redux/slices/groceryList/itemsSlice.js index 95d07c5..8a160a8 100644 --- a/src/redux/slices/groceryList/itemsSlice.js +++ b/src/redux/slices/groceryList/itemsSlice.js @@ -4,10 +4,11 @@ const initialState = [ { amount: 5, person: 'Wolf', - details: 'margarita', + details: 'Gezout', productId: '20fdc79cde62', checked: false, + modalVisible: false, }, { amount: 12, @@ -15,6 +16,7 @@ const initialState = [ productId: '20fdc79cde63', checked: false, + modalVisible: false, }, { amount: 2, @@ -22,14 +24,25 @@ const initialState = [ productId: '20fdc79cde64', checked: false, + modalVisible: false, }, { - amount: 2, + amount: 4, person: 'Storm', - details: 'Gezout', + details: 'margarita', productId: '20fdc79cde60', checked: false, + modalVisible: false, + }, + { + amount: 3, + person: 'Wolfje', + details: 'Aardbei', + + productId: 'Jam', + checked: false, + modalVisible: false, }, ] @@ -40,7 +53,13 @@ const itemsSlice = createSlice({ reducers: { itemAdded: { reducer(state, action) { - state.push(action.payload); + let item = state.find((item) => item.productId === action.payload.productId) + if (item) { + item.amount += action.payload.amount; + } + else { + state.push(action.payload); + } }, prepare(productId, amount, person, details) { return { @@ -50,6 +69,7 @@ const itemsSlice = createSlice({ details: details, productId: productId, + modalVisible: false, checked: false, } } @@ -57,7 +77,8 @@ const itemsSlice = createSlice({ }, checkToggle: { reducer(state, action) { - state.find((item) => item.productId === action.payload.productId).checked = !state.find((item) => item.productId === action.payload.productId).checked; + let item = state.find((item) => item.productId === action.payload.productId) + item.checked = !item.checked }, prepare(productId) { return { @@ -67,25 +88,48 @@ const itemsSlice = createSlice({ } } }, - amountUp: { + modalToggle: { reducer(state, action) { - state.find((item) => item.productId === action.payload.id).amount += Number(action.payload.amount); + let item = state.find((item) => item.productId === action.payload.productId) + if (!state.find((item) => item.modalVisible === true) || item.modalVisible) { + item.modalVisible = !item.modalVisible; + } }, - prepare(id, amount) { + prepare(productId) { return { payload: { - id, - amount, + productId: productId } } } }, - removeCheckedItems(state){ - return state.filter(item=>item.checked === false) + removeCheckedItems(state) { + return state.filter(item => item.checked === false) + }, + editItem: { + reducer(state, action) { + const { productId, amount, person, details } = action.payload + let item = state.find((item) => item.productId === productId) + item.productId = productId + item.amount = amount + item.person = person + item.details = details + }, + prepare(productId, amount, person, details) { + return { + payload: { + amount: Number(amount), + person: person, + details: details, + + productId: productId, + } + } + } } }, }); -export const { itemAdded, checkToggle, amountUp, removeCheckedItems } = itemsSlice.actions; +export const { itemAdded, checkToggle, modalToggle, removeCheckedItems, editItem } = itemsSlice.actions; export default itemsSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/groceryList/productsSlice.js b/src/redux/slices/groceryList/productsSlice.js index e5b460c..873925e 100644 --- a/src/redux/slices/groceryList/productsSlice.js +++ b/src/redux/slices/groceryList/productsSlice.js @@ -1,4 +1,4 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice, nanoid } from '@reduxjs/toolkit'; const initialState = [ { @@ -9,6 +9,7 @@ const initialState = [ nutrients: [{ calories: 120 }, { proteins: 20 }], + modalVisible: false, checked: false, id: '20fdc79cde60', }, @@ -18,6 +19,7 @@ const initialState = [ image: 'https://www.kitchensanctuary.com/wp-content/uploads/2020/10/Lasagne-square-FS-79.jpg', price: 1.99, + modalVisible: false, checked: false, id: '20fdc79cde64', }, @@ -28,19 +30,54 @@ const initialState = [ nutrients: [{ calories: 120 }, { proteins: 20 }], price: 1.39, + modalVisible: false, checked: false, id: '20fdc79cde63', }, { - productName: 'Gedroogde worstjes', - tag: 'Snack', + productName: 'Dried sausages', + tag: 'Meat', image: 'https://www.aldi.be/content/aldi/belgium/promotions/source-localenhancement/2019/2019-01/2019-01-02/vast_assortiment/1308/1/0/_jcr_content/assets/imported-images/BILD_INTERNET2/1308_PRIMARY_0_nl-fr-de_Mini_sticks_100_g_Panda_GROEP.png/_jcr_content/renditions/opt.1250w.png.res/1590539744886/opt.1250w.png', price: 1.79, + modalVisible: false, checked: false, id: '20fdc79cde62', }, + { + productName: 'Minced meat', + tag: 'Meat', + image: 'https://www.aldi.nl/content/dam/aldi/netherlands/offers/weekactie/2020/wk19/1387_01.png/_jcr_content/renditions/opt.1250w.png.res/1587557386020/opt.1250w.png', + price: 3.99, + + + modalVisible: false, + checked: false, + id: '20adc79cde61', + }, + { + productName: 'Veggie soup', + tag: 'Vegetables & fruit', + image: 'https://www.cookingclassy.com/wp-content/uploads/2014/10/vegetable-soup-7.jpg', + price: 1.49, + + + modalVisible: false, + checked: false, + id: '20fdc29cde61', + }, + { + productName: 'Dorito\'s', + tag: 'Snack', + image: 'https://40aprons.com/wp-content/uploads/2020/09/taco-bell-nacho-cheese-sauce-recipe-4.jpg', + price: 1.12, + + + modalVisible: false, + checked: false, + id: '29fdc29cde61', + }, { productName: 'Berliner ball', tag: 'Snack', @@ -48,8 +85,9 @@ const initialState = [ price: 1.79, + modalVisible: false, checked: false, - id: '20fdc79cde61', + id: '20fdc71cde61', }, ] @@ -61,24 +99,49 @@ const productsSlice = createSlice({ reducer(state, action) { state.push(action.payload); }, - prepare(productName, tag, price, image, nutrients) { + prepare(productName, tag, price, image) { return { payload: { productName, tag, - price, + price: Number(price), image, - nutrients, - id: Math.floor((1 + Math.random()) * 0x1000000000000).toString(16).substring(1), + modalVisible: false, checked: false, + + id: nanoid(), + } + } + } + }, + editProduct: { + reducer(state, action) { + const { productId, productName, tag, price, image } = action.payload + let product = state.find((product) => product.id === productId) + + product.productName = productName + product.tag = tag + product.price = price + product.image = image + }, + prepare(productId, productName, tag, price, image) { + return { + payload: { + productId: productId, + + productName: productName, + tag: tag, + price: price, + image: image } } } }, checkToggle: { reducer(state, action) { - state.find((product) => product.id === action.payload.id).checked = !state.find((product) => product.id === action.payload.id).checked; + let product = state.find((product) => product.id === action.payload.id) + product.checked = !product.checked; }, prepare(id) { return { @@ -88,9 +151,24 @@ const productsSlice = createSlice({ } } }, + modalToggle: { + reducer(state, action) { + let product = state.find((product) => product.id === action.payload.productId) + if (!state.find((nProduct) => nProduct.modalVisible === true) || product.modalVisible) { + product.modalVisible = !product.modalVisible; + } + }, + prepare(productId) { + return { + payload: { + productId: productId + } + } + } + }, }, }); -export const { productAdded, checkToggle } = productsSlice.actions; +export const { productAdded, editProduct, checkToggle, modalToggle } = productsSlice.actions; export default productsSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/groceryList/storageSlice.js b/src/redux/slices/groceryList/storageSlice.js index 6f0a683..fbcfe86 100644 --- a/src/redux/slices/groceryList/storageSlice.js +++ b/src/redux/slices/groceryList/storageSlice.js @@ -1,13 +1,60 @@ -import {createSlice} from '@reduxjs/toolkit' +import { createSlice } from '@reduxjs/toolkit' const initialState = [ - + { id: '20fdc79cde60', amount: 5 }, ] const storageSlice = createSlice({ name: 'storage', initialState, - reducers:{ - - } -}) \ No newline at end of file + reducers: { + pushItems: { + reducer(state, action) { + action.payload.items.forEach((item) => { + let storageItem = state.find((storageItem) => storageItem.id === item.productId) + if (storageItem) { + state.find((storageItem) => storageItem.id === item.productId).amount += item.amount + } + else { + state.push({ id: item.productId, amount: item.amount }) + } + }) + }, + prepare(items) { + return { + payload: { + items, + } + } + } + }, + changeAmount: { + reducer(state, action) { + state.find((storageItem) => storageItem.id === action.payload.id).amount += action.payload.difference + }, + prepare(id, difference) { + return { + payload: { + id, + difference, + } + } + } + }, + removeItem: { + reducer(state, action) { + state.splice(state.findIndex((storageItem) => storageItem.id === action.payload.id),1) + }, + prepare(id) { + return { + payload: { + id, + } + } + } + } + }, +}) + +export const { pushItems, changeAmount, removeItem } = storageSlice.actions +export default storageSlice.reducer \ No newline at end of file diff --git a/src/redux/slices/groceryList/tagsSlice.js b/src/redux/slices/groceryList/tagsSlice.js index cc3579b..2877399 100644 --- a/src/redux/slices/groceryList/tagsSlice.js +++ b/src/redux/slices/groceryList/tagsSlice.js @@ -1,9 +1,9 @@ import { createSlice } from '@reduxjs/toolkit'; const initialState = [ - { tagName: 'no-tag', color: '#253f34' }, + { tagName: 'uncathegorized', color: '#253f34' }, { tagName: 'Meat', color: '#ef5350' }, - { tagName: 'Vegetable', color: '#BDD684' }, + { tagName: 'Vegetables & fruit', color: '#BDD684' }, { tagName: 'Meal', color: '#FA9600' }, { tagName: 'Snack', color: '#9575CD' }, { tagName: 'Instant', color: '#86CAC5' }, diff --git a/src/redux/slices/groceryList/toggleSlice.js b/src/redux/slices/groceryList/toggleSlice.js index d9640d2..91bdbe8 100644 --- a/src/redux/slices/groceryList/toggleSlice.js +++ b/src/redux/slices/groceryList/toggleSlice.js @@ -3,6 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'; const initialState = { addItemModalVisible: false, addProductModalVisible: false, + popupVisibility: false, } const toggleSlice = createSlice({ diff --git a/src/redux/store.js b/src/redux/store.js index df850a5..6528b31 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -2,8 +2,10 @@ import { configureStore } from '@reduxjs/toolkit'; import itemsReducer from './slices/groceryList/itemsSlice'; import productsReducer from './slices/groceryList/productsSlice'; -import toggleReducer from './slices/groceryList/toggleSlice'; +import storageReducer from './slices/groceryList/storageSlice'; + import tagsReducer from './slices/groceryList/tagsSlice'; +import toggleReducer from './slices/groceryList/toggleSlice'; @@ -11,6 +13,8 @@ export default configureStore({ reducer: { items: itemsReducer, products: productsReducer, + storage: storageReducer, + tags: tagsReducer, toggle: toggleReducer, }, diff --git a/src/screens/storageManagement/GroceryList.js b/src/screens/storageManagement/GroceryList.js index 7f3e3b6..5e61371 100644 --- a/src/screens/storageManagement/GroceryList.js +++ b/src/screens/storageManagement/GroceryList.js @@ -1,14 +1,13 @@ import React, { useRef, useEffect, useState } from 'react'; -import { StyleSheet, View, ScrollView, Text, Animated, TouchableOpacity, Image } from 'react-native'; +import { StyleSheet, View, FlatList, Text, Animated, TouchableOpacity, Image } from 'react-native'; import { useHeaderHeight } from '@react-navigation/stack'; //dependencies\ //components import { AddItemButton, PushItemsToStorageButton, RemoveItemsButton, AddNewRecipeButton } from '../../components/groceryComponents/GroceryListButtons'; -import { ListedItem } from '../../components/groceryComponents/ListedItem'; +import ListedItem from '../../components/groceryComponents/ListedItem'; //redux - import { useSelector } from 'react-redux'; //themes @@ -98,25 +97,36 @@ const AnimButtons = () => { function GroceryList() { - const items = useSelector(state => state.items); + const products = useSelector(state=> state.products) - const itemList = items.map((item, index) => { - return - }); + const compareFunction = (a,b) => { + const newA = products.find((product=>product.id === a.productId)) + let tagA + newA ? tagA = newA.tag : tagA = "uncathegorized" + const newB = products.find((product=>product.id === b.productId)) + let tagB + newB ? tagB = newB.tag : tagB = "uncathegorized" + return tagA.localeCompare(tagB) + } + + const items = useSelector(state => state.items).slice().sort(compareFunction); return ( - {/* Scroll */} - - - - {itemList} - - - + } + ListFooterComponent={} + initialNumToRender={10} + maxToRenderPerBatch={10} + data={items} + renderItem={({ item }) => ( + + )} + keyExtractor={(item, index) => { + return index.toString(); + }} + /> + {items.find(item => item.modalVisible === true) ? : } {/* buttons */} @@ -145,4 +155,4 @@ const styles = StyleSheet.create({ bottom: 10, } }); -export default GroceryList; \ No newline at end of file +export default React.memo(GroceryList); \ No newline at end of file diff --git a/src/screens/storageManagement/Products.js b/src/screens/storageManagement/Products.js index 1d9d5bf..b1ffdda 100644 --- a/src/screens/storageManagement/Products.js +++ b/src/screens/storageManagement/Products.js @@ -1,11 +1,11 @@ import React from 'react'; -import { StyleSheet, View, ScrollView, Text } from 'react-native'; +import { StyleSheet, View, FlatList } from 'react-native'; import { useHeaderHeight } from '@react-navigation/stack'; //dependencies\ //components import { AddProductButton } from '../../components/groceryComponents/ProductButtons'; -import { ListedProduct } from '../../components/groceryComponents/ListedProduct'; +import ListedProduct from '../../components/groceryComponents/ListedProduct'; import { COLORS } from '../../themes/Colors'; @@ -14,23 +14,24 @@ import { useSelector } from 'react-redux'; function Products() { - const products = useSelector(state => state.products) - const productsList = products.map((product, index) => { - return - }); + const products = useSelector(state => state.products).slice().sort((a,b)=> a.tag.localeCompare(b.tag)) return ( {/* Scroll */} - - - - {productsList} - - - + } + ListFooterComponent={} + initialNumToRender={10} + maxToRenderPerBatch={10} + data={products} + renderItem={({ item, index }) => { + return + }} + keyExtractor={(item, index) => { + return index.toString(); + }} + /> + {products.find(product=>product.modalVisible === true) ? : } {/* buttons */} @@ -56,4 +57,4 @@ const styles = StyleSheet.create({ bottom: 10, } }); -export default Products; \ No newline at end of file +export default React.memo(Products); \ No newline at end of file diff --git a/src/screens/storageManagement/Storage.js b/src/screens/storageManagement/Storage.js index a572868..36ad719 100644 --- a/src/screens/storageManagement/Storage.js +++ b/src/screens/storageManagement/Storage.js @@ -1,26 +1,41 @@ import React from 'react'; import { StyleSheet, View, ScrollView, Text } from 'react-native'; -//dependencies\ -//components -import AddItemButton from '../../components/groceryComponents/GroceryListButtons'; -import { COLORS } from '../../themes/Colors'; +//dependencies +import { useHeaderHeight } from '@react-navigation/stack'; +//components +import StorageList from '../../components/groceryComponents/StorageList'; + +import { COLORS } from '../../themes/Colors'; +//redux +import { useSelector } from 'react-redux'; function Storage() { + const tags = useSelector(state => state.tags) return ( + + + {tags.map((tag, index)=>{ + return + })} + + ); } const styles = StyleSheet.create({ background: { - flex:1, - backgroundColor: COLORS.dp00, + flex: 1, + backgroundColor: COLORS.dp00, } }); export default Storage; \ No newline at end of file diff --git a/src/themes/Colors.js b/src/themes/Colors.js index b4d9f86..af462e4 100644 --- a/src/themes/Colors.js +++ b/src/themes/Colors.js @@ -12,11 +12,12 @@ export const COLORS = { secondary: '#f2aeac', //secondary secondaryVar: '#ffffff', //secondary variant + price: '#C6A337', //layout colors #DAE9FF dp00: '#0C0F12', //0% - dp01: '#161a1e', //5% + dp01: '#121518', //5% dp02: '#1a1e23', //7% dp03: '#1f2225', //8% dp04: '#1c2025', //9%