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
compileSdkVersion = 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.
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-stack": "^6.9.13",
"@realm/react": "^0.5.1",
"@shopify/flash-list": "^1.4.3",
"react": "18.2.0",
"react-native": "0.72.1",
"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",

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,88 @@
import React from 'react';
import { Text } from 'react-native-paper';
import React, { useState } from 'react';
import { View } from 'react-native';
import {
Button,
Menu,
IconButton,
Divider,
useTheme,
} from 'react-native-paper';
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 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 (
<PaddedView centered>
<Text>Home</Text>
<PaddedView>
<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>
);
};

View File

@@ -31,13 +31,22 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
centeredVertical: {
alignItems: 'center',
},
centerText: {
textAlign: 'center',
},
flexRow: {
flexDirection: 'row',
},
flexRowSpaceBetween: {
flexDirection: 'row',
justifyContent: 'space-between',
},
flexRowReverse: {
flexDirection: 'row-reverse',
},
});
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 { 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 };