Add tag selector layout animations
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@karaolidis/terminally-online",
|
"name": "@karaolidis/terminally-online",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@karaolidis/terminally-online",
|
"name": "@karaolidis/terminally-online",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bankify/redux-persist-realm": "^0.1.3",
|
"@bankify/redux-persist-realm": "^0.1.3",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@karaolidis/terminally-online",
|
"name": "@karaolidis/terminally-online",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
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 { PaperProvider } from 'react-native-paper';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import { RealmProvider } from '@realm/react';
|
import { RealmProvider } from '@realm/react';
|
||||||
@@ -27,6 +33,7 @@ const App = () => {
|
|||||||
const theme = isDarkMode ? darkTheme : lightTheme;
|
const theme = isDarkMode ? darkTheme : lightTheme;
|
||||||
|
|
||||||
const onBeforeLift = async () => {
|
const onBeforeLift = async () => {
|
||||||
|
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||||
await store.dispatch(validateSettings());
|
await store.dispatch(validateSettings());
|
||||||
const { settings } = store.getState();
|
const { settings } = store.getState();
|
||||||
if (!settings.storageUri) {
|
if (!settings.storageUri) {
|
||||||
|
@@ -2,7 +2,6 @@ export {
|
|||||||
MemesList,
|
MemesList,
|
||||||
MemeEditor,
|
MemeEditor,
|
||||||
MemesHeader,
|
MemesHeader,
|
||||||
MemeTagSearchModal,
|
|
||||||
MemeTagSelector,
|
MemeTagSelector,
|
||||||
MemeViewItem,
|
MemeViewItem,
|
||||||
} from './memes';
|
} from './memes';
|
||||||
|
@@ -2,6 +2,5 @@ export { default as MemesList } from './memesList/memesList';
|
|||||||
export { default as MemeEditor } from './memeEditor';
|
export { default as MemeEditor } from './memeEditor';
|
||||||
export { default as MemeFail } from './memeFail';
|
export { default as MemeFail } from './memeFail';
|
||||||
export { default as MemesHeader } from './memesHeader';
|
export { default as MemesHeader } from './memesHeader';
|
||||||
export { default as MemeTagSearchModal } from './memeTagSearchModal';
|
export { default as MemeTagSelector } from './memeTagSelector/memeTagSelector';
|
||||||
export { default as MemeTagSelector } from './memeTagSelector';
|
|
||||||
export { default as MemeViewItem } from './memeViewItem';
|
export { default as MemeViewItem } from './memeViewItem';
|
||||||
|
@@ -41,7 +41,7 @@ const MemeEditor = ({
|
|||||||
const { width } = useSafeAreaFrame();
|
const { width } = useSafeAreaFrame();
|
||||||
|
|
||||||
const { dimensions, loading, error } = useImageDimensions({ uri: memeUri });
|
const { dimensions, loading, error } = useImageDimensions({ uri: memeUri });
|
||||||
setMemeUriError(error);
|
useEffect(() => setMemeUriError(error), [error, setMemeUriError]);
|
||||||
|
|
||||||
if (!memeUriError && (loading || !dimensions)) return <LoadingView />;
|
if (!memeUriError && (loading || !dimensions)) return <LoadingView />;
|
||||||
|
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { useQuery, useRealm } from '@realm/react';
|
import { useQuery, useRealm } from '@realm/react';
|
||||||
import { Chip, Modal, Portal, Searchbar, useTheme } from 'react-native-paper';
|
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 { FlashList } from '@shopify/flash-list';
|
||||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||||
import { TAG_SORT, tagSortQuery } from '../../types';
|
import { TAG_SORT, tagSortQuery } from '../../../types';
|
||||||
import { TagChip } from '../tags';
|
import { TagChip } from '../../tags';
|
||||||
import { Tag } from '../../database';
|
import { Tag } from '../../../database';
|
||||||
import { validateTagName } from '../../utilities';
|
import { validateTagName } from '../../../utilities';
|
||||||
|
|
||||||
const memeTagSearchModalStyles = StyleSheet.create({
|
const memeTagSearchModalStyles = StyleSheet.create({
|
||||||
modal: {
|
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 = ({
|
const MemeTagSearchModal = ({
|
||||||
visible,
|
visible,
|
||||||
setVisible,
|
setVisible,
|
||||||
@@ -46,13 +62,10 @@ const MemeTagSearchModal = ({
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [tagName, setTagName] = useState(validateTagName(search));
|
const [tagName, setTagName] = useState(validateTagName(search));
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTagName(validateTagName(search));
|
|
||||||
}, [search]);
|
|
||||||
|
|
||||||
const handleSearch = (newSearch: string) => {
|
const handleSearch = (newSearch: string) => {
|
||||||
flashListRef.current?.scrollToOffset({ offset: 0 });
|
flashListRef.current?.scrollToOffset({ offset: 0 });
|
||||||
setSearch(newSearch);
|
setSearch(newSearch);
|
||||||
|
setTagName(validateTagName(newSearch));
|
||||||
};
|
};
|
||||||
|
|
||||||
const tags = useQuery<Tag>(
|
const tags = useQuery<Tag>(
|
||||||
@@ -78,6 +91,8 @@ const MemeTagSearchModal = ({
|
|||||||
const id = tag.id.toHexString();
|
const id = tag.id.toHexString();
|
||||||
memeTags.delete(id) || memeTags.set(id, tag);
|
memeTags.delete(id) || memeTags.set(id, tag);
|
||||||
setMemeTags(new Map(memeTags));
|
setMemeTags(new Map(memeTags));
|
||||||
|
flashListRef.current?.prepareForLayoutAnimationRender();
|
||||||
|
LayoutAnimation.configureNext(tagLayoutAnimation);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateTag = (name: string) => {
|
const handleCreateTag = (name: string) => {
|
||||||
@@ -90,6 +105,8 @@ const MemeTagSearchModal = ({
|
|||||||
if (!tag) return;
|
if (!tag) return;
|
||||||
memeTags.set(tag.id.toHexString(), tag);
|
memeTags.set(tag.id.toHexString(), tag);
|
||||||
setMemeTags(new Map(memeTags));
|
setMemeTags(new Map(memeTags));
|
||||||
|
flashListRef.current?.prepareForLayoutAnimationRender();
|
||||||
|
LayoutAnimation.configureNext(tagLayoutAnimation);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -114,6 +131,7 @@ const MemeTagSearchModal = ({
|
|||||||
ref={flashListRef}
|
ref={flashListRef}
|
||||||
data={tags}
|
data={tags}
|
||||||
extraData={memeTags}
|
extraData={memeTags}
|
||||||
|
keyExtractor={tag => tag.id.toHexString()}
|
||||||
horizontal
|
horizontal
|
||||||
estimatedItemSize={120}
|
estimatedItemSize={120}
|
||||||
estimatedListSize={{
|
estimatedListSize={{
|
@@ -1,11 +1,11 @@
|
|||||||
import React, { ComponentProps, useState } from 'react';
|
import React, { ComponentProps, useRef, useState } from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { LayoutAnimation, StyleSheet, View } from 'react-native';
|
||||||
import { Chip } from 'react-native-paper';
|
import { Chip } from 'react-native-paper';
|
||||||
import { FlashList } from '@shopify/flash-list';
|
import { FlashList } from '@shopify/flash-list';
|
||||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||||
import { TagChip } from '../tags';
|
import { TagChip } from '../../tags';
|
||||||
import { Tag } from '../../database';
|
import { Tag } from '../../../database';
|
||||||
import { MemeTagSearchModal } from '.';
|
import MemeTagSearchModal from './memeTagSearchModal';
|
||||||
|
|
||||||
const memeTagSelectorStyles = StyleSheet.create({
|
const memeTagSelectorStyles = StyleSheet.create({
|
||||||
tagChip: {
|
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 = ({
|
const MemeTagSelector = ({
|
||||||
memeTags,
|
memeTags,
|
||||||
setMemeTags,
|
setMemeTags,
|
||||||
@@ -23,20 +39,26 @@ const MemeTagSelector = ({
|
|||||||
} & ComponentProps<typeof View>) => {
|
} & ComponentProps<typeof View>) => {
|
||||||
const { width } = useSafeAreaFrame();
|
const { width } = useSafeAreaFrame();
|
||||||
|
|
||||||
|
const flashListRef = useRef<FlashList<Tag>>(null);
|
||||||
|
|
||||||
const [flashListMargin, setFlashListMargin] = useState(0);
|
const [flashListMargin, setFlashListMargin] = useState(0);
|
||||||
const [tagSearchModalVisible, setTagSearchModalVisible] = useState(false);
|
const [tagSearchModalVisible, setTagSearchModalVisible] = useState(false);
|
||||||
|
|
||||||
const handleTagPress = (tag: Tag) => {
|
const handleTagPress = (tag: Tag) => {
|
||||||
const id = tag.id.toHexString();
|
const id = tag.id.toHexString();
|
||||||
memeTags.delete(id) || memeTags.set(id, tag);
|
memeTags.delete(id);
|
||||||
setMemeTags(new Map(memeTags));
|
setMemeTags(new Map(memeTags));
|
||||||
|
flashListRef.current?.prepareForLayoutAnimationRender();
|
||||||
|
LayoutAnimation.configureNext(tagLayoutAnimation);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View {...props}>
|
<View {...props}>
|
||||||
<FlashList
|
<FlashList
|
||||||
|
ref={flashListRef}
|
||||||
data={[...memeTags.values()]}
|
data={[...memeTags.values()]}
|
||||||
|
keyExtractor={tag => tag.id.toHexString()}
|
||||||
horizontal
|
horizontal
|
||||||
estimatedItemSize={120}
|
estimatedItemSize={120}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
Reference in New Issue
Block a user