Add navigation element animations

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-16 15:50:55 +03:00
parent 622d88cf40
commit 6e1f7bd81f
19 changed files with 450 additions and 277 deletions

View 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;

View File

@@ -1,6 +1,5 @@
export { default as FloatingActionButton } from './floatingActionButton';
export { default as HideableBottomNavigationBar } from './hideableBottomNavigationBar';
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 TagPreview } from './tagPreview';

View File

@@ -1,15 +1,21 @@
import React from 'react';
import { ActivityIndicator } from 'react-native';
import { ActivityIndicator, View } from 'react-native';
import { useTheme } from 'react-native-paper';
import { RootView } from '.';
import styles from '../styles';
const LoadingView = () => {
const { colors } = useTheme();
return (
<RootView centered>
<View
style={[
styles.centered,
styles.flex,
styles.fullSize,
{ backgroundColor: colors.background },
]}>
<ActivityIndicator size="large" color={colors.primary} />
</RootView>
</View>
);
};

View File

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

View File

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

View File

@@ -1,20 +1,26 @@
import React from 'react';
import {
CommonActions,
NavigationContainer as NavigationContainerBase,
} from '@react-navigation/native';
import { NavigationContainer as NavigationContainerBase } from '@react-navigation/native';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
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 { darkNavigationTheme, lightNavigationTheme } from './theme';
import { FloatingActionButton } from './components';
import {
FloatingActionButton,
HideableBottomNavigationBar,
} from './components';
import { ROUTE } from './types';
import { RootState } from './state';
const TabNavigator = () => {
const navVisible = useSelector(
(state: RootState) => state.navigation.navVisible,
);
const [route, setRoute] = React.useState(ROUTE.HOME);
const TabNavigatorBase = createBottomTabNavigator();
const [showFab, setShowFab] = React.useState(true);
return (
<>
@@ -23,39 +29,13 @@ const TabNavigator = () => {
headerShown: false,
}}
tabBar={({ navigation, state, descriptors, insets }) => (
<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,
});
}
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;
}}
<HideableBottomNavigationBar
navigation={navigation}
state={state}
descriptors={descriptors}
insets={insets}
visible={!!navVisible}
routeCallback={newRoute => setRoute(newRoute)}
/>
)}>
<TabNavigatorBase.Screen
@@ -86,14 +66,14 @@ const TabNavigator = () => {
}}
/>
</TabNavigatorBase.Navigator>
<FloatingActionButton visible={showFab} />
<FloatingActionButton visible={navVisible && route !== ROUTE.SETTINGS} />
</>
);
};
const NavigationContainer = () => {
const StackNavigatorBase = createNativeStackNavigator();
const theme = useTheme();
const StackNavigatorBase = createNativeStackNavigator();
return (
<NavigationContainerBase

View File

@@ -1,10 +1,14 @@
import React from 'react';
import { Appbar, Text } from 'react-native-paper';
import { RootScrollView } from '../components';
import { Appbar, Text, useTheme } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { useDimensions } from '../contexts';
import { ScrollView } from 'react-native';
import styles from '../styles';
const AddMeme = () => {
const navigation = useNavigation();
const { colors } = useTheme();
const { orientation } = useDimensions();
return (
<>
@@ -12,9 +16,18 @@ const AddMeme = () => {
<Appbar.BackAction onPress={() => navigation.goBack()} />
<Appbar.Content title="Add Meme" />
</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>
</RootScrollView>
</ScrollView>
</>
);
};

View File

@@ -1,15 +1,24 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { TextInput, Appbar, HelperText, Button } from 'react-native-paper';
import { ScrollView, View } from 'react-native';
import {
TextInput,
Appbar,
HelperText,
Button,
useTheme,
} from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { BSON } from 'realm';
import { useRealm } from '@realm/react';
import { RootScrollView, TagPreview } from '../components';
import { TagPreview } from '../components';
import styles from '../styles';
import { generateRandomColor, isValidColor } from '../utilities';
import { useDimensions } from '../contexts';
const AddTag = () => {
const navigation = useNavigation();
const { colors } = useTheme();
const { orientation } = useDimensions();
const realm = useRealm();
const [tagName, setTagName] = useState('newTag');
@@ -61,9 +70,16 @@ const AddTag = () => {
<Appbar.BackAction onPress={() => navigation.goBack()} />
<Appbar.Content title="Add Tag" />
</Appbar.Header>
<RootScrollView
padded
style={[styles.flexGrow, styles.flexColumnSpaceBetween]}>
<ScrollView
contentContainerStyle={[
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]}>
<TagPreview name={tagName} color={validatedTagColor} />
<TextInput
@@ -105,7 +121,7 @@ const AddTag = () => {
Save
</Button>
</View>
</RootScrollView>
</ScrollView>
</>
);
};

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { useQuery } from '@realm/react';
import {
Button,
Menu,
@@ -10,9 +11,8 @@ import {
HelperText,
} from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { RootScrollView } from '../components';
import styles from '../styles';
import { HOME_SORT, SORT_DIRECTION } from '../types';
import { MEME_SORT, SORT_DIRECTION } from '../types';
import { getSortIcon, getViewIcon } from '../utilities';
import {
RootState,
@@ -24,7 +24,7 @@ import {
setHomeFilter,
} from '../state';
import { MEME_TYPE, Meme, memeTypePlural } from '../database';
import { useQuery } from '@realm/react';
import { useDimensions } from '../contexts';
const homeStyles = StyleSheet.create({
headerButtonView: {
@@ -36,7 +36,8 @@ const homeStyles = StyleSheet.create({
});
const Home = () => {
const theme = useTheme();
const { colors } = useTheme();
const { orientation } = useDimensions();
const sort = useSelector((state: RootState) => state.home.sort);
const sortDirection = useSelector(
(state: RootState) => state.home.sortDirection,
@@ -51,12 +52,12 @@ const Home = () => {
const [sortMenuVisible, setSortMenuVisible] = useState(false);
const [filterMenuVisible, setFilterMenuVisible] = useState(false);
const handleSortModeChange = (newSort: HOME_SORT) => {
const handleSortModeChange = (newSort: MEME_SORT) => {
if (newSort === sort) {
dispatch(toggleHomeSortDirection());
} else {
dispatch(setHomeSort(newSort));
if (newSort === HOME_SORT.TITLE) {
if (newSort === MEME_SORT.TITLE) {
dispatch(setHomeSortDirection(SORT_DIRECTION.ASCENDING));
} else {
dispatch(setHomeSortDirection(SORT_DIRECTION.DESCENDING));
@@ -74,7 +75,14 @@ const Home = () => {
const memes = useQuery<Meme>(Meme.schema.name);
return (
<RootScrollView padded>
<View
style={[
orientation == 'portrait' && styles.paddingTop,
orientation == 'landscape' && styles.smallPaddingTop,
styles.paddingHorizontal,
styles.fullSize,
{ backgroundColor: colors.background },
]}>
<Searchbar
placeholder="Search Memes"
value={search}
@@ -99,16 +107,16 @@ const Home = () => {
Sort By: {sort}
</Button>
}>
{Object.keys(HOME_SORT).map(key => {
{Object.keys(MEME_SORT).map(key => {
return (
<Menu.Item
key={key}
onPress={() =>
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]}>
<IconButton
icon={getViewIcon(view)}
iconColor={theme.colors.primary}
iconColor={colors.primary}
size={16}
onPress={() => dispatch(cycleHomeView())}
/>
<IconButton
icon={favoritesOnly ? 'heart' : 'heart-outline'}
iconColor={theme.colors.primary}
iconColor={colors.primary}
size={16}
onPress={() => dispatch(toggleHomeFavoritesOnly())}
/>
@@ -134,7 +142,7 @@ const Home = () => {
<IconButton
onPress={() => setFilterMenuVisible(true)}
icon={filter ? 'filter' : 'filter-outline'}
iconColor={theme.colors.primary}
iconColor={colors.primary}
size={16}
/>
}>
@@ -162,13 +170,13 @@ const Home = () => {
<Divider />
{/* TODO: Meme Views */}
{memes.length === 0 && (
<View style={styles.alignCenter}>
<HelperText type={'info'} style={homeStyles.helperText}>
No memes found
</HelperText>
</View>
<HelperText
type={'info'}
style={[homeStyles.helperText, styles.centerText]}>
No memes found
</HelperText>
)}
</RootScrollView>
</View>
);
};

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { ScrollView, StyleSheet, View } from 'react-native';
import {
Button,
List,
@@ -7,13 +7,13 @@ import {
Snackbar,
Switch,
Text,
useTheme,
} from 'react-native-paper';
import { openDocumentTree } from 'react-native-scoped-storage';
import { useDispatch, useSelector } from 'react-redux';
import { RootScrollView } from '../components';
import type {} from 'redux-thunk/extend-redux';
import styles from '../styles';
import { RootState, updateNoMedia, updateStorageUri } from '../state';
import type {} from 'redux-thunk/extend-redux';
import { useDimensions } from '../contexts';
const settingsScreenStyles = StyleSheet.create({
@@ -23,9 +23,10 @@ const settingsScreenStyles = StyleSheet.create({
});
const SettingsScreen = () => {
const { colors } = useTheme();
const { orientation, responsive } = useDimensions();
const noMedia = useSelector((state: RootState) => state.settings.noMedia);
const dispatch = useDispatch();
const { responsive } = useDimensions();
const [optimizingDatabase, setOptimizingDatabase] = useState(false);
const [snackbarVisible, setSnackbarVisible] = useState(false);
@@ -41,7 +42,14 @@ const SettingsScreen = () => {
return (
<>
<RootScrollView padded>
<ScrollView
contentContainerStyle={[
orientation == 'portrait' && styles.paddingTop,
orientation == 'landscape' && styles.smallPaddingTop,
styles.paddingHorizontal,
styles.fullSize,
{ backgroundColor: colors.background },
]}>
<View>
<List.Section>
<List.Subheader>Database</List.Subheader>
@@ -86,7 +94,7 @@ const SettingsScreen = () => {
</View>
</List.Section>
</View>
</RootScrollView>
</ScrollView>
<Portal>
<Snackbar
visible={snackbarVisible}

View File

@@ -1,5 +1,12 @@
import React, { useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import React, { useEffect, useRef, useState } from 'react';
import {
StyleSheet,
View,
Text,
NativeSyntheticEvent,
NativeScrollEvent,
Animated,
} from 'react-native';
import {
Button,
Divider,
@@ -7,23 +14,33 @@ import {
Menu,
Searchbar,
TouchableRipple,
useTheme,
} from 'react-native-paper';
import { useQuery, useRealm } from '@realm/react';
import { useDispatch, useSelector } from 'react-redux';
import { FlashList } from '@shopify/flash-list';
import { RootView, TagChip } from '../components';
import { TagChip } from '../components';
import { Tag, deleteTag } from '../database';
import styles from '../styles';
import {
RootState,
setNavVisible,
setTagsSort,
setTagsSortDirection,
toggleTagsSortDirection,
} from '../state';
import { SORT_DIRECTION, TAG_SORT, tagSortQuery } from '../types';
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: {
height: 50,
},
@@ -32,7 +49,8 @@ const tagStyles = StyleSheet.create({
justifyContent: 'space-between',
flexDirection: 'row',
alignItems: 'center',
padding: 10,
paddingVertical: 10,
paddingHorizontal: 15,
},
tagView: {
flexShrink: 1,
@@ -41,38 +59,23 @@ const tagStyles = StyleSheet.create({
helperText: {
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 { colors } = useTheme();
const { orientation } = useDimensions();
const realm = useRealm();
const sort = useSelector((state: RootState) => state.tags.sort);
const sortDirection = useSelector(
(state: RootState) => state.tags.sortDirection,
);
const navVisisble = useSelector(
(state: RootState) => state.navigation.navVisible,
);
const dispatch = useDispatch();
const [sortMenuVisible, setSortMenuVisible] = useState(false);
@@ -91,61 +94,141 @@ const Tags = () => {
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 tags = useQuery<Tag>(Tag.schema.name)
.filtered(`name CONTAINS[c] "${search}"`)
.sorted(tagSortQuery(sort), sortDirection === SORT_DIRECTION.DESCENDING);
const tags = useQuery<Tag>(
Tag.schema.name,
collection =>
collection
.filtered(`name CONTAINS[c] "${search}"`)
.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 (
<RootView padded>
<Searchbar
placeholder="Search Tags"
value={search}
onChangeText={(value: string) => {
setSearch(value);
}}
/>
<View
<View
style={[
styles.paddingHorizontal,
styles.fullSize,
{ backgroundColor: colors.background },
]}>
<Animated.View
style={[
styles.flexRow,
styles.alignCenter,
tagStyles.headerButtonView,
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,
},
]}>
<Menu
visible={sortMenuVisible}
onDismiss={() => setSortMenuVisible(false)}
anchor={
<Button
onPress={() => setSortMenuVisible(true)}
icon={getSortIcon(sort, sortDirection)}
contentStyle={styles.flexRowReverse}
compact>
Sort By: {sort}
</Button>
}>
{Object.keys(TAG_SORT).map(key => {
return (
<Menu.Item
key={key}
onPress={() =>
handleSortModeChange(TAG_SORT[key as keyof typeof TAG_SORT])
}
title={TAG_SORT[key as keyof typeof TAG_SORT]}
/>
);
})}
</Menu>
</View>
<Divider />
<Searchbar
placeholder="Search Tags"
value={search}
onChangeText={(value: string) => {
setSearch(value);
}}
/>
<View
style={[
styles.flexRow,
styles.alignCenter,
tagsStyles.headerButtonView,
]}>
<Menu
visible={sortMenuVisible}
onDismiss={() => setSortMenuVisible(false)}
anchor={
<Button
onPress={() => setSortMenuVisible(true)}
icon={getSortIcon(sort, sortDirection)}
contentStyle={styles.flexRowReverse}
compact>
Sort By: {sort}
</Button>
}>
{Object.keys(TAG_SORT).map(key => {
return (
<Menu.Item
key={key}
onPress={() =>
handleSortModeChange(TAG_SORT[key as keyof typeof TAG_SORT])
}
title={TAG_SORT[key as keyof typeof TAG_SORT]}
/>
);
})}
</Menu>
</View>
<Divider />
</Animated.View>
<FlashList
data={tags}
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 />}
ListEmptyComponent={() => <ListEmpty />}
ListEmptyComponent={() => (
<HelperText
type={'info'}
style={[tagsStyles.helperText, styles.centerText]}>
No tags found
</HelperText>
)}
onScroll={handleScroll}
/>
</RootView>
</View>
);
};

View File

@@ -1,16 +1,17 @@
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 { openDocumentTree } from 'react-native-scoped-storage';
import { RootView } from '../components';
import styles from '../styles';
import { noOp } from '../utilities';
import { updateStorageUri } from '../state';
import { useDimensions } from '../contexts';
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
const { colors } = useTheme();
const { orientation, responsive } = useDimensions();
const dispatch = useDispatch();
const { responsive } = useDimensions();
const selectStorageLocation = async () => {
const uri = await openDocumentTree(true).catch(noOp);
@@ -20,7 +21,16 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
};
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
variant="displayMedium"
style={[
@@ -39,7 +49,7 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
}}>
Select Storage Location
</Button>
</RootView>
</View>
);
};

View File

@@ -1,9 +1,9 @@
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';
interface HomeState {
sort: HOME_SORT;
sort: MEME_SORT;
sortDirection: SORT_DIRECTION;
view: VIEW;
favoritesOnly: boolean;
@@ -11,7 +11,7 @@ interface HomeState {
}
const initialState: HomeState = {
sort: HOME_SORT.TITLE,
sort: MEME_SORT.TITLE,
sortDirection: SORT_DIRECTION.ASCENDING,
view: VIEW.MASONRY,
favoritesOnly: false,
@@ -22,7 +22,7 @@ const homeSlice = createSlice({
name: 'home',
initialState,
reducers: {
setHomeSort: (state, action: PayloadAction<HOME_SORT>) => {
setHomeSort: (state, action: PayloadAction<MEME_SORT>) => {
state.sort = action.payload;
},
setHomeSortDirection: (state, action: PayloadAction<SORT_DIRECTION>) => {

View File

@@ -13,22 +13,26 @@ import { createRealmPersistStorage } from '@bankify/redux-persist-realm';
import settingsReducer from './settings';
import homeReducer from './home';
import tagsReducer from './tags';
import navigationReducer from './navigation';
const rootReducer = combineReducers({
settings: settingsReducer,
home: homeReducer,
tags: tagsReducer,
navigation: navigationReducer,
});
interface RootState {
settings: ReturnType<typeof settingsReducer>;
home: ReturnType<typeof homeReducer>;
tags: ReturnType<typeof tagsReducer>;
navigation: ReturnType<typeof navigationReducer>;
}
const persistConfig = {
key: 'root',
storage: createRealmPersistStorage({ path: 'redux.realm' }),
blacklist: ['navigation'],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
@@ -69,3 +73,8 @@ export {
setTagsSortDirection,
toggleTagsSortDirection,
} from './tags';
export {
type NavigationState,
setNavVisible,
toggleNavVisible,
} from './navigation';

31
src/state/navigation.ts Normal file
View 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;

View File

@@ -8,7 +8,7 @@ interface TagsState {
const initialState: TagsState = {
sort: TAG_SORT.NAME,
sortDirection: SORT_DIRECTION.DESCENDING,
sortDirection: SORT_DIRECTION.ASCENDING,
};
const tagsSlice = createSlice({

View File

@@ -1,6 +1,6 @@
export { ROUTE } from './route';
export {
HOME_SORT,
MEME_SORT,
homeSortQuery,
TAG_SORT,
tagSortQuery,

View File

@@ -1,4 +1,4 @@
enum HOME_SORT {
enum MEME_SORT {
TITLE = 'Title',
DATE_CREATED = 'Date Created',
DATE_MODIFIED = 'Date Modified',
@@ -7,24 +7,24 @@ enum HOME_SORT {
SIZE = 'Size',
}
const homeSortQuery = (sort: HOME_SORT) => {
const homeSortQuery = (sort: MEME_SORT) => {
switch (sort) {
case HOME_SORT.TITLE: {
case MEME_SORT.TITLE: {
return 'title';
}
case HOME_SORT.DATE_CREATED: {
case MEME_SORT.DATE_CREATED: {
return 'dateCreated';
}
case HOME_SORT.DATE_MODIFIED: {
case MEME_SORT.DATE_MODIFIED: {
return 'dateModified';
}
case HOME_SORT.DATE_USED: {
case MEME_SORT.DATE_USED: {
return 'dateUsed';
}
case HOME_SORT.TIMES_USED: {
case MEME_SORT.TIMES_USED: {
return 'timesUsed';
}
case HOME_SORT.SIZE: {
case MEME_SORT.SIZE: {
return 'size';
}
}
@@ -67,4 +67,4 @@ enum SORT_DIRECTION {
DESCENDING = 1,
}
export { HOME_SORT, homeSortQuery, TAG_SORT, tagSortQuery, SORT_DIRECTION };
export { MEME_SORT, homeSortQuery, TAG_SORT, tagSortQuery, SORT_DIRECTION };

View File

@@ -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 = (
sort: HOME_SORT | TAG_SORT,
sort: MEME_SORT | TAG_SORT,
sortDirection: SORT_DIRECTION,
) => {
let sortIcon = '';
switch (sort) {
case HOME_SORT.TITLE:
case MEME_SORT.TITLE:
case TAG_SORT.NAME:
case TAG_SORT.COLOR: {
sortIcon = 'sort-alphabetical';
break;
}
case HOME_SORT.DATE_CREATED:
case HOME_SORT.DATE_MODIFIED:
case HOME_SORT.DATE_USED:
case MEME_SORT.DATE_CREATED:
case MEME_SORT.DATE_MODIFIED:
case MEME_SORT.DATE_USED:
case TAG_SORT.DATE_CREATED:
case TAG_SORT.DATE_MODIFIED: {
sortIcon = 'sort-calendar';
break;
}
case HOME_SORT.TIMES_USED:
case HOME_SORT.SIZE:
case MEME_SORT.TIMES_USED:
case MEME_SORT.SIZE:
case TAG_SORT.MEMES_LENGTH:
case TAG_SORT.TIMES_USED: {
sortIcon = 'sort-numeric';