Add home screen view options

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-11 17:08:43 +03:00
parent ca67493302
commit 2e147060c0
15 changed files with 2814 additions and 949 deletions

View File

@@ -6,6 +6,7 @@ buildscript {
minSdkVersion = 21 minSdkVersion = 21
compileSdkVersion = 33 compileSdkVersion = 33
targetSdkVersion = 33 targetSdkVersion = 33
kotlinVersion = "1.8.22"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620" ndkVersion = "23.1.7779620"

3541
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,9 +19,11 @@
"@react-navigation/native": "^6.1.7", "@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13", "@react-navigation/native-stack": "^6.9.13",
"@realm/react": "^0.5.1", "@realm/react": "^0.5.1",
"@shopify/flash-list": "^1.4.3",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.1", "react-native": "0.72.1",
"react-native-document-picker": "^9.0.1", "react-native-document-picker": "^9.0.1",
"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",

View File

@@ -8,7 +8,7 @@ import { Meme, Tag } from './database';
import NavigationContainer from './navigation'; import NavigationContainer from './navigation';
import { SettingsProvider } from './contexts'; import { SettingsProvider } from './contexts';
function App() { const App = () => {
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
const isDarkMode = colorScheme === 'dark'; const isDarkMode = colorScheme === 'dark';
const theme = isDarkMode ? darkTheme : lightTheme; const theme = isDarkMode ? darkTheme : lightTheme;
@@ -28,6 +28,6 @@ function App() {
</PaperProvider> </PaperProvider>
</RealmProvider> </RealmProvider>
); );
} };
export default App; export default App;

View File

@@ -29,7 +29,7 @@ const SettingsContext = createContext<SettingsContextType | undefined>(
undefined, undefined,
); );
function SettingsProvider({ children }: { children: ReactNode }) { const SettingsProvider = ({ children }: { children: ReactNode }) => {
const [storageUri, setStorageUri] = useState(''); const [storageUri, setStorageUri] = useState('');
const [noMedia, setNoMedia] = useState(false); const [noMedia, setNoMedia] = useState(false);
@@ -128,22 +128,25 @@ function SettingsProvider({ children }: { children: ReactNode }) {
)} )}
</SettingsContext.Provider> </SettingsContext.Provider>
); );
} };
function useStorageUri(): [string, (newStorageUri: string) => Promise<void>] { const useStorageUri = (): [
string,
(newStorageUri: string) => Promise<void>,
] => {
const context = useContext(SettingsContext); const context = useContext(SettingsContext);
if (!context) { if (!context) {
throw new Error('useStorageUri must be used within a SettingsProvider'); throw new Error('useStorageUri must be used within a SettingsProvider');
} }
return [context.storageUri, context.setStorageUri]; return [context.storageUri, context.setStorageUri];
} };
function useNoMedia(): [boolean, (newNoMedia: boolean) => Promise<void>] { const useNoMedia = (): [boolean, (newNoMedia: boolean) => Promise<void>] => {
const context = useContext(SettingsContext); const context = useContext(SettingsContext);
if (!context) { if (!context) {
throw new Error('useNoMedia must be used within a SettingsProvider'); throw new Error('useNoMedia must be used within a SettingsProvider');
} }
return [context.noMedia, context.setNoMedia]; return [context.noMedia, context.setNoMedia];
} };
export { SettingsProvider, useStorageUri, useNoMedia }; export { SettingsProvider, useStorageUri, useNoMedia };

View File

@@ -14,6 +14,7 @@ class Meme extends Realm.Object<Meme> {
id!: Realm.BSON.UUID; id!: Realm.BSON.UUID;
type!: MEME_TYPE; type!: MEME_TYPE;
uri!: string; uri!: string;
size!: number;
title!: string; title!: string;
description?: string; description?: string;
isFavorite!: boolean; isFavorite!: boolean;
@@ -30,6 +31,7 @@ class Meme extends Realm.Object<Meme> {
id: 'uuid', id: 'uuid',
type: { type: 'string', indexed: true }, type: { type: 'string', indexed: true },
uri: 'string', uri: 'string',
size: 'int',
title: 'string', title: 'string',
description: 'string?', description: 'string?',
isFavorite: { type: 'bool', indexed: true, default: false }, isFavorite: { type: 'bool', indexed: true, default: false },

View File

@@ -12,7 +12,7 @@ import { horizontalScale } from './styles';
import { FloatingActionButton } from './components'; import { FloatingActionButton } from './components';
import { darkNavigationTheme, lightNavigationTheme } from './theme'; import { darkNavigationTheme, lightNavigationTheme } from './theme';
function TabNavigator() { const TabNavigator = () => {
const TabNavigatorBase = createBottomTabNavigator(); const TabNavigatorBase = createBottomTabNavigator();
const [fabVisible, setFabVisible] = React.useState(true); const [fabVisible, setFabVisible] = React.useState(true);
@@ -102,9 +102,9 @@ function TabNavigator() {
</Portal.Host> </Portal.Host>
</> </>
); );
} };
function NavigationContainer() { const NavigationContainer = () => {
const StackNavigatorBase = createNativeStackNavigator(); const StackNavigatorBase = createNativeStackNavigator();
const theme = useTheme(); const theme = useTheme();
@@ -121,6 +121,6 @@ function NavigationContainer() {
</StackNavigatorBase.Navigator> </StackNavigatorBase.Navigator>
</NavigationContainerBase> </NavigationContainerBase>
); );
} };
export default NavigationContainer; export default NavigationContainer;

