diff --git a/src/components/hideableBottomNavigationBar.tsx b/src/components/hideableBottomNavigationBar.tsx new file mode 100644 index 0000000..10eda50 --- /dev/null +++ b/src/components/hideableBottomNavigationBar.tsx @@ -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; + state: TabNavigationState; + 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 ( + + { + 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; + }} + /> + + ); +}; + +export default HideableBottomNavigationBar; diff --git a/src/components/index.ts b/src/components/index.ts index b49eafa..3d9cf86 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -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'; diff --git a/src/components/loadingView.tsx b/src/components/loadingView.tsx index b98008d..eef0623 100644 --- a/src/components/loadingView.tsx +++ b/src/components/loadingView.tsx @@ -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 ( - + - + ); }; diff --git a/src/components/rootScrollView.tsx b/src/components/rootScrollView.tsx deleted file mode 100644 index 9b0372d..0000000 --- a/src/components/rootScrollView.tsx +++ /dev/null @@ -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; - centered?: boolean; - padded?: boolean; -}) => { - const { colors } = useTheme(); - const { orientation } = useDimensions(); - - return ( - - {children} - - ); -}; - -export default RootScrollView; diff --git a/src/components/rootView.tsx b/src/components/rootView.tsx deleted file mode 100644 index f2a82ac..0000000 --- a/src/components/rootView.tsx +++ /dev/null @@ -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; - centered?: boolean; - padded?: boolean; -}) => { - const { colors } = useTheme(); - const { orientation } = useDimensions(); - - return ( - - {children} - - ); -}; - -export default RootView; diff --git a/src/navigation.tsx b/src/navigation.tsx index 63dc544..8033f53 100644 --- a/src/navigation.tsx +++ b/src/navigation.tsx @@ -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 }) => ( - { - 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; - }} + setRoute(newRoute)} /> )}> { }} /> - + ); }; const NavigationContainer = () => { - const StackNavigatorBase = createNativeStackNavigator(); const theme = useTheme(); + const StackNavigatorBase = createNativeStackNavigator(); return ( { const navigation = useNavigation(); + const { colors } = useTheme(); + const { orientation } = useDimensions(); return ( <> @@ -12,9 +16,18 @@ const AddMeme = () => { navigation.goBack()} /> - + Add Meme - + ); }; diff --git a/src/screens/addTag.tsx b/src/screens/addTag.tsx index c9edb79..809d200 100644 --- a/src/screens/addTag.tsx +++ b/src/screens/addTag.tsx @@ -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 = () => { navigation.goBack()} /> - + { Save - + ); }; diff --git a/src/screens/home.tsx b/src/screens/home.tsx index 09507bb..b60bb65 100644 --- a/src/screens/home.tsx +++ b/src/screens/home.tsx @@ -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.schema.name); return ( - + { Sort By: {sort} }> - {Object.keys(HOME_SORT).map(key => { + {Object.keys(MEME_SORT).map(key => { return ( 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 = () => { dispatch(cycleHomeView())} /> dispatch(toggleHomeFavoritesOnly())} /> @@ -134,7 +142,7 @@ const Home = () => { setFilterMenuVisible(true)} icon={filter ? 'filter' : 'filter-outline'} - iconColor={theme.colors.primary} + iconColor={colors.primary} size={16} /> }> @@ -162,13 +170,13 @@ const Home = () => { {/* TODO: Meme Views */} {memes.length === 0 && ( - - - No memes found - - + + No memes found + )} - + ); }; diff --git a/src/screens/settings.tsx b/src/screens/settings.tsx index 6a2e61d..1052c9c 100644 --- a/src/screens/settings.tsx +++ b/src/screens/settings.tsx @@ -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 ( <> - + Database @@ -86,7 +94,7 @@ const SettingsScreen = () => { - + { - const realm = useRealm(); - - return ( - deleteTag(realm, tag)}> - - - - - {tag.memesLength} - - - ); -}; - -const ListEmpty = () => { - return ( - - - No tags found - - - ); -}; - 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.schema.name) - .filtered(`name CONTAINS[c] "${search}"`) - .sorted(tagSortQuery(sort), sortDirection === SORT_DIRECTION.DESCENDING); + const tags = useQuery( + 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) => { + 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 ( - - { - setSearch(value); - }} - /> - + - setSortMenuVisible(false)} - anchor={ - - }> - {Object.keys(TAG_SORT).map(key => { - return ( - - handleSortModeChange(TAG_SORT[key as keyof typeof TAG_SORT]) - } - title={TAG_SORT[key as keyof typeof TAG_SORT]} - /> - ); - })} - - - + { + setSearch(value); + }} + /> + + setSortMenuVisible(false)} + anchor={ + + }> + {Object.keys(TAG_SORT).map(key => { + return ( + + handleSortModeChange(TAG_SORT[key as keyof typeof TAG_SORT]) + } + title={TAG_SORT[key as keyof typeof TAG_SORT]} + /> + ); + })} + + + + } + showsVerticalScrollIndicator={false} + renderItem={({ item: tag }) => ( + deleteTag(realm, tag)}> + + + + + {tag.memesLength} + + + )} + contentContainerStyle={tagsStyles.flashList} ItemSeparatorComponent={() => } - ListEmptyComponent={() => } + ListEmptyComponent={() => ( + + No tags found + + )} + onScroll={handleScroll} /> - + ); }; diff --git a/src/screens/welcome.tsx b/src/screens/welcome.tsx index 6667f0e..37a1b3f 100644 --- a/src/screens/welcome.tsx +++ b/src/screens/welcome.tsx @@ -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 ( - + void }) => { }}> Select Storage Location - + ); }; diff --git a/src/state/home.ts b/src/state/home.ts index 4b17baa..e237670 100644 --- a/src/state/home.ts +++ b/src/state/home.ts @@ -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) => { + setHomeSort: (state, action: PayloadAction) => { state.sort = action.payload; }, setHomeSortDirection: (state, action: PayloadAction) => { diff --git a/src/state/index.ts b/src/state/index.ts index 0f28f7c..576c603 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -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; home: ReturnType; tags: ReturnType; + navigation: ReturnType; } 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'; diff --git a/src/state/navigation.ts b/src/state/navigation.ts new file mode 100644 index 0000000..c45e133 --- /dev/null +++ b/src/state/navigation.ts @@ -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) => { + state.navVisible = action.payload; + }, + toggleNavVisible: state => { + state.navVisible = !state.navVisible; + }, + }, +}); + +const { setNavVisible, toggleNavVisible } = navigationSlice.actions; + +export { + type NavigationState, + setNavVisible, + toggleNavVisible, +}; +export default navigationSlice.reducer; diff --git a/src/state/tags.ts b/src/state/tags.ts index 0bf90db..62e80cd 100644 --- a/src/state/tags.ts +++ b/src/state/tags.ts @@ -8,7 +8,7 @@ interface TagsState { const initialState: TagsState = { sort: TAG_SORT.NAME, - sortDirection: SORT_DIRECTION.DESCENDING, + sortDirection: SORT_DIRECTION.ASCENDING, }; const tagsSlice = createSlice({ diff --git a/src/types/index.ts b/src/types/index.ts index a1a8b18..a7d5446 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,6 @@ export { ROUTE } from './route'; export { - HOME_SORT, + MEME_SORT, homeSortQuery, TAG_SORT, tagSortQuery, diff --git a/src/types/sort.ts b/src/types/sort.ts index 886e9b0..1159911 100644 --- a/src/types/sort.ts +++ b/src/types/sort.ts @@ -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 }; diff --git a/src/utilities/icon.ts b/src/utilities/icon.ts index d0d0d2e..5edfcc1 100644 --- a/src/utilities/icon.ts +++ b/src/utilities/icon.ts @@ -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';