Add searchbar to home screen
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
1687
package-lock.json
generated
1687
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
26
src/app.tsx
26
src/app.tsx
@@ -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>
|
||||
|
@@ -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={[
|
||||
{
|
||||
|
@@ -1,2 +1,2 @@
|
||||
export { MEME_TYPE, Meme } from './meme';
|
||||
export { MEME_TYPE, memeTypePlural, Meme } from './meme';
|
||||
export { Tag } from './tag';
|
||||
|
@@ -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 };
|
||||
|
@@ -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}
|
||||
|
@@ -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 />
|
||||
|
@@ -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';
|
||||
|
@@ -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;
|
@@ -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;
|
||||
|
@@ -58,4 +58,5 @@ export {
|
||||
cycleView,
|
||||
setFavoritesOnly,
|
||||
toggleFavoritesOnly,
|
||||
setFilter,
|
||||
} from './home';
|
||||
|
@@ -37,6 +37,9 @@ const styles = StyleSheet.create({
|
||||
centerText: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
flexRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
|
Reference in New Issue
Block a user