View File

@@ -1,12 +1,88 @@
import React from 'react'; import React, { useState } from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper'; import {
Button,
Menu,
IconButton,
Divider,
useTheme,
} from 'react-native-paper';
import { PaddedView } from '../components'; import { PaddedView } from '../components';
import styles, { verticalScale } from '../styles';
import { SORT, SORT_DIRECTION, VIEW } from '../types';
import { getNextView, getSortIcon, getViewIcon } from '../utilities';
const Home = () => { const Home = () => {
const theme = useTheme();
const [sortMenuVisible, setSortMenuVisible] = useState(false);
const [sort, setSort] = useState(SORT.TITLE);
const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASCENDING);
const [viewType, setViewType] = useState(VIEW.MASONRY);
const [favoritesOnly, setFavoritesOnly] = useState(false);
const handleSortModeChange = (newSort: SORT) => {
if (newSort === sort) {
setSortDirection(
sortDirection === SORT_DIRECTION.ASCENDING
? SORT_DIRECTION.DESCENDING
: SORT_DIRECTION.ASCENDING,
);
} else {
setSort(newSort);
if (newSort === SORT.TITLE) {
setSortDirection(SORT_DIRECTION.ASCENDING);
} else {
setSortDirection(SORT_DIRECTION.DESCENDING);
}
}
setSortMenuVisible(false);
};
return ( return (
<PaddedView centered> <PaddedView>
<Text>Home</Text> <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}>
<IconButton
icon={favoritesOnly ? 'heart' : 'heart-outline'}
iconColor={theme.colors.primary}
size={verticalScale(16)}
onPress={() => setFavoritesOnly(!favoritesOnly)}
/>
<IconButton
icon={getViewIcon(viewType)}
iconColor={theme.colors.primary}
size={verticalScale(16)}
onPress={() => setViewType(getNextView(viewType))}
/>
</View>
</View>
<Divider />
</PaddedView> </PaddedView>
); );
}; };

View File

@@ -31,13 +31,22 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
centeredVertical: {
alignItems: 'center',
},
centerText: { centerText: {
textAlign: 'center', textAlign: 'center',
}, },
flexRow: {
flexDirection: 'row',
},
flexRowSpaceBetween: { flexRowSpaceBetween: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
}, },
flexRowReverse: {
flexDirection: 'row-reverse',
},
}); });
export { horizontalScale, verticalScale, moderateScale, styles as default }; export { horizontalScale, verticalScale, moderateScale, styles as default };

2
src/types/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { SORT, SORT_DIRECTION } from './sort';
export { VIEW } from './view';

15
src/types/sort.ts Normal file
View File

@@ -0,0 +1,15 @@
enum SORT {
TITLE = 'Title',
DATE_CREATED = 'Date Created',
DATE_MODIFIED = 'Date Modified',
DATE_USED = 'Last Used',
TIMES_USED = 'Times Used',
SIZE = 'Size',
}
enum SORT_DIRECTION {
ASCENDING = 'Ascending',
DESCENDING = 'Descending',
}
export { SORT, SORT_DIRECTION };

7
src/types/view.ts Normal file
View File

@@ -0,0 +1,7 @@
enum VIEW {
MASONRY = 'Masonry',
GRID = 'Grid',
LIST = 'List',
}
export { VIEW };

View File

@@ -1 +1,3 @@
export { isPermissionForPath, clearPermissions } from './permissions'; export { isPermissionForPath, clearPermissions } from './permissions';
export { getSortIcon } from './sort';
export { getViewIcon, getNextView } from './view';

38
src/utilities/sort.ts Normal file
View File

@@ -0,0 +1,38 @@
import { SORT, SORT_DIRECTION } from '../types';
const getSortIcon = (sort: SORT, sortDirection: SORT_DIRECTION) => {
let sortIcon = '';
switch (sort) {
case SORT.TITLE: {
sortIcon = 'sort-alphabetical';
break;
}
case SORT.DATE_CREATED:
case SORT.DATE_MODIFIED:
case SORT.DATE_USED: {
sortIcon = 'sort-calendar';
break;
}
case SORT.TIMES_USED:
case SORT.SIZE: {
sortIcon = 'sort-numeric';
break;
}
}
switch (sortDirection) {
case SORT_DIRECTION.ASCENDING: {
sortIcon += '-ascending';
break;
}
case SORT_DIRECTION.DESCENDING: {
sortIcon += '-descending';
break;
}
}
return sortIcon;
};
export { getSortIcon };

31
src/utilities/view.ts Normal file
View File

@@ -0,0 +1,31 @@
import { VIEW } from '../types';
const getViewIcon = (view: VIEW) => {
switch (view) {
case VIEW.MASONRY: {
return 'view-dashboard';
}
case VIEW.GRID: {
return 'view-grid';
}
case VIEW.LIST: {
return 'view-list';
}
}
};
const getNextView = (view: VIEW) => {
switch (view) {
case VIEW.MASONRY: {
return VIEW.GRID;
}
case VIEW.GRID: {
return VIEW.LIST;
}
case VIEW.LIST: {
return VIEW.MASONRY;
}
}
};
export { getViewIcon, getNextView };