Add tag selector layout animations

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-26 18:31:32 +03:00
parent acba17462f
commit cecede4e28
8 changed files with 69 additions and 24 deletions

4
package-lock.json generated
View File

@@ -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",

View File

@@ -1,6 +1,6 @@
{
"name": "@karaolidis/terminally-online",
"version": "0.0.1",
"version": "0.0.2",
"private": true,
"scripts": {
"postinstall": "patch-package",

View File

@@ -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) {

View File

@@ -2,7 +2,6 @@ export {
MemesList,
MemeEditor,
MemesHeader,
MemeTagSearchModal,
MemeTagSelector,
MemeViewItem,
} from './memes';

View File

@@ -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';

View File

@@ -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 <LoadingView />;

View File

@@ -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<Tag>(
@@ -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={{

View File

@@ -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<typeof View>) => {
const { width } = useSafeAreaFrame();
const flashListRef = useRef<FlashList<Tag>>(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 (
<>
<View {...props}>
<FlashList
ref={flashListRef}
data={[...memeTags.values()]}
keyExtractor={tag => tag.id.toHexString()}
horizontal
estimatedItemSize={120}
showsHorizontalScrollIndicator={false}