diff --git a/package-lock.json b/package-lock.json index 16c2f69..e997a8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@karaolidis/terminally-online", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@karaolidis/terminally-online", - "version": "0.0.1", + "version": "0.0.2", "hasInstallScript": true, "dependencies": { "@bankify/redux-persist-realm": "^0.1.3", diff --git a/package.json b/package.json index a2aebef..2f86d06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@karaolidis/terminally-online", - "version": "0.0.1", + "version": "0.0.2", "private": true, "scripts": { "postinstall": "patch-package", diff --git a/src/app.tsx b/src/app.tsx index 783ee99..8a38997 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,5 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { AppState, StatusBar, useColorScheme, StyleSheet } from 'react-native'; +import { + AppState, + StatusBar, + useColorScheme, + StyleSheet, + UIManager, +} from 'react-native'; import { PaperProvider } from 'react-native-paper'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { RealmProvider } from '@realm/react'; @@ -27,6 +33,7 @@ const App = () => { const theme = isDarkMode ? darkTheme : lightTheme; const onBeforeLift = async () => { + UIManager.setLayoutAnimationEnabledExperimental(true); await store.dispatch(validateSettings()); const { settings } = store.getState(); if (!settings.storageUri) { diff --git a/src/components/index.ts b/src/components/index.ts index ad5eaa2..c6aa651 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,7 +2,6 @@ export { MemesList, MemeEditor, MemesHeader, - MemeTagSearchModal, MemeTagSelector, MemeViewItem, } from './memes'; diff --git a/src/components/memes/index.ts b/src/components/memes/index.ts index 97bc873..dfd802c 100644 --- a/src/components/memes/index.ts +++ b/src/components/memes/index.ts @@ -2,6 +2,5 @@ export { default as MemesList } from './memesList/memesList'; export { default as MemeEditor } from './memeEditor'; export { default as MemeFail } from './memeFail'; export { default as MemesHeader } from './memesHeader'; -export { default as MemeTagSearchModal } from './memeTagSearchModal'; -export { default as MemeTagSelector } from './memeTagSelector'; +export { default as MemeTagSelector } from './memeTagSelector/memeTagSelector'; export { default as MemeViewItem } from './memeViewItem'; diff --git a/src/components/memes/memeEditor.tsx b/src/components/memes/memeEditor.tsx index d811de1..ca3b439 100644 --- a/src/components/memes/memeEditor.tsx +++ b/src/components/memes/memeEditor.tsx @@ -41,7 +41,7 @@ const MemeEditor = ({ const { width } = useSafeAreaFrame(); const { dimensions, loading, error } = useImageDimensions({ uri: memeUri }); - setMemeUriError(error); + useEffect(() => setMemeUriError(error), [error, setMemeUriError]); if (!memeUriError && (loading || !dimensions)) return ; diff --git a/src/components/memes/memeTagSearchModal.tsx b/src/components/memes/memeTagSelector/memeTagSearchModal.tsx similarity index 77% rename from src/components/memes/memeTagSearchModal.tsx rename to src/components/memes/memeTagSelector/memeTagSearchModal.tsx index de60300..cf6f408 100644 --- a/src/components/memes/memeTagSearchModal.tsx +++ b/src/components/memes/memeTagSelector/memeTagSearchModal.tsx @@ -1,13 +1,13 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { useQuery, useRealm } from '@realm/react'; import { Chip, Modal, Portal, Searchbar, useTheme } from 'react-native-paper'; -import { StyleSheet } from 'react-native'; +import { LayoutAnimation, StyleSheet } from 'react-native'; import { FlashList } from '@shopify/flash-list'; import { useSafeAreaFrame } from 'react-native-safe-area-context'; -import { TAG_SORT, tagSortQuery } from '../../types'; -import { TagChip } from '../tags'; -import { Tag } from '../../database'; -import { validateTagName } from '../../utilities'; +import { TAG_SORT, tagSortQuery } from '../../../types'; +import { TagChip } from '../../tags'; +import { Tag } from '../../../database'; +import { validateTagName } from '../../../utilities'; const memeTagSearchModalStyles = StyleSheet.create({ modal: { @@ -26,6 +26,22 @@ const memeTagSearchModalStyles = StyleSheet.create({ }, }); +const tagLayoutAnimation = { + duration: 150, + create: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, + update: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, + delete: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, +}; + const MemeTagSearchModal = ({ visible, setVisible, @@ -46,13 +62,10 @@ const MemeTagSearchModal = ({ const [search, setSearch] = useState(''); const [tagName, setTagName] = useState(validateTagName(search)); - useEffect(() => { - setTagName(validateTagName(search)); - }, [search]); - const handleSearch = (newSearch: string) => { flashListRef.current?.scrollToOffset({ offset: 0 }); setSearch(newSearch); + setTagName(validateTagName(newSearch)); }; const tags = useQuery( @@ -78,6 +91,8 @@ const MemeTagSearchModal = ({ const id = tag.id.toHexString(); memeTags.delete(id) || memeTags.set(id, tag); setMemeTags(new Map(memeTags)); + flashListRef.current?.prepareForLayoutAnimationRender(); + LayoutAnimation.configureNext(tagLayoutAnimation); }; const handleCreateTag = (name: string) => { @@ -90,6 +105,8 @@ const MemeTagSearchModal = ({ if (!tag) return; memeTags.set(tag.id.toHexString(), tag); setMemeTags(new Map(memeTags)); + flashListRef.current?.prepareForLayoutAnimationRender(); + LayoutAnimation.configureNext(tagLayoutAnimation); }; return ( @@ -114,6 +131,7 @@ const MemeTagSearchModal = ({ ref={flashListRef} data={tags} extraData={memeTags} + keyExtractor={tag => tag.id.toHexString()} horizontal estimatedItemSize={120} estimatedListSize={{ diff --git a/src/components/memes/memeTagSelector.tsx b/src/components/memes/memeTagSelector/memeTagSelector.tsx similarity index 67% rename from src/components/memes/memeTagSelector.tsx rename to src/components/memes/memeTagSelector/memeTagSelector.tsx index a4a00ac..064e2b7 100644 --- a/src/components/memes/memeTagSelector.tsx +++ b/src/components/memes/memeTagSelector/memeTagSelector.tsx @@ -1,11 +1,11 @@ -import React, { ComponentProps, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; +import React, { ComponentProps, useRef, useState } from 'react'; +import { LayoutAnimation, StyleSheet, View } from 'react-native'; import { Chip } from 'react-native-paper'; import { FlashList } from '@shopify/flash-list'; import { useSafeAreaFrame } from 'react-native-safe-area-context'; -import { TagChip } from '../tags'; -import { Tag } from '../../database'; -import { MemeTagSearchModal } from '.'; +import { TagChip } from '../../tags'; +import { Tag } from '../../../database'; +import MemeTagSearchModal from './memeTagSearchModal'; const memeTagSelectorStyles = StyleSheet.create({ tagChip: { @@ -13,6 +13,22 @@ const memeTagSelectorStyles = StyleSheet.create({ }, }); +const tagLayoutAnimation = { + duration: 150, + create: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, + update: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, + delete: { + type: LayoutAnimation.Types.easeInEaseOut, + property: LayoutAnimation.Properties.opacity, + }, +}; + const MemeTagSelector = ({ memeTags, setMemeTags, @@ -23,20 +39,26 @@ const MemeTagSelector = ({ } & ComponentProps) => { const { width } = useSafeAreaFrame(); + const flashListRef = useRef>(null); + const [flashListMargin, setFlashListMargin] = useState(0); const [tagSearchModalVisible, setTagSearchModalVisible] = useState(false); const handleTagPress = (tag: Tag) => { const id = tag.id.toHexString(); - memeTags.delete(id) || memeTags.set(id, tag); + memeTags.delete(id); setMemeTags(new Map(memeTags)); + flashListRef.current?.prepareForLayoutAnimationRender(); + LayoutAnimation.configureNext(tagLayoutAnimation); }; return ( <> tag.id.toHexString()} horizontal estimatedItemSize={120} showsHorizontalScrollIndicator={false}