From 04661ca356bcf1f42753b4f76334a9163955b3e8 Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Sun, 23 Jul 2023 20:20:11 +0300 Subject: [PATCH] Add memes views & searching Signed-off-by: Nikolaos Karaolidis --- package-lock.json | 104 +++++++----- package.json | 4 +- src/components/index.ts | 3 + .../memes/gridView/memesGridItem.tsx | 45 ++++++ .../memes/gridView/memesGridView.tsx | 74 +++++++++ src/components/memes/index.ts | 3 + .../memes/listView/memesListItem.tsx | 98 ++++++++++++ .../memes/listView/memesListView.tsx | 68 ++++++++ .../memes/masonryView/memesMasonryItem.tsx | 57 +++++++ .../memes/masonryView/memesMasonryView.tsx | 75 +++++++++ src/components/memes/memeCard.tsx | 51 ------ src/components/memes/memeEditor.tsx | 29 +--- src/database/meme.ts | 6 +- src/database/tag.ts | 4 +- src/screens/addMeme.tsx | 19 +-- src/screens/editMeme.tsx | 38 ++--- src/screens/editTag.tsx | 5 +- src/screens/memes.tsx | 148 ++++++++++++------ src/screens/settings.tsx | 95 ++++++++--- src/screens/tags.tsx | 9 +- src/state/index.ts | 2 + src/state/memes.ts | 4 + src/state/settings.ts | 15 +- src/types/view.ts | 1 + src/utilities/dimensions.ts | 9 ++ src/utilities/icon.ts | 3 + src/utilities/index.ts | 2 +- src/utilities/validation.ts | 13 -- 28 files changed, 737 insertions(+), 247 deletions(-) create mode 100644 src/components/memes/gridView/memesGridItem.tsx create mode 100644 src/components/memes/gridView/memesGridView.tsx create mode 100644 src/components/memes/listView/memesListItem.tsx create mode 100644 src/components/memes/listView/memesListView.tsx create mode 100644 src/components/memes/masonryView/memesMasonryItem.tsx create mode 100644 src/components/memes/masonryView/memesMasonryView.tsx delete mode 100644 src/components/memes/memeCard.tsx create mode 100644 src/utilities/dimensions.ts diff --git a/package-lock.json b/package-lock.json index cb69163..65d5f12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "react-native-file-access": "^3.0.4", "react-native-gesture-handler": "^2.12.0", "react-native-get-random-values": "^1.9.0", + "react-native-image-zoom-viewer": "^3.0.1", "react-native-mime-types": "^2.4.0", "react-native-paper": "^5.9.1", "react-native-reanimated": "^3.3.0", @@ -31,7 +32,7 @@ "react-native-screens": "^3.22.1", "react-native-share": "^9.2.3", "react-native-vector-icons": "^9.2.0", - "react-native-video": "^5.2.1", + "react-native-video": "^6.0.0-alpha.6", "react-redux": "^8.1.1", "realm": "^11.10.1", "redux-persist": "^6.0.0" @@ -48,6 +49,7 @@ "@types/metro-config": "^0.76.3", "@types/react": "^18.2.14", "@types/react-native-vector-icons": "^6.4.13", + "@types/react-native-video": "^5.0.15", "@types/react-test-renderer": "^18.0.0", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", @@ -4602,6 +4604,16 @@ "@types/react-native": "^0.70" } }, + "node_modules/@types/react-native-video": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/@types/react-native-video/-/react-native-video-5.0.15.tgz", + "integrity": "sha512-li3yBYQ+D5GqZl0Y+M/vCTPfZwVyUU67CtSjEg+/ERkgEpvHDH+gQaoc9O00ttXr8kvqEzpiC6Ca9juIfeIlMA==", + "dev": true, + "dependencies": { + "@types/react": "*", + "@types/react-native": "*" + } + }, "node_modules/@types/react-test-renderer": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz", @@ -6541,11 +6553,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.455.tgz", "integrity": "sha512-8tgdX0Odl24LtmLwxotpJCVjIndN559AvaOtd67u+2mo+IDsgsTF580NB+uuDCqsHw8yFg53l5+imFV9Fw3cbA==" }, - "node_modules/eme-encryption-scheme-polyfill": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.1.tgz", - "integrity": "sha512-njD17wcUrbqCj0ArpLu5zWXtaiupHb/2fIUQGdInf83GlI+Q6mmqaPGLdrke4savKAu15J/z1Tg/ivDgl14g0g==" - }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -13407,6 +13414,27 @@ "react-native": ">=0.56" } }, + "node_modules/react-native-image-pan-zoom": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz", + "integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-image-zoom-viewer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz", + "integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==", + "dependencies": { + "react-native-image-pan-zoom": "^2.1.12" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-mime-types": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/react-native-mime-types/-/react-native-mime-types-2.4.0.tgz", @@ -13528,14 +13556,13 @@ } }, "node_modules/react-native-video": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-5.2.1.tgz", - "integrity": "sha512-aJlr9MeTuQ0LpZ4n+EC9RvhoKeiPbLtI2Rxy8u7zo/wzGevbRpWHSBj9xZ5YDBXnAVXzuqyNIkGhdw7bfdIBZw==", + "version": "6.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-6.0.0-alpha.6.tgz", + "integrity": "sha512-MCqHfPGuqVokvJOkvidhD5/eGYkWZrDEcSDtlkwVo36V2157L6lZyt3mqb5tPR+e5jSz+c/ht2JpEhP1bCm/Dw==", "dependencies": { "deprecated-react-native-prop-types": "^2.2.0", "keymirror": "^0.1.1", - "prop-types": "^15.7.2", - "shaka-player": "^2.5.9" + "prop-types": "^15.7.2" } }, "node_modules/react-native-video/node_modules/deprecated-react-native-prop-types": { @@ -14500,15 +14527,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/shaka-player": { - "version": "2.5.23", - "resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-2.5.23.tgz", - "integrity": "sha512-3MC9k0OXJGw8AZ4n/ZNCZS2yDxx+3as5KgH6Tx4Q5TRboTBBCu6dYPI5vp1DxKeyU12MBN1Zcbs7AKzXv2EnCg==", - "deprecated": "Shaka Player < v3.2 is no longer supported.", - "dependencies": { - "eme-encryption-scheme-polyfill": "^2.0.1" - } - }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -19177,6 +19195,16 @@ "@types/react-native": "^0.70" } }, + "@types/react-native-video": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/@types/react-native-video/-/react-native-video-5.0.15.tgz", + "integrity": "sha512-li3yBYQ+D5GqZl0Y+M/vCTPfZwVyUU67CtSjEg+/ERkgEpvHDH+gQaoc9O00ttXr8kvqEzpiC6Ca9juIfeIlMA==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/react-native": "*" + } + }, "@types/react-test-renderer": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz", @@ -20617,11 +20645,6 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.455.tgz", "integrity": "sha512-8tgdX0Odl24LtmLwxotpJCVjIndN559AvaOtd67u+2mo+IDsgsTF580NB+uuDCqsHw8yFg53l5+imFV9Fw3cbA==" }, - "eme-encryption-scheme-polyfill": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.1.tgz", - "integrity": "sha512-njD17wcUrbqCj0ArpLu5zWXtaiupHb/2fIUQGdInf83GlI+Q6mmqaPGLdrke4savKAu15J/z1Tg/ivDgl14g0g==" - }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -25788,6 +25811,20 @@ "fast-base64-decode": "^1.0.0" } }, + "react-native-image-pan-zoom": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz", + "integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==", + "requires": {} + }, + "react-native-image-zoom-viewer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz", + "integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==", + "requires": { + "react-native-image-pan-zoom": "^2.1.12" + } + }, "react-native-mime-types": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/react-native-mime-types/-/react-native-mime-types-2.4.0.tgz", @@ -25879,14 +25916,13 @@ } }, "react-native-video": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-5.2.1.tgz", - "integrity": "sha512-aJlr9MeTuQ0LpZ4n+EC9RvhoKeiPbLtI2Rxy8u7zo/wzGevbRpWHSBj9xZ5YDBXnAVXzuqyNIkGhdw7bfdIBZw==", + "version": "6.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-6.0.0-alpha.6.tgz", + "integrity": "sha512-MCqHfPGuqVokvJOkvidhD5/eGYkWZrDEcSDtlkwVo36V2157L6lZyt3mqb5tPR+e5jSz+c/ht2JpEhP1bCm/Dw==", "requires": { "deprecated-react-native-prop-types": "^2.2.0", "keymirror": "^0.1.1", - "prop-types": "^15.7.2", - "shaka-player": "^2.5.9" + "prop-types": "^15.7.2" }, "dependencies": { "deprecated-react-native-prop-types": { @@ -26511,14 +26547,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "shaka-player": { - "version": "2.5.23", - "resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-2.5.23.tgz", - "integrity": "sha512-3MC9k0OXJGw8AZ4n/ZNCZS2yDxx+3as5KgH6Tx4Q5TRboTBBCu6dYPI5vp1DxKeyU12MBN1Zcbs7AKzXv2EnCg==", - "requires": { - "eme-encryption-scheme-polyfill": "^2.0.1" - } - }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index c24a9f7..8d9d395 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-native-file-access": "^3.0.4", "react-native-gesture-handler": "^2.12.0", "react-native-get-random-values": "^1.9.0", + "react-native-image-zoom-viewer": "^3.0.1", "react-native-mime-types": "^2.4.0", "react-native-paper": "^5.9.1", "react-native-reanimated": "^3.3.0", @@ -36,7 +37,7 @@ "react-native-screens": "^3.22.1", "react-native-share": "^9.2.3", "react-native-vector-icons": "^9.2.0", - "react-native-video": "^5.2.1", + "react-native-video": "^6.0.0-alpha.6", "react-redux": "^8.1.1", "realm": "^11.10.1", "redux-persist": "^6.0.0" @@ -53,6 +54,7 @@ "@types/metro-config": "^0.76.3", "@types/react": "^18.2.14", "@types/react-native-vector-icons": "^6.4.13", + "@types/react-native-video": "^5.0.15", "@types/react-test-renderer": "^18.0.0", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", diff --git a/src/components/index.ts b/src/components/index.ts index 640de38..554bc2b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,4 +1,7 @@ export { + MemesGridView, + MemesListView, + MemesMasonryView, MemeEditor, MemesHeader, MemeTagSearchModal, diff --git a/src/components/memes/gridView/memesGridItem.tsx b/src/components/memes/gridView/memesGridItem.tsx new file mode 100644 index 0000000..e9f9e90 --- /dev/null +++ b/src/components/memes/gridView/memesGridItem.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { useNavigation, NavigationProp } from '@react-navigation/native'; +import { Image, TouchableHighlight, View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { Meme } from '../../../database'; +import { ROUTE, RootStackParamList } from '../../../types'; +import { useDimensions } from '../../../contexts'; +import { RootState } from '../../../state'; + +const MemesGridItem = ({ meme }: { meme: Meme }) => { + const { navigate } = useNavigation>(); + const { dimensions } = useDimensions(); + const gridColumns = useSelector( + (state: RootState) => state.settings.gridColumns, + ); + + const [imageWidth, setImageWidth] = useState(); + const [imageHeight, setImageHeight] = useState(); + + Image.getSize(meme.uri, () => { + const paddedWidth = (dimensions.width * 0.92 - 5) / gridColumns; + setImageWidth(paddedWidth); + setImageHeight(paddedWidth); + }); + + return ( + <> + {imageWidth && imageHeight && ( + + + navigate(ROUTE.EDIT_MEME, { id: meme.id.toHexString() }) + }> + + + + )} + + ); +}; + +export default MemesGridItem; diff --git a/src/components/memes/gridView/memesGridView.tsx b/src/components/memes/gridView/memesGridView.tsx new file mode 100644 index 0000000..2523585 --- /dev/null +++ b/src/components/memes/gridView/memesGridView.tsx @@ -0,0 +1,74 @@ +import React, { RefObject } from 'react'; +import { Meme } from '../../../database'; +import { FlashList } from '@shopify/flash-list'; +import { HelperText } from 'react-native-paper'; +import { + NativeScrollEvent, + NativeSyntheticEvent, + StyleSheet, +} from 'react-native'; +import { useSelector } from 'react-redux'; +import styles from '../../../styles'; +import { RootState } from '../../../state'; +import { ORIENTATION, useDimensions } from '../../../contexts'; +import { getFlashListItemHeight } from '../../../utilities'; +import MemesGridItem from './memesGridItem'; + +const gridViewStyles = StyleSheet.create({ + helperText: { + marginVertical: 10, + }, + flashList: { + paddingBottom: 100, + paddingHorizontal: 2.5, + }, +}); + +const MemesGridView = ({ + memes, + flashListRef, + flashListPadding, + handleScroll, +}: { + memes: Realm.Results>; + flashListRef: RefObject>; + flashListPadding: number; + handleScroll: (event: NativeSyntheticEvent) => void; +}) => { + const { orientation, dimensions } = useDimensions(); + const gridColumns = useSelector( + (state: RootState) => state.settings.gridColumns, + ); + + return ( + } + contentContainerStyle={{ + paddingTop: + flashListPadding + + dimensions.height * + (orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04), + ...gridViewStyles.flashList, + }} + ListEmptyComponent={() => ( + + No memes found + + )} + onScroll={handleScroll} + /> + ); +}; + +export default MemesGridView; diff --git a/src/components/memes/index.ts b/src/components/memes/index.ts index 3163636..2095c14 100644 --- a/src/components/memes/index.ts +++ b/src/components/memes/index.ts @@ -1,3 +1,6 @@ +export { default as MemesGridView } from './gridView/memesGridView'; +export { default as MemesListView } from './listView/memesListView'; +export { default as MemesMasonryView } from './masonryView/memesMasonryView'; export { default as MemeEditor } from './memeEditor'; export { default as MemesHeader } from './memesHeader'; export { default as MemeTagSearchModal } from './memeTagSearchModal'; diff --git a/src/components/memes/listView/memesListItem.tsx b/src/components/memes/listView/memesListItem.tsx new file mode 100644 index 0000000..7bb419a --- /dev/null +++ b/src/components/memes/listView/memesListItem.tsx @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { Image, StyleSheet, View } from 'react-native'; +import { useNavigation, NavigationProp } from '@react-navigation/native'; +import { Text, TouchableRipple } from 'react-native-paper'; +import { Meme } from '../../../database'; +import { ROUTE, RootStackParamList } from '../../../types'; +import styles from '../../../styles'; +import { useDimensions } from '../../../contexts'; + +const memesListItemStyles = StyleSheet.create({ + view: { + paddingVertical: 10, + }, + image: { + borderRadius: 5, + }, + detailsView: { + marginLeft: 10, + }, + text: { + marginRight: 5, + marginBottom: 5, + }, +}); + +const MemesListItem = ({ meme }: { meme: Meme }) => { + const { navigate } = useNavigation>(); + const { dimensions } = useDimensions(); + + const [imageWidth, setImageWidth] = useState(); + const [imageHeight, setImageHeight] = useState(); + + Image.getSize(meme.uri, () => { + const paddedWidth = 75; + setImageWidth(paddedWidth); + setImageHeight(paddedWidth); + }); + + return ( + <> + {imageWidth && imageHeight && ( + + navigate(ROUTE.EDIT_MEME, { id: meme.id.toHexString() }) + } + style={[memesListItemStyles.view, styles.flexRow]}> + <> + + + + + + {meme.title} + + + + {meme.dateModified.toLocaleDateString()} • {meme.size / 1000} + KB + + + + {meme.tags.map(tag => ( + + #{tag.name} + + ))} + + + + + )} + + ); +}; + +export default MemesListItem; diff --git a/src/components/memes/listView/memesListView.tsx b/src/components/memes/listView/memesListView.tsx new file mode 100644 index 0000000..3afcee1 --- /dev/null +++ b/src/components/memes/listView/memesListView.tsx @@ -0,0 +1,68 @@ +import React, { RefObject } from 'react'; +import { Meme } from '../../../database'; +import { FlashList } from '@shopify/flash-list'; +import { Divider, HelperText } from 'react-native-paper'; +import { + NativeScrollEvent, + NativeSyntheticEvent, + StyleSheet, +} from 'react-native'; +import styles from '../../../styles'; +import { ORIENTATION, useDimensions } from '../../../contexts'; +import MemesListItem from './memesListItem'; + +const gridViewStyles = StyleSheet.create({ + helperText: { + marginVertical: 10, + }, + flashList: { + paddingBottom: 100, + paddingHorizontal: 5, + }, +}); + +const MemesListView = ({ + memes, + flashListRef, + flashListPadding, + handleScroll, +}: { + memes: Realm.Results>; + flashListRef: RefObject>; + flashListPadding: number; + handleScroll: (event: NativeSyntheticEvent) => void; +}) => { + const { orientation, dimensions } = useDimensions(); + + return ( + } + ItemSeparatorComponent={() => } + contentContainerStyle={{ + paddingTop: + flashListPadding + + dimensions.height * + (orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04), + ...gridViewStyles.flashList, + }} + ListEmptyComponent={() => ( + + No memes found + + )} + onScroll={handleScroll} + /> + ); +}; + +export default MemesListView; diff --git a/src/components/memes/masonryView/memesMasonryItem.tsx b/src/components/memes/masonryView/memesMasonryItem.tsx new file mode 100644 index 0000000..6f687ae --- /dev/null +++ b/src/components/memes/masonryView/memesMasonryItem.tsx @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import { Image, StyleSheet, TouchableHighlight } from 'react-native'; +import { useNavigation, NavigationProp } from '@react-navigation/native'; +import { useSelector } from 'react-redux'; +import { Meme } from '../../../database'; +import { ROUTE, RootStackParamList } from '../../../types'; +import { useDimensions } from '../../../contexts'; +import { RootState } from '../../../state'; + +const memeMasonryItemStyles = StyleSheet.create({ + view: { + margin: 2.5, + borderRadius: 5, + }, + image: { + borderRadius: 5, + }, +}); + +const MemesMasonryItem = ({ meme }: { meme: Meme }) => { + const { navigate } = useNavigation>(); + const { dimensions } = useDimensions(); + const masonryColumns = useSelector( + (state: RootState) => state.settings.masonryColumns, + ); + + const [imageWidth, setImageWidth] = useState(); + const [imageHeight, setImageHeight] = useState(); + + Image.getSize(meme.uri, (width, height) => { + const paddedWidth = (dimensions.width * 0.92) / masonryColumns - 5; + setImageWidth(paddedWidth); + setImageHeight((paddedWidth / width) * height); + }); + + return ( + <> + {imageWidth && imageHeight && ( + + navigate(ROUTE.EDIT_MEME, { id: meme.id.toHexString() }) + } + style={memeMasonryItemStyles.view}> + + + )} + + ); +}; + +export default MemesMasonryItem; diff --git a/src/components/memes/masonryView/memesMasonryView.tsx b/src/components/memes/masonryView/memesMasonryView.tsx new file mode 100644 index 0000000..8e1e22a --- /dev/null +++ b/src/components/memes/masonryView/memesMasonryView.tsx @@ -0,0 +1,75 @@ +import React, { RefObject } from 'react'; +import { Meme } from '../../../database'; +import { FlashList, MasonryFlashList } from '@shopify/flash-list'; +import { HelperText } from 'react-native-paper'; +import { + NativeScrollEvent, + NativeSyntheticEvent, + StyleSheet, +} from 'react-native'; +import { useSelector } from 'react-redux'; +import styles from '../../../styles'; +import { RootState } from '../../../state'; +import { ORIENTATION, useDimensions } from '../../../contexts'; +import { getFlashListItemHeight } from '../../../utilities'; +import MemesMasonryItem from './memesMasonryItem'; + +const memeMasonryViewStyles = StyleSheet.create({ + helperText: { + marginVertical: 10, + }, + flashList: { + paddingBottom: 100, + // Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876 + paddingHorizontal: 0.1, + }, +}); + +const MemesMasonryView = ({ + memes, + flashListRef, + flashListPadding, + handleScroll, +}: { + memes: Realm.Results>; + flashListRef: RefObject>; + flashListPadding: number; + handleScroll: (event: NativeSyntheticEvent) => void; +}) => { + const { orientation, dimensions } = useDimensions(); + const masonryColumns = useSelector( + (state: RootState) => state.settings.masonryColumns, + ); + + return ( + } + contentContainerStyle={{ + paddingTop: + flashListPadding + + dimensions.height * + (orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04), + ...memeMasonryViewStyles.flashList, + }} + ListEmptyComponent={() => ( + + No memes found + + )} + onScroll={handleScroll} + /> + ); +}; + +export default MemesMasonryView; diff --git a/src/components/memes/memeCard.tsx b/src/components/memes/memeCard.tsx deleted file mode 100644 index 6cb1022..0000000 --- a/src/components/memes/memeCard.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useState } from 'react'; -import { useNavigation, NavigationProp } from '@react-navigation/native'; -import { Meme } from '../../database'; -import { ROUTE, RootStackParamList } from '../../types'; -import { Card } from 'react-native-paper'; -import { Image, StyleSheet } from 'react-native'; -import { useDimensions } from '../../contexts'; - -const memeCardStyles = StyleSheet.create({ - card: { - margin: 5, - }, -}); - -const MemeCard = ({ meme }: { meme: Meme }) => { - const { navigate } = useNavigation>(); - const { dimensions } = useDimensions(); - - const [imageWidth, setImageWidth] = useState(); - const [imageHeight, setImageHeight] = useState(); - - Image.getSize(meme.uri, (width, height) => { - const paddedWidth = (dimensions.width * 0.92) / 2 - 10; - setImageWidth(paddedWidth); - setImageHeight((paddedWidth / width) * height); - }); - - return ( - <> - {imageWidth && imageHeight && ( - - navigate(ROUTE.EDIT_MEME, { id: meme.id.toHexString() }) - } - style={memeCardStyles.card}> - - - - )} - - ); -}; - -export default MemeCard; diff --git a/src/components/memes/memeEditor.tsx b/src/components/memes/memeEditor.tsx index 355f957..0ebc4c8 100644 --- a/src/components/memes/memeEditor.tsx +++ b/src/components/memes/memeEditor.tsx @@ -5,11 +5,7 @@ import { useDimensions } from '../../contexts'; import LoadingView from '../loadingView'; import { MemeTagSelector } from '.'; import { Tag } from '../../database'; -import { - StringValidationResult, - validateMemeDescription, - validateMemeTitle, -} from '../../utilities'; +import { StringValidationResult, validateMemeTitle } from '../../utilities'; const memeEditorStyles = { image: { @@ -28,16 +24,12 @@ const MemeEditor = ({ imageUri, memeTitle, setMemeTitle, - memeDescription, - setMemeDescription, memeTags, setMemeTags, }: { imageUri: string; memeTitle: StringValidationResult; setMemeTitle: (name: StringValidationResult) => void; - memeDescription: StringValidationResult; - setMemeDescription: (description: StringValidationResult) => void; memeTags: Map; setMemeTags: (tags: Map) => void; }) => { @@ -48,8 +40,12 @@ const MemeEditor = ({ Image.getSize(imageUri, (width, height) => { const paddedWidth = dimensions.width * 0.92; + const paddedHeight = Math.max( + Math.min((paddedWidth / width) * height, 500), + 100, + ); setImageWidth(paddedWidth); - setImageHeight((paddedWidth / width) * height); + setImageHeight(paddedHeight); }); if (!imageWidth || !imageHeight) return ; @@ -76,24 +72,13 @@ const MemeEditor = ({ }, memeEditorStyles.image, ]} + resizeMode="contain" /> - - setMemeDescription(validateMemeDescription(description)) - } - error={!memeDescription.valid} - /> ); }; diff --git a/src/database/meme.ts b/src/database/meme.ts index 504fcc4..7e3c030 100644 --- a/src/database/meme.ts +++ b/src/database/meme.ts @@ -24,9 +24,8 @@ class Meme extends Object { uri!: string; size!: number; title!: string; - description!: string; isFavorite!: boolean; - tags!: Tag[] | Realm.Set; + tags!: Realm.List; tagsLength!: number; dateCreated!: Date; dateModified!: Date; @@ -42,9 +41,8 @@ class Meme extends Object { uri: 'string', size: 'int', title: 'string', - description: { type: 'string', default: '' }, isFavorite: { type: 'bool', indexed: true, default: false }, - tags: { type: 'set', objectType: 'Tag', default: [] }, + tags: { type: 'list', objectType: 'Tag', default: [] }, tagsLength: { type: 'int', default: 0 }, dateCreated: { type: 'date', default: () => new Date() }, dateModified: { type: 'date', default: () => new Date() }, diff --git a/src/database/tag.ts b/src/database/tag.ts index f54f46f..f311326 100644 --- a/src/database/tag.ts +++ b/src/database/tag.ts @@ -7,7 +7,7 @@ class Tag extends Object { id!: BSON.UUID; name!: string; color!: string; - memes!: Meme[] | Realm.Set; + memes!: Realm.List; memesLength!: number; dateCreated!: Date; dateModified!: Date; @@ -21,7 +21,7 @@ class Tag extends Object { id: { type: 'uuid', default: () => new BSON.UUID() }, name: { type: 'string', indexed: true }, color: { type: 'string', default: () => generateRandomColor() }, - memes: { type: 'set', objectType: 'Meme', default: [] }, + memes: { type: 'list', objectType: 'Meme', default: [] }, memesLength: { type: 'int', default: 0 }, dateCreated: { type: 'date', default: () => new Date() }, dateModified: { type: 'date', default: () => new Date() }, diff --git a/src/screens/addMeme.tsx b/src/screens/addMeme.tsx index 004328e..e85fe1a 100644 --- a/src/screens/addMeme.tsx +++ b/src/screens/addMeme.tsx @@ -13,11 +13,7 @@ import styles from '../styles'; import { ROUTE, RootStackParamList } from '../types'; import { Meme, Tag } from '../database'; import { RootState } from '../state'; -import { - getMemeType, - validateMemeDescription, - validateMemeTitle, -} from '../utilities'; +import { getMemeType, validateMemeTitle } from '../utilities'; import { MemeEditor } from '../components'; const AddMeme = ({ @@ -35,9 +31,6 @@ const AddMeme = ({ const { file } = route.params; const [memeTitle, setMemeTitle] = useState(validateMemeTitle('New Meme')); - const [memeDescription, setMemeDescription] = useState( - validateMemeDescription(''), - ); const [memeIsFavorite, setMemeIsFavorite] = useState(false); const [memeTags, setMemeTags] = useState(new Map()); @@ -69,7 +62,6 @@ const AddMeme = ({ uri, size, title: memeTitle.parsed, - description: memeDescription.parsed, isFavorite: memeIsFavorite, tags: [...memeTags.values()], tagsLength: memeTags.size, @@ -77,9 +69,8 @@ const AddMeme = ({ memeTags.forEach(tag => { tag.dateModified = new Date(); - const memes = tag.memes as Realm.Set; - memes.add(meme); - tag.memesLength = memes.size; + tag.memes.push(meme); + tag.memesLength = tag.memes.length; }); }); @@ -111,8 +102,6 @@ const AddMeme = ({ imageUri={file.uri} memeTitle={memeTitle} setMemeTitle={setMemeTitle} - memeDescription={memeDescription} - setMemeDescription={setMemeDescription} memeTags={memeTags} setMemeTags={setMemeTags} /> @@ -122,7 +111,7 @@ const AddMeme = ({ mode="contained" icon="floppy" onPress={handleSave} - disabled={!memeTitle.valid || !memeDescription.valid || isSaving} + disabled={!memeTitle.valid || isSaving} loading={isSaving}> Save diff --git a/src/screens/editMeme.tsx b/src/screens/editMeme.tsx index 56d3b98..029f45b 100644 --- a/src/screens/editMeme.tsx +++ b/src/screens/editMeme.tsx @@ -10,7 +10,7 @@ import { ORIENTATION, useDimensions } from '../contexts'; import styles from '../styles'; import { RootStackParamList, ROUTE } from '../types'; import { Tag, Meme } from '../database'; -import { validateMemeTitle, validateMemeDescription } from '../utilities'; +import { validateMemeTitle } from '../utilities'; import { MemeEditor } from '../components'; const EditMeme = ({ @@ -28,14 +28,8 @@ const EditMeme = ({ )!; const [memeTitle, setMemeTitle] = useState(validateMemeTitle(meme.title)); - const [memeDescription, setMemeDescription] = useState( - validateMemeDescription(meme.description), - ); - const [memeIsFavorite, setMemeIsFavorite] = useState(meme.isFavorite); const [memeTags, setMemeTags] = useState( - new Map( - (meme.tags as Realm.Set).map(tag => [tag.id.toHexString(), tag]), - ), + new Map(meme.tags.map(tag => [tag.id.toHexString(), tag])), ); const [isSaving, setIsSaving] = useState(false); @@ -46,24 +40,22 @@ const EditMeme = ({ realm.write(() => { meme.tags.forEach(tag => { if (!memeTags.has(tag.id.toHexString())) { - const memes = tag.memes as Realm.Set; - memes.delete(meme); - tag.memesLength = memes.size; + tag.memes.slice(tag.memes.indexOf(meme), 1); + tag.memesLength -= 1; tag.dateModified = new Date(); } }); memeTags.forEach(tag => { - if (!(meme.tags as Realm.Set).has(tag)) { - const memes = tag.memes as Realm.Set; - memes.add(meme); - tag.memesLength = memes.size; + if (!meme.tags.some(memeTag => memeTag.id.equals(tag.id))) { + tag.memes.push(meme); + tag.memesLength = tag.memes.length; tag.dateModified = new Date(); } }); meme.title = memeTitle.parsed; - meme.description = memeDescription.parsed; + // @ts-expect-error - Realm is a fuck meme.tags = [...memeTags.values()]; meme.tagsLength = memeTags.size; meme.dateModified = new Date(); @@ -74,9 +66,8 @@ const EditMeme = ({ const handleFavorite = () => { realm.write(() => { - meme.isFavorite = !memeIsFavorite; + meme.isFavorite = !meme.isFavorite; }); - setMemeIsFavorite(!memeIsFavorite); }; const handleDelete = async () => { @@ -86,9 +77,8 @@ const EditMeme = ({ realm.write(() => { for (const tag of meme.tags) { tag.dateModified = new Date(); - const memes = tag.memes as Realm.Set; - memes.delete(meme); - tag.memesLength = memes.size; + tag.memes.slice(tag.memes.indexOf(meme), 1); + tag.memesLength -= 1; } realm.delete(meme); @@ -103,7 +93,7 @@ const EditMeme = ({ goBack()} /> @@ -123,8 +113,6 @@ const EditMeme = ({ imageUri={meme.uri} memeTitle={memeTitle} setMemeTitle={setMemeTitle} - memeDescription={memeDescription} - setMemeDescription={setMemeDescription} memeTags={memeTags} setMemeTags={setMemeTags} /> @@ -134,7 +122,7 @@ const EditMeme = ({ mode="contained" icon="floppy" onPress={handleSave} - disabled={!memeTitle.valid || !memeDescription.valid || isSaving} + disabled={!memeTitle.valid || isSaving} loading={isSaving}> Save diff --git a/src/screens/editTag.tsx b/src/screens/editTag.tsx index 539b759..da001c6 100644 --- a/src/screens/editTag.tsx +++ b/src/screens/editTag.tsx @@ -43,9 +43,8 @@ const EditTag = ({ realm.write(() => { for (const meme of tag.memes) { meme.dateModified = new Date(); - const tags = meme.tags as Realm.Set; - tags.delete(tag); - meme.tagsLength = tags.size; + meme.tags.slice(meme.tags.indexOf(tag), 1); + meme.tagsLength -= 1; } realm.delete(tag); diff --git a/src/screens/memes.tsx b/src/screens/memes.tsx index 12df1f8..462cf08 100644 --- a/src/screens/memes.tsx +++ b/src/screens/memes.tsx @@ -1,38 +1,79 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { RefObject, useCallback, useRef, useState } from 'react'; import { BackHandler, NativeScrollEvent, NativeSyntheticEvent, - StyleSheet, View, } from 'react-native'; import { useQuery } from '@realm/react'; -import { useTheme, HelperText } from 'react-native-paper'; +import { useTheme } from 'react-native-paper'; import { useDispatch, useSelector } from 'react-redux'; -import { FlashList, MasonryFlashList } from '@shopify/flash-list'; +import { FlashList } from '@shopify/flash-list'; import { useFocusEffect } from '@react-navigation/native'; import styles from '../styles'; -import { SORT_DIRECTION, memesSortQuery } from '../types'; +import { SORT_DIRECTION, VIEW, memesSortQuery } from '../types'; import { RootState, setNavVisible } from '../state'; import { Meme } from '../database'; -import { ORIENTATION, useDimensions } from '../contexts'; -import { HideableHeader, MemesHeader } from '../components'; -import MemeCard from '../components/memes/memeCard'; +import { + HideableHeader, + MemesHeader, + MemesMasonryView, + MemesGridView, + MemesListView, +} from '../components'; -const memesStyles = StyleSheet.create({ - helperText: { - marginVertical: 10, - }, - flashList: { - paddingBottom: 100, - // Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876 - paddingHorizontal: 0.01, - }, -}); +const MemesView = ({ + memes, + flashListRef, + flashListPadding, + handleScroll, +}: { + memes: Realm.Results>; + flashListRef: RefObject>; + flashListPadding: number; + handleScroll: (event: NativeSyntheticEvent) => void; +}) => { + const view = useSelector((state: RootState) => state.memes.view); + + switch (view) { + case VIEW.MASONRY: { + return ( + + ); + } + case VIEW.GRID: { + return ( + + ); + } + case VIEW.LIST: { + return ( + + ); + } + default: { + return <>; + } + } +}; const Memes = () => { const { colors } = useTheme(); - const { dimensions, orientation } = useDimensions(); const sort = useSelector((state: RootState) => state.memes.sort); const sortDirection = useSelector( (state: RootState) => state.memes.sortDirection, @@ -54,10 +95,31 @@ const Memes = () => { collectionIn => { let collection = collectionIn; + const tokens = search + .match(/"[^"]+"|\S+/gi) + ?.map(token => token.replaceAll(/["']/g, '')); + + const tags = tokens + ?.filter(token => token.startsWith('#')) + .map(tag => tag.slice(1)); + + const words = tokens?.filter(token => !token.startsWith('#')); + + const tagsQuery = tags + ?.map((tag, index) => `ANY tags.name CONTAINS[c] $${index}`) + .join(' OR '); + + const wordsQuery = words + ?.map((word, index) => `title CONTAINS[c] $${index}`) + .join(' OR '); + if (favoritesOnly) collection = collection.filtered('isFavorite == true'); if (filter) collection = collection.filtered('type == $0', filter); - if (search) { - collection = collection.filtered('title CONTAINS[c] $0', search); + if (tags && tagsQuery) { + collection = collection.filtered(tagsQuery, ...tags); + } + if (words && wordsQuery) { + collection = collection.filtered(wordsQuery, ...words); } collection = collection.sorted( @@ -79,7 +141,9 @@ const Memes = () => { dispatch(setNavVisible(true)); } else { const diff = currentOffset - scrollOffset; - if (Math.abs(diff) > 50) dispatch(setNavVisible(diff < 0)); + if (Math.abs(diff) > 50) { + dispatch(setNavVisible(diff < 0)); + } } setScrollOffset(currentOffset); @@ -89,7 +153,6 @@ const Memes = () => { useFocusEffect( useCallback(() => { - dispatch(setNavVisible(true)); const handleBackPress = () => { if (scrollOffset > 0) { flashListRef.current?.scrollToOffset({ offset: 0, animated: true }); @@ -102,7 +165,13 @@ const Memes = () => { return () => BackHandler.removeEventListener('hardwareBackPress', handleBackPress); - }, [dispatch, scrollOffset]), + }, [scrollOffset]), + ); + + useFocusEffect( + useCallback(() => { + dispatch(setNavVisible(true)); + }, [dispatch]), ); return ( @@ -121,32 +190,11 @@ const Memes = () => { }} /> - } - contentContainerStyle={{ - paddingTop: - flashListPadding + - dimensions.height * - (orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04), - ...memesStyles.flashList, - }} - ListEmptyComponent={() => ( - - No memes found - - )} - onScroll={handleScroll} + ); diff --git a/src/screens/settings.tsx b/src/screens/settings.tsx index add1cbe..c7e9fab 100644 --- a/src/screens/settings.tsx +++ b/src/screens/settings.tsx @@ -4,6 +4,7 @@ import { Button, List, Portal, + SegmentedButtons, Snackbar, Switch, Text, @@ -13,19 +14,34 @@ import { openDocumentTree } from 'react-native-scoped-storage'; import { useDispatch, useSelector } from 'react-redux'; import type {} from 'redux-thunk/extend-redux'; import styles from '../styles'; -import { RootState, setNoMedia, setStorageUri } from '../state'; +import { + RootState, + setGridColumns, + setMasonryColumns, + setNoMedia, + setStorageUri, +} from '../state'; import { ORIENTATION, useDimensions } from '../contexts'; -const settingsScreenStyles = StyleSheet.create({ +const settingsStyles = StyleSheet.create({ snackbar: { marginBottom: 90, }, + marginBottom: { + marginBottom: 15, + }, }); -const SettingsScreen = () => { +const Settings = () => { const { colors } = useTheme(); - const { orientation, responsive } = useDimensions(); + const { orientation } = useDimensions(); const noMedia = useSelector((state: RootState) => state.settings.noMedia); + const masonryColumns = useSelector( + (state: RootState) => state.settings.masonryColumns, + ); + const gridColumns = useSelector( + (state: RootState) => state.settings.gridColumns, + ); const dispatch = useDispatch(); const [isOptimizingDatabase, setIsOptimizingDatabase] = useState(false); @@ -52,21 +68,56 @@ const SettingsScreen = () => { ]}> - Database - + Views + + Masonry Columns + + { + void dispatch( + setMasonryColumns(Number.parseInt(value) as 1 | 2 | 3 | 4), + ); + }} + buttons={[ + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '3', value: '3' }, + { label: '4', value: '4' }, + ]} + style={settingsStyles.marginBottom} + /> + + Grid Columns + + { + void dispatch( + setGridColumns(Number.parseInt(value) as 1 | 2 | 3 | 4), + ); + }} + buttons={[ + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '3', value: '3' }, + { label: '4', value: '4' }, + ]} + /> Media Storage + setSnackbarVisible(false)} - style={settingsScreenStyles.snackbar} + style={settingsStyles.snackbar} action={{ label: 'Dismiss', onPress: () => setSnackbarVisible(false), @@ -108,4 +165,4 @@ const SettingsScreen = () => { ); }; -export default SettingsScreen; +export default Settings; diff --git a/src/screens/tags.tsx b/src/screens/tags.tsx index 9a6a20a..2a65277 100644 --- a/src/screens/tags.tsx +++ b/src/screens/tags.tsx @@ -80,7 +80,6 @@ const Tags = () => { useFocusEffect( useCallback(() => { - dispatch(setNavVisible(true)); const handleBackPress = () => { if (scrollOffset > 0) { flashListRef.current?.scrollToOffset({ offset: 0, animated: true }); @@ -93,7 +92,13 @@ const Tags = () => { return () => BackHandler.removeEventListener('hardwareBackPress', handleBackPress); - }, [dispatch, scrollOffset]), + }, [scrollOffset]), + ); + + useFocusEffect( + useCallback(() => { + dispatch(setNavVisible(true)); + }, [dispatch]), ); return ( diff --git a/src/state/index.ts b/src/state/index.ts index 896a8c7..44208bc 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -54,6 +54,8 @@ export { type SettingsState, setStorageUri, setNoMedia, + setMasonryColumns, + setGridColumns, validateSettings, } from './settings'; export { diff --git a/src/state/memes.ts b/src/state/memes.ts index d2b5c9a..bb57422 100644 --- a/src/state/memes.ts +++ b/src/state/memes.ts @@ -37,6 +37,10 @@ const memesSlice = createSlice({ cycleMemesView: state => { switch (state.view) { case VIEW.MASONRY: { + state.view = VIEW.GRID; + break; + } + case VIEW.GRID: { state.view = VIEW.LIST; break; } diff --git a/src/state/settings.ts b/src/state/settings.ts index e4d34ec..699acaf 100644 --- a/src/state/settings.ts +++ b/src/state/settings.ts @@ -11,11 +11,15 @@ import { RootState } from '.'; interface SettingsState { storageUri: string | undefined; noMedia: boolean; + masonryColumns: 1 | 2 | 3 | 4; + gridColumns: 1 | 2 | 3 | 4; } const initialState: SettingsState = { storageUri: undefined, noMedia: false, + masonryColumns: 2, + gridColumns: 3, }; const settingsSlice = createSlice({ @@ -28,10 +32,17 @@ const settingsSlice = createSlice({ setNoMedia: (state, action: PayloadAction) => { state.noMedia = action.payload; }, + setMasonryColumns: (state, action: PayloadAction<1 | 2 | 3 | 4>) => { + state.masonryColumns = action.payload; + }, + setGridColumns: (state, action: PayloadAction<1 | 2 | 3 | 4>) => { + state.gridColumns = action.payload; + }, }, }); -const { setStorageUri, setNoMedia } = settingsSlice.actions; +const { setStorageUri, setNoMedia, setMasonryColumns, setGridColumns } = + settingsSlice.actions; const updateStorageUri = createAsyncThunk( 'settings/updateStorageUri', @@ -111,6 +122,8 @@ export { type SettingsState, updateStorageUri as setStorageUri, updateNoMedia as setNoMedia, + setMasonryColumns, + setGridColumns, validateSettings, }; export default settingsSlice.reducer; diff --git a/src/types/view.ts b/src/types/view.ts index af21a2b..82051a1 100644 --- a/src/types/view.ts +++ b/src/types/view.ts @@ -1,5 +1,6 @@ enum VIEW { MASONRY = 'Masonry', + GRID = 'Grid', LIST = 'List', } diff --git a/src/utilities/dimensions.ts b/src/utilities/dimensions.ts new file mode 100644 index 0000000..8e8ba06 --- /dev/null +++ b/src/utilities/dimensions.ts @@ -0,0 +1,9 @@ +const getFlashListItemHeight = (numColumns: number) => { + const A = 500; + const B = 300; + const C = 1; + const height = A - B * Math.log(numColumns + C); + return Math.max(Math.round(height), 0); +}; + +export { getFlashListItemHeight }; diff --git a/src/utilities/icon.ts b/src/utilities/icon.ts index e55f868..584c1f2 100644 --- a/src/utilities/icon.ts +++ b/src/utilities/icon.ts @@ -50,6 +50,9 @@ const getViewIcon = (view: VIEW) => { case VIEW.MASONRY: { return 'view-dashboard'; } + case VIEW.GRID: { + return 'view-grid'; + } case VIEW.LIST: { return 'view-list'; } diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 442c013..ca88de1 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -7,6 +7,7 @@ export { } from './color'; export { packageName, appName, fileProvider, noOp } from './constants'; export { multipleIdQuery } from './database'; +export { getFlashListItemHeight } from './dimensions'; export { allowedImageMimeTypes, allowedGifMimeTypes, @@ -18,7 +19,6 @@ export { getSortIcon, getViewIcon } from './icon'; export { type StringValidationResult, validateMemeTitle, - validateMemeDescription, validateTagName, validateColor, } from './validation'; diff --git a/src/utilities/validation.ts b/src/utilities/validation.ts index ddd3ecf..5886ba4 100644 --- a/src/utilities/validation.ts +++ b/src/utilities/validation.ts @@ -26,18 +26,6 @@ const validateMemeTitle = (title: string): StringValidationResult => { }; }; -const validateMemeDescription = ( - description: string, -): StringValidationResult => { - const parsedDescription = description.trim(); - - return { - valid: true, - raw: description, - parsed: parsedDescription, - }; -}; - const validateTagName = (name: string): StringValidationResult => { const parsedName = name.trim(); @@ -79,7 +67,6 @@ const validateColor = (color: string): StringValidationResult => { export { type StringValidationResult, validateMemeTitle, - validateMemeDescription, validateTagName, validateColor, };