Add searchbar to home screen

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-12 16:55:48 +03:00
parent ce8ef46c20
commit 5bf066ac98
13 changed files with 358 additions and 1560 deletions

1687
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,13 +23,13 @@
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@shopify/flash-list": "^1.4.3", "@shopify/flash-list": "^1.4.3",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.1", "react-native": "0.72.2",
"react-native-document-picker": "^9.0.1", "react-native-document-picker": "^9.0.1",
"react-native-fast-image": "^8.6.3", "react-native-fast-image": "^8.6.3",
"react-native-file-access": "^3.0.4", "react-native-file-access": "^3.0.4",
"react-native-gesture-handler": "^2.12.0", "react-native-gesture-handler": "^2.12.0",
"react-native-paper": "^5.9.1", "react-native-paper": "^5.9.1",
"react-native-reanimated": "2.2.4", "react-native-reanimated": "^3.3.0",
"react-native-safe-area-context": "^4.6.4", "react-native-safe-area-context": "^4.6.4",
"react-native-scoped-storage": "^1.9.3", "react-native-scoped-storage": "^1.9.3",
"react-native-screens": "^3.22.1", "react-native-screens": "^3.22.1",

View File

@@ -12,6 +12,8 @@ import NavigationContainer from './navigation';
import { store, persistor, validateSettings } from './state'; import { store, persistor, validateSettings } from './state';
import { LoadingView } from './components'; import { LoadingView } from './components';
import { Welcome } from './screens'; import { Welcome } from './screens';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import styles from './styles';
const App = () => { const App = () => {
const [showWelcome, setShowWelcome] = useState(false); const [showWelcome, setShowWelcome] = useState(false);
@@ -47,17 +49,19 @@ const App = () => {
persistor={persistor} persistor={persistor}
onBeforeLift={onBeforeLift}> onBeforeLift={onBeforeLift}>
<RealmProvider schema={[Meme, Tag]}> <RealmProvider schema={[Meme, Tag]}>
<SafeAreaProvider> <GestureHandlerRootView style={styles.flex}>
<StatusBar <SafeAreaProvider>
barStyle={isDarkMode ? 'light-content' : 'dark-content'} <StatusBar
backgroundColor={theme.colors.background} barStyle={isDarkMode ? 'light-content' : 'dark-content'}
/> backgroundColor={theme.colors.background}
{showWelcome ? ( />
<Welcome onWelcomeComplete={() => setShowWelcome(false)} /> {showWelcome ? (
) : ( <Welcome onWelcomeComplete={() => setShowWelcome(false)} />
<NavigationContainer /> ) : (
)} <NavigationContainer />
</SafeAreaProvider> )}
</SafeAreaProvider>
</GestureHandlerRootView>
</RealmProvider> </RealmProvider>
</PersistGate> </PersistGate>
</Provider> </Provider>

View File

@@ -1,26 +1,46 @@
import * as React from 'react'; import * as React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet, Keyboard } from 'react-native';
import { FAB, Portal } from 'react-native-paper'; import { FAB, Portal } from 'react-native-paper';
import { verticalScale } from '../styles'; import { horizontalScale, verticalScale } from '../styles';
import { ParamListBase, useNavigation } from '@react-navigation/native'; import { ParamListBase, useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useEffect, useState } from 'react';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
fab: { fab: {
paddingBottom: verticalScale(75), position: 'absolute',
right: horizontalScale(10),
bottom: verticalScale(75),
}, },
}); });
const FloatingActionButton = ({ visible }: { visible?: boolean }) => { const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
const [state, setState] = React.useState(false); const [state, setState] = useState(false);
const { navigate } = const { navigate } =
useNavigation<NativeStackNavigationProp<ParamListBase>>(); useNavigation<NativeStackNavigationProp<ParamListBase>>();
const [keyboardOpen, setKeyboardOpen] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => setKeyboardOpen(true),
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => setKeyboardOpen(false),
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
return ( return (
<Portal> <Portal>
<FAB.Group <FAB.Group
open={state} open={state}
visible={visible ?? true} visible={visible && !keyboardOpen}
icon={state ? 'image' : 'plus'} icon={state ? 'image' : 'plus'}
actions={[ actions={[
{ {

View File

@@ -1,2 +1,2 @@
export { MEME_TYPE, Meme } from './meme'; export { MEME_TYPE, memeTypePlural, Meme } from './meme';
export { Tag } from './tag'; export { Tag } from './tag';

View File

@@ -2,18 +2,27 @@ import { Realm } from '@realm/react';
import { Tag } from './tag'; import { Tag } from './tag';
enum MEME_TYPE { enum MEME_TYPE {
IMAGE = 'IMAGE', IMAGE = 'Image',
GIF = 'GIF', GIF = 'GIF',
ALMBUM = 'ALMBUM', VIDEO = 'Video',
VIDEO = 'VIDEO', AUDIO = 'Audio',
AUDIO = 'AUDIO', ALBUM = 'Album',
TEXT = 'TEXT', TEXT = 'Text',
} }
const memeTypePlural = {
[MEME_TYPE.IMAGE]: 'Images',
[MEME_TYPE.GIF]: 'GIFs',
[MEME_TYPE.VIDEO]: 'Videos',
[MEME_TYPE.AUDIO]: 'Audio',
[MEME_TYPE.ALBUM]: 'Albums',
[MEME_TYPE.TEXT]: 'Text',
};
class Meme extends Realm.Object<Meme> { class Meme extends Realm.Object<Meme> {
id!: Realm.BSON.UUID; id!: Realm.BSON.UUID;
type!: MEME_TYPE; type!: MEME_TYPE;
uri!: string; uri!: Realm.List<string>;
size!: number; size!: number;
title!: string; title!: string;
description?: string; description?: string;
@@ -30,7 +39,7 @@ class Meme extends Realm.Object<Meme> {
properties: { properties: {
id: 'uuid', id: 'uuid',
type: { type: 'string', indexed: true }, type: { type: 'string', indexed: true },
uri: 'string', uri: 'string[]',
size: 'int', size: 'int',
title: 'string', title: 'string',
description: 'string?', description: 'string?',
@@ -44,4 +53,4 @@ class Meme extends Realm.Object<Meme> {
}; };
} }
export { MEME_TYPE, Meme }; export { MEME_TYPE, memeTypePlural, Meme };

View File

@@ -7,7 +7,7 @@ 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, Portal, useTheme } from 'react-native-paper'; import { BottomNavigation, Portal, useTheme } from 'react-native-paper';
import { Home, Search, Tags, Settings, AddItem } from './screens'; import { Home, Tags, Settings, AddItem } from './screens';
import { horizontalScale } from './styles'; import { horizontalScale } from './styles';
import { FloatingActionButton } from './components'; import { FloatingActionButton } from './components';
import { darkNavigationTheme, lightNavigationTheme } from './theme'; import { darkNavigationTheme, lightNavigationTheme } from './theme';
@@ -70,15 +70,6 @@ const TabNavigator = () => {
), ),
}} }}
/> />
<TabNavigatorBase.Screen
name="Search"
component={Search}
options={{
tabBarIcon: ({ color, size }) => (
<FontAwesome5 name="search" color={color} size={size} />
),
}}
/>
<TabNavigatorBase.Screen <TabNavigatorBase.Screen
name="Tags" name="Tags"
component={Tags} component={Tags}

View File

@@ -6,6 +6,7 @@ import {
IconButton, IconButton,
Divider, Divider,
useTheme, useTheme,
Searchbar,
} from 'react-native-paper'; } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { PaddedView } from '../components'; import { PaddedView } from '../components';
@@ -19,7 +20,9 @@ import {
setSortDirection, setSortDirection,
toggleFavoritesOnly, toggleFavoritesOnly,
setSort, setSort,
setFilter,
} from '../state'; } from '../state';
import { MEME_TYPE, memeTypePlural } from '../database';
const Home = () => { const Home = () => {
const theme = useTheme(); const theme = useTheme();
@@ -32,9 +35,11 @@ const Home = () => {
const favoritesOnly = useSelector( const favoritesOnly = useSelector(
(state: RootState) => state.home.favoritesOnly, (state: RootState) => state.home.favoritesOnly,
); );
const filter = useSelector((state: RootState) => state.home.filter);
const dispatch = useDispatch(); const dispatch = useDispatch();
const [sortMenuVisible, setSortMenuVisible] = useState(false); const [sortMenuVisible, setSortMenuVisible] = useState(false);
const [filterMenuVisible, setFilterMenuVisible] = useState(false);
const handleSortModeChange = (newSort: SORT) => { const handleSortModeChange = (newSort: SORT) => {
if (newSort === sort) { if (newSort === sort) {
@@ -50,45 +55,86 @@ const Home = () => {
setSortMenuVisible(false); setSortMenuVisible(false);
}; };
const handleFilterChange = (newFilter: MEME_TYPE | undefined) => {
dispatch(setFilter(newFilter));
setFilterMenuVisible(false);
};
const [search, setSearch] = useState('');
return ( return (
<PaddedView> <PaddedView>
<Searchbar placeholder="Search" value={search} onChangeText={setSearch} />
<View style={[styles.flexRowSpaceBetween, styles.centeredVertical]}> <View style={[styles.flexRowSpaceBetween, styles.centeredVertical]}>
<Menu <View style={[styles.flexRow, styles.centeredVertical]}>
visible={sortMenuVisible} <Menu
onDismiss={() => setSortMenuVisible(false)} visible={sortMenuVisible}
anchor={ onDismiss={() => setSortMenuVisible(false)}
<Button anchor={
onPress={() => setSortMenuVisible(true)} <Button
icon={getSortIcon(sort, sortDirection)} onPress={() => setSortMenuVisible(true)}
contentStyle={styles.flexRowReverse}> icon={getSortIcon(sort, sortDirection)}
Sort By: {sort} contentStyle={styles.flexRowReverse}
</Button> compact>
}> Sort By: {sort}
{Object.keys(SORT).map(key => { </Button>
return ( }>
<Menu.Item {Object.keys(SORT).map(key => {
key={key} return (
onPress={() => <Menu.Item
handleSortModeChange(SORT[key as keyof typeof SORT]) key={key}
} onPress={() =>
title={SORT[key as keyof typeof SORT]} handleSortModeChange(SORT[key as keyof typeof SORT])
/> }
); title={SORT[key as keyof typeof SORT]}
})} />
</Menu> );
})}
</Menu>
</View>
<View style={styles.flexRow}> <View style={styles.flexRow}>
<IconButton
icon={favoritesOnly ? 'heart' : 'heart-outline'}
iconColor={theme.colors.primary}
size={verticalScale(16)}
onPress={() => dispatch(toggleFavoritesOnly())}
/>
<IconButton <IconButton
icon={getViewIcon(view)} icon={getViewIcon(view)}
iconColor={theme.colors.primary} iconColor={theme.colors.primary}
size={verticalScale(16)} size={verticalScale(16)}
onPress={() => dispatch(cycleView())} onPress={() => dispatch(cycleView())}
/> />
<IconButton
icon={favoritesOnly ? 'heart' : 'heart-outline'}
iconColor={theme.colors.primary}
size={verticalScale(16)}
onPress={() => dispatch(toggleFavoritesOnly())}
/>
<Menu
visible={filterMenuVisible}
onDismiss={() => setFilterMenuVisible(false)}
anchor={
<IconButton
onPress={() => setFilterMenuVisible(true)}
icon={filter ? 'filter' : 'filter-outline'}
iconColor={theme.colors.primary}
size={verticalScale(16)}
/>
}>
<Menu.Item
// eslint-disable-next-line unicorn/no-useless-undefined
onPress={() => handleFilterChange(undefined)}
title="All"
/>
{Object.keys(MEME_TYPE).map(key => {
return (
<Menu.Item
key={key}
onPress={() =>
handleFilterChange(MEME_TYPE[key as keyof typeof MEME_TYPE])
}
title={
memeTypePlural[MEME_TYPE[key as keyof typeof MEME_TYPE]]
}
/>
);
})}
</Menu>
</View> </View>
</View> </View>
<Divider /> <Divider />

View File

@@ -1,6 +1,5 @@
export { default as AddItem } from './addItem'; export { default as AddItem } from './addItem';
export { default as Home } from './home'; export { default as Home } from './home';
export { default as Search } from './search';
export { default as Settings } from './settings'; export { default as Settings } from './settings';
export { default as Tags } from './tags'; export { default as Tags } from './tags';
export { default as Welcome } from './welcome'; export { default as Welcome } from './welcome';

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { Text } from 'react-native-paper';
import { PaddedView } from '../components';
const Search = () => {
return (
<PaddedView centered>
<Text>Search</Text>
</PaddedView>
);
};
export default Search;

View File

@@ -1,11 +1,13 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SORT, SORT_DIRECTION, VIEW } from '../types'; import { SORT, SORT_DIRECTION, VIEW } from '../types';
import { MEME_TYPE } from '../database';
interface HomeState { interface HomeState {
sort: SORT; sort: SORT;
sortDirection: SORT_DIRECTION; sortDirection: SORT_DIRECTION;
view: VIEW; view: VIEW;
favoritesOnly: boolean; favoritesOnly: boolean;
filter: MEME_TYPE | undefined;
} }
const initialState: HomeState = { const initialState: HomeState = {
@@ -13,6 +15,7 @@ const initialState: HomeState = {
sortDirection: SORT_DIRECTION.ASCENDING, sortDirection: SORT_DIRECTION.ASCENDING,
view: VIEW.MASONRY, view: VIEW.MASONRY,
favoritesOnly: false, favoritesOnly: false,
filter: undefined,
}; };
const homeSlice = createSlice({ const homeSlice = createSlice({
@@ -53,6 +56,9 @@ const homeSlice = createSlice({
toggleFavoritesOnly: state => { toggleFavoritesOnly: state => {
state.favoritesOnly = !state.favoritesOnly; state.favoritesOnly = !state.favoritesOnly;
}, },
setFilter: (state, action: PayloadAction<MEME_TYPE | undefined>) => {
state.filter = action.payload;
},
}, },
}); });
@@ -64,6 +70,7 @@ const {
cycleView, cycleView,
setFavoritesOnly, setFavoritesOnly,
toggleFavoritesOnly, toggleFavoritesOnly,
setFilter,
} = homeSlice.actions; } = homeSlice.actions;
export { export {
@@ -75,5 +82,6 @@ export {
cycleView, cycleView,
setFavoritesOnly, setFavoritesOnly,
toggleFavoritesOnly, toggleFavoritesOnly,
setFilter,
}; };
export default homeSlice.reducer; export default homeSlice.reducer;

View File

@@ -58,4 +58,5 @@ export {
cycleView, cycleView,
setFavoritesOnly, setFavoritesOnly,
toggleFavoritesOnly, toggleFavoritesOnly,
setFilter,
} from './home'; } from './home';

View File

@@ -37,6 +37,9 @@ const styles = StyleSheet.create({
centerText: { centerText: {
textAlign: 'center', textAlign: 'center',
}, },
flex: {
flex: 1,
},
flexRow: { flexRow: {
flexDirection: 'row', flexDirection: 'row',
}, },