Fix various spacing issues
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@bankify/redux-persist-realm": "^0.1.3",
|
||||
"@likashefqet/react-native-image-zoom": "^1.3.0",
|
||||
"@react-native-clipboard/clipboard": "^1.11.2",
|
||||
"@react-native-community/hooks": "^3.0.0",
|
||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||
"@react-navigation/native": "^6.1.7",
|
||||
"@react-navigation/native-stack": "^6.9.13",
|
||||
@@ -4112,6 +4113,15 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-community/hooks": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/hooks/-/hooks-3.0.0.tgz",
|
||||
"integrity": "sha512-g2OyxXHfwIytXUJitBR6Z/ISoOfp0WKx5FOv+NqJ/CrWjRDcTw6zXE5I1C9axfuh30kJqzWchVfCDrkzZYTxqg==",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0.2",
|
||||
"react-native": ">=0.65"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/assets-registry": {
|
||||
"version": "0.72.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",
|
||||
@@ -18759,6 +18769,12 @@
|
||||
"joi": "^17.2.1"
|
||||
}
|
||||
},
|
||||
"@react-native-community/hooks": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/hooks/-/hooks-3.0.0.tgz",
|
||||
"integrity": "sha512-g2OyxXHfwIytXUJitBR6Z/ISoOfp0WKx5FOv+NqJ/CrWjRDcTw6zXE5I1C9axfuh30kJqzWchVfCDrkzZYTxqg==",
|
||||
"requires": {}
|
||||
},
|
||||
"@react-native/assets-registry": {
|
||||
"version": "0.72.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",
|
||||
|
@@ -17,6 +17,7 @@
|
||||
"@bankify/redux-persist-realm": "^0.1.3",
|
||||
"@likashefqet/react-native-image-zoom": "^1.3.0",
|
||||
"@react-native-clipboard/clipboard": "^1.11.2",
|
||||
"@react-native-community/hooks": "^3.0.0",
|
||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||
"@react-navigation/native": "^6.1.7",
|
||||
"@react-navigation/native-stack": "^6.9.13",
|
||||
|
47
src/app.tsx
47
src/app.tsx
@@ -14,7 +14,6 @@ import { store, persistor, validateSettings } from './state';
|
||||
import { LoadingView } from './components';
|
||||
import { Welcome } from './screens';
|
||||
import styles from './styles';
|
||||
import { DimensionsProvider } from './contexts';
|
||||
|
||||
const App = () => {
|
||||
const [showWelcome, setShowWelcome] = useState(false);
|
||||
@@ -44,30 +43,28 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<PaperProvider theme={theme}>
|
||||
<DimensionsProvider>
|
||||
<ReduxProvider store={store}>
|
||||
<PersistGate
|
||||
loading={<LoadingView />}
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}>
|
||||
<RealmProvider schema={[Meme, Tag]}>
|
||||
<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>
|
||||
</ReduxProvider>
|
||||
</DimensionsProvider>
|
||||
<ReduxProvider store={store}>
|
||||
<PersistGate
|
||||
loading={<LoadingView />}
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}>
|
||||
<RealmProvider schema={[Meme, Tag]}>
|
||||
<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>
|
||||
</ReduxProvider>
|
||||
</PaperProvider>
|
||||
);
|
||||
};
|
||||
|
@@ -4,9 +4,9 @@ import { FAB } from 'react-native-paper';
|
||||
import { ParamListBase, useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { pickSingle } from 'react-native-document-picker';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import { ROUTE } from '../types';
|
||||
import { allowedMimeTypes, noOp } from '../utilities';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
|
||||
const floatingActionButtonStyles = StyleSheet.create({
|
||||
fab: {
|
||||
@@ -22,7 +22,7 @@ const floatingActionButtonStyles = StyleSheet.create({
|
||||
const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
|
||||
const { navigate } =
|
||||
useNavigation<NativeStackNavigationProp<ParamListBase>>();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
|
||||
const [state, setState] = useState(false);
|
||||
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
||||
@@ -70,7 +70,7 @@ const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
|
||||
navigate(ROUTE.ADD_MEME, { file });
|
||||
}}
|
||||
style={
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
orientation === 'portrait'
|
||||
? floatingActionButtonStyles.fab
|
||||
: floatingActionButtonStyles.fabLandscape
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Animated, StyleSheet } from 'react-native';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import styles from '../styles';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import styles from '../styles';
|
||||
|
||||
const hideableHeaderStyles = StyleSheet.create({
|
||||
headerView: {
|
||||
@@ -22,7 +22,7 @@ const HideableHeader = ({
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
|
||||
const headerAnim = useRef(new Animated.Value(visible ? 1 : 0)).current;
|
||||
|
||||
@@ -38,9 +38,7 @@ const HideableHeader = ({
|
||||
<Animated.View
|
||||
style={[
|
||||
hideableHeaderStyles.headerView,
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
? styles.paddingTop
|
||||
: styles.smallPaddingTop,
|
||||
orientation === 'portrait' ? styles.paddingTop : styles.smallPaddingTop,
|
||||
styles.paddingHorizontal,
|
||||
{
|
||||
transform: [
|
||||
|
@@ -9,7 +9,7 @@ const LoadingView = () => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.centered,
|
||||
styles.center,
|
||||
styles.flex,
|
||||
styles.fullSize,
|
||||
{ backgroundColor: colors.background },
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { HelperText, TextInput } from 'react-native-paper';
|
||||
import { Image } from 'react-native';
|
||||
import { useDimensions } from '../../contexts';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import LoadingView from '../loadingView';
|
||||
import { MemeTagSelector } from '.';
|
||||
import { Tag } from '../../database';
|
||||
import { StringValidationResult, validateMemeTitle } from '../../utilities';
|
||||
import { useImageDimensions } from '@react-native-community/hooks';
|
||||
|
||||
const memeEditorStyles = {
|
||||
image: {
|
||||
@@ -33,30 +34,11 @@ const MemeEditor = ({
|
||||
memeTags: Map<string, Tag>;
|
||||
setMemeTags: (tags: Map<string, Tag>) => void;
|
||||
}) => {
|
||||
const { dimensions } = useDimensions();
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
const { dimensions, loading, error } = useImageDimensions({ uri: memeUri });
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
setImageWidth(undefined);
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
setImageHeight(undefined);
|
||||
|
||||
Image.getSize(memeUri, (width, height) => {
|
||||
const paddedWidth = dimensions.width * 0.92;
|
||||
const paddedHeight = Math.max(
|
||||
Math.min((paddedWidth / width) * height, 500),
|
||||
100,
|
||||
);
|
||||
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight(paddedHeight);
|
||||
});
|
||||
}, [memeUri, dimensions.width]);
|
||||
|
||||
if (!imageWidth || !imageHeight) return <LoadingView />;
|
||||
if (loading || error || !dimensions) return <LoadingView />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -75,8 +57,14 @@ const MemeEditor = ({
|
||||
source={{ uri: memeUri }}
|
||||
style={[
|
||||
{
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
width: width * 0.92,
|
||||
height: Math.max(
|
||||
Math.min(
|
||||
((width * 0.92) / dimensions.width) * dimensions.height,
|
||||
500,
|
||||
),
|
||||
100,
|
||||
),
|
||||
},
|
||||
memeEditorStyles.image,
|
||||
]}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { TagChip } from '../tags';
|
||||
import { Tag } from '../../database';
|
||||
import { useQuery, useRealm } from '@realm/react';
|
||||
import { TAG_SORT, tagSortQuery } from '../../types';
|
||||
import { Chip, Modal, Portal, Searchbar, useTheme } from 'react-native-paper';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import styles from '../../styles';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { TAG_SORT, tagSortQuery } from '../../types';
|
||||
import { TagChip } from '../tags';
|
||||
import { Tag } from '../../database';
|
||||
import styles from '../../styles';
|
||||
import { validateTagName } from '../../utilities';
|
||||
|
||||
const memeTagSearchModalStyles = StyleSheet.create({
|
||||
@@ -36,6 +37,7 @@ const MemeTagSearchModal = ({
|
||||
memeTags: Map<string, Tag>;
|
||||
setMemeTags: (tags: Map<string, Tag>) => void;
|
||||
}) => {
|
||||
const { width } = useSafeAreaFrame();
|
||||
const { colors } = useTheme();
|
||||
const realm = useRealm();
|
||||
|
||||
@@ -115,6 +117,10 @@ const MemeTagSearchModal = ({
|
||||
extraData={memeTags}
|
||||
horizontal
|
||||
estimatedItemSize={120}
|
||||
estimatedListSize={{
|
||||
width: width - 10,
|
||||
height: 34.5,
|
||||
}}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
renderItem={({ item: tag }) => (
|
||||
@@ -135,6 +141,7 @@ const MemeTagSearchModal = ({
|
||||
Create Tag #{tagName.valid ? tagName.parsed : 'newTag'}
|
||||
</Chip>
|
||||
)}
|
||||
fadingEdgeLength={50}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import React, { ComponentProps, useState } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { Chip } from 'react-native-paper';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { TagChip } from '../tags';
|
||||
import { Tag } from '../../database';
|
||||
import { useDimensions } from '../../contexts';
|
||||
import { MemeTagSearchModal } from '.';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
|
||||
const memeTagSelectorStyles = StyleSheet.create({
|
||||
tagChip: {
|
||||
@@ -21,7 +21,7 @@ const MemeTagSelector = ({
|
||||
memeTags: Map<string, Tag>;
|
||||
setMemeTags: (tags: Map<string, Tag>) => void;
|
||||
} & ComponentProps<typeof View>) => {
|
||||
const { dimensions } = useDimensions();
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
const [flashListMargin, setFlashListMargin] = useState(0);
|
||||
const [tagSearchModalVisible, setTagSearchModalVisible] = useState(false);
|
||||
@@ -37,7 +37,6 @@ const MemeTagSelector = ({
|
||||
<View {...props}>
|
||||
<FlashList
|
||||
data={[...memeTags.values()]}
|
||||
extraData={memeTags}
|
||||
horizontal
|
||||
estimatedItemSize={120}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
@@ -55,7 +54,7 @@ const MemeTagSelector = ({
|
||||
onPress={() => setTagSearchModalVisible(true)}
|
||||
onLayout={event =>
|
||||
setFlashListMargin(
|
||||
dimensions.width * 0.92 - event.nativeEvent.layout.width,
|
||||
width * 0.92 - event.nativeEvent.layout.width,
|
||||
)
|
||||
}
|
||||
style={{
|
||||
@@ -64,6 +63,7 @@ const MemeTagSelector = ({
|
||||
Add Tag
|
||||
</Chip>
|
||||
)}
|
||||
fadingEdgeLength={50}
|
||||
/>
|
||||
</View>
|
||||
<MemeTagSearchModal
|
||||
|
@@ -1,51 +1,36 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, useTheme } from 'react-native-paper';
|
||||
import React from 'react';
|
||||
import { ImageZoom } from '@likashefqet/react-native-image-zoom';
|
||||
import { Meme } from '../../database';
|
||||
import { useDimensions } from '../../contexts';
|
||||
import { Image, View } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import styles from '../../styles';
|
||||
import LoadingView from '../loadingView';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { useImageDimensions } from '@react-native-community/hooks';
|
||||
|
||||
const MemeViewItem = ({ meme }: { meme: Meme }) => {
|
||||
const { dimensions } = useDimensions();
|
||||
const { colors } = useTheme();
|
||||
const { height, width } = useSafeAreaFrame();
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri });
|
||||
|
||||
useEffect(() => {
|
||||
Image.getSize(meme.uri, (width, height) => {
|
||||
const ratio = width / height;
|
||||
const screenRatio = dimensions.width / dimensions.height - 160;
|
||||
|
||||
if (ratio > screenRatio) {
|
||||
setImageWidth(dimensions.width);
|
||||
setImageHeight(dimensions.width / ratio);
|
||||
} else {
|
||||
setImageWidth(dimensions.height * ratio);
|
||||
setImageHeight(dimensions.height);
|
||||
}
|
||||
});
|
||||
}, [meme.uri, dimensions.width, dimensions.height]);
|
||||
if (loading || error || !dimensions) return <LoadingView />;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
width: dimensions.width,
|
||||
height: dimensions.height - 160,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
styles.centered,
|
||||
]}>
|
||||
{imageWidth && imageHeight ? (
|
||||
<ImageZoom
|
||||
source={{ uri: meme.uri }}
|
||||
style={{ width: imageWidth, height: imageHeight }}
|
||||
/>
|
||||
) : (
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
)}
|
||||
<View style={[{ width, height }, styles.center]}>
|
||||
<ImageZoom
|
||||
source={{ uri: meme.uri }}
|
||||
style={
|
||||
dimensions.aspectRatio > width / (height - 128)
|
||||
? {
|
||||
width,
|
||||
height: width / (dimensions.width / dimensions.height),
|
||||
}
|
||||
: {
|
||||
width: (height - 128) * (dimensions.width / dimensions.height),
|
||||
height: height - 128,
|
||||
}
|
||||
}
|
||||
minScale={0.5}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Image, TouchableHighlight, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { Image, TouchableHighlight } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { useImageDimensions } from '@react-native-community/hooks';
|
||||
import { Meme } from '../../../database';
|
||||
import { useDimensions } from '../../../contexts';
|
||||
import { RootState } from '../../../state';
|
||||
|
||||
const MemesGridItem = ({
|
||||
@@ -14,33 +15,27 @@ const MemesGridItem = ({
|
||||
index: number;
|
||||
focusMeme: (index: number) => void;
|
||||
}) => {
|
||||
const { dimensions } = useDimensions();
|
||||
const { width } = useSafeAreaFrame();
|
||||
const gridColumns = useSelector(
|
||||
(state: RootState) => state.settings.gridColumns,
|
||||
);
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri });
|
||||
|
||||
Image.getSize(meme.uri, () => {
|
||||
const paddedWidth = (dimensions.width * 0.92 - 5) / gridColumns;
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight(paddedWidth);
|
||||
});
|
||||
if (loading || error || !dimensions) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{imageWidth && imageHeight && (
|
||||
<View>
|
||||
<TouchableHighlight onPress={() => focusMeme(index)}>
|
||||
<Image
|
||||
source={{ uri: meme.uri }}
|
||||
style={[{ width: imageWidth, height: imageHeight }]}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
<TouchableHighlight onPress={() => focusMeme(index)}>
|
||||
<Image
|
||||
source={{ uri: meme.uri }}
|
||||
style={[
|
||||
{
|
||||
width: (width * 0.92 - 5) / gridColumns,
|
||||
height: (width * 0.92 - 5) / gridColumns,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import {
|
||||
} from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Divider, HelperText } from 'react-native-paper';
|
||||
import { useDimensions, ORIENTATION } from '../../../contexts';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { Meme } from '../../../database';
|
||||
import { RootState } from '../../../state';
|
||||
import { VIEW } from '../../../types';
|
||||
@@ -17,35 +17,32 @@ import MemesMasonryItem from './memesMasonryItem';
|
||||
import MemesGridItem from './memesGridItem';
|
||||
import MemesListItem from './memesListItem';
|
||||
|
||||
const memesMasonryListStyles = StyleSheet.create({
|
||||
const sharedMemesListStyles = StyleSheet.create({
|
||||
flashList: {
|
||||
paddingBottom: 100,
|
||||
// Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876
|
||||
paddingHorizontal: 0.1,
|
||||
},
|
||||
helperText: {
|
||||
marginVertical: 15,
|
||||
},
|
||||
});
|
||||
|
||||
const memesMasonryListStyles = StyleSheet.create({
|
||||
flashList: {
|
||||
// Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876
|
||||
paddingHorizontal: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
const memesGridListStyles = StyleSheet.create({
|
||||
flashList: {
|
||||
paddingBottom: 100,
|
||||
paddingHorizontal: 2.5,
|
||||
},
|
||||
helperText: {
|
||||
marginVertical: 12.5,
|
||||
},
|
||||
});
|
||||
|
||||
const memesListListStyles = StyleSheet.create({
|
||||
flashList: {
|
||||
paddingBottom: 100,
|
||||
paddingHorizontal: 5,
|
||||
},
|
||||
helperText: {
|
||||
marginVertical: 15,
|
||||
},
|
||||
});
|
||||
|
||||
const MemesList = ({
|
||||
@@ -61,7 +58,7 @@ const MemesList = ({
|
||||
handleScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
focusMeme: (index: number) => void;
|
||||
}) => {
|
||||
const { dimensions, orientation } = useDimensions();
|
||||
const { height, width } = useSafeAreaFrame();
|
||||
const view = useSelector((state: RootState) => state.memes.view);
|
||||
const masonryColumns = useSelector(
|
||||
(state: RootState) => state.settings.masonryColumns,
|
||||
@@ -70,10 +67,6 @@ const MemesList = ({
|
||||
(state: RootState) => state.settings.gridColumns,
|
||||
);
|
||||
|
||||
const extraFlashListPadding =
|
||||
flashListPadding +
|
||||
dimensions.height * (orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04);
|
||||
|
||||
return (
|
||||
<>
|
||||
{view === VIEW.MASONRY && (
|
||||
@@ -82,8 +75,8 @@ const MemesList = ({
|
||||
data={memes}
|
||||
estimatedItemSize={getFlashListItemHeight(masonryColumns)}
|
||||
estimatedListSize={{
|
||||
height: dimensions.height,
|
||||
width: dimensions.width * 0.92,
|
||||
height,
|
||||
width: width * 0.92,
|
||||
}}
|
||||
numColumns={masonryColumns}
|
||||
showsVerticalScrollIndicator={false}
|
||||
@@ -91,17 +84,19 @@ const MemesList = ({
|
||||
<MemesMasonryItem meme={meme} index={index} focusMeme={focusMeme} />
|
||||
)}
|
||||
contentContainerStyle={{
|
||||
paddingTop: extraFlashListPadding,
|
||||
paddingTop: flashListPadding,
|
||||
...sharedMemesListStyles.flashList,
|
||||
...memesMasonryListStyles.flashList,
|
||||
}}
|
||||
ListEmptyComponent={() => (
|
||||
<HelperText
|
||||
type={'info'}
|
||||
style={[memesMasonryListStyles.helperText, styles.centerText]}>
|
||||
style={[sharedMemesListStyles.helperText, styles.centerText]}>
|
||||
No memes found
|
||||
</HelperText>
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
fadingEdgeLength={100}
|
||||
/>
|
||||
)}
|
||||
{view === VIEW.GRID && (
|
||||
@@ -110,8 +105,8 @@ const MemesList = ({
|
||||
data={memes}
|
||||
estimatedItemSize={getFlashListItemHeight(gridColumns)}
|
||||
estimatedListSize={{
|
||||
height: dimensions.height,
|
||||
width: dimensions.width * 0.92,
|
||||
height: height,
|
||||
width: width * 0.92,
|
||||
}}
|
||||
numColumns={gridColumns}
|
||||
showsVerticalScrollIndicator={false}
|
||||
@@ -119,17 +114,19 @@ const MemesList = ({
|
||||
<MemesGridItem meme={meme} index={index} focusMeme={focusMeme} />
|
||||
)}
|
||||
contentContainerStyle={{
|
||||
paddingTop: extraFlashListPadding + 2.5,
|
||||
paddingTop: flashListPadding,
|
||||
...sharedMemesListStyles.flashList,
|
||||
...memesGridListStyles.flashList,
|
||||
}}
|
||||
ListEmptyComponent={() => (
|
||||
<HelperText
|
||||
type={'info'}
|
||||
style={[memesGridListStyles.helperText, styles.centerText]}>
|
||||
style={[sharedMemesListStyles.helperText, styles.centerText]}>
|
||||
No memes found
|
||||
</HelperText>
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
fadingEdgeLength={100}
|
||||
/>
|
||||
)}
|
||||
{view === VIEW.LIST && (
|
||||
@@ -138,8 +135,8 @@ const MemesList = ({
|
||||
data={memes}
|
||||
estimatedItemSize={50}
|
||||
estimatedListSize={{
|
||||
height: dimensions.height,
|
||||
width: dimensions.width * 0.92,
|
||||
height: height,
|
||||
width: width * 0.92,
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={({ item: meme, index }) => (
|
||||
@@ -147,17 +144,19 @@ const MemesList = ({
|
||||
)}
|
||||
ItemSeparatorComponent={() => <Divider />}
|
||||
contentContainerStyle={{
|
||||
paddingTop: extraFlashListPadding,
|
||||
paddingTop: flashListPadding,
|
||||
...sharedMemesListStyles.flashList,
|
||||
...memesListListStyles.flashList,
|
||||
}}
|
||||
ListEmptyComponent={() => (
|
||||
<HelperText
|
||||
type={'info'}
|
||||
style={[memesListListStyles.helperText, styles.centerText]}>
|
||||
style={[sharedMemesListStyles.helperText, styles.centerText]}>
|
||||
No memes found
|
||||
</HelperText>
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
fadingEdgeLength={100}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@@ -1,15 +1,18 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Image, StyleSheet, View } from 'react-native';
|
||||
import { Text, TouchableRipple } from 'react-native-paper';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { useImageDimensions } from '@react-native-community/hooks';
|
||||
import { Meme } from '../../../database';
|
||||
import styles from '../../../styles';
|
||||
import { useDimensions } from '../../../contexts';
|
||||
|
||||
const memesListItemStyles = StyleSheet.create({
|
||||
view: {
|
||||
paddingVertical: 10,
|
||||
},
|
||||
image: {
|
||||
height: 75,
|
||||
width: 75,
|
||||
borderRadius: 5,
|
||||
},
|
||||
detailsView: {
|
||||
@@ -30,71 +33,54 @@ const MemesListItem = ({
|
||||
index: number;
|
||||
focusMeme: (index: number) => void;
|
||||
}) => {
|
||||
const { dimensions } = useDimensions();
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri });
|
||||
|
||||
Image.getSize(meme.uri, () => {
|
||||
const paddedWidth = 75;
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight(paddedWidth);
|
||||
});
|
||||
if (loading || error || !dimensions) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{imageWidth && imageHeight && (
|
||||
<TouchableRipple
|
||||
onPress={() => focusMeme(index)}
|
||||
style={[memesListItemStyles.view, styles.flexRow]}>
|
||||
<>
|
||||
<View style={{ width: imageWidth, height: imageHeight }}>
|
||||
<Image
|
||||
source={{ uri: meme.uri }}
|
||||
<TouchableRipple
|
||||
onPress={() => focusMeme(index)}
|
||||
style={[memesListItemStyles.view, styles.flexRow]}>
|
||||
<>
|
||||
<Image source={{ uri: meme.uri }} style={memesListItemStyles.image} />
|
||||
<View
|
||||
style={[
|
||||
memesListItemStyles.detailsView,
|
||||
styles.flexColumn,
|
||||
{
|
||||
width: width * 0.92 - 75 - 10,
|
||||
},
|
||||
]}>
|
||||
<Text variant="titleMedium" style={memesListItemStyles.text}>
|
||||
{meme.title}
|
||||
</Text>
|
||||
<View style={styles.flexRow}>
|
||||
<Text variant="labelSmall" style={memesListItemStyles.text}>
|
||||
{meme.dateModified.toLocaleDateString()} • {meme.size / 1000}
|
||||
KB
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.flexRow, styles.flexWrap]}>
|
||||
{meme.tags.map(tag => (
|
||||
<Text
|
||||
variant="labelMedium"
|
||||
key={tag.id.toHexString()}
|
||||
style={[
|
||||
{ width: imageWidth, height: imageHeight },
|
||||
memesListItemStyles.image,
|
||||
{
|
||||
color: tag.color,
|
||||
},
|
||||
memesListItemStyles.text,
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
memesListItemStyles.detailsView,
|
||||
styles.flexColumn,
|
||||
{
|
||||
width: dimensions.width * 0.92 - imageWidth - 10,
|
||||
},
|
||||
]}>
|
||||
<Text variant="titleMedium" style={memesListItemStyles.text}>
|
||||
{meme.title}
|
||||
numberOfLines={1}>
|
||||
#{tag.name}
|
||||
</Text>
|
||||
<View style={styles.flexRow}>
|
||||
<Text variant="labelSmall" style={memesListItemStyles.text}>
|
||||
{meme.dateModified.toLocaleDateString()} • {meme.size / 1000}
|
||||
KB
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.flexRow, styles.flexWrap]}>
|
||||
{meme.tags.map(tag => (
|
||||
<Text
|
||||
variant="labelMedium"
|
||||
key={tag.id.toHexString()}
|
||||
style={[
|
||||
{
|
||||
color: tag.color,
|
||||
},
|
||||
memesListItemStyles.text,
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
#{tag.name}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Image, StyleSheet, TouchableHighlight } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { Meme } from '../../../database';
|
||||
import { useDimensions } from '../../../contexts';
|
||||
import { RootState } from '../../../state';
|
||||
import { useImageDimensions } from '@react-native-community/hooks';
|
||||
|
||||
const memeMasonryItemStyles = StyleSheet.create({
|
||||
view: {
|
||||
@@ -24,36 +25,31 @@ const MemesMasonryItem = ({
|
||||
index: number;
|
||||
focusMeme: (index: number) => void;
|
||||
}) => {
|
||||
const { dimensions } = useDimensions();
|
||||
const { width } = useSafeAreaFrame();
|
||||
const masonryColumns = useSelector(
|
||||
(state: RootState) => state.settings.masonryColumns,
|
||||
);
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri });
|
||||
|
||||
Image.getSize(meme.uri, (width, height) => {
|
||||
const paddedWidth = (dimensions.width * 0.92) / masonryColumns - 5;
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight((paddedWidth / width) * height);
|
||||
});
|
||||
if (loading || error || !dimensions) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{imageWidth && imageHeight && (
|
||||
<TouchableHighlight
|
||||
onPress={() => focusMeme(index)}
|
||||
style={memeMasonryItemStyles.view}>
|
||||
<Image
|
||||
source={{ uri: meme.uri }}
|
||||
style={[
|
||||
memeMasonryItemStyles.image,
|
||||
{ width: imageWidth, height: imageHeight },
|
||||
]}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
)}
|
||||
</>
|
||||
<TouchableHighlight
|
||||
onPress={() => focusMeme(index)}
|
||||
style={memeMasonryItemStyles.view}>
|
||||
<Image
|
||||
source={{ uri: meme.uri }}
|
||||
style={[
|
||||
memeMasonryItemStyles.image,
|
||||
{
|
||||
width: (width * 0.92) / masonryColumns - 5,
|
||||
height:
|
||||
((width * 0.92) / masonryColumns - 5) / dimensions.aspectRatio,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -14,11 +14,13 @@ const tagChipStyles = StyleSheet.create({
|
||||
const TagChip = ({
|
||||
tag,
|
||||
active = true,
|
||||
elevated = false,
|
||||
onPress,
|
||||
...props
|
||||
}: {
|
||||
tag: Tag;
|
||||
active?: boolean;
|
||||
elevated?: boolean;
|
||||
onPress?: () => void;
|
||||
} & Omit<ComponentProps<typeof Chip>, 'children'>) => {
|
||||
const theme = useTheme();
|
||||
@@ -45,6 +47,7 @@ const TagChip = ({
|
||||
);
|
||||
}}
|
||||
compact
|
||||
elevated={elevated}
|
||||
theme={chipTheme}
|
||||
mode={active ? 'flat' : 'outlined'}
|
||||
style={[tagChipStyles.chip, props.style]}
|
||||
|
@@ -3,10 +3,12 @@ import { StyleSheet, View } from 'react-native';
|
||||
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
||||
import { Chip, useTheme } from 'react-native-paper';
|
||||
import styles from '../../styles';
|
||||
import { useDimensions } from '../../contexts';
|
||||
import { getContrastColor } from '../../utilities';
|
||||
|
||||
const tagPreviewStyles = StyleSheet.create({
|
||||
view: {
|
||||
margin: '10%',
|
||||
},
|
||||
chip: {
|
||||
padding: 5,
|
||||
},
|
||||
@@ -17,7 +19,6 @@ const tagPreviewStyles = StyleSheet.create({
|
||||
|
||||
const TagPreview = ({ name, color }: { name: string; color: string }) => {
|
||||
const theme = useTheme();
|
||||
const { responsive } = useDimensions();
|
||||
|
||||
const chipTheme = useMemo(() => {
|
||||
return {
|
||||
@@ -32,14 +33,7 @@ const TagPreview = ({ name, color }: { name: string; color: string }) => {
|
||||
const contrastColor = getContrastColor(color);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.justifyCenter,
|
||||
styles.flexRow,
|
||||
{
|
||||
margin: responsive.verticalScale(50),
|
||||
},
|
||||
]}>
|
||||
<View style={[styles.justifyCenter, styles.flexRow, tagPreviewStyles.view]}>
|
||||
<Chip
|
||||
icon={() => {
|
||||
return <FontAwesome5 name="tag" size={14} color={contrastColor} />;
|
||||
|
@@ -1,97 +0,0 @@
|
||||
import React, {
|
||||
ReactNode,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Dimensions, ScaledSize } from 'react-native';
|
||||
|
||||
const guidelineBaseWidth = 350;
|
||||
const guidelineBaseHeight = 680;
|
||||
|
||||
enum ORIENTATION {
|
||||
PORTRAIT = 'portrait',
|
||||
LANDSCAPE = 'landscape',
|
||||
}
|
||||
|
||||
interface ScaleFunctions {
|
||||
horizontalScale: (size: number) => number;
|
||||
verticalScale: (size: number) => number;
|
||||
moderateHorizontalScale: (size: number, factor?: number) => number;
|
||||
moderateVerticalScale: (size: number, factor?: number) => number;
|
||||
}
|
||||
|
||||
interface DimensionsContext {
|
||||
orientation: ORIENTATION;
|
||||
dimensions: ScaledSize;
|
||||
responsive: ScaleFunctions;
|
||||
}
|
||||
|
||||
const createScaleFunctions = (dimensionsIn: ScaledSize) => {
|
||||
const horizontalScale = (size: number) =>
|
||||
(dimensionsIn.width / guidelineBaseWidth) * size;
|
||||
const verticalScale = (size: number) =>
|
||||
(dimensionsIn.height / guidelineBaseHeight) * size;
|
||||
const moderateHorizontalScale = (size: number, factor = 0.5) =>
|
||||
size + (horizontalScale(size) - size) * factor;
|
||||
const moderateVerticalScale = (size: number, factor = 0.5) =>
|
||||
size + (verticalScale(size) - size) * factor;
|
||||
|
||||
return {
|
||||
horizontalScale,
|
||||
verticalScale,
|
||||
moderateHorizontalScale,
|
||||
moderateVerticalScale,
|
||||
};
|
||||
};
|
||||
|
||||
const DimensionsContext = createContext<DimensionsContext | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const DimensionsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
|
||||
|
||||
const orientation = useMemo(() => {
|
||||
return dimensions.width > dimensions.height
|
||||
? ORIENTATION.LANDSCAPE
|
||||
: ORIENTATION.PORTRAIT;
|
||||
}, [dimensions]);
|
||||
|
||||
const responsiveScale = createScaleFunctions(dimensions);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = ({ window }: { window: ScaledSize }) => {
|
||||
setDimensions(window);
|
||||
};
|
||||
|
||||
const subscription = Dimensions.addEventListener('change', onChange);
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DimensionsContext.Provider
|
||||
value={{
|
||||
orientation,
|
||||
dimensions,
|
||||
responsive: responsiveScale,
|
||||
}}>
|
||||
{children}
|
||||
</DimensionsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useDimensions = (): DimensionsContext => {
|
||||
const context = useContext(DimensionsContext);
|
||||
if (!context) {
|
||||
throw new Error('useDimensions must be used within a DimensionsProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export { ORIENTATION, DimensionsProvider, useDimensions };
|
@@ -1 +0,0 @@
|
||||
export { ORIENTATION, DimensionsProvider, useDimensions } from './dimensions';
|
@@ -1,7 +1,6 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Appbar, Button, useTheme } from 'react-native-paper';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { useRealm } from '@realm/react';
|
||||
@@ -9,16 +8,17 @@ import { BSON } from 'realm';
|
||||
import { AndroidScoped, FileSystem } from 'react-native-file-access';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { extension } from 'react-native-mime-types';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import {
|
||||
DocumentPickerResponse,
|
||||
pickSingle,
|
||||
} from 'react-native-document-picker';
|
||||
import styles from '../styles';
|
||||
import { ROUTE, RootStackParamList } from '../types';
|
||||
import { Meme, Tag } from '../database';
|
||||
import { RootState } from '../state';
|
||||
import { allowedMimeTypes, getMemeType, validateMemeTitle } from '../utilities';
|
||||
import { MemeEditor } from '../components';
|
||||
import {
|
||||
DocumentPickerResponse,
|
||||
pickSingle,
|
||||
} from 'react-native-document-picker';
|
||||
|
||||
const addMemeStyles = StyleSheet.create({
|
||||
saveAndAddButton: {
|
||||
@@ -36,7 +36,7 @@ const AddMeme = ({
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.ADD_MEME>) => {
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const realm = useRealm();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const storageUri = useSelector(
|
||||
@@ -104,7 +104,7 @@ const AddMeme = ({
|
||||
</Appbar.Header>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
orientation === 'portrait'
|
||||
? styles.paddingVertical
|
||||
: styles.smallPaddingVertical,
|
||||
styles.paddingHorizontal,
|
||||
|
@@ -3,13 +3,13 @@ import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import { Appbar, Button, useTheme } from 'react-native-paper';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useRealm } from '@realm/react';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import styles from '../styles';
|
||||
import {
|
||||
generateRandomColor,
|
||||
validateColor,
|
||||
validateTagName,
|
||||
} from '../utilities';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import { Tag } from '../database';
|
||||
import { TagEditor } from '../components';
|
||||
|
||||
@@ -27,7 +27,7 @@ const addTagStyles = StyleSheet.create({
|
||||
const AddTag = () => {
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const realm = useRealm();
|
||||
|
||||
const [tagName, setTagName] = useState(validateTagName('newTag'));
|
||||
@@ -57,7 +57,7 @@ const AddTag = () => {
|
||||
</Appbar.Header>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
orientation === 'portrait'
|
||||
? styles.paddingVertical
|
||||
: styles.smallPaddingVertical,
|
||||
styles.paddingHorizontal,
|
||||
|
@@ -4,8 +4,8 @@ import { Appbar, Button, useTheme } from 'react-native-paper';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { useObject, useRealm } from '@realm/react';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import { BSON } from 'realm';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import styles from '../styles';
|
||||
import { RootStackParamList, ROUTE } from '../types';
|
||||
import { Tag, Meme } from '../database';
|
||||
@@ -17,7 +17,7 @@ const EditMeme = ({
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.EDIT_MEME>) => {
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const realm = useRealm();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -80,7 +80,7 @@ const EditMeme = ({
|
||||
</Appbar.Header>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
orientation === 'portrait'
|
||||
? styles.paddingVertical
|
||||
: styles.smallPaddingVertical,
|
||||
styles.paddingHorizontal,
|
||||
|
@@ -5,9 +5,9 @@ import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { BSON } from 'realm';
|
||||
import { useObject, useRealm } from '@realm/react';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import { TagEditor } from '../components';
|
||||
import styles from '../styles';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import { ROUTE, RootStackParamList } from '../types';
|
||||
import { Tag } from '../database';
|
||||
import { deleteTag, validateColor, validateTagName } from '../utilities';
|
||||
@@ -17,7 +17,7 @@ const EditTag = ({
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.EDIT_TAG>) => {
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const realm = useRealm();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -52,7 +52,7 @@ const EditTag = ({
|
||||
</Appbar.Header>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
orientation === 'portrait'
|
||||
? styles.paddingVertical
|
||||
: styles.smallPaddingVertical,
|
||||
styles.paddingHorizontal,
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { useQuery, useRealm } from '@realm/react';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { Appbar, Portal, Snackbar } from 'react-native-paper';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { RootStackParamList, ROUTE } from '../types';
|
||||
import { Meme } from '../database';
|
||||
import { useDimensions } from '../contexts';
|
||||
import { MemeViewItem } from '../components';
|
||||
import {
|
||||
copyMeme,
|
||||
@@ -20,22 +20,32 @@ import { NavigationProp, useNavigation } from '@react-navigation/native';
|
||||
import styles from '../styles';
|
||||
|
||||
const memeViewStyles = StyleSheet.create({
|
||||
// eslint-disable-next-line react-native/no-color-literals
|
||||
header: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
// eslint-disable-next-line react-native/no-color-literals
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 80,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
snackbar: {
|
||||
marginBottom: 90,
|
||||
marginBottom: 64,
|
||||
},
|
||||
});
|
||||
|
||||
const MemeView = ({
|
||||
route,
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.MEME_VIEW>) => {
|
||||
const { orientation, dimensions } = useDimensions();
|
||||
const { height, width } = useSafeAreaFrame();
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const realm = useRealm();
|
||||
|
||||
@@ -55,35 +65,30 @@ const MemeView = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
<Appbar.Header style={memeViewStyles.header}>
|
||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||
<Appbar.Content title={memes[index].title} />
|
||||
</Appbar.Header>
|
||||
<View>
|
||||
<FlashList
|
||||
ref={flashListRef}
|
||||
key={orientation}
|
||||
data={memes}
|
||||
initialScrollIndex={index}
|
||||
onScroll={event => {
|
||||
const newIndex = Math.round(
|
||||
event.nativeEvent.contentOffset.x /
|
||||
event.nativeEvent.layoutMeasurement.width,
|
||||
);
|
||||
if (newIndex !== index) setIndex(newIndex);
|
||||
}}
|
||||
estimatedItemSize={dimensions.width}
|
||||
pagingEnabled
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
estimatedListSize={{
|
||||
height: dimensions.height - 160,
|
||||
width: dimensions.width,
|
||||
}}
|
||||
renderItem={({ item: meme }) => <MemeViewItem meme={meme} />}
|
||||
/>
|
||||
</View>
|
||||
<Appbar style={[memeViewStyles.footer, styles.flexRowSpaceEvenly]}>
|
||||
<FlashList
|
||||
ref={flashListRef}
|
||||
key={height}
|
||||
data={memes}
|
||||
initialScrollIndex={index}
|
||||
onScroll={event => {
|
||||
const newIndex = Math.round(
|
||||
event.nativeEvent.contentOffset.x /
|
||||
event.nativeEvent.layoutMeasurement.width,
|
||||
);
|
||||
if (newIndex !== index) setIndex(newIndex);
|
||||
}}
|
||||
estimatedItemSize={width}
|
||||
estimatedListSize={{ height, width }}
|
||||
pagingEnabled
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
renderItem={({ item: meme }) => <MemeViewItem meme={meme} />}
|
||||
/>
|
||||
<Appbar style={[styles.flexRowSpaceEvenly, memeViewStyles.footer]}>
|
||||
<Appbar.Action
|
||||
icon={memes[index].isFavorite ? 'heart' : 'heart-outline'}
|
||||
onPress={() => favoriteMeme(realm, memes[index])}
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
View,
|
||||
useWindowDimensions,
|
||||
} from 'react-native';
|
||||
import { useQuery } from '@realm/react';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
@@ -20,11 +21,14 @@ import { ROUTE, SORT_DIRECTION, memesSortQuery } from '../types';
|
||||
import { RootState, setNavVisible } from '../state';
|
||||
import { Meme } from '../database';
|
||||
import { HideableHeader, MemesHeader, MemesList } from '../components';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
|
||||
const Memes = () => {
|
||||
const { colors } = useTheme();
|
||||
const { navigate } =
|
||||
useNavigation<NativeStackNavigationProp<ParamListBase>>();
|
||||
const { height } = useWindowDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const sort = useSelector((state: RootState) => state.memes.sort);
|
||||
const sortDirection = useSelector(
|
||||
(state: RootState) => state.memes.sortDirection,
|
||||
@@ -135,7 +139,10 @@ const Memes = () => {
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
onLayout={event => {
|
||||
setFlashListPadding(event.nativeEvent.layout.height);
|
||||
setFlashListPadding(
|
||||
event.nativeEvent.layout.height +
|
||||
height * (orientation === 'portrait' ? 0.02 : 0.04),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</HideableHeader>
|
||||
|
@@ -13,6 +13,7 @@ import {
|
||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import type {} from 'redux-thunk/extend-redux';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import styles from '../styles';
|
||||
import {
|
||||
RootState,
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
setNoMedia,
|
||||
setStorageUri,
|
||||
} from '../state';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
|
||||
const settingsStyles = StyleSheet.create({
|
||||
snackbar: {
|
||||
@@ -34,7 +34,7 @@ const settingsStyles = StyleSheet.create({
|
||||
|
||||
const Settings = () => {
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const noMedia = useSelector((state: RootState) => state.settings.noMedia);
|
||||
const masonryColumns = useSelector(
|
||||
(state: RootState) => state.settings.masonryColumns,
|
||||
@@ -60,85 +60,80 @@ const Settings = () => {
|
||||
<>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
orientation === 'portrait'
|
||||
? styles.paddingTop
|
||||
: styles.smallPaddingTop,
|
||||
styles.paddingHorizontal,
|
||||
{ backgroundColor: colors.background },
|
||||
]}>
|
||||
<View>
|
||||
<List.Section>
|
||||
<List.Subheader>Views</List.Subheader>
|
||||
<Text
|
||||
style={[
|
||||
settingsStyles.marginBottom,
|
||||
styles.smallPaddingHorizontal,
|
||||
]}>
|
||||
Masonry Columns
|
||||
</Text>
|
||||
<SegmentedButtons
|
||||
value={masonryColumns.toString()}
|
||||
<List.Section>
|
||||
<List.Subheader>Views</List.Subheader>
|
||||
<Text
|
||||
style={[
|
||||
settingsStyles.marginBottom,
|
||||
styles.smallPaddingHorizontal,
|
||||
]}>
|
||||
Masonry Columns
|
||||
</Text>
|
||||
<SegmentedButtons
|
||||
value={masonryColumns.toString()}
|
||||
onValueChange={value => {
|
||||
void dispatch(
|
||||
setMasonryColumns(Number.parseInt(value) as 1 | 2 | 3 | 4),
|
||||
);
|
||||
}}
|
||||
buttons={[
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '2', value: '2' },
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
]}
|
||||
style={settingsStyles.marginBottom}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
settingsStyles.marginBottom,
|
||||
styles.smallPaddingHorizontal,
|
||||
]}>
|
||||
Grid Columns
|
||||
</Text>
|
||||
<SegmentedButtons
|
||||
value={gridColumns.toString()}
|
||||
onValueChange={value => {
|
||||
void dispatch(
|
||||
setGridColumns(Number.parseInt(value) as 1 | 2 | 3 | 4),
|
||||
);
|
||||
}}
|
||||
buttons={[
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '2', value: '2' },
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
]}
|
||||
/>
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<List.Subheader>Media Storage</List.Subheader>
|
||||
<Button
|
||||
mode="elevated"
|
||||
style={settingsStyles.marginBottom}
|
||||
onPress={async () => {
|
||||
const { uri } = await openDocumentTree(true);
|
||||
void dispatch(setStorageUri(uri));
|
||||
}}>
|
||||
Change External Storage Path
|
||||
</Button>
|
||||
<View
|
||||
style={[styles.flexRowSpaceBetween, styles.smallPaddingHorizontal]}>
|
||||
<Text>Hide media from gallery</Text>
|
||||
<Switch
|
||||
value={noMedia}
|
||||
onValueChange={value => {
|
||||
void dispatch(
|
||||
setMasonryColumns(Number.parseInt(value) as 1 | 2 | 3 | 4),
|
||||
);
|
||||
void dispatch(setNoMedia(value));
|
||||
}}
|
||||
buttons={[
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '2', value: '2' },
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
]}
|
||||
style={settingsStyles.marginBottom}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
settingsStyles.marginBottom,
|
||||
styles.smallPaddingHorizontal,
|
||||
]}>
|
||||
Grid Columns
|
||||
</Text>
|
||||
<SegmentedButtons
|
||||
value={gridColumns.toString()}
|
||||
onValueChange={value => {
|
||||
void dispatch(
|
||||
setGridColumns(Number.parseInt(value) as 1 | 2 | 3 | 4),
|
||||
);
|
||||
}}
|
||||
buttons={[
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '2', value: '2' },
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
]}
|
||||
/>
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<List.Subheader>Media Storage</List.Subheader>
|
||||
<Button
|
||||
mode="elevated"
|
||||
style={settingsStyles.marginBottom}
|
||||
onPress={async () => {
|
||||
const { uri } = await openDocumentTree(true);
|
||||
void dispatch(setStorageUri(uri));
|
||||
}}>
|
||||
Change External Storage Path
|
||||
</Button>
|
||||
<View
|
||||
style={[
|
||||
styles.flexRowSpaceBetween,
|
||||
styles.smallPaddingHorizontal,
|
||||
]}>
|
||||
<Text>Hide media from gallery</Text>
|
||||
<Switch
|
||||
value={noMedia}
|
||||
onValueChange={value => {
|
||||
void dispatch(setNoMedia(value));
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</List.Section>
|
||||
</View>
|
||||
</View>
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<List.Subheader>Database</List.Subheader>
|
||||
<Button
|
||||
|
@@ -11,12 +11,13 @@ import { useQuery } from '@realm/react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||
import { HideableHeader, TagRow, TagsHeader } from '../components';
|
||||
import { Tag } from '../database';
|
||||
import styles from '../styles';
|
||||
import { RootState, setNavVisible } from '../state';
|
||||
import { SORT_DIRECTION, tagSortQuery } from '../types';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
|
||||
const tagsStyles = StyleSheet.create({
|
||||
flashList: {
|
||||
@@ -29,7 +30,8 @@ const tagsStyles = StyleSheet.create({
|
||||
|
||||
const Tags = () => {
|
||||
const { colors } = useTheme();
|
||||
const { dimensions, orientation } = useDimensions();
|
||||
const { height, width } = useSafeAreaFrame();
|
||||
const orientation = useDeviceOrientation();
|
||||
const sort = useSelector((state: RootState) => state.tags.sort);
|
||||
const sortDirection = useSelector(
|
||||
(state: RootState) => state.tags.sortDirection,
|
||||
@@ -122,16 +124,15 @@ const Tags = () => {
|
||||
data={tags}
|
||||
estimatedItemSize={50}
|
||||
estimatedListSize={{
|
||||
height: dimensions.height,
|
||||
width: dimensions.width * 0.92,
|
||||
height,
|
||||
width: width * 0.92,
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={({ item: tag }) => <TagRow tag={tag} />}
|
||||
contentContainerStyle={{
|
||||
paddingTop:
|
||||
flashListPadding +
|
||||
dimensions.height *
|
||||
(orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04),
|
||||
height * (orientation === 'portrait' ? 0.02 : 0.04),
|
||||
...tagsStyles.flashList,
|
||||
}}
|
||||
ItemSeparatorComponent={() => <Divider />}
|
||||
@@ -143,6 +144,7 @@ const Tags = () => {
|
||||
</HelperText>
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
fadingEdgeLength={100}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@@ -3,10 +3,10 @@ import { StyleSheet, View } from 'react-native';
|
||||
import { Button, Text, useTheme } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||
import { useDeviceOrientation } from '@react-native-community/hooks';
|
||||
import styles from '../styles';
|
||||
import { noOp } from '../utilities';
|
||||
import { setStorageUri } from '../state';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
|
||||
const welcomeStyles = StyleSheet.create({
|
||||
text: {
|
||||
@@ -19,7 +19,7 @@ const welcomeStyles = StyleSheet.create({
|
||||
|
||||
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const orientation = useDeviceOrientation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const selectStorageLocation = async () => {
|
||||
@@ -32,11 +32,9 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
? styles.paddingTop
|
||||
: styles.smallPaddingTop,
|
||||
orientation === 'portrait' ? styles.paddingTop : styles.smallPaddingTop,
|
||||
styles.paddingHorizontal,
|
||||
styles.centered,
|
||||
styles.center,
|
||||
styles.flex,
|
||||
styles.fullSize,
|
||||
{ backgroundColor: colors.background },
|
||||
|
@@ -25,7 +25,7 @@ const styles = StyleSheet.create({
|
||||
paddingTop: {
|
||||
paddingTop: '4%',
|
||||
},
|
||||
centered: {
|
||||
center: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
Reference in New Issue
Block a user