From fd80356251d3382f4f1b5d41764853a26384c1f3 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Mon, 29 Jan 2024 17:01:39 -0300 Subject: [PATCH 01/21] chore: add gorhom/bottom-sheet lib --- package-lock.json | 46 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 3 ++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85a6d44..89dd431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@expo-google-fonts/roboto": "^0.2.3", + "@gorhom/bottom-sheet": "^4.6.0", "@hookform/resolvers": "^3.3.2", "@react-native-community/datetimepicker": "7.2.0", "@react-navigation/bottom-tabs": "^6.5.11", @@ -24,7 +25,7 @@ "react": "18.2.0", "react-hook-form": "^7.49.2", "react-native": "0.72.6", - "react-native-gesture-handler": "^2.14.0", + "react-native-gesture-handler": "~2.12.0", "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", @@ -3298,6 +3299,43 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" }, + "node_modules/@gorhom/bottom-sheet": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-4.6.0.tgz", + "integrity": "sha512-XgNflkhATUqTIiMDGuLaQZAtjUzcrhGOEJGHT+7Tou1ctTMb958YRGGnU9KFo5TkD6YCZcfWfxHPi9F0FF+DjA==", + "dependencies": { + "@gorhom/portal": "1.0.14", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-native": "*", + "react": "*", + "react-native": "*", + "react-native-gesture-handler": ">=1.10.1", + "react-native-reanimated": ">=2.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-native": { + "optional": true + } + } + }, + "node_modules/@gorhom/portal": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz", + "integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==", + "dependencies": { + "nanoid": "^3.3.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -12510,9 +12548,9 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz", - "integrity": "sha512-cOmdaqbpzjWrOLUpX3hdSjsMby5wq3PIEdMq7okJeg9DmCzanysHSrktw1cXWNc/B5MAgxAn9J7Km0/4UIqKAQ==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.1.tgz", + "integrity": "sha512-deqh36bw82CFUV9EC4tTo2PP1i9HfCOORGS3Zmv71UYhEZEHkzZv18IZNPB+2Awzj45vLIidZxGYGFxHlDSQ5A==", "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", diff --git a/package.json b/package.json index 3b11ed9..7be31c8 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@expo-google-fonts/roboto": "^0.2.3", + "@gorhom/bottom-sheet": "^4.6.0", "@hookform/resolvers": "^3.3.2", "@react-native-community/datetimepicker": "7.2.0", "@react-navigation/bottom-tabs": "^6.5.11", @@ -26,7 +27,7 @@ "react": "18.2.0", "react-hook-form": "^7.49.2", "react-native": "0.72.6", - "react-native-gesture-handler": "^2.14.0", + "react-native-gesture-handler": "~2.12.0", "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", From 462c43591775619ec0ea9fec7e2fd9f8d9d63073 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Mon, 29 Jan 2024 17:03:02 -0300 Subject: [PATCH 02/21] feat: create bottomSheetFilter and filterMenu components --- .../bottomSheetFilter/filterMenu/index.tsx | 16 +++++++ .../bottomSheetFilter/filterMenu/styles.ts | 38 +++++++++++++++ .../components/bottomSheetFilter/index.tsx | 46 +++++++++++++++++++ .../components/bottomSheetFilter/styles.ts | 17 +++++++ 4 files changed, 117 insertions(+) create mode 100644 src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx create mode 100644 src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts create mode 100644 src/screens/home/components/bottomSheetFilter/index.tsx create mode 100644 src/screens/home/components/bottomSheetFilter/styles.ts diff --git a/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx b/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx new file mode 100644 index 0000000..39e35a0 --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx @@ -0,0 +1,16 @@ +import { FilterButton, FilterIcon, FilterMenuContainer, FilterText } from "./styles"; + +interface FilterMenuProps { + trainningsCount: number; + onButtonPress: () => void; +} + +export const FilterMenu = ({ trainningsCount, onButtonPress }: FilterMenuProps) => ( + + {trainningsCount} Trainnings + + + Filters + + +); diff --git a/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts b/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts new file mode 100644 index 0000000..c3e5285 --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts @@ -0,0 +1,38 @@ +import styled from "styled-components/native"; +import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; + +export const FilterMenuContainer = styled.View` + flex-direction: row; + justify-content: space-around; + align-items: center; + margin-top: 12px; +`; + +interface FilterButtonProps { + buttonText?: boolean; +} + +export const FilterText = styled.Text.attrs(() => ({ + maxFontSizeMultiplier: 1, +}))` + font-size: 16px; + font-family: ${(props) => props.theme.fonts.Medium}; + + color: ${(props) => (props.buttonText ? props.theme["primary"] : props.theme["gray-350"])}; +`; + +export const FilterButton = styled.TouchableOpacity` + flex-direction: row; + align-items: center; + gap: 8px; + + padding: 8px 24px; + + border-radius: 6px; + background-color: ${(props) => props.theme["secondary"]}; +`; + +export const FilterIcon = styled(MaterialCommunityIcons)` + font-size: 28px; + color: ${(props) => props.theme["primary"]}; +`; diff --git a/src/screens/home/components/bottomSheetFilter/index.tsx b/src/screens/home/components/bottomSheetFilter/index.tsx new file mode 100644 index 0000000..9764c8b --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/index.tsx @@ -0,0 +1,46 @@ +import { Text, TouchableOpacity } from "react-native"; +import { useTheme } from "styled-components/native"; +import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "react"; +import BottomSheet, { BottomSheetBackdrop, BottomSheetScrollView } from "@gorhom/bottom-sheet"; + +import { ApplyFilterButton, ApplyFilterButtonText } from "./styles"; + +export const BottomSheetFilter = forwardRef((_, ref) => { + const { "gray-150": bottomSheetBackground, "primary-lighter": indicatorBackground } = useTheme(); + + const bottomSheetRef = useRef(null); + const bottomSheetSnapPoints = useMemo(() => ["75%"], []); + + const renderBackdrop = useCallback( + (props: any) => , + [], + ); + + useImperativeHandle(ref, () => ({ + openBottomSheetFilter: () => { + bottomSheetRef.current?.expand(); + }, + })); + + return ( + + + DAYS + Last 7 days + Last 15 days + Last 30 days + + bottomSheetRef.current?.close()} activeOpacity={0.9}> + Apply + + + ); +}); diff --git a/src/screens/home/components/bottomSheetFilter/styles.ts b/src/screens/home/components/bottomSheetFilter/styles.ts new file mode 100644 index 0000000..72a4d1d --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/styles.ts @@ -0,0 +1,17 @@ +import styled from "styled-components/native"; + +export const ApplyFilterButton = styled.TouchableOpacity` + align-items: center; + width: 80%; + margin: 0 auto 28px; + padding: 14px; + border-radius: 99px; + background-color: ${(props) => props.theme["primary-lighter"]}; +`; + +export const ApplyFilterButtonText = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1 }))` + font-size: 16px; + font-family: ${(props) => props.theme.fonts.Bold}; + + color: ${(props) => props.theme["primaryA"]}; +`; From ac74df185bb2ded2f9b80bb253b45f4724c60734 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Mon, 29 Jan 2024 17:03:30 -0300 Subject: [PATCH 03/21] feat: refactor homeScreen and add HomeScreenContent component --- src/components/trainningCard/styles.ts | 2 +- src/screens/home/homeScreenContent.tsx | 31 ++++++++++++++++++++++++ src/screens/home/index.tsx | 33 +++++++++++++++++--------- 3 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 src/screens/home/homeScreenContent.tsx diff --git a/src/components/trainningCard/styles.ts b/src/components/trainningCard/styles.ts index 5d5b969..0bf0716 100644 --- a/src/components/trainningCard/styles.ts +++ b/src/components/trainningCard/styles.ts @@ -19,7 +19,7 @@ export const TrainningCardContainer = styled(Animated.View)` border: 1px solid transparent; border-radius: 16px; - elevation: 12; + elevation: 4; background-color: ${(props) => props.theme.primaryB}; `; diff --git a/src/screens/home/homeScreenContent.tsx b/src/screens/home/homeScreenContent.tsx new file mode 100644 index 0000000..fc4194f --- /dev/null +++ b/src/screens/home/homeScreenContent.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +import { TrainningCardList } from "./components/trainningCardList"; +import { EmptyCardList } from "./components/emptytrainningCardList"; +import { FilterMenu } from "./components/bottomSheetFilter/filterMenu"; + +import { NewWorkoutFormFieldsProps } from "../../models/newWorkoutFormFieldsModel"; +import { ListOfTrainningCardSkeleton } from "../../components/trainningCardSkeleton"; + +interface HomeScreenContentProps { + trainnings: NewWorkoutFormFieldsProps[]; + isLoading: boolean; + onFilterButtonPress: () => void; +} + +export const HomeScreenContent = ({ + trainnings, + isLoading, + onFilterButtonPress, +}: HomeScreenContentProps) => { + if (trainnings.length === 0) return ; + + if (isLoading) return ; + + return ( + <> + + + + ); +}; diff --git a/src/screens/home/index.tsx b/src/screens/home/index.tsx index bfc6ea5..db4121e 100644 --- a/src/screens/home/index.tsx +++ b/src/screens/home/index.tsx @@ -1,24 +1,25 @@ -import { useCallback } from "react"; import { useUnit } from "effector-react"; +import { useCallback, useRef } from "react"; import { useFocusEffect, useIsFocused } from "@react-navigation/native"; import { GetAllTrainningsUseCase } from "../../useCases/getAllTrainningsUseCase"; import GetTrainningsStore from "../../stores/getTrainningsStore/getTrainningsStore"; -import { ListOfTrainningCardSkeleton } from "../../components/trainningCardSkeleton"; -import { TrainningCardList } from "./components/trainningCardList"; -import { EmptyCardList } from "./components/emptytrainningCardList"; +import { HomeScreenContent } from "./homeScreenContent"; +import { BottomSheetFilter } from "./components/bottomSheetFilter"; + +type BottomSheetHandle = { + openBottomSheetFilter: () => void; +}; export const HomeScreen = () => { + const bottomSheetRef = useRef(null); + const { trainnings, isLoading } = useUnit(GetTrainningsStore); const isFocused = useIsFocused(); - const handleHomeScreenLoading = () => { - if (trainnings.length == 0) return ; - - if (isLoading) return ; - - return ; + const handleButtonPress = () => { + bottomSheetRef.current?.openBottomSheetFilter(); }; useFocusEffect( @@ -27,5 +28,15 @@ export const HomeScreen = () => { }, [isFocused]), ); - return handleHomeScreenLoading(); + return ( + <> + + + + + ); }; From fbf18a23106adce4b8f316d9faeea2dc6a650750 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 7 Feb 2024 16:38:08 -0300 Subject: [PATCH 04/21] chore: add react native paper lib --- package-lock.json | 114 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89dd431..1eef276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,8 +26,9 @@ "react-hook-form": "^7.49.2", "react-native": "0.72.6", "react-native-gesture-handler": "~2.12.0", + "react-native-paper": "^5.12.3", "react-native-reanimated": "~3.3.0", - "react-native-safe-area-context": "4.6.3", + "react-native-safe-area-context": "^4.6.3", "react-native-screens": "~3.22.0", "styled-components": "^5.3.10", "yup": "^1.3.3" @@ -2014,6 +2015,26 @@ "node": ">=6.9.0" } }, + "node_modules/@callstack/react-theme-provider": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@callstack/react-theme-provider/-/react-theme-provider-3.0.9.tgz", + "integrity": "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA==", + "dependencies": { + "deepmerge": "^3.2.0", + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@callstack/react-theme-provider/node_modules/deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -12563,6 +12584,31 @@ "react-native": "*" } }, + "node_modules/react-native-paper": { + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.12.3.tgz", + "integrity": "sha512-nH1e1pGPE/aOE5YR2GRX7CfMHFA9cAfrAfgCtwL4amJPDZCoVjc5yt2VDiUE1rT+JUfk0qdICMP3UggxvjMgug==", + "dependencies": { + "@callstack/react-theme-provider": "^3.0.9", + "color": "^3.1.2", + "use-latest-callback": "^0.1.5" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-safe-area-context": "*", + "react-native-vector-icons": "*" + } + }, + "node_modules/react-native-paper/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/react-native-reanimated": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.3.0.tgz", @@ -12606,6 +12652,72 @@ "react-native": "*" } }, + "node_modules/react-native-vector-icons": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.3.tgz", + "integrity": "sha512-ZgVlV5AdQgnPHHvBEihGf2xwyziT1acpXV1U+WfCgCv3lcEeCRsmwAsBU+kUSNsU+8TcWVsX04kdI6qUaS8D7w==", + "peer": true, + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/react-native/node_modules/promise": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", diff --git a/package.json b/package.json index 7be31c8..0f9bab0 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ "react-hook-form": "^7.49.2", "react-native": "0.72.6", "react-native-gesture-handler": "~2.12.0", + "react-native-paper": "^5.12.3", "react-native-reanimated": "~3.3.0", - "react-native-safe-area-context": "4.6.3", + "react-native-safe-area-context": "^4.6.3", "react-native-screens": "~3.22.0", "styled-components": "^5.3.10", "yup": "^1.3.3" From 348a4f5a5e06304c98a75594dd9ebc8394a2e7bf Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 7 Feb 2024 16:38:53 -0300 Subject: [PATCH 05/21] feat: create Radio Button component --- .../bottomSheetFilter/radioButton/index.tsx | 37 +++++++++++++++++++ .../bottomSheetFilter/radioButton/styles.ts | 13 +++++++ 2 files changed, 50 insertions(+) create mode 100644 src/screens/home/components/bottomSheetFilter/radioButton/index.tsx create mode 100644 src/screens/home/components/bottomSheetFilter/radioButton/styles.ts diff --git a/src/screens/home/components/bottomSheetFilter/radioButton/index.tsx b/src/screens/home/components/bottomSheetFilter/radioButton/index.tsx new file mode 100644 index 0000000..c7489e1 --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/radioButton/index.tsx @@ -0,0 +1,37 @@ +import { Controller } from "react-hook-form"; +import { RadioButton } from "react-native-paper"; + +import { RadioButtonContainer, RadioButtonLabel } from "./styles"; + +interface RadioButtonControllerProps { + control: any; + name: string; + value: string; + label: string; + setValue: (name: string, value: string) => void; +} + +export const RadioButtonController = ({ + control, + name, + value, + label, + setValue, +}: RadioButtonControllerProps) => { + return ( + ( + + setValue(name, value)} + /> + {label} + + )} + /> + ); +}; diff --git a/src/screens/home/components/bottomSheetFilter/radioButton/styles.ts b/src/screens/home/components/bottomSheetFilter/radioButton/styles.ts new file mode 100644 index 0000000..30b291a --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/radioButton/styles.ts @@ -0,0 +1,13 @@ +import styled from "styled-components/native"; + +export const RadioButtonContainer = styled.View` + flex-direction: row; + align-items: center; +`; + +export const RadioButtonLabel = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1 }))` + font-size: 16px; + font-family: ${(props) => props.theme.fonts.Regular}; + + color: ${(props) => props.theme["primaryA"]}; +`; From c0c47d28c6418e83f028060f6f76e3c8e6456165 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 7 Feb 2024 16:39:27 -0300 Subject: [PATCH 06/21] feat: create FilterHead component --- .../bottomSheetFilter/filterHeader/index.tsx | 27 ++++++++++++++++ .../bottomSheetFilter/filterHeader/styles.ts | 31 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx create mode 100644 src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts diff --git a/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx b/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx new file mode 100644 index 0000000..fadfc63 --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { + BottomSheetFilterHeader, + BottomSheetFilterIconContainer, + BottomSheetFilterIcon, + BottomSheetFilterIconText, +} from "./styles"; +import { TouchableOpacity } from "react-native"; + +interface FilterHeaderProps { + handleCloseBottomSheet: () => void; +} + +export const FilterHeader = ({ handleCloseBottomSheet }: FilterHeaderProps) => { + return ( + + + + Filter + + + + + + + ); +}; diff --git a/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts b/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts new file mode 100644 index 0000000..7eaa114 --- /dev/null +++ b/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts @@ -0,0 +1,31 @@ +import styled from "styled-components/native"; +import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; + +export const BottomSheetFilterHeader = styled.View` + flex-direction: row; + justify-content: space-between; + align-items: center; + + margin-bottom: 32px; +`; + +export const BottomSheetFilterIconContainer = styled.View` + flex-direction: row; + align-items: center; + gap: 6px; +`; + +export const BottomSheetFilterIcon = styled(MaterialCommunityIcons)` + font-size: 28px; + color: ${(props) => props.theme["primary"]}; +`; + +export const BottomSheetFilterIconText = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1 }))` + font-size: 16px; + font-family: ${(props) => props.theme.fonts.Regular}; + + text-transform: uppercase; + letter-spacing: 0.5px; + + color: ${(props) => props.theme["primaryA"]}; +`; From 78898ab34481f8e6d52715d3ad649683567deb86 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 7 Feb 2024 16:40:21 -0300 Subject: [PATCH 07/21] refactor: bottom sheet filter component --- App.tsx | 1 + .../bottomSheetFilter/filterMenu/index.tsx | 1 + .../components/bottomSheetFilter/index.tsx | 90 ++++++++++++++----- .../components/bottomSheetFilter/styles.ts | 47 +++++++++- 4 files changed, 116 insertions(+), 23 deletions(-) diff --git a/App.tsx b/App.tsx index 41e3e71..44b161b 100644 --- a/App.tsx +++ b/App.tsx @@ -39,6 +39,7 @@ export default function App() { {handleFontsLoading()} + ); diff --git a/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx b/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx index 39e35a0..6fb337c 100644 --- a/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx @@ -8,6 +8,7 @@ interface FilterMenuProps { export const FilterMenu = ({ trainningsCount, onButtonPress }: FilterMenuProps) => ( {trainningsCount} Trainnings + Filters diff --git a/src/screens/home/components/bottomSheetFilter/index.tsx b/src/screens/home/components/bottomSheetFilter/index.tsx index 9764c8b..1743180 100644 --- a/src/screens/home/components/bottomSheetFilter/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/index.tsx @@ -1,15 +1,25 @@ -import { Text, TouchableOpacity } from "react-native"; -import { useTheme } from "styled-components/native"; +import { FieldValues, SubmitHandler, useForm } from "react-hook-form"; +import BottomSheet, { BottomSheetBackdrop } from "@gorhom/bottom-sheet"; import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "react"; -import BottomSheet, { BottomSheetBackdrop, BottomSheetScrollView } from "@gorhom/bottom-sheet"; -import { ApplyFilterButton, ApplyFilterButtonText } from "./styles"; +import { FilterHeader } from "./filterHeader"; +import { RadioButtonController } from "./radioButton"; -export const BottomSheetFilter = forwardRef((_, ref) => { - const { "gray-150": bottomSheetBackground, "primary-lighter": indicatorBackground } = useTheme(); +import { + ApplyFilterButton, + ApplyFilterButtonText, + BottomSheetContainer, + BottomSheetFilterTopicTitle, + BottomSheetScrollViewContainer, + RadioButtonFilterByDaysContainer, +} from "./styles"; +export const BottomSheetFilter = forwardRef((_, ref) => { const bottomSheetRef = useRef(null); - const bottomSheetSnapPoints = useMemo(() => ["75%"], []); + const bottomSheetSnapPoints = useMemo(() => ["55%"], []); + + const { control, handleSubmit, setValue, reset, watch } = useForm(); + const anyFilterIsSelected = watch("days") === undefined; const renderBackdrop = useCallback( (props: any) => , @@ -22,25 +32,63 @@ export const BottomSheetFilter = forwardRef((_, ref) => { }, })); + const handleCloseBottomSheet = () => { + reset(); + bottomSheetRef.current?.close(); + }; + + const handleFilterSubmit: SubmitHandler = (formData) => { + console.log(formData); + + reset(); + bottomSheetRef.current?.close(); + }; + return ( - - - DAYS - Last 7 days - Last 15 days - Last 30 days - - bottomSheetRef.current?.close()} activeOpacity={0.9}> - Apply + + + + Recent Days + + + + + + + + + + + + Apply - + ); }); diff --git a/src/screens/home/components/bottomSheetFilter/styles.ts b/src/screens/home/components/bottomSheetFilter/styles.ts index 72a4d1d..1888639 100644 --- a/src/screens/home/components/bottomSheetFilter/styles.ts +++ b/src/screens/home/components/bottomSheetFilter/styles.ts @@ -1,17 +1,60 @@ -import styled from "styled-components/native"; +import styled, { css } from "styled-components/native"; +import BottomSheet, { BottomSheetScrollView } from "@gorhom/bottom-sheet"; + +export const BottomSheetContainer = styled(BottomSheet).attrs((props) => ({ + index: -1, + enablePanDownToClose: true, + backgroundStyle: { + backgroundColor: props.theme["gray-150"], + }, + handleIndicatorStyle: { + backgroundColor: props.theme["primary-lighter"], + }, +}))``; + +export const BottomSheetScrollViewContainer = styled(BottomSheetScrollView)` + margin: 12px 28px; +`; + +export const BottomSheetFilterTopicTitle = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1 }))` + font-size: 16px; + font-family: ${(props) => props.theme.fonts.Medium}; + + margin-left: 10px; + + text-transform: uppercase; + letter-spacing: 0.5px; + + color: ${(props) => props.theme["primaryA"]}; +`; + +export const RadioButtonFilterByDaysContainer = styled.View` + flex-direction: row; + align-items: center; + justify-content: space-around; + margin-top: 8px; +`; export const ApplyFilterButton = styled.TouchableOpacity` align-items: center; + width: 80%; margin: 0 auto 28px; padding: 14px; + border-radius: 99px; background-color: ${(props) => props.theme["primary-lighter"]}; + + ${(props) => + props.disabled && + css` + background-color: ${(props) => props.theme["gray-300"]}; + `} `; export const ApplyFilterButtonText = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1 }))` font-size: 16px; font-family: ${(props) => props.theme.fonts.Bold}; - color: ${(props) => props.theme["primaryA"]}; + color: ${(props) => (props.disabled ? props.theme["gray-400"] : props.theme["primaryA"])}; `; From ffe68ec1722ba4a3ebcf2540d78ba1d5b32da97a Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 15:34:58 -0300 Subject: [PATCH 08/21] refactor: ApplyFilterButton styles in BottomSheetFilter component --- .../home/components/bottomSheetFilter/styles.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/screens/home/components/bottomSheetFilter/styles.ts b/src/screens/home/components/bottomSheetFilter/styles.ts index 1888639..771d0e8 100644 --- a/src/screens/home/components/bottomSheetFilter/styles.ts +++ b/src/screens/home/components/bottomSheetFilter/styles.ts @@ -1,4 +1,4 @@ -import styled, { css } from "styled-components/native"; +import styled from "styled-components/native"; import BottomSheet, { BottomSheetScrollView } from "@gorhom/bottom-sheet"; export const BottomSheetContainer = styled(BottomSheet).attrs((props) => ({ @@ -43,13 +43,8 @@ export const ApplyFilterButton = styled.TouchableOpacity` padding: 14px; border-radius: 99px; - background-color: ${(props) => props.theme["primary-lighter"]}; - - ${(props) => - props.disabled && - css` - background-color: ${(props) => props.theme["gray-300"]}; - `} + background-color: ${(props) => + props.disabled ? props.theme["gray-400"] : props.theme["primary-lighter"]}; `; export const ApplyFilterButtonText = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1 }))` From b67d87f15e898c1b29a9105bc4563bf38f868dfc Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 15:35:23 -0300 Subject: [PATCH 09/21] feat: add formatDateForSQL function --- src/shared/utils/SqlDateFormatter/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/shared/utils/SqlDateFormatter/index.ts diff --git a/src/shared/utils/SqlDateFormatter/index.ts b/src/shared/utils/SqlDateFormatter/index.ts new file mode 100644 index 0000000..fe708a4 --- /dev/null +++ b/src/shared/utils/SqlDateFormatter/index.ts @@ -0,0 +1,6 @@ +export const formatDateForSQL = (date: Date): string => { + const year = date.getFullYear().toString(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + return `${year}/${month}/${day}`; +}; From e6ea3fcbcb4dbf544b982438a3eab4a4d6064938 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 15:35:38 -0300 Subject: [PATCH 10/21] feat: add getTrainningsByLastNDays method to TrainningRepository --- src/data/Repositories/index.ts | 35 ++++++++++++++++++- .../components/bottomSheetFilter/index.tsx | 8 +++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/data/Repositories/index.ts b/src/data/Repositories/index.ts index 0d245e9..0dd824f 100644 --- a/src/data/Repositories/index.ts +++ b/src/data/Repositories/index.ts @@ -1,5 +1,6 @@ -import { NewWorkoutFormFieldsProps } from "../../models/newWorkoutFormFieldsModel"; import { dataBaseConnection } from "../dbConnection"; +import { formatDateForSQL } from "../../shared/utils/SqlDateFormatter"; +import { NewWorkoutFormFieldsProps } from "../../models/newWorkoutFormFieldsModel"; const db = dataBaseConnection(); @@ -64,6 +65,37 @@ const getAllTrainningsFromDatabase = (): Promise => }); }; +const getTrainningsByLastNDays = ({ days }: any): Promise => { + return new Promise((resolve, reject) => { + const numberOfDays = parseInt(days, 10); + + if (isNaN(numberOfDays)) { + reject("Invalid number of days"); + return; + } + + db.transaction((tx) => { + const currentDate = new Date(); + const startDate = new Date(currentDate.getTime() - (numberOfDays - 1) * 24 * 60 * 60 * 1000); + + const formattedStartDate = formatDateForSQL(startDate); + const formattedCurrentDate = formatDateForSQL(currentDate); + + tx.executeSql( + "SELECT *, substr(trainningDate, 7, 4) || '-' || substr(trainningDate, 4, 2) || '-' || substr(trainningDate, 1, 2) AS formattedTrainningDate FROM Trainning WHERE substr(trainningDate, 7, 4) || '/' || substr(trainningDate, 4, 2) || '/' || substr(trainningDate, 1, 2) BETWEEN ? AND ? ORDER BY trainningDate DESC", + [formattedStartDate, formattedCurrentDate], + (_, { rows }) => { + resolve(rows._array); + }, + (_, error) => { + console.error("SQL Error:", error); + reject("Ops... Something went wrong! Try again later."); + return false; + }, + ); + }); + }); +}; const deleteTrainningFromDatabase = (id: number) => { return new Promise((resolve, reject) => { @@ -104,6 +136,7 @@ const dropTrainningTable = () => { export const TrainningRepository = { addTrainningToDatabase, getAllTrainningsFromDatabase, + getTrainningsByLastNDays, deleteTrainningFromDatabase, dropTrainningTable, }; diff --git a/src/screens/home/components/bottomSheetFilter/index.tsx b/src/screens/home/components/bottomSheetFilter/index.tsx index 1743180..114cee2 100644 --- a/src/screens/home/components/bottomSheetFilter/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/index.tsx @@ -13,6 +13,7 @@ import { BottomSheetScrollViewContainer, RadioButtonFilterByDaysContainer, } from "./styles"; +import { TrainningRepository } from "../../../../data/Repositories"; export const BottomSheetFilter = forwardRef((_, ref) => { const bottomSheetRef = useRef(null); @@ -37,8 +38,11 @@ export const BottomSheetFilter = forwardRef((_, ref) => { bottomSheetRef.current?.close(); }; - const handleFilterSubmit: SubmitHandler = (formData) => { - console.log(formData); + const handleFilterSubmit: SubmitHandler = async (formData) => { + const { days } = formData; + + const filteredTrainningsByDays = await TrainningRepository.getTrainningsByLastNDays({ days }); + console.log(filteredTrainningsByDays); reset(); bottomSheetRef.current?.close(); From 395c1774e9b3e1f0ac7f82329cd26ad779b3ecc6 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 15:42:28 -0300 Subject: [PATCH 11/21] refactor: styling issues in TrainningCard and FilterMenu components --- src/components/trainningCard/styles.ts | 2 +- .../home/components/bottomSheetFilter/filterMenu/styles.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/trainningCard/styles.ts b/src/components/trainningCard/styles.ts index 0bf0716..c36c351 100644 --- a/src/components/trainningCard/styles.ts +++ b/src/components/trainningCard/styles.ts @@ -11,7 +11,7 @@ export const DeleteCardIconContainer = styled.View` right: 40px; top: 45%; `; -export const TrainningCardContainer = styled(Animated.View)` +export const TrainningCardContainer: any = styled(Animated.View)` gap: 14px; margin: 20px 16px 5px; padding: 16px; diff --git a/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts b/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts index c3e5285..a7e6ed0 100644 --- a/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts +++ b/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts @@ -5,7 +5,7 @@ export const FilterMenuContainer = styled.View` flex-direction: row; justify-content: space-around; align-items: center; - margin-top: 12px; + margin: 16px 0 8px; `; interface FilterButtonProps { From d549d0bcdfc3c609e140252b8747a2619b897351 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 16:27:53 -0300 Subject: [PATCH 12/21] feat: add filtered trainings Store functionality --- .../filteredTrainnings/getTrainningsEvents.ts | 9 +++++ .../filteredTrainnings/getTrainningsState.ts | 8 +++++ .../filteredTrainnings/getTrainningsStore.ts | 35 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts create mode 100644 src/stores/getTrainningsStore/filteredTrainnings/getTrainningsState.ts create mode 100644 src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts diff --git a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts new file mode 100644 index 0000000..253fa8a --- /dev/null +++ b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts @@ -0,0 +1,9 @@ +import { createEvent } from "effector"; + +import { NewWorkoutFormFieldsProps } from "../../../models/newWorkoutFormFieldsModel"; + +export const loadFilteredTrainnings = createEvent("loadFilteredTrainnings"); +export const loadFilteredTrainningsDone = createEvent( + "loadFilteredTrainningsDone", +); +export const loadFilteredTrainningsFail = createEvent("loadFilteredTrainningsFail"); diff --git a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsState.ts b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsState.ts new file mode 100644 index 0000000..18060ed --- /dev/null +++ b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsState.ts @@ -0,0 +1,8 @@ +import { NewWorkoutFormFieldsProps } from "../../../models/newWorkoutFormFieldsModel"; + +export interface GetFilteredTrainningsState { + hasError: boolean; + isLoading: boolean; + errorMessage: string; + filteredTrainnings: NewWorkoutFormFieldsProps[]; +} diff --git a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts new file mode 100644 index 0000000..15d8ebc --- /dev/null +++ b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts @@ -0,0 +1,35 @@ +import { createStore } from "effector"; +import { GetFilteredTrainningsState } from "./getTrainningsState"; +import { + loadFilteredTrainnings, + loadFilteredTrainningsDone, + loadFilteredTrainningsFail, +} from "./getTrainningsEvents"; + +const initialState: GetFilteredTrainningsState = { + hasError: false, + isLoading: false, + errorMessage: "", + filteredTrainnings: [], +}; + +export const GetFilteredTrainningsStore = createStore(initialState) + .on(loadFilteredTrainnings, (state) => ({ + ...state, + isLoading: true, + hasError: false, + errorMessage: "", + })) + .on(loadFilteredTrainningsDone, (state, data) => ({ + ...state, + isLoading: false, + hasError: false, + errorMessage: "", + filteredTrainnings: data, + })) + .on(loadFilteredTrainningsFail, (_, data) => ({ + hasError: true, + isLoading: false, + errorMessage: data?.toString(), + filteredTrainnings: [], + })); From e37919ac1f2366d3601b88ee906a771564aa9fe2 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 16:28:28 -0300 Subject: [PATCH 13/21] feat: add filterTrainnings utility function --- src/shared/utils/filterTrainnings/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/shared/utils/filterTrainnings/index.ts diff --git a/src/shared/utils/filterTrainnings/index.ts b/src/shared/utils/filterTrainnings/index.ts new file mode 100644 index 0000000..45ac731 --- /dev/null +++ b/src/shared/utils/filterTrainnings/index.ts @@ -0,0 +1,9 @@ +import { NewWorkoutFormFieldsProps } from "../../../models/newWorkoutFormFieldsModel"; + +export const filterTrainnings = ( + trainnings: NewWorkoutFormFieldsProps[], + filteredTrainnings: NewWorkoutFormFieldsProps[], +): NewWorkoutFormFieldsProps[] => { + if (filteredTrainnings.length > 0) return filteredTrainnings; + return trainnings; +}; From d236bac5e734566a4fae3ce45e28c1c041129418 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 16:28:45 -0300 Subject: [PATCH 14/21] feat: add getFilteredTrainningsUseCase to load and filter trainings --- .../getFilteredTrainningsUseCase/index.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/useCases/getFilteredTrainningsUseCase/index.ts diff --git a/src/useCases/getFilteredTrainningsUseCase/index.ts b/src/useCases/getFilteredTrainningsUseCase/index.ts new file mode 100644 index 0000000..91cb194 --- /dev/null +++ b/src/useCases/getFilteredTrainningsUseCase/index.ts @@ -0,0 +1,27 @@ +import { TrainningRepository } from "../../data/Repositories"; +import { NewWorkoutFormFieldsProps } from "../../models/newWorkoutFormFieldsModel"; +import { + loadFilteredTrainnings, + loadFilteredTrainningsDone, + loadFilteredTrainningsFail, +} from "../../stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents"; + +type Props = { + days: string; +}; + +const execute = async ({ days }: Props): Promise => { + loadFilteredTrainnings(); + + return TrainningRepository.getFilteredTrainnings(days) + .then((FilteredTrainnings: NewWorkoutFormFieldsProps[]) => { + loadFilteredTrainningsDone(FilteredTrainnings); + }) + .catch(({ message }) => { + loadFilteredTrainningsFail(message); + }); +}; + +export const GetFilteredTrainningsUseCase = { + execute, +}; From 98417912df8f7be013b60888ef8c8d81f0de128b Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Wed, 14 Feb 2024 16:29:45 -0300 Subject: [PATCH 15/21] refactor: trainning filtering and update trainning card list --- src/data/Repositories/index.ts | 4 ++-- .../bottomSheetFilter/filterMenu/index.tsx | 10 +++++++++- .../home/components/bottomSheetFilter/index.tsx | 15 +++++++++++---- .../home/components/bottomSheetFilter/styles.ts | 8 +++++++- .../home/components/trainningCardList/index.tsx | 7 ++++++- src/screens/home/index.tsx | 8 +++++++- 6 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/data/Repositories/index.ts b/src/data/Repositories/index.ts index 0dd824f..6e25e68 100644 --- a/src/data/Repositories/index.ts +++ b/src/data/Repositories/index.ts @@ -65,7 +65,7 @@ const getAllTrainningsFromDatabase = (): Promise => }); }; -const getTrainningsByLastNDays = ({ days }: any): Promise => { +const getFilteredTrainnings = (days: any): Promise => { return new Promise((resolve, reject) => { const numberOfDays = parseInt(days, 10); @@ -136,7 +136,7 @@ const dropTrainningTable = () => { export const TrainningRepository = { addTrainningToDatabase, getAllTrainningsFromDatabase, - getTrainningsByLastNDays, + getFilteredTrainnings, deleteTrainningFromDatabase, dropTrainningTable, }; diff --git a/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx b/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx index 6fb337c..9101bdb 100644 --- a/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/filterMenu/index.tsx @@ -5,9 +5,17 @@ interface FilterMenuProps { onButtonPress: () => void; } +const TrainningCountLabel = (trainningCount: number) => { + if (trainningCount === 1) return "Trainning"; + + return `Trainnings`; +}; + export const FilterMenu = ({ trainningsCount, onButtonPress }: FilterMenuProps) => ( - {trainningsCount} Trainnings + + {trainningsCount} {TrainningCountLabel(trainningsCount)} + diff --git a/src/screens/home/components/bottomSheetFilter/index.tsx b/src/screens/home/components/bottomSheetFilter/index.tsx index 114cee2..2bc7c4c 100644 --- a/src/screens/home/components/bottomSheetFilter/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/index.tsx @@ -1,7 +1,11 @@ +import { useUnit } from "effector-react"; import { FieldValues, SubmitHandler, useForm } from "react-hook-form"; import BottomSheet, { BottomSheetBackdrop } from "@gorhom/bottom-sheet"; import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "react"; +import { GetFilteredTrainningsUseCase } from "../../../../useCases/getFilteredTrainningsUseCase"; +import { GetFilteredTrainningsStore } from "../../../../stores/getTrainningsStore/filteredTrainnings/getTrainningsStore"; + import { FilterHeader } from "./filterHeader"; import { RadioButtonController } from "./radioButton"; @@ -11,11 +15,13 @@ import { BottomSheetContainer, BottomSheetFilterTopicTitle, BottomSheetScrollViewContainer, + LoadingIndicator, RadioButtonFilterByDaysContainer, } from "./styles"; -import { TrainningRepository } from "../../../../data/Repositories"; export const BottomSheetFilter = forwardRef((_, ref) => { + const { isLoading } = useUnit(GetFilteredTrainningsStore); + const bottomSheetRef = useRef(null); const bottomSheetSnapPoints = useMemo(() => ["55%"], []); @@ -41,8 +47,7 @@ export const BottomSheetFilter = forwardRef((_, ref) => { const handleFilterSubmit: SubmitHandler = async (formData) => { const { days } = formData; - const filteredTrainningsByDays = await TrainningRepository.getTrainningsByLastNDays({ days }); - console.log(filteredTrainningsByDays); + await GetFilteredTrainningsUseCase.execute({ days }); reset(); bottomSheetRef.current?.close(); @@ -91,7 +96,9 @@ export const BottomSheetFilter = forwardRef((_, ref) => { onPress={handleSubmit(handleFilterSubmit)} activeOpacity={0.9} > - Apply + + {isLoading ? : "Apply"} + ); diff --git a/src/screens/home/components/bottomSheetFilter/styles.ts b/src/screens/home/components/bottomSheetFilter/styles.ts index 771d0e8..7202e88 100644 --- a/src/screens/home/components/bottomSheetFilter/styles.ts +++ b/src/screens/home/components/bottomSheetFilter/styles.ts @@ -1,4 +1,5 @@ import styled from "styled-components/native"; +import { ActivityIndicator } from "react-native"; import BottomSheet, { BottomSheetScrollView } from "@gorhom/bottom-sheet"; export const BottomSheetContainer = styled(BottomSheet).attrs((props) => ({ @@ -51,5 +52,10 @@ export const ApplyFilterButtonText = styled.Text.attrs(() => ({ maxFontSizeMulti font-size: 16px; font-family: ${(props) => props.theme.fonts.Bold}; - color: ${(props) => (props.disabled ? props.theme["gray-400"] : props.theme["primaryA"])}; + color: ${(props) => (props.disabled ? props.theme["gray-150"] : props.theme["primaryA"])}; `; + +export const LoadingIndicator = styled(ActivityIndicator).attrs((props) => ({ + size: "small", + color: props.theme["primary-dark"], +}))``; diff --git a/src/screens/home/components/trainningCardList/index.tsx b/src/screens/home/components/trainningCardList/index.tsx index 87df776..7cae541 100644 --- a/src/screens/home/components/trainningCardList/index.tsx +++ b/src/screens/home/components/trainningCardList/index.tsx @@ -2,19 +2,24 @@ import { FlatList } from "react-native"; import { useUnit } from "effector-react"; import { TrainningCard } from "../../../../components/trainningCard"; +import { filterTrainnings } from "../../../../shared/utils/filterTrainnings"; import DeleteTrainningUseCase from "../../../../useCases/deleteTrainningUseCase"; import GetTrainningsStore from "../../../../stores/getTrainningsStore/getTrainningsStore"; +import { GetFilteredTrainningsStore } from "../../../../stores/getTrainningsStore/filteredTrainnings/getTrainningsStore"; export const TrainningCardList = () => { + const { filteredTrainnings } = useUnit(GetFilteredTrainningsStore); const { trainnings } = useUnit(GetTrainningsStore); + const trainningsToRender = filterTrainnings(trainnings, filteredTrainnings); + const handleCardTrainningDelete = async (id: number) => { await DeleteTrainningUseCase.execute(id); }; return ( Math.random().toString()} renderItem={({ item }) => ( { const bottomSheetRef = useRef(null); const { trainnings, isLoading } = useUnit(GetTrainningsStore); + const { filteredTrainnings } = useUnit(GetFilteredTrainningsStore); + + const trainningsToRender = filterTrainnings(trainnings, filteredTrainnings); + const isFocused = useIsFocused(); const handleButtonPress = () => { @@ -31,7 +37,7 @@ export const HomeScreen = () => { return ( <> From dd63f8001df4a6c2606406f416c8a5a7e0b17dc8 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Thu, 15 Feb 2024 12:57:51 -0300 Subject: [PATCH 16/21] refactor: update padding in Belt component --- src/screens/newWorkout/formComponents/belts/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/newWorkout/formComponents/belts/styles.ts b/src/screens/newWorkout/formComponents/belts/styles.ts index b0fbf6f..ea06e34 100644 --- a/src/screens/newWorkout/formComponents/belts/styles.ts +++ b/src/screens/newWorkout/formComponents/belts/styles.ts @@ -15,7 +15,7 @@ export const Belt = styled.View` align-items: center; justify-content: space-between; - padding: 0 4px; + padding: 0 0 0 2px; border-radius: 8px; border: 1px solid transparent; From c63bcc2c05aea5aea5ae6269e14f352a7bfef531 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Thu, 15 Feb 2024 12:58:54 -0300 Subject: [PATCH 17/21] feat: add a specific store to clear all filters --- .../filteredTrainnings/getTrainningsEvents.ts | 1 + .../filteredTrainnings/getTrainningsStore.ts | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts index 253fa8a..95180e5 100644 --- a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts +++ b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents.ts @@ -6,4 +6,5 @@ export const loadFilteredTrainnings = createEvent("loadFilteredTrainnings"); export const loadFilteredTrainningsDone = createEvent( "loadFilteredTrainningsDone", ); +export const loadResetFilteredTrainningsDone = createEvent("loadResetFilteredTrainningsDone"); export const loadFilteredTrainningsFail = createEvent("loadFilteredTrainningsFail"); diff --git a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts index 15d8ebc..2805522 100644 --- a/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts +++ b/src/stores/getTrainningsStore/filteredTrainnings/getTrainningsStore.ts @@ -4,6 +4,7 @@ import { loadFilteredTrainnings, loadFilteredTrainningsDone, loadFilteredTrainningsFail, + loadResetFilteredTrainningsDone, } from "./getTrainningsEvents"; const initialState: GetFilteredTrainningsState = { @@ -27,6 +28,13 @@ export const GetFilteredTrainningsStore = createStore ({ + ...state, + isLoading: false, + hasError: false, + errorMessage: "", + filteredTrainnings: [], + })) .on(loadFilteredTrainningsFail, (_, data) => ({ hasError: true, isLoading: false, From c281af1c86aff331cd3bb849dd9f801cb9dc940b Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Thu, 15 Feb 2024 13:00:17 -0300 Subject: [PATCH 18/21] feat: add button to clear all filters --- .../bottomSheetFilter/filterHeader/index.tsx | 25 ++++++++++++++----- .../bottomSheetFilter/filterHeader/styles.ts | 21 ++++++++++++++++ .../bottomSheetFilter/filterMenu/styles.ts | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx b/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx index fadfc63..6a07d30 100644 --- a/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/filterHeader/index.tsx @@ -1,23 +1,36 @@ import React from "react"; +import { TouchableOpacity, View } from "react-native"; + import { BottomSheetFilterHeader, BottomSheetFilterIconContainer, BottomSheetFilterIcon, BottomSheetFilterIconText, + BottomSheetFilterClearAllButton, + BottomSheetFilterClearAllText, } from "./styles"; -import { TouchableOpacity } from "react-native"; interface FilterHeaderProps { handleCloseBottomSheet: () => void; + handleClearAllFilters: () => void; } -export const FilterHeader = ({ handleCloseBottomSheet }: FilterHeaderProps) => { +export const FilterHeader = ({ + handleCloseBottomSheet, + handleClearAllFilters, +}: FilterHeaderProps) => { return ( - - - Filter - + + + + Filter + + + + Clear All + + diff --git a/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts b/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts index 7eaa114..3bada5c 100644 --- a/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts +++ b/src/screens/home/components/bottomSheetFilter/filterHeader/styles.ts @@ -29,3 +29,24 @@ export const BottomSheetFilterIconText = styled.Text.attrs(() => ({ maxFontSizeM color: ${(props) => props.theme["primaryA"]}; `; + +export const BottomSheetFilterClearAllButton = styled.TouchableOpacity` + margin: 12px 0 0 8px; + padding: 6px 16px; + + border: 1px solid transparent; + border-radius: 6px; + background-color: ${(props) => props.theme["primary-lighter"]}; +`; + +export const BottomSheetFilterClearAllText = styled.Text.attrs(() => ({ + maxFontSizeMultiplier: 1, +}))` + font-size: 16px; + font-family: ${(props) => props.theme.fonts.Medium}; + + text-transform: capitalize; + letter-spacing: 0.5px; + + color: ${(props) => props.theme["primaryA"]}; +`; diff --git a/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts b/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts index a7e6ed0..dae3621 100644 --- a/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts +++ b/src/screens/home/components/bottomSheetFilter/filterMenu/styles.ts @@ -15,7 +15,7 @@ interface FilterButtonProps { export const FilterText = styled.Text.attrs(() => ({ maxFontSizeMultiplier: 1, }))` - font-size: 16px; + font-size: 18px; font-family: ${(props) => props.theme.fonts.Medium}; color: ${(props) => (props.buttonText ? props.theme["primary"] : props.theme["gray-350"])}; From 98d097e369d29d7dbc2c83bfad92acfc1f3f7c52 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Thu, 15 Feb 2024 13:00:37 -0300 Subject: [PATCH 19/21] feat: add clear all filters functionality to bottom sheet filter --- .../home/components/bottomSheetFilter/index.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/screens/home/components/bottomSheetFilter/index.tsx b/src/screens/home/components/bottomSheetFilter/index.tsx index 2bc7c4c..bb7d5fe 100644 --- a/src/screens/home/components/bottomSheetFilter/index.tsx +++ b/src/screens/home/components/bottomSheetFilter/index.tsx @@ -5,6 +5,7 @@ import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "r import { GetFilteredTrainningsUseCase } from "../../../../useCases/getFilteredTrainningsUseCase"; import { GetFilteredTrainningsStore } from "../../../../stores/getTrainningsStore/filteredTrainnings/getTrainningsStore"; +import { loadResetFilteredTrainningsDone } from "../../../../stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents"; import { FilterHeader } from "./filterHeader"; import { RadioButtonController } from "./radioButton"; @@ -40,17 +41,22 @@ export const BottomSheetFilter = forwardRef((_, ref) => { })); const handleCloseBottomSheet = () => { - reset(); bottomSheetRef.current?.close(); }; + const handleClearAllFilters = () => { + loadResetFilteredTrainningsDone(); + reset(); + + handleCloseBottomSheet(); + }; + const handleFilterSubmit: SubmitHandler = async (formData) => { const { days } = formData; await GetFilteredTrainningsUseCase.execute({ days }); - reset(); - bottomSheetRef.current?.close(); + handleCloseBottomSheet(); }; return ( @@ -60,7 +66,10 @@ export const BottomSheetFilter = forwardRef((_, ref) => { backdropComponent={renderBackdrop} > - + Recent Days From f2696edd29963519c27afe9f28d389d82b8de974 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Thu, 22 Feb 2024 16:47:24 -0300 Subject: [PATCH 20/21] chore: add lib for toast message --- package-lock.json | 10 ++++++++++ package.json | 1 + 2 files changed, 11 insertions(+) diff --git a/package-lock.json b/package-lock.json index 1eef276..f1d48ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "^4.6.3", "react-native-screens": "~3.22.0", + "react-native-toast-message": "^2.2.0", "styled-components": "^5.3.10", "yup": "^1.3.3" }, @@ -12652,6 +12653,15 @@ "react-native": "*" } }, + "node_modules/react-native-toast-message": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-native-toast-message/-/react-native-toast-message-2.2.0.tgz", + "integrity": "sha512-AFti8VzUk6JvyGAlLm9/BknTNDXrrhqnUk7ak/pM7uCTxDPveAu2ekszU0on6vnUPFnG04H/QfYE2IlETqeaWw==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-vector-icons": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.3.tgz", diff --git a/package.json b/package.json index 0f9bab0..167905f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "^4.6.3", "react-native-screens": "~3.22.0", + "react-native-toast-message": "^2.2.0", "styled-components": "^5.3.10", "yup": "^1.3.3" }, From 3f7c6805b9dd9a02819695539a44662b118c46e9 Mon Sep 17 00:00:00 2001 From: Heitor Gandolfi Date: Thu, 22 Feb 2024 16:47:58 -0300 Subject: [PATCH 21/21] feat: add Toast component and toast configuration --- App.tsx | 3 +++ src/shared/utils/toastConfigs/index.tsx | 24 +++++++++++++++++++ .../getFilteredTrainningsUseCase/index.ts | 16 ++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/shared/utils/toastConfigs/index.tsx diff --git a/App.tsx b/App.tsx index 44b161b..025c241 100644 --- a/App.tsx +++ b/App.tsx @@ -1,3 +1,4 @@ +import Toast from "react-native-toast-message"; import { ActivityIndicator, StatusBar } from "react-native"; import { ThemeProvider } from "styled-components/native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; @@ -12,6 +13,7 @@ import { import { Routes } from "./src/routes"; import { defaultTheme } from "./src/styles/themes/default"; +import { toastConfig } from "./src/shared/utils/toastConfigs"; export default function App() { const [LoadFonts] = useFonts({ @@ -38,6 +40,7 @@ export default function App() { <> {handleFontsLoading()} + diff --git a/src/shared/utils/toastConfigs/index.tsx b/src/shared/utils/toastConfigs/index.tsx new file mode 100644 index 0000000..5c0ca77 --- /dev/null +++ b/src/shared/utils/toastConfigs/index.tsx @@ -0,0 +1,24 @@ +import { ErrorToast } from "react-native-toast-message"; + +export const toastConfig = { + defaultToast: (props: any) => ( + + ), +}; diff --git a/src/useCases/getFilteredTrainningsUseCase/index.ts b/src/useCases/getFilteredTrainningsUseCase/index.ts index 91cb194..efc63b1 100644 --- a/src/useCases/getFilteredTrainningsUseCase/index.ts +++ b/src/useCases/getFilteredTrainningsUseCase/index.ts @@ -1,20 +1,34 @@ +import Toast from "react-native-toast-message"; + import { TrainningRepository } from "../../data/Repositories"; -import { NewWorkoutFormFieldsProps } from "../../models/newWorkoutFormFieldsModel"; import { loadFilteredTrainnings, loadFilteredTrainningsDone, loadFilteredTrainningsFail, } from "../../stores/getTrainningsStore/filteredTrainnings/getTrainningsEvents"; +import { NewWorkoutFormFieldsProps } from "../../models/newWorkoutFormFieldsModel"; type Props = { days: string; }; +const showToast = () => { + Toast.show({ + autoHide: true, + visibilityTime: 3500, + type: "defaultToast", + text1: "Ops...", + text2: "There is no trainnings in this time interval", + position: "bottom", + }); +}; + const execute = async ({ days }: Props): Promise => { loadFilteredTrainnings(); return TrainningRepository.getFilteredTrainnings(days) .then((FilteredTrainnings: NewWorkoutFormFieldsProps[]) => { + if (FilteredTrainnings.length === 0) showToast(); loadFilteredTrainningsDone(FilteredTrainnings); }) .catch(({ message }) => {