Fix layout bugs

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-22 12:28:02 +03:00
parent a4726eb89a
commit fa0a89f324
25 changed files with 561 additions and 400 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Appbar, Button, useTheme } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { useDimensions } from '../contexts';
import { ORIENTATION, useDimensions } from '../contexts';
import { ScrollView, View } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useRealm } from '@realm/react';
@@ -11,7 +11,7 @@ import { useSelector } from 'react-redux';
import { extension } from 'react-native-mime-types';
import styles from '../styles';
import { ROUTE, RootStackParamList } from '../types';
import { MEME_TYPE, Meme, Tag } from '../database';
import { Meme, Tag } from '../database';
import { RootState } from '../state';
import {
getMemeType,
@@ -32,11 +32,9 @@ const AddMeme = ({
(state: RootState) => state.settings.storageUri,
)!;
const { uri } = route.params;
const memeType =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uri.length > 1 ? MEME_TYPE.ALBUM : getMemeType(uri[0].type!);
const uri = route.params.uri[0].uri;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const memeType = getMemeType(route.params.uri[0].type!);
const [memeTitle, setMemeTitle] = useState(validateMemeTitle('New Meme'));
const [memeDescription, setMemeDescription] = useState(
@@ -55,7 +53,7 @@ const AddMeme = ({
const hash: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const fileExtension = extension(uri[0].type!);
const fileExtension = extension(memeType!);
if (!fileExtension) navigation.goBack();
savedUri.push(
@@ -65,7 +63,7 @@ const AddMeme = ({
),
);
await FileSystem.cp(uri[0].uri, savedUri[0]);
await FileSystem.cp(uri, savedUri[0]);
const { size } = await FileSystem.stat(savedUri[0]);
hash.push(await FileSystem.hash(savedUri[0], 'MD5'));
@@ -107,8 +105,9 @@ const AddMeme = ({
</Appbar.Header>
<ScrollView
contentContainerStyle={[
orientation == 'portrait' && styles.paddingVertical,
orientation == 'landscape' && styles.smallPaddingVertical,
orientation === ORIENTATION.PORTRAIT
? styles.paddingVertical
: styles.smallPaddingVertical,
styles.paddingHorizontal,
styles.flexGrow,
styles.flexColumnSpaceBetween,
@@ -116,7 +115,7 @@ const AddMeme = ({
]}>
<View style={[styles.flex, styles.justifyStart]}>
<MemeEditor
imageUri={uri.map(uriIn => uriIn.uri)}
imageUri={uri}
memeTitle={memeTitle}
setMemeTitle={setMemeTitle}
memeDescription={memeDescription}

View File

@@ -9,7 +9,7 @@ import {
validateColor,
validateTagName,
} from '../utilities';
import { useDimensions } from '../contexts';
import { ORIENTATION, useDimensions } from '../contexts';
import { Tag } from '../database';
import { TagEditor } from '../components';
@@ -43,8 +43,9 @@ const AddTag = () => {
</Appbar.Header>
<ScrollView
contentContainerStyle={[
orientation == 'portrait' && styles.paddingVertical,
orientation == 'landscape' && styles.smallPaddingVertical,
orientation === ORIENTATION.PORTRAIT
? styles.paddingVertical
: styles.smallPaddingVertical,
styles.paddingHorizontal,
styles.flexGrow,
styles.flexColumnSpaceBetween,

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { ScrollView } from 'react-native';
import { Appbar, useTheme } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { useDimensions } from '../contexts';
import { ORIENTATION, useDimensions } from '../contexts';
import styles from '../styles';
const EditMeme = () => {
@@ -18,8 +18,9 @@ const EditMeme = () => {
</Appbar.Header>
<ScrollView
contentContainerStyle={[
orientation == 'portrait' && styles.paddingVertical,
orientation == 'landscape' && styles.smallPaddingVertical,
orientation === ORIENTATION.PORTRAIT
? styles.paddingVertical
: styles.smallPaddingVertical,
styles.paddingHorizontal,
styles.flexGrow,
styles.flexColumnSpaceBetween,

View File

@@ -7,7 +7,7 @@ import { BSON } from 'realm';
import { useRealm } from '@realm/react';
import { TagEditor } from '../components';
import styles from '../styles';
import { useDimensions } from '../contexts';
import { ORIENTATION, useDimensions } from '../contexts';
import { ROUTE, RootStackParamList } from '../types';
import { Tag } from '../database';
import { validateColor, validateTagName } from '../utilities';
@@ -63,8 +63,9 @@ const EditTag = ({
</Appbar.Header>
<ScrollView
contentContainerStyle={[
orientation == 'portrait' && styles.paddingVertical,
orientation == 'landscape' && styles.smallPaddingVertical,
orientation === ORIENTATION.PORTRAIT
? styles.paddingVertical
: styles.smallPaddingVertical,
styles.paddingHorizontal,
styles.flexGrow,
styles.flexColumnSpaceBetween,

View File

@@ -1,35 +1,24 @@
import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import React, { useCallback, useRef, useState } from 'react';
import {
BackHandler,
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
View,
} from 'react-native';
import { useQuery } from '@realm/react';
import {
Button,
Menu,
IconButton,
Divider,
useTheme,
Searchbar,
HelperText,
} from 'react-native-paper';
import { useTheme, HelperText } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { FlashList } from '@shopify/flash-list';
import { useFocusEffect } from '@react-navigation/native';
import styles from '../styles';
import { MEME_SORT, SORT_DIRECTION, memesSortQuery } from '../types';
import { getSortIcon, getViewIcon } from '../utilities';
import {
RootState,
cycleMemesView,
toggleMemesSortDirection,
setMemesSortDirection,
toggleMemesFavoritesOnly,
setMemesSort,
setMemesFilter,
} from '../state';
import { MEME_TYPE, Meme, memeTypePlural } from '../database';
import { SORT_DIRECTION, memesSortQuery } from '../types';
import { RootState, setNavVisible } from '../state';
import { Meme, Tag } from '../database';
import { useDimensions } from '../contexts';
import { HideableHeader, MemesHeader } from '../components';
const memesStyles = StyleSheet.create({
headerButtonView: {
height: 50,
},
helperText: {
marginVertical: 10,
},
@@ -47,30 +36,12 @@ const Memes = () => {
(state: RootState) => state.memes.favoritesOnly,
);
const filter = useSelector((state: RootState) => state.memes.filter);
const navVisisble = useSelector(
(state: RootState) => state.navigation.navVisible,
);
const dispatch = useDispatch();
const [sortMenuVisible, setSortMenuVisible] = useState(false);
const [filterMenuVisible, setFilterMenuVisible] = useState(false);
const handleSortModeChange = (newSort: MEME_SORT) => {
if (newSort === sort) {
dispatch(toggleMemesSortDirection());
} else {
dispatch(setMemesSort(newSort));
if (newSort === MEME_SORT.TITLE) {
dispatch(setMemesSortDirection(SORT_DIRECTION.ASCENDING));
} else {
dispatch(setMemesSortDirection(SORT_DIRECTION.DESCENDING));
}
}
setSortMenuVisible(false);
};
const handleFilterChange = (newFilter: MEME_TYPE | undefined) => {
dispatch(setMemesFilter(newFilter));
setFilterMenuVisible(false);
};
const [flashListPadding, setFlashListPadding] = useState(0);
const [search, setSearch] = useState('');
const memes = useQuery<Meme>(
@@ -94,110 +65,56 @@ const Memes = () => {
[sort, sortDirection, favoritesOnly, filter, search],
);
const [scrollOffset, setScrollOffset] = useState(0);
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const currentOffset = event.nativeEvent.contentOffset.y;
if (currentOffset <= 150) {
dispatch(setNavVisible(true));
} else {
const diff = currentOffset - scrollOffset;
if (Math.abs(diff) > 50) dispatch(setNavVisible(diff < 0));
}
setScrollOffset(currentOffset);
};
const flashListRef = useRef<FlashList<Tag>>(null);
useFocusEffect(
useCallback(() => {
const handleBackPress = () => {
if (scrollOffset > 0) {
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
return true;
}
return false;
};
BackHandler.addEventListener('hardwareBackPress', handleBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
}, [flashListRef, scrollOffset]),
);
return (
<View
style={[
orientation == 'portrait' && styles.paddingTop,
orientation == 'landscape' && styles.smallPaddingTop,
styles.paddingHorizontal,
styles.fullSize,
{ backgroundColor: colors.background },
]}>
<Searchbar
placeholder="Search Memes"
value={search}
onChangeText={setSearch}
/>
<View
style={[
styles.flexRowSpaceBetween,
styles.alignCenter,
memesStyles.headerButtonView,
]}>
<View style={[styles.flexRow, styles.alignCenter]}>
<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(MEME_SORT).map(key => {
return (
<Menu.Item
key={key}
onPress={() =>
handleSortModeChange(
MEME_SORT[key as keyof typeof MEME_SORT],
)
}
title={MEME_SORT[key as keyof typeof MEME_SORT]}
/>
);
})}
</Menu>
</View>
<View style={[styles.flexRow, styles.alignCenter]}>
<IconButton
icon={getViewIcon(view)}
iconColor={colors.primary}
size={16}
animated
onPress={() => dispatch(cycleMemesView())}
/>
<IconButton
icon={favoritesOnly ? 'heart' : 'heart-outline'}
iconColor={colors.primary}
size={16}
animated
onPress={() => dispatch(toggleMemesFavoritesOnly())}
/>
<Menu
visible={filterMenuVisible}
onDismiss={() => setFilterMenuVisible(false)}
anchor={
<IconButton
onPress={() => setFilterMenuVisible(true)}
icon={filter ? 'filter' : 'filter-outline'}
iconColor={colors.primary}
size={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 />
{/* TODO: Meme Views */}
{memes.length === 0 && (
<HelperText
type={'info'}
style={[memesStyles.helperText, styles.centerText]}>
No memes found
</HelperText>
)}
<HideableHeader visible={navVisisble}>
<MemesHeader
search={search}
setSearch={setSearch}
onLayout={event => {
setFlashListPadding(event.nativeEvent.layout.height);
}}
/>
</HideableHeader>
</View>
);
};

View File

@@ -13,12 +13,8 @@ import { openDocumentTree } from 'react-native-scoped-storage';
import { useDispatch, useSelector } from 'react-redux';
import type {} from 'redux-thunk/extend-redux';
import styles from '../styles';
import {
RootState,
setNoMedia,
setStorageUri,
} from '../state';
import { useDimensions } from '../contexts';
import { RootState, setNoMedia, setStorageUri } from '../state';
import { ORIENTATION, useDimensions } from '../contexts';
const settingsScreenStyles = StyleSheet.create({
snackbar: {
@@ -48,8 +44,9 @@ const SettingsScreen = () => {
<>
<ScrollView
contentContainerStyle={[
orientation == 'portrait' && styles.paddingTop,
orientation == 'landscape' && styles.smallPaddingTop,
orientation === ORIENTATION.PORTRAIT
? styles.paddingTop
: styles.smallPaddingTop,
styles.paddingHorizontal,
{ backgroundColor: colors.background },
]}>

View File

@@ -6,70 +6,30 @@ import {
NativeScrollEvent,
BackHandler,
} from 'react-native';
import {
Button,
Divider,
HelperText,
Menu,
Searchbar,
Text,
TouchableRipple,
useTheme,
} from 'react-native-paper';
import { Divider, HelperText, useTheme } from 'react-native-paper';
import { useQuery } from '@realm/react';
import { useDispatch, useSelector } from 'react-redux';
import { FlashList } from '@shopify/flash-list';
import {
NavigationProp,
useFocusEffect,
useNavigation,
} from '@react-navigation/native';
import { HideableHeader, TagChip } from '../components';
import { useFocusEffect } from '@react-navigation/native';
import { HideableHeader, TagRow, TagsHeader } from '../components';
import { Tag } from '../database';
import styles from '../styles';
import {
RootState,
setNavVisible,
setTagsSort,
setTagsSortDirection,
toggleTagsSortDirection,
} from '../state';
import {
ROUTE,
RootStackParamList,
SORT_DIRECTION,
TAG_SORT,
tagSortQuery,
} from '../types';
import { getSortIcon } from '../utilities';
import { RootState, setNavVisible } from '../state';
import { SORT_DIRECTION, tagSortQuery } from '../types';
import { ORIENTATION, useDimensions } from '../contexts';
const tagsStyles = StyleSheet.create({
headerButtonView: {
height: 50,
},
tagRow: {
justifyContent: 'space-between',
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
paddingHorizontal: 15,
},
tagChip: {
flexShrink: 1,
maxWidth: '80%',
},
helperText: {
marginVertical: 10,
},
flashList: {
paddingTop: 122,
paddingBottom: 25,
paddingBottom: 100,
},
});
const Tags = () => {
const { colors } = useTheme();
const { navigate } = useNavigation<NavigationProp<RootStackParamList>>();
const { dimensions, orientation } = useDimensions();
const sort = useSelector((state: RootState) => state.tags.sort);
const sortDirection = useSelector(
(state: RootState) => state.tags.sortDirection,
@@ -79,22 +39,7 @@ const Tags = () => {
);
const dispatch = useDispatch();
const [sortMenuVisible, setSortMenuVisible] = useState(false);
const handleSortModeChange = (newSort: TAG_SORT) => {
if (newSort === sort) {
dispatch(toggleTagsSortDirection());
} else {
dispatch(setTagsSort(newSort));
if (newSort === TAG_SORT.NAME) {
dispatch(setTagsSortDirection(SORT_DIRECTION.ASCENDING));
} else {
dispatch(setTagsSortDirection(SORT_DIRECTION.DESCENDING));
}
}
setSortMenuVisible(false);
};
const [flashListPadding, setFlashListPadding] = useState(0);
const [search, setSearch] = useState('');
const tags = useQuery<Tag>(
@@ -158,73 +103,39 @@ const Tags = () => {
{ backgroundColor: colors.background },
]}>
<HideableHeader visible={navVisisble}>
<Searchbar
placeholder="Search Tags"
value={search}
onChangeText={(value: string) => {
setSearch(value);
<TagsHeader
search={search}
setSearch={setSearch}
onLayout={event => {
setFlashListPadding(event.nativeEvent.layout.height);
}}
/>
<View
style={[
styles.flexRow,
styles.alignCenter,
tagsStyles.headerButtonView,
]}>
<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(TAG_SORT).map(key => {
return (
<Menu.Item
key={key}
onPress={() =>
handleSortModeChange(TAG_SORT[key as keyof typeof TAG_SORT])
}
title={TAG_SORT[key as keyof typeof TAG_SORT]}
/>
);
})}
</Menu>
</View>
<Divider />
</HideableHeader>
<FlashList
ref={flashListRef}
data={tags}
estimatedItemSize={52}
showsVerticalScrollIndicator={false}
renderItem={({ item: tag }) => (
<TouchableRipple
onPress={() =>
navigate(ROUTE.EDIT_TAG, { id: tag.id.toHexString() })
}>
<View style={tagsStyles.tagRow}>
<TagChip tag={tag} style={tagsStyles.tagChip} />
<Text>{tag.memesLength}</Text>
</View>
</TouchableRipple>
)}
contentContainerStyle={tagsStyles.flashList}
ItemSeparatorComponent={() => <Divider />}
ListEmptyComponent={() => (
<HelperText
type={'info'}
style={[tagsStyles.helperText, styles.centerText]}>
No tags found
</HelperText>
)}
onScroll={handleScroll}
/>
{flashListPadding > 0 && (
<FlashList
ref={flashListRef}
data={tags}
estimatedItemSize={52}
showsVerticalScrollIndicator={false}
renderItem={({ item: tag }) => <TagRow tag={tag} />}
contentContainerStyle={{
paddingTop:
flashListPadding +
dimensions.height *
(orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04),
...tagsStyles.flashList,
}}
ItemSeparatorComponent={() => <Divider />}
ListEmptyComponent={() => (
<HelperText
type={'info'}
style={[tagsStyles.helperText, styles.centerText]}>
No tags found
</HelperText>
)}
onScroll={handleScroll}
/>
)}
</View>
);
};

View File

@@ -1,16 +1,25 @@
import React from 'react';
import { View } from 'react-native';
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 styles from '../styles';
import { noOp } from '../utilities';
import { setStorageUri } from '../state';
import { useDimensions } from '../contexts';
import { ORIENTATION, useDimensions } from '../contexts';
const welcomeStyles = StyleSheet.create({
text: {
marginBottom: 30,
},
button: {
marginBottom: 100,
},
});
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
const { colors } = useTheme();
const { orientation, responsive } = useDimensions();
const { orientation } = useDimensions();
const dispatch = useDispatch();
const selectStorageLocation = async () => {
@@ -23,8 +32,9 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
return (
<View
style={[
orientation == 'portrait' && styles.paddingTop,
orientation == 'landscape' && styles.smallPaddingTop,
orientation === ORIENTATION.PORTRAIT
? styles.paddingTop
: styles.smallPaddingTop,
styles.paddingHorizontal,
styles.centered,
styles.flex,
@@ -33,20 +43,13 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
]}>
<Text
variant="displayMedium"
style={[
{
marginBottom: responsive.verticalScale(30),
},
styles.centerText,
]}>
style={[welcomeStyles.text, styles.centerText]}>
Welcome to Terminally Online!
</Text>
<Button
mode="contained"
onPress={selectStorageLocation}
style={{
marginBottom: responsive.verticalScale(100),
}}>
style={welcomeStyles.button}>
Select Storage Location
</Button>
</View>