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",
"@shopify/flash-list": "^1.4.3",
"react": "18.2.0",
"react-native": "0.72.1",
"react-native": "0.72.2",
"react-native-document-picker": "^9.0.1",
"react-native-fast-image": "^8.6.3",
"react-native-file-access": "^3.0.4",
"react-native-gesture-handler": "^2.12.0",
"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-scoped-storage": "^1.9.3",
"react-native-screens": "^3.22.1",

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,27 @@ import { Realm } from '@realm/react';
import { Tag } from './tag';
enum MEME_TYPE {
IMAGE = 'IMAGE',
IMAGE = 'Image',
GIF = 'GIF',
ALMBUM = 'ALMBUM',
VIDEO = 'VIDEO',
AUDIO = 'AUDIO',
TEXT = 'TEXT',
VIDEO = 'Video',
AUDIO = 'Audio',
ALBUM = 'Album',
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> {
id!: Realm.BSON.UUID;
type!: MEME_TYPE;
uri!: string;
uri!: Realm.List<string>;
size!: number;
title!: string;
description?: string;
@@ -30,7 +39,7 @@ class Meme extends Realm.Object<Meme> {
properties: {
id: 'uuid',
type: { type: 'string', indexed: true },
uri: 'string',
uri: 'string[]',
size: 'int',
title: '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 { createNativeStackNavigator } from '@react-navigation/native-stack';
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 { FloatingActionButton } from './components';
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
name="Tags"
component={Tags}

View File

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

View File

@@ -1,6 +1,5 @@
export { default as AddItem } from './addItem';
export { default as Home } from './home';
export { default as Search } from './search';
export { default as Settings } from './settings';
export { default as Tags } from './tags';
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 { SORT, SORT_DIRECTION, VIEW } from '../types';
import { MEME_TYPE } from '../database';
interface HomeState {
sort: SORT;
sortDirection: SORT_DIRECTION;
view: VIEW;
favoritesOnly: boolean;
filter: MEME_TYPE | undefined;
}
const initialState: HomeState = {
@@ -13,6 +15,7 @@ const initialState: HomeState = {
sortDirection: SORT_DIRECTION.ASCENDING,
view: VIEW.MASONRY,
favoritesOnly: false,
filter: undefined,
};
const homeSlice = createSlice({
@@ -53,6 +56,9 @@ const homeSlice = createSlice({
toggleFavoritesOnly: state => {
state.favoritesOnly = !state.favoritesOnly;
},
setFilter: (state, action: PayloadAction<MEME_TYPE | undefined>) => {
state.filter = action.payload;
},
},
});
@@ -64,6 +70,7 @@ const {
cycleView,
setFavoritesOnly,
toggleFavoritesOnly,
setFilter,
} = homeSlice.actions;
export {
@@ -75,5 +82,6 @@ export {
cycleView,
setFavoritesOnly,
toggleFavoritesOnly,
setFilter,
};
export default homeSlice.reducer;

View File

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

View File

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