Add navigation element animations
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
101
src/components/hideableBottomNavigationBar.tsx
Normal file
101
src/components/hideableBottomNavigationBar.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { BottomNavigation } from 'react-native-paper';
|
||||||
|
import { Animated, StyleSheet } from 'react-native';
|
||||||
|
import {
|
||||||
|
CommonActions,
|
||||||
|
NavigationHelpers,
|
||||||
|
ParamListBase,
|
||||||
|
TabNavigationState,
|
||||||
|
} from '@react-navigation/native';
|
||||||
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||||
|
import { BottomTabNavigationEventMap } from '@react-navigation/bottom-tabs';
|
||||||
|
import { BottomTabDescriptorMap } from '@react-navigation/bottom-tabs/lib/typescript/src/types';
|
||||||
|
import { ROUTE } from '../types';
|
||||||
|
|
||||||
|
const hideableBottomNavigationBarStyles = StyleSheet.create({
|
||||||
|
bar: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const HideableBottomNavigationBar = ({
|
||||||
|
navigation,
|
||||||
|
state,
|
||||||
|
descriptors,
|
||||||
|
insets,
|
||||||
|
visible = true,
|
||||||
|
routeCallback,
|
||||||
|
}: {
|
||||||
|
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||||
|
state: TabNavigationState<ParamListBase>;
|
||||||
|
descriptors: BottomTabDescriptorMap;
|
||||||
|
insets: EdgeInsets;
|
||||||
|
visible?: boolean;
|
||||||
|
routeCallback?: (route: ROUTE) => void;
|
||||||
|
}) => {
|
||||||
|
const visibleAnim = useRef(new Animated.Value(visible ? 0 : 1)).current;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Animated.timing(visibleAnim, {
|
||||||
|
toValue: visible ? 0 : 1,
|
||||||
|
duration: visible ? 200 : 150,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start();
|
||||||
|
}, [visible, visibleAnim]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={{
|
||||||
|
marginBottom: insets.bottom,
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: visibleAnim.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 80],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...hideableBottomNavigationBarStyles.bar,
|
||||||
|
}}>
|
||||||
|
<BottomNavigation.Bar
|
||||||
|
navigationState={state}
|
||||||
|
safeAreaInsets={insets}
|
||||||
|
onTabPress={({ route, preventDefault }) => {
|
||||||
|
const event = navigation.emit({
|
||||||
|
type: 'tabPress',
|
||||||
|
target: route.key,
|
||||||
|
canPreventDefault: true,
|
||||||
|
});
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
preventDefault();
|
||||||
|
} else {
|
||||||
|
navigation.dispatch({
|
||||||
|
...CommonActions.navigate(route.name, route.params),
|
||||||
|
target: state.key,
|
||||||
|
});
|
||||||
|
if (routeCallback) routeCallback(route.name as ROUTE);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderIcon={({ route, focused, color }) => {
|
||||||
|
const { options } = descriptors[route.key];
|
||||||
|
if (options.tabBarIcon) {
|
||||||
|
return options.tabBarIcon({
|
||||||
|
focused,
|
||||||
|
color,
|
||||||
|
size: 22,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
getLabelText={({ route }) => {
|
||||||
|
const { options } = descriptors[route.key];
|
||||||
|
return options.title ?? route.name;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HideableBottomNavigationBar;
|
@@ -1,6 +1,5 @@
|
|||||||
export { default as FloatingActionButton } from './floatingActionButton';
|
export { default as FloatingActionButton } from './floatingActionButton';
|
||||||
|
export { default as HideableBottomNavigationBar } from './hideableBottomNavigationBar';
|
||||||
export { default as LoadingView } from './loadingView';
|
export { default as LoadingView } from './loadingView';
|
||||||
export { default as RootScrollView } from './rootScrollView';
|
|
||||||
export { default as RootView } from './rootView';
|
|
||||||
export { default as TagChip } from './tagChip';
|
export { default as TagChip } from './tagChip';
|
||||||
export { default as TagPreview } from './tagPreview';
|
export { default as TagPreview } from './tagPreview';
|
||||||
|
@@ -1,15 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator } from 'react-native';
|
import { ActivityIndicator, View } from 'react-native';
|
||||||
import { useTheme } from 'react-native-paper';
|
import { useTheme } from 'react-native-paper';
|
||||||
import { RootView } from '.';
|
import styles from '../styles';
|
||||||
|
|
||||||
const LoadingView = () => {
|
const LoadingView = () => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootView centered>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.centered,
|
||||||
|
styles.flex,
|
||||||
|
styles.fullSize,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}>
|
||||||
<ActivityIndicator size="large" color={colors.primary} />
|
<ActivityIndicator size="large" color={colors.primary} />
|
||||||
</RootView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
import React, { ReactNode } from 'react';
|
|
||||||
import { StyleProp, ScrollView, ViewStyle } from 'react-native';
|
|
||||||
import { useTheme } from 'react-native-paper';
|
|
||||||
import styles from '../styles';
|
|
||||||
import { useDimensions } from '../contexts';
|
|
||||||
|
|
||||||
const RootScrollView = ({
|
|
||||||
children,
|
|
||||||
style,
|
|
||||||
centered,
|
|
||||||
padded,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
style?: StyleProp<ViewStyle>;
|
|
||||||
centered?: boolean;
|
|
||||||
padded?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
const { orientation } = useDimensions();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
contentContainerStyle={[
|
|
||||||
padded &&
|
|
||||||
orientation == 'portrait' && [
|
|
||||||
styles.paddingHorizontal,
|
|
||||||
styles.paddingTop,
|
|
||||||
],
|
|
||||||
padded &&
|
|
||||||
orientation == 'landscape' && [
|
|
||||||
styles.paddingHorizontal,
|
|
||||||
styles.smallPaddingTop,
|
|
||||||
],
|
|
||||||
centered && [styles.centered, styles.flex],
|
|
||||||
styles.fullSize,
|
|
||||||
centered && [styles.centered, styles.flex],
|
|
||||||
styles.fullSize,
|
|
||||||
{ backgroundColor: colors.background },
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
nestedScrollEnabled>
|
|
||||||
{children}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RootScrollView;
|
|
@@ -1,44 +0,0 @@
|
|||||||
import React, { ReactNode } from 'react';
|
|
||||||
import { StyleProp, View, ViewStyle } from 'react-native';
|
|
||||||
import { useTheme } from 'react-native-paper';
|
|
||||||
import styles from '../styles';
|
|
||||||
import { useDimensions } from '../contexts';
|
|
||||||
|
|
||||||
const RootView = ({
|
|
||||||
children,
|
|
||||||
style,
|
|
||||||
centered,
|
|
||||||
padded,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
style?: StyleProp<ViewStyle>;
|
|
||||||
centered?: boolean;
|
|
||||||
padded?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
const { orientation } = useDimensions();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
padded &&
|
|
||||||
orientation == 'portrait' && [
|
|
||||||
styles.paddingHorizontal,
|
|
||||||
styles.paddingTop,
|
|
||||||
],
|
|
||||||
padded &&
|
|
||||||
orientation == 'landscape' && [
|
|
||||||
styles.paddingHorizontal,
|
|
||||||
styles.smallPaddingTop,
|
|
||||||
],
|
|
||||||
centered && [styles.centered, styles.flex],
|
|
||||||
styles.fullSize,
|
|
||||||
{ backgroundColor: colors.background },
|
|
||||||
style,
|
|
||||||
]}>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RootView;
|
|
@@ -1,20 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { NavigationContainer as NavigationContainerBase } from '@react-navigation/native';
|
||||||
CommonActions,
|
|
||||||
NavigationContainer as NavigationContainerBase,
|
|
||||||
} from '@react-navigation/native';
|
|
||||||
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { BottomNavigation, useTheme } from 'react-native-paper';
|
import { useTheme } from 'react-native-paper';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { Home, Tags, Settings, AddMeme, AddTag } from './screens';
|
import { Home, Tags, Settings, AddMeme, AddTag } from './screens';
|
||||||
import { darkNavigationTheme, lightNavigationTheme } from './theme';
|
import { darkNavigationTheme, lightNavigationTheme } from './theme';
|
||||||
import { FloatingActionButton } from './components';
|
import {
|
||||||
|
FloatingActionButton,
|
||||||
|
HideableBottomNavigationBar,
|
||||||
|
} from './components';
|
||||||
import { ROUTE } from './types';
|
import { ROUTE } from './types';
|
||||||
|
import { RootState } from './state';
|
||||||
|
|
||||||
const TabNavigator = () => {
|
const TabNavigator = () => {
|
||||||
|
const navVisible = useSelector(
|
||||||
|
(state: RootState) => state.navigation.navVisible,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [route, setRoute] = React.useState(ROUTE.HOME);
|
||||||
const TabNavigatorBase = createBottomTabNavigator();
|
const TabNavigatorBase = createBottomTabNavigator();
|
||||||
const [showFab, setShowFab] = React.useState(true);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -23,39 +29,13 @@ const TabNavigator = () => {
|
|||||||
headerShown: false,
|
headerShown: false,
|
||||||
}}
|
}}
|
||||||
tabBar={({ navigation, state, descriptors, insets }) => (
|
tabBar={({ navigation, state, descriptors, insets }) => (
|
||||||
<BottomNavigation.Bar
|
<HideableBottomNavigationBar
|
||||||
navigationState={state}
|
navigation={navigation}
|
||||||
safeAreaInsets={insets}
|
state={state}
|
||||||
onTabPress={({ route, preventDefault }) => {
|
descriptors={descriptors}
|
||||||
const event = navigation.emit({
|
insets={insets}
|
||||||
type: 'tabPress',
|
visible={!!navVisible}
|
||||||
target: route.key,
|
routeCallback={newRoute => setRoute(newRoute)}
|
||||||
canPreventDefault: true,
|
|
||||||
});
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
preventDefault();
|
|
||||||
} else {
|
|
||||||
navigation.dispatch({
|
|
||||||
...CommonActions.navigate(route.name, route.params),
|
|
||||||
target: state.key,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setShowFab((route.name as ROUTE) !== ROUTE.SETTINGS);
|
|
||||||
}}
|
|
||||||
renderIcon={({ route, focused, color }) => {
|
|
||||||
const { options } = descriptors[route.key];
|
|
||||||
if (options.tabBarIcon) {
|
|
||||||
return options.tabBarIcon({
|
|
||||||
focused,
|
|
||||||
color,
|
|
||||||
size: 22,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
getLabelText={({ route }) => {
|
|
||||||
const { options } = descriptors[route.key];
|
|
||||||
return options.title ?? route.name;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}>
|
)}>
|
||||||
<TabNavigatorBase.Screen
|
<TabNavigatorBase.Screen
|
||||||
@@ -86,14 +66,14 @@ const TabNavigator = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TabNavigatorBase.Navigator>
|
</TabNavigatorBase.Navigator>
|
||||||
<FloatingActionButton visible={showFab} />
|
<FloatingActionButton visible={navVisible && route !== ROUTE.SETTINGS} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavigationContainer = () => {
|
const NavigationContainer = () => {
|
||||||
const StackNavigatorBase = createNativeStackNavigator();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const StackNavigatorBase = createNativeStackNavigator();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainerBase
|
<NavigationContainerBase
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Appbar, Text } from 'react-native-paper';
|
import { Appbar, Text, useTheme } from 'react-native-paper';
|
||||||
import { RootScrollView } from '../components';
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
import { ScrollView } from 'react-native';
|
||||||
|
import styles from '../styles';
|
||||||
|
|
||||||
const AddMeme = () => {
|
const AddMeme = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { orientation } = useDimensions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -12,9 +16,18 @@ const AddMeme = () => {
|
|||||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||||
<Appbar.Content title="Add Meme" />
|
<Appbar.Content title="Add Meme" />
|
||||||
</Appbar.Header>
|
</Appbar.Header>
|
||||||
<RootScrollView centered padded>
|
<ScrollView
|
||||||
|
contentContainerStyle={[
|
||||||
|
orientation == 'portrait' && styles.paddingVertical,
|
||||||
|
orientation == 'landscape' && styles.smallPaddingVertical,
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
[styles.centered, styles.flex],
|
||||||
|
styles.fullSize,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}
|
||||||
|
nestedScrollEnabled>
|
||||||
<Text>Add Meme</Text>
|
<Text>Add Meme</Text>
|
||||||
</RootScrollView>
|
</ScrollView>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,15 +1,24 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View } from 'react-native';
|
import { ScrollView, View } from 'react-native';
|
||||||
import { TextInput, Appbar, HelperText, Button } from 'react-native-paper';
|
import {
|
||||||
|
TextInput,
|
||||||
|
Appbar,
|
||||||
|
HelperText,
|
||||||
|
Button,
|
||||||
|
useTheme,
|
||||||
|
} from 'react-native-paper';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { BSON } from 'realm';
|
import { BSON } from 'realm';
|
||||||
import { useRealm } from '@realm/react';
|
import { useRealm } from '@realm/react';
|
||||||
import { RootScrollView, TagPreview } from '../components';
|
import { TagPreview } from '../components';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { generateRandomColor, isValidColor } from '../utilities';
|
import { generateRandomColor, isValidColor } from '../utilities';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const AddTag = () => {
|
const AddTag = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { orientation } = useDimensions();
|
||||||
const realm = useRealm();
|
const realm = useRealm();
|
||||||
|
|
||||||
const [tagName, setTagName] = useState('newTag');
|
const [tagName, setTagName] = useState('newTag');
|
||||||
@@ -61,9 +70,16 @@ const AddTag = () => {
|
|||||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||||
<Appbar.Content title="Add Tag" />
|
<Appbar.Content title="Add Tag" />
|
||||||
</Appbar.Header>
|
</Appbar.Header>
|
||||||
<RootScrollView
|
<ScrollView
|
||||||
padded
|
contentContainerStyle={[
|
||||||
style={[styles.flexGrow, styles.flexColumnSpaceBetween]}>
|
orientation == 'portrait' && styles.paddingVertical,
|
||||||
|
orientation == 'landscape' && styles.smallPaddingVertical,
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
styles.fullSize,
|
||||||
|
styles.flexGrow,
|
||||||
|
styles.flexColumnSpaceBetween,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}>
|
||||||
<View style={[styles.flex, styles.justifyStart]}>
|
<View style={[styles.flex, styles.justifyStart]}>
|
||||||
<TagPreview name={tagName} color={validatedTagColor} />
|
<TagPreview name={tagName} color={validatedTagColor} />
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -105,7 +121,7 @@ const AddTag = () => {
|
|||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</RootScrollView>
|
</ScrollView>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import { useQuery } from '@realm/react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Menu,
|
Menu,
|
||||||
@@ -10,9 +11,8 @@ import {
|
|||||||
HelperText,
|
HelperText,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { RootScrollView } from '../components';
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { HOME_SORT, SORT_DIRECTION } from '../types';
|
import { MEME_SORT, SORT_DIRECTION } from '../types';
|
||||||
import { getSortIcon, getViewIcon } from '../utilities';
|
import { getSortIcon, getViewIcon } from '../utilities';
|
||||||
import {
|
import {
|
||||||
RootState,
|
RootState,
|
||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
setHomeFilter,
|
setHomeFilter,
|
||||||
} from '../state';
|
} from '../state';
|
||||||
import { MEME_TYPE, Meme, memeTypePlural } from '../database';
|
import { MEME_TYPE, Meme, memeTypePlural } from '../database';
|
||||||
import { useQuery } from '@realm/react';
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const homeStyles = StyleSheet.create({
|
const homeStyles = StyleSheet.create({
|
||||||
headerButtonView: {
|
headerButtonView: {
|
||||||
@@ -36,7 +36,8 @@ const homeStyles = StyleSheet.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const theme = useTheme();
|
const { colors } = useTheme();
|
||||||
|
const { orientation } = useDimensions();
|
||||||
const sort = useSelector((state: RootState) => state.home.sort);
|
const sort = useSelector((state: RootState) => state.home.sort);
|
||||||
const sortDirection = useSelector(
|
const sortDirection = useSelector(
|
||||||
(state: RootState) => state.home.sortDirection,
|
(state: RootState) => state.home.sortDirection,
|
||||||
@@ -51,12 +52,12 @@ const Home = () => {
|
|||||||
const [sortMenuVisible, setSortMenuVisible] = useState(false);
|
const [sortMenuVisible, setSortMenuVisible] = useState(false);
|
||||||
const [filterMenuVisible, setFilterMenuVisible] = useState(false);
|
const [filterMenuVisible, setFilterMenuVisible] = useState(false);
|
||||||
|
|
||||||
const handleSortModeChange = (newSort: HOME_SORT) => {
|
const handleSortModeChange = (newSort: MEME_SORT) => {
|
||||||
if (newSort === sort) {
|
if (newSort === sort) {
|
||||||
dispatch(toggleHomeSortDirection());
|
dispatch(toggleHomeSortDirection());
|
||||||
} else {
|
} else {
|
||||||
dispatch(setHomeSort(newSort));
|
dispatch(setHomeSort(newSort));
|
||||||
if (newSort === HOME_SORT.TITLE) {
|
if (newSort === MEME_SORT.TITLE) {
|
||||||
dispatch(setHomeSortDirection(SORT_DIRECTION.ASCENDING));
|
dispatch(setHomeSortDirection(SORT_DIRECTION.ASCENDING));
|
||||||
} else {
|
} else {
|
||||||
dispatch(setHomeSortDirection(SORT_DIRECTION.DESCENDING));
|
dispatch(setHomeSortDirection(SORT_DIRECTION.DESCENDING));
|
||||||
@@ -74,7 +75,14 @@ const Home = () => {
|
|||||||
const memes = useQuery<Meme>(Meme.schema.name);
|
const memes = useQuery<Meme>(Meme.schema.name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootScrollView padded>
|
<View
|
||||||
|
style={[
|
||||||
|
orientation == 'portrait' && styles.paddingTop,
|
||||||
|
orientation == 'landscape' && styles.smallPaddingTop,
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
styles.fullSize,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}>
|
||||||
<Searchbar
|
<Searchbar
|
||||||
placeholder="Search Memes"
|
placeholder="Search Memes"
|
||||||
value={search}
|
value={search}
|
||||||
@@ -99,16 +107,16 @@ const Home = () => {
|
|||||||
Sort By: {sort}
|
Sort By: {sort}
|
||||||
</Button>
|
</Button>
|
||||||
}>
|
}>
|
||||||
{Object.keys(HOME_SORT).map(key => {
|
{Object.keys(MEME_SORT).map(key => {
|
||||||
return (
|
return (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={key}
|
key={key}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
handleSortModeChange(
|
handleSortModeChange(
|
||||||
HOME_SORT[key as keyof typeof HOME_SORT],
|
MEME_SORT[key as keyof typeof MEME_SORT],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
title={HOME_SORT[key as keyof typeof HOME_SORT]}
|
title={MEME_SORT[key as keyof typeof MEME_SORT]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -117,13 +125,13 @@ const Home = () => {
|
|||||||
<View style={[styles.flexRow, styles.alignCenter]}>
|
<View style={[styles.flexRow, styles.alignCenter]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={getViewIcon(view)}
|
icon={getViewIcon(view)}
|
||||||
iconColor={theme.colors.primary}
|
iconColor={colors.primary}
|
||||||
size={16}
|
size={16}
|
||||||
onPress={() => dispatch(cycleHomeView())}
|
onPress={() => dispatch(cycleHomeView())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={favoritesOnly ? 'heart' : 'heart-outline'}
|
icon={favoritesOnly ? 'heart' : 'heart-outline'}
|
||||||
iconColor={theme.colors.primary}
|
iconColor={colors.primary}
|
||||||
size={16}
|
size={16}
|
||||||
onPress={() => dispatch(toggleHomeFavoritesOnly())}
|
onPress={() => dispatch(toggleHomeFavoritesOnly())}
|
||||||
/>
|
/>
|
||||||
@@ -134,7 +142,7 @@ const Home = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => setFilterMenuVisible(true)}
|
onPress={() => setFilterMenuVisible(true)}
|
||||||
icon={filter ? 'filter' : 'filter-outline'}
|
icon={filter ? 'filter' : 'filter-outline'}
|
||||||
iconColor={theme.colors.primary}
|
iconColor={colors.primary}
|
||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
}>
|
}>
|
||||||
@@ -162,13 +170,13 @@ const Home = () => {
|
|||||||
<Divider />
|
<Divider />
|
||||||
{/* TODO: Meme Views */}
|
{/* TODO: Meme Views */}
|
||||||
{memes.length === 0 && (
|
{memes.length === 0 && (
|
||||||
<View style={styles.alignCenter}>
|
<HelperText
|
||||||
<HelperText type={'info'} style={homeStyles.helperText}>
|
type={'info'}
|
||||||
|
style={[homeStyles.helperText, styles.centerText]}>
|
||||||
No memes found
|
No memes found
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
</RootScrollView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
List,
|
List,
|
||||||
@@ -7,13 +7,13 @@ import {
|
|||||||
Snackbar,
|
Snackbar,
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
|
useTheme,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { RootScrollView } from '../components';
|
import type {} from 'redux-thunk/extend-redux';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { RootState, updateNoMedia, updateStorageUri } from '../state';
|
import { RootState, updateNoMedia, updateStorageUri } from '../state';
|
||||||
import type {} from 'redux-thunk/extend-redux';
|
|
||||||
import { useDimensions } from '../contexts';
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const settingsScreenStyles = StyleSheet.create({
|
const settingsScreenStyles = StyleSheet.create({
|
||||||
@@ -23,9 +23,10 @@ const settingsScreenStyles = StyleSheet.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const SettingsScreen = () => {
|
const SettingsScreen = () => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { orientation, responsive } = useDimensions();
|
||||||
const noMedia = useSelector((state: RootState) => state.settings.noMedia);
|
const noMedia = useSelector((state: RootState) => state.settings.noMedia);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { responsive } = useDimensions();
|
|
||||||
|
|
||||||
const [optimizingDatabase, setOptimizingDatabase] = useState(false);
|
const [optimizingDatabase, setOptimizingDatabase] = useState(false);
|
||||||
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
||||||
@@ -41,7 +42,14 @@ const SettingsScreen = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RootScrollView padded>
|
<ScrollView
|
||||||
|
contentContainerStyle={[
|
||||||
|
orientation == 'portrait' && styles.paddingTop,
|
||||||
|
orientation == 'landscape' && styles.smallPaddingTop,
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
styles.fullSize,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}>
|
||||||
<View>
|
<View>
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Subheader>Database</List.Subheader>
|
<List.Subheader>Database</List.Subheader>
|
||||||
@@ -86,7 +94,7 @@ const SettingsScreen = () => {
|
|||||||
</View>
|
</View>
|
||||||
</List.Section>
|
</List.Section>
|
||||||
</View>
|
</View>
|
||||||
</RootScrollView>
|
</ScrollView>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
visible={snackbarVisible}
|
visible={snackbarVisible}
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { StyleSheet, View, Text } from 'react-native';
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
NativeSyntheticEvent,
|
||||||
|
NativeScrollEvent,
|
||||||
|
Animated,
|
||||||
|
} from 'react-native';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
@@ -7,23 +14,33 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Searchbar,
|
Searchbar,
|
||||||
TouchableRipple,
|
TouchableRipple,
|
||||||
|
useTheme,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { useQuery, useRealm } from '@realm/react';
|
import { useQuery, useRealm } from '@realm/react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { FlashList } from '@shopify/flash-list';
|
import { FlashList } from '@shopify/flash-list';
|
||||||
import { RootView, TagChip } from '../components';
|
import { TagChip } from '../components';
|
||||||
import { Tag, deleteTag } from '../database';
|
import { Tag, deleteTag } from '../database';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import {
|
import {
|
||||||
RootState,
|
RootState,
|
||||||
|
setNavVisible,
|
||||||
setTagsSort,
|
setTagsSort,
|
||||||
setTagsSortDirection,
|
setTagsSortDirection,
|
||||||
toggleTagsSortDirection,
|
toggleTagsSortDirection,
|
||||||
} from '../state';
|
} from '../state';
|
||||||
import { SORT_DIRECTION, TAG_SORT, tagSortQuery } from '../types';
|
import { SORT_DIRECTION, TAG_SORT, tagSortQuery } from '../types';
|
||||||
import { getSortIcon } from '../utilities';
|
import { getSortIcon } from '../utilities';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const tagStyles = StyleSheet.create({
|
const tagsStyles = StyleSheet.create({
|
||||||
|
headerView: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 100,
|
||||||
|
},
|
||||||
headerButtonView: {
|
headerButtonView: {
|
||||||
height: 50,
|
height: 50,
|
||||||
},
|
},
|
||||||
@@ -32,7 +49,8 @@ const tagStyles = StyleSheet.create({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: 10,
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 15,
|
||||||
},
|
},
|
||||||
tagView: {
|
tagView: {
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
@@ -41,38 +59,23 @@ const tagStyles = StyleSheet.create({
|
|||||||
helperText: {
|
helperText: {
|
||||||
marginVertical: 10,
|
marginVertical: 10,
|
||||||
},
|
},
|
||||||
|
flashList: {
|
||||||
|
paddingTop: 125,
|
||||||
|
paddingBottom: 25,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const TagRow = ({ tag }: { tag: Tag }) => {
|
|
||||||
const realm = useRealm();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableRipple onPress={() => deleteTag(realm, tag)}>
|
|
||||||
<View style={tagStyles.tagRow}>
|
|
||||||
<View style={tagStyles.tagView}>
|
|
||||||
<TagChip tag={tag} />
|
|
||||||
</View>
|
|
||||||
<Text>{tag.memesLength}</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableRipple>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ListEmpty = () => {
|
|
||||||
return (
|
|
||||||
<View style={styles.alignCenter}>
|
|
||||||
<HelperText type={'info'} style={tagStyles.helperText}>
|
|
||||||
No tags found
|
|
||||||
</HelperText>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Tags = () => {
|
const Tags = () => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { orientation } = useDimensions();
|
||||||
|
const realm = useRealm();
|
||||||
const sort = useSelector((state: RootState) => state.tags.sort);
|
const sort = useSelector((state: RootState) => state.tags.sort);
|
||||||
const sortDirection = useSelector(
|
const sortDirection = useSelector(
|
||||||
(state: RootState) => state.tags.sortDirection,
|
(state: RootState) => state.tags.sortDirection,
|
||||||
);
|
);
|
||||||
|
const navVisisble = useSelector(
|
||||||
|
(state: RootState) => state.navigation.navVisible,
|
||||||
|
);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [sortMenuVisible, setSortMenuVisible] = useState(false);
|
const [sortMenuVisible, setSortMenuVisible] = useState(false);
|
||||||
@@ -91,14 +94,75 @@ const Tags = () => {
|
|||||||
setSortMenuVisible(false);
|
setSortMenuVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sortMenuAnim = useRef(new Animated.Value(navVisisble ? 1 : 0)).current;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Animated.timing(sortMenuAnim, {
|
||||||
|
toValue: navVisisble ? 1 : 0,
|
||||||
|
duration: navVisisble ? 200 : 150,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start();
|
||||||
|
}, [navVisisble, sortMenuAnim]);
|
||||||
|
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const tags = useQuery<Tag>(Tag.schema.name)
|
const tags = useQuery<Tag>(
|
||||||
|
Tag.schema.name,
|
||||||
|
collection =>
|
||||||
|
collection
|
||||||
.filtered(`name CONTAINS[c] "${search}"`)
|
.filtered(`name CONTAINS[c] "${search}"`)
|
||||||
.sorted(tagSortQuery(sort), sortDirection === SORT_DIRECTION.DESCENDING);
|
.sorted(
|
||||||
|
tagSortQuery(sort),
|
||||||
|
sortDirection === SORT_DIRECTION.DESCENDING,
|
||||||
|
),
|
||||||
|
[search, sort, sortDirection],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [scrollOffset, setScrollOffset] = useState(0);
|
||||||
|
|
||||||
|
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
|
const currentOffset = event.nativeEvent.contentOffset.y;
|
||||||
|
|
||||||
|
if (currentOffset <= 150) {
|
||||||
|
dispatch(setNavVisible(true));
|
||||||
|
setScrollOffset(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = currentOffset - scrollOffset;
|
||||||
|
if (Math.abs(diff) < 50) return;
|
||||||
|
|
||||||
|
dispatch(setNavVisible(diff < 0));
|
||||||
|
setScrollOffset(currentOffset);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootView padded>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
styles.fullSize,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
tagsStyles.headerView,
|
||||||
|
orientation == 'portrait' && styles.paddingTop,
|
||||||
|
orientation == 'landscape' && styles.smallPaddingTop,
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
{
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: sortMenuAnim.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [-130, 0],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
backgroundColor: colors.background,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
<Searchbar
|
<Searchbar
|
||||||
placeholder="Search Tags"
|
placeholder="Search Tags"
|
||||||
value={search}
|
value={search}
|
||||||
@@ -110,7 +174,7 @@ const Tags = () => {
|
|||||||
style={[
|
style={[
|
||||||
styles.flexRow,
|
styles.flexRow,
|
||||||
styles.alignCenter,
|
styles.alignCenter,
|
||||||
tagStyles.headerButtonView,
|
tagsStyles.headerButtonView,
|
||||||
]}>
|
]}>
|
||||||
<Menu
|
<Menu
|
||||||
visible={sortMenuVisible}
|
visible={sortMenuVisible}
|
||||||
@@ -138,14 +202,33 @@ const Tags = () => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
</View>
|
</View>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
</Animated.View>
|
||||||
<FlashList
|
<FlashList
|
||||||
data={tags}
|
data={tags}
|
||||||
estimatedItemSize={52}
|
estimatedItemSize={52}
|
||||||
renderItem={({ item }) => <TagRow tag={item} />}
|
showsVerticalScrollIndicator={false}
|
||||||
|
renderItem={({ item: tag }) => (
|
||||||
|
<TouchableRipple onPress={() => deleteTag(realm, tag)}>
|
||||||
|
<View style={tagsStyles.tagRow}>
|
||||||
|
<View style={tagsStyles.tagView}>
|
||||||
|
<TagChip tag={tag} />
|
||||||
|
</View>
|
||||||
|
<Text>{tag.memesLength}</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableRipple>
|
||||||
|
)}
|
||||||
|
contentContainerStyle={tagsStyles.flashList}
|
||||||
ItemSeparatorComponent={() => <Divider />}
|
ItemSeparatorComponent={() => <Divider />}
|
||||||
ListEmptyComponent={() => <ListEmpty />}
|
ListEmptyComponent={() => (
|
||||||
|
<HelperText
|
||||||
|
type={'info'}
|
||||||
|
style={[tagsStyles.helperText, styles.centerText]}>
|
||||||
|
No tags found
|
||||||
|
</HelperText>
|
||||||
|
)}
|
||||||
|
onScroll={handleScroll}
|
||||||
/>
|
/>
|
||||||
</RootView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,16 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Text } from 'react-native-paper';
|
import { View } from 'react-native';
|
||||||
|
import { Button, Text, useTheme } from 'react-native-paper';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||||
import { RootView } from '../components';
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { noOp } from '../utilities';
|
import { noOp } from '../utilities';
|
||||||
import { updateStorageUri } from '../state';
|
import { updateStorageUri } from '../state';
|
||||||
import { useDimensions } from '../contexts';
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { orientation, responsive } = useDimensions();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { responsive } = useDimensions();
|
|
||||||
|
|
||||||
const selectStorageLocation = async () => {
|
const selectStorageLocation = async () => {
|
||||||
const uri = await openDocumentTree(true).catch(noOp);
|
const uri = await openDocumentTree(true).catch(noOp);
|
||||||
@@ -20,7 +21,16 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootView centered padded>
|
<View
|
||||||
|
style={[
|
||||||
|
orientation == 'portrait' && styles.paddingTop,
|
||||||
|
orientation == 'landscape' && styles.smallPaddingTop,
|
||||||
|
styles.paddingHorizontal,
|
||||||
|
styles.centered,
|
||||||
|
styles.flex,
|
||||||
|
styles.fullSize,
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
]}>
|
||||||
<Text
|
<Text
|
||||||
variant="displayMedium"
|
variant="displayMedium"
|
||||||
style={[
|
style={[
|
||||||
@@ -39,7 +49,7 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
|||||||
}}>
|
}}>
|
||||||
Select Storage Location
|
Select Storage Location
|
||||||
</Button>
|
</Button>
|
||||||
</RootView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { HOME_SORT, SORT_DIRECTION, VIEW } from '../types';
|
import { MEME_SORT, SORT_DIRECTION, VIEW } from '../types';
|
||||||
import { MEME_TYPE } from '../database';
|
import { MEME_TYPE } from '../database';
|
||||||
|
|
||||||
interface HomeState {
|
interface HomeState {
|
||||||
sort: HOME_SORT;
|
sort: MEME_SORT;
|
||||||
sortDirection: SORT_DIRECTION;
|
sortDirection: SORT_DIRECTION;
|
||||||
view: VIEW;
|
view: VIEW;
|
||||||
favoritesOnly: boolean;
|
favoritesOnly: boolean;
|
||||||
@@ -11,7 +11,7 @@ interface HomeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: HomeState = {
|
const initialState: HomeState = {
|
||||||
sort: HOME_SORT.TITLE,
|
sort: MEME_SORT.TITLE,
|
||||||
sortDirection: SORT_DIRECTION.ASCENDING,
|
sortDirection: SORT_DIRECTION.ASCENDING,
|
||||||
view: VIEW.MASONRY,
|
view: VIEW.MASONRY,
|
||||||
favoritesOnly: false,
|
favoritesOnly: false,
|
||||||
@@ -22,7 +22,7 @@ const homeSlice = createSlice({
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setHomeSort: (state, action: PayloadAction<HOME_SORT>) => {
|
setHomeSort: (state, action: PayloadAction<MEME_SORT>) => {
|
||||||
state.sort = action.payload;
|
state.sort = action.payload;
|
||||||
},
|
},
|
||||||
setHomeSortDirection: (state, action: PayloadAction<SORT_DIRECTION>) => {
|
setHomeSortDirection: (state, action: PayloadAction<SORT_DIRECTION>) => {
|
||||||
|
@@ -13,22 +13,26 @@ import { createRealmPersistStorage } from '@bankify/redux-persist-realm';
|
|||||||
import settingsReducer from './settings';
|
import settingsReducer from './settings';
|
||||||
import homeReducer from './home';
|
import homeReducer from './home';
|
||||||
import tagsReducer from './tags';
|
import tagsReducer from './tags';
|
||||||
|
import navigationReducer from './navigation';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
home: homeReducer,
|
home: homeReducer,
|
||||||
tags: tagsReducer,
|
tags: tagsReducer,
|
||||||
|
navigation: navigationReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
interface RootState {
|
interface RootState {
|
||||||
settings: ReturnType<typeof settingsReducer>;
|
settings: ReturnType<typeof settingsReducer>;
|
||||||
home: ReturnType<typeof homeReducer>;
|
home: ReturnType<typeof homeReducer>;
|
||||||
tags: ReturnType<typeof tagsReducer>;
|
tags: ReturnType<typeof tagsReducer>;
|
||||||
|
navigation: ReturnType<typeof navigationReducer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
storage: createRealmPersistStorage({ path: 'redux.realm' }),
|
storage: createRealmPersistStorage({ path: 'redux.realm' }),
|
||||||
|
blacklist: ['navigation'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||||
@@ -69,3 +73,8 @@ export {
|
|||||||
setTagsSortDirection,
|
setTagsSortDirection,
|
||||||
toggleTagsSortDirection,
|
toggleTagsSortDirection,
|
||||||
} from './tags';
|
} from './tags';
|
||||||
|
export {
|
||||||
|
type NavigationState,
|
||||||
|
setNavVisible,
|
||||||
|
toggleNavVisible,
|
||||||
|
} from './navigation';
|
||||||
|
31
src/state/navigation.ts
Normal file
31
src/state/navigation.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
interface NavigationState {
|
||||||
|
navVisible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: NavigationState = {
|
||||||
|
navVisible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigationSlice = createSlice({
|
||||||
|
name: 'navigation',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setNavVisible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.navVisible = action.payload;
|
||||||
|
},
|
||||||
|
toggleNavVisible: state => {
|
||||||
|
state.navVisible = !state.navVisible;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setNavVisible, toggleNavVisible } = navigationSlice.actions;
|
||||||
|
|
||||||
|
export {
|
||||||
|
type NavigationState,
|
||||||
|
setNavVisible,
|
||||||
|
toggleNavVisible,
|
||||||
|
};
|
||||||
|
export default navigationSlice.reducer;
|
@@ -8,7 +8,7 @@ interface TagsState {
|
|||||||
|
|
||||||
const initialState: TagsState = {
|
const initialState: TagsState = {
|
||||||
sort: TAG_SORT.NAME,
|
sort: TAG_SORT.NAME,
|
||||||
sortDirection: SORT_DIRECTION.DESCENDING,
|
sortDirection: SORT_DIRECTION.ASCENDING,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagsSlice = createSlice({
|
const tagsSlice = createSlice({
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
export { ROUTE } from './route';
|
export { ROUTE } from './route';
|
||||||
export {
|
export {
|
||||||
HOME_SORT,
|
MEME_SORT,
|
||||||
homeSortQuery,
|
homeSortQuery,
|
||||||
TAG_SORT,
|
TAG_SORT,
|
||||||
tagSortQuery,
|
tagSortQuery,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
enum HOME_SORT {
|
enum MEME_SORT {
|
||||||
TITLE = 'Title',
|
TITLE = 'Title',
|
||||||
DATE_CREATED = 'Date Created',
|
DATE_CREATED = 'Date Created',
|
||||||
DATE_MODIFIED = 'Date Modified',
|
DATE_MODIFIED = 'Date Modified',
|
||||||
@@ -7,24 +7,24 @@ enum HOME_SORT {
|
|||||||
SIZE = 'Size',
|
SIZE = 'Size',
|
||||||
}
|
}
|
||||||
|
|
||||||
const homeSortQuery = (sort: HOME_SORT) => {
|
const homeSortQuery = (sort: MEME_SORT) => {
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case HOME_SORT.TITLE: {
|
case MEME_SORT.TITLE: {
|
||||||
return 'title';
|
return 'title';
|
||||||
}
|
}
|
||||||
case HOME_SORT.DATE_CREATED: {
|
case MEME_SORT.DATE_CREATED: {
|
||||||
return 'dateCreated';
|
return 'dateCreated';
|
||||||
}
|
}
|
||||||
case HOME_SORT.DATE_MODIFIED: {
|
case MEME_SORT.DATE_MODIFIED: {
|
||||||
return 'dateModified';
|
return 'dateModified';
|
||||||
}
|
}
|
||||||
case HOME_SORT.DATE_USED: {
|
case MEME_SORT.DATE_USED: {
|
||||||
return 'dateUsed';
|
return 'dateUsed';
|
||||||
}
|
}
|
||||||
case HOME_SORT.TIMES_USED: {
|
case MEME_SORT.TIMES_USED: {
|
||||||
return 'timesUsed';
|
return 'timesUsed';
|
||||||
}
|
}
|
||||||
case HOME_SORT.SIZE: {
|
case MEME_SORT.SIZE: {
|
||||||
return 'size';
|
return 'size';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,4 +67,4 @@ enum SORT_DIRECTION {
|
|||||||
DESCENDING = 1,
|
DESCENDING = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
export { HOME_SORT, homeSortQuery, TAG_SORT, tagSortQuery, SORT_DIRECTION };
|
export { MEME_SORT, homeSortQuery, TAG_SORT, tagSortQuery, SORT_DIRECTION };
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
import { HOME_SORT, SORT_DIRECTION, TAG_SORT, VIEW } from '../types';
|
import { MEME_SORT, SORT_DIRECTION, TAG_SORT, VIEW } from '../types';
|
||||||
|
|
||||||
const getSortIcon = (
|
const getSortIcon = (
|
||||||
sort: HOME_SORT | TAG_SORT,
|
sort: MEME_SORT | TAG_SORT,
|
||||||
sortDirection: SORT_DIRECTION,
|
sortDirection: SORT_DIRECTION,
|
||||||
) => {
|
) => {
|
||||||
let sortIcon = '';
|
let sortIcon = '';
|
||||||
|
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case HOME_SORT.TITLE:
|
case MEME_SORT.TITLE:
|
||||||
case TAG_SORT.NAME:
|
case TAG_SORT.NAME:
|
||||||
case TAG_SORT.COLOR: {
|
case TAG_SORT.COLOR: {
|
||||||
sortIcon = 'sort-alphabetical';
|
sortIcon = 'sort-alphabetical';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HOME_SORT.DATE_CREATED:
|
case MEME_SORT.DATE_CREATED:
|
||||||
case HOME_SORT.DATE_MODIFIED:
|
case MEME_SORT.DATE_MODIFIED:
|
||||||
case HOME_SORT.DATE_USED:
|
case MEME_SORT.DATE_USED:
|
||||||
case TAG_SORT.DATE_CREATED:
|
case TAG_SORT.DATE_CREATED:
|
||||||
case TAG_SORT.DATE_MODIFIED: {
|
case TAG_SORT.DATE_MODIFIED: {
|
||||||
sortIcon = 'sort-calendar';
|
sortIcon = 'sort-calendar';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HOME_SORT.TIMES_USED:
|
case MEME_SORT.TIMES_USED:
|
||||||
case HOME_SORT.SIZE:
|
case MEME_SORT.SIZE:
|
||||||
case TAG_SORT.MEMES_LENGTH:
|
case TAG_SORT.MEMES_LENGTH:
|
||||||
case TAG_SORT.TIMES_USED: {
|
case TAG_SORT.TIMES_USED: {
|
||||||
sortIcon = 'sort-numeric';
|
sortIcon = 'sort-numeric';
|
||||||
|
Reference in New Issue
Block a user