Refactor dimension handling
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
51
src/app.tsx
51
src/app.tsx
@@ -3,17 +3,18 @@ import { AppState, StatusBar, useColorScheme } from 'react-native';
|
|||||||
import { PaperProvider } from 'react-native-paper';
|
import { PaperProvider } from 'react-native-paper';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import { RealmProvider } from '@realm/react';
|
import { RealmProvider } from '@realm/react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import type {} from 'redux-thunk/extend-redux';
|
import type {} from 'redux-thunk/extend-redux';
|
||||||
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import { lightTheme, darkTheme } from './theme';
|
import { lightTheme, darkTheme } from './theme';
|
||||||
import { Meme, Tag } from './database';
|
import { Meme, Tag } from './database';
|
||||||
import NavigationContainer from './navigation';
|
import NavigationContainer from './navigation';
|
||||||
import { store, persistor, validateSettings } from './state';
|
import { store, persistor, validateSettings } from './state';
|
||||||
import { LoadingView } from './components';
|
import { LoadingView } from './components';
|
||||||
import { Welcome } from './screens';
|
import { Welcome } from './screens';
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { DimensionsProvider } from './contexts';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [showWelcome, setShowWelcome] = useState(false);
|
const [showWelcome, setShowWelcome] = useState(false);
|
||||||
@@ -43,28 +44,30 @@ const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PaperProvider theme={theme}>
|
<PaperProvider theme={theme}>
|
||||||
<Provider store={store}>
|
<DimensionsProvider>
|
||||||
<PersistGate
|
<ReduxProvider store={store}>
|
||||||
loading={<LoadingView />}
|
<PersistGate
|
||||||
persistor={persistor}
|
loading={<LoadingView />}
|
||||||
onBeforeLift={onBeforeLift}>
|
persistor={persistor}
|
||||||
<RealmProvider schema={[Meme, Tag]}>
|
onBeforeLift={onBeforeLift}>
|
||||||
<GestureHandlerRootView style={styles.flex}>
|
<RealmProvider schema={[Meme, Tag]}>
|
||||||
<SafeAreaProvider>
|
<GestureHandlerRootView style={styles.flex}>
|
||||||
<StatusBar
|
<SafeAreaProvider>
|
||||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
<StatusBar
|
||||||
backgroundColor={theme.colors.background}
|
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||||
/>
|
backgroundColor={theme.colors.background}
|
||||||
{showWelcome ? (
|
/>
|
||||||
<Welcome onWelcomeComplete={() => setShowWelcome(false)} />
|
{showWelcome ? (
|
||||||
) : (
|
<Welcome onWelcomeComplete={() => setShowWelcome(false)} />
|
||||||
<NavigationContainer />
|
) : (
|
||||||
)}
|
<NavigationContainer />
|
||||||
</SafeAreaProvider>
|
)}
|
||||||
</GestureHandlerRootView>
|
</SafeAreaProvider>
|
||||||
</RealmProvider>
|
</GestureHandlerRootView>
|
||||||
</PersistGate>
|
</RealmProvider>
|
||||||
</Provider>
|
</PersistGate>
|
||||||
|
</ReduxProvider>
|
||||||
|
</DimensionsProvider>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,24 +1,22 @@
|
|||||||
import * as React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { StyleSheet, Keyboard } from 'react-native';
|
import { StyleSheet, Keyboard } from 'react-native';
|
||||||
import { FAB, Portal } from 'react-native-paper';
|
import { FAB } from 'react-native-paper';
|
||||||
import { horizontalScale, verticalScale } from '../styles';
|
|
||||||
import { ParamListBase, useNavigation } from '@react-navigation/native';
|
import { ParamListBase, useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { useEffect, useState } from 'react';
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
fab: {
|
fab: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: horizontalScale(10),
|
|
||||||
bottom: verticalScale(75),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
|
const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
|
||||||
const [state, setState] = useState(false);
|
|
||||||
const { navigate } =
|
const { navigate } =
|
||||||
useNavigation<NativeStackNavigationProp<ParamListBase>>();
|
useNavigation<NativeStackNavigationProp<ParamListBase>>();
|
||||||
|
const dimensions = useDimensions();
|
||||||
|
|
||||||
|
const [state, setState] = useState(false);
|
||||||
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const keyboardDidShowListener = Keyboard.addListener(
|
const keyboardDidShowListener = Keyboard.addListener(
|
||||||
@@ -37,35 +35,39 @@ const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<FAB.Group
|
||||||
<FAB.Group
|
open={state}
|
||||||
open={state}
|
visible={visible && !keyboardOpen}
|
||||||
visible={visible && !keyboardOpen}
|
icon={state ? 'image' : 'plus'}
|
||||||
icon={state ? 'image' : 'plus'}
|
actions={[
|
||||||
actions={[
|
{
|
||||||
{
|
icon: 'tag',
|
||||||
icon: 'tag',
|
label: 'Tag',
|
||||||
label: 'Tag',
|
onPress: () => navigate('Add Tag'),
|
||||||
onPress: () => navigate('Add Tag'),
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: 'note-text',
|
||||||
icon: 'note-text',
|
label: 'Text',
|
||||||
label: 'Text',
|
onPress: () => navigate('Add Meme'),
|
||||||
onPress: () => navigate('Add Meme'),
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: 'image-album',
|
||||||
icon: 'image-album',
|
label: 'Album',
|
||||||
label: 'Album',
|
onPress: () => navigate('Add Meme'),
|
||||||
onPress: () => navigate('Add Meme'),
|
},
|
||||||
},
|
]}
|
||||||
]}
|
onStateChange={({ open }) => setState(open)}
|
||||||
onStateChange={({ open }) => setState(open)}
|
onPress={() => {
|
||||||
onPress={() => {
|
if (state) navigate('Add Meme');
|
||||||
if (state) navigate('Add Meme');
|
}}
|
||||||
}}
|
style={[
|
||||||
style={styles.fab}
|
styles.fab,
|
||||||
/>
|
{
|
||||||
</Portal>
|
paddingRight: dimensions.responsive.horizontalScale(10),
|
||||||
|
paddingBottom: dimensions.responsive.verticalScale(75),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
export { default as FloatingActionButton } from './floatingActionButton';
|
export { default as FloatingActionButton } from './floatingActionButton';
|
||||||
export { default as LoadingView } from './loadingView';
|
export { default as LoadingView } from './loadingView';
|
||||||
export { default as PaddedView } from './paddedView';
|
export { default as RootScrollView } from './rootScrollView';
|
||||||
|
export { default as RootView } from './rootView';
|
||||||
|
export { default as TagPreview } from './tagPreview';
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator } from 'react-native';
|
import { ActivityIndicator } from 'react-native';
|
||||||
import { useTheme } from 'react-native-paper';
|
import { useTheme } from 'react-native-paper';
|
||||||
import PaddedView from './paddedView';
|
import { RootView } from '.';
|
||||||
|
|
||||||
const LoadingView = () => {
|
const LoadingView = () => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaddedView centered>
|
<RootView centered>
|
||||||
<ActivityIndicator size="large" color={colors.primary} />
|
<ActivityIndicator size="large" color={colors.primary} />
|
||||||
</PaddedView>
|
</RootView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
37
src/components/rootScrollView.tsx
Normal file
37
src/components/rootScrollView.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import {
|
||||||
|
StyleProp,
|
||||||
|
ScrollView,
|
||||||
|
ViewStyle,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useTheme } from 'react-native-paper';
|
||||||
|
import styles from '../styles';
|
||||||
|
|
||||||
|
const RootScrollView = ({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
centered,
|
||||||
|
padded,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
|
centered?: boolean;
|
||||||
|
padded?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={[
|
||||||
|
padded && styles.padding,
|
||||||
|
centered && [styles.centered, styles.flex],
|
||||||
|
{ backgroundColor: colors.background },
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
nestedScrollEnabled>
|
||||||
|
{children}
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RootScrollView;
|
@@ -3,22 +3,24 @@ import { StyleProp, View, ViewStyle } from 'react-native';
|
|||||||
import { useTheme } from 'react-native-paper';
|
import { useTheme } from 'react-native-paper';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
|
|
||||||
const PaddedView = ({
|
const RootView = ({
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
centered,
|
centered,
|
||||||
|
padded,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
|
padded?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.padding,
|
padded && styles.padding,
|
||||||
centered && styles.centered,
|
centered && [styles.centered, styles.flex],
|
||||||
{ backgroundColor: colors.background },
|
{ backgroundColor: colors.background },
|
||||||
style,
|
style,
|
||||||
]}>
|
]}>
|
||||||
@@ -27,4 +29,4 @@ const PaddedView = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PaddedView;
|
export default RootView;
|
48
src/components/tagPreview.tsx
Normal file
48
src/components/tagPreview.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import { Chip } from 'react-native-paper';
|
||||||
|
import styles from '../styles';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
import { getContrastColor } from '../utilities';
|
||||||
|
|
||||||
|
const TagPreview = (properties: { name: string; color: string }) => {
|
||||||
|
const dimensions = useDimensions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.centeredHorizontal,
|
||||||
|
styles.flexRow,
|
||||||
|
{
|
||||||
|
margin: dimensions.responsive.verticalScale(50),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Chip
|
||||||
|
icon={() => {
|
||||||
|
return (
|
||||||
|
<FontAwesome5
|
||||||
|
name="tag"
|
||||||
|
size={dimensions.static.horizontalScale(12)}
|
||||||
|
color={getContrastColor(properties.color)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
elevated
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
backgroundColor: properties.color,
|
||||||
|
padding: dimensions.static.verticalScale(5),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
textStyle={[
|
||||||
|
{ fontSize: dimensions.static.horizontalScale(15) },
|
||||||
|
{ color: getContrastColor(properties.color) },
|
||||||
|
]}>
|
||||||
|
{'#' + properties.name}
|
||||||
|
</Chip>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagPreview;
|
90
src/contexts/dimensions.tsx
Normal file
90
src/contexts/dimensions.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React, {
|
||||||
|
ReactNode,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { Dimensions, ScaledSize } from 'react-native';
|
||||||
|
|
||||||
|
const guidelineBaseWidth = 350;
|
||||||
|
const guidelineBaseHeight = 680;
|
||||||
|
|
||||||
|
interface ScaleFunctions {
|
||||||
|
horizontalScale: (size: number) => number;
|
||||||
|
verticalScale: (size: number) => number;
|
||||||
|
moderateScale: (size: number, factor?: number) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DimensionsContext {
|
||||||
|
orientation: 'portrait' | 'landscape';
|
||||||
|
responsive: ScaleFunctions;
|
||||||
|
static: ScaleFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScaleFunctions = (dimensionsIn: ScaledSize) => {
|
||||||
|
const horizontalScale = (size: number) =>
|
||||||
|
(dimensionsIn.width / guidelineBaseWidth) * size;
|
||||||
|
const verticalScale = (size: number) =>
|
||||||
|
(dimensionsIn.height / guidelineBaseHeight) * size;
|
||||||
|
const moderateScale = (size: number, factor = 0.5) =>
|
||||||
|
size + (horizontalScale(size) - size) * factor;
|
||||||
|
|
||||||
|
return { horizontalScale, verticalScale, moderateScale };
|
||||||
|
};
|
||||||
|
|
||||||
|
const DimensionsContext = createContext<DimensionsContext | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const DimensionsProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
|
||||||
|
const orientation =
|
||||||
|
dimensions.width > dimensions.height ? 'landscape' : 'portrait';
|
||||||
|
|
||||||
|
const [initialDimensions, setInitialDimensions] = useState(dimensions);
|
||||||
|
const [initialOrientation] = useState(orientation);
|
||||||
|
|
||||||
|
if (initialOrientation === 'landscape') {
|
||||||
|
setInitialDimensions({
|
||||||
|
width: initialDimensions.height,
|
||||||
|
height: initialDimensions.width,
|
||||||
|
} as ScaledSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responsiveScale = createScaleFunctions(dimensions);
|
||||||
|
const staticScale = createScaleFunctions(initialDimensions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onChange = ({ window }: { window: ScaledSize }) => {
|
||||||
|
setDimensions(window);
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = Dimensions.addEventListener('change', onChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription.remove();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DimensionsContext.Provider
|
||||||
|
value={{
|
||||||
|
orientation,
|
||||||
|
responsive: responsiveScale,
|
||||||
|
static: staticScale,
|
||||||
|
}}>
|
||||||
|
{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 { DimensionsProvider, useDimensions };
|
1
src/contexts/index.ts
Normal file
1
src/contexts/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { DimensionsProvider, useDimensions } from './dimensions';
|
@@ -6,91 +6,87 @@ import {
|
|||||||
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { BottomNavigation, Portal, useTheme } from 'react-native-paper';
|
import { BottomNavigation, useTheme } from 'react-native-paper';
|
||||||
import { Home, Tags, Settings, AddMeme, AddTag } from './screens';
|
import { Home, Tags, Settings, AddMeme, AddTag } from './screens';
|
||||||
import { horizontalScale } from './styles';
|
|
||||||
import { FloatingActionButton } from './components';
|
|
||||||
import { darkNavigationTheme, lightNavigationTheme } from './theme';
|
import { darkNavigationTheme, lightNavigationTheme } from './theme';
|
||||||
|
import { useDimensions } from './contexts';
|
||||||
|
import { FloatingActionButton } from './components';
|
||||||
|
|
||||||
const TabNavigator = () => {
|
const TabNavigator = () => {
|
||||||
|
const dimensions = useDimensions();
|
||||||
|
|
||||||
const TabNavigatorBase = createBottomTabNavigator();
|
const TabNavigatorBase = createBottomTabNavigator();
|
||||||
const [fabVisible, setFabVisible] = React.useState(true);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Portal.Host>
|
<TabNavigatorBase.Navigator
|
||||||
<TabNavigatorBase.Navigator
|
screenOptions={{
|
||||||
screenOptions={{
|
headerShown: false,
|
||||||
headerShown: false,
|
}}
|
||||||
}}
|
tabBar={({ navigation, state, descriptors, insets }) => (
|
||||||
tabBar={({ navigation, state, descriptors, insets }) => (
|
<BottomNavigation.Bar
|
||||||
<BottomNavigation.Bar
|
navigationState={state}
|
||||||
navigationState={state}
|
safeAreaInsets={insets}
|
||||||
safeAreaInsets={insets}
|
onTabPress={({ route, preventDefault }) => {
|
||||||
onTabPress={({ route, preventDefault }) => {
|
const event = navigation.emit({
|
||||||
const event = navigation.emit({
|
type: 'tabPress',
|
||||||
type: 'tabPress',
|
target: route.key,
|
||||||
target: route.key,
|
canPreventDefault: true,
|
||||||
canPreventDefault: true,
|
});
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
preventDefault();
|
||||||
|
} else {
|
||||||
|
navigation.dispatch({
|
||||||
|
...CommonActions.navigate(route.name, route.params),
|
||||||
|
target: state.key,
|
||||||
});
|
});
|
||||||
if (event.defaultPrevented) {
|
}
|
||||||
preventDefault();
|
}}
|
||||||
} else {
|
renderIcon={({ route, focused, color }) => {
|
||||||
navigation.dispatch({
|
const { options } = descriptors[route.key];
|
||||||
...CommonActions.navigate(route.name, route.params),
|
if (options.tabBarIcon) {
|
||||||
target: state.key,
|
return options.tabBarIcon({
|
||||||
});
|
focused,
|
||||||
}
|
color,
|
||||||
route.name === 'Settings'
|
size: dimensions.static.horizontalScale(20),
|
||||||
? setFabVisible(false)
|
});
|
||||||
: setFabVisible(true);
|
}
|
||||||
}}
|
}}
|
||||||
renderIcon={({ route, focused, color }) => {
|
getLabelText={({ route }) => {
|
||||||
const { options } = descriptors[route.key];
|
const { options } = descriptors[route.key];
|
||||||
if (options.tabBarIcon) {
|
return options.title ?? route.name;
|
||||||
return options.tabBarIcon({
|
|
||||||
focused,
|
|
||||||
color,
|
|
||||||
size: horizontalScale(20),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
getLabelText={({ route }) => {
|
|
||||||
const { options } = descriptors[route.key];
|
|
||||||
return options.title ?? route.name;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}>
|
|
||||||
<TabNavigatorBase.Screen
|
|
||||||
name="Home"
|
|
||||||
component={Home}
|
|
||||||
options={{
|
|
||||||
tabBarIcon: ({ color, size }) => (
|
|
||||||
<FontAwesome5 name="home" color={color} size={size} />
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TabNavigatorBase.Screen
|
)}>
|
||||||
name="Tags"
|
<TabNavigatorBase.Screen
|
||||||
component={Tags}
|
name="Home"
|
||||||
options={{
|
component={Home}
|
||||||
tabBarIcon: ({ color, size }) => (
|
options={{
|
||||||
<FontAwesome5 name="tags" color={color} size={size} />
|
tabBarIcon: ({ color, size }) => (
|
||||||
),
|
<FontAwesome5 name="home" color={color} size={size} />
|
||||||
}}
|
),
|
||||||
/>
|
}}
|
||||||
<TabNavigatorBase.Screen
|
/>
|
||||||
name="Settings"
|
<TabNavigatorBase.Screen
|
||||||
component={Settings}
|
name="Tags"
|
||||||
options={{
|
component={Tags}
|
||||||
tabBarIcon: ({ color, size }) => (
|
options={{
|
||||||
<FontAwesome5 name="cog" color={color} size={size} />
|
tabBarIcon: ({ color, size }) => (
|
||||||
),
|
<FontAwesome5 name="tags" color={color} size={size} />
|
||||||
}}
|
),
|
||||||
/>
|
}}
|
||||||
</TabNavigatorBase.Navigator>
|
/>
|
||||||
<FloatingActionButton visible={fabVisible} />
|
<TabNavigatorBase.Screen
|
||||||
</Portal.Host>
|
name="Settings"
|
||||||
|
component={Settings}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ color, size }) => (
|
||||||
|
<FontAwesome5 name="cog" color={color} size={size} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TabNavigatorBase.Navigator>
|
||||||
|
<FloatingActionButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Appbar, Text } from 'react-native-paper';
|
import { Appbar, Text } from 'react-native-paper';
|
||||||
import { PaddedView } from '../components';
|
import { RootScrollView } from '../components';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
const AddMeme = () => {
|
const AddMeme = () => {
|
||||||
@@ -12,9 +12,9 @@ const AddMeme = () => {
|
|||||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||||
<Appbar.Content title="Add Meme" />
|
<Appbar.Content title="Add Meme" />
|
||||||
</Appbar.Header>
|
</Appbar.Header>
|
||||||
<PaddedView centered>
|
<RootScrollView centered padded>
|
||||||
<Text>Add Meme</Text>
|
<Text>Add Meme</Text>
|
||||||
</PaddedView>
|
</RootScrollView>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,37 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import {
|
import { TextInput, Appbar, HelperText, Button } from 'react-native-paper';
|
||||||
Chip,
|
|
||||||
TextInput,
|
|
||||||
Appbar,
|
|
||||||
HelperText,
|
|
||||||
Button,
|
|
||||||
} from 'react-native-paper';
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { BSON } from 'realm';
|
import { BSON } from 'realm';
|
||||||
import { useRealm } from '@realm/react';
|
import { useRealm } from '@realm/react';
|
||||||
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
import { RootScrollView, TagPreview } from '../components';
|
||||||
import { PaddedView } from '../components';
|
import styles from '../styles';
|
||||||
import styles, { horizontalScale, verticalScale } from '../styles';
|
import { generateRandomColor, isValidColor } from '../utilities';
|
||||||
import {
|
|
||||||
generateRandomColor,
|
|
||||||
getContrastColor,
|
|
||||||
isValidColor,
|
|
||||||
} from '../utilities';
|
|
||||||
|
|
||||||
const tagStyles = StyleSheet.create({
|
|
||||||
preview: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginVertical: verticalScale(75),
|
|
||||||
},
|
|
||||||
chip: {
|
|
||||||
padding: horizontalScale(5),
|
|
||||||
},
|
|
||||||
chipText: {
|
|
||||||
fontSize: horizontalScale(15),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const AddTag = () => {
|
const AddTag = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
@@ -87,34 +62,19 @@ const AddTag = () => {
|
|||||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||||
<Appbar.Content title="Add Tag" />
|
<Appbar.Content title="Add Tag" />
|
||||||
</Appbar.Header>
|
</Appbar.Header>
|
||||||
<PaddedView style={[styles.flex, styles.flexColumnSpaceBetween]}>
|
<RootScrollView
|
||||||
<View>
|
padded
|
||||||
<View style={[tagStyles.preview]}>
|
style={[styles.flexGrow, styles.flexColumnSpaceBetween]}>
|
||||||
<Chip
|
<View style={[styles.flex, styles.justifyStart]}>
|
||||||
icon={() => {
|
<TagPreview name={tagName} color={validatedTagColor} />
|
||||||
return (
|
|
||||||
<FontAwesome5
|
|
||||||
name="tag"
|
|
||||||
size={horizontalScale(12)}
|
|
||||||
color={getContrastColor(validatedTagColor)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
elevated
|
|
||||||
style={[tagStyles.chip, { backgroundColor: validatedTagColor }]}
|
|
||||||
textStyle={[
|
|
||||||
tagStyles.chipText,
|
|
||||||
{ color: getContrastColor(validatedTagColor) },
|
|
||||||
]}>
|
|
||||||
{'#' + tagName}
|
|
||||||
</Chip>
|
|
||||||
</View>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
label="Tag Name"
|
label="Tag Name"
|
||||||
value={tagName}
|
value={tagName}
|
||||||
onChangeText={handleTagNameChange}
|
onChangeText={handleTagNameChange}
|
||||||
error={!!tagNameError}
|
error={!!tagNameError}
|
||||||
|
autoCapitalize="none"
|
||||||
|
selectTextOnFocus
|
||||||
/>
|
/>
|
||||||
<HelperText type="error" visible={!!tagNameError}>
|
<HelperText type="error" visible={!!tagNameError}>
|
||||||
{tagNameError}
|
{tagNameError}
|
||||||
@@ -125,6 +85,7 @@ const AddTag = () => {
|
|||||||
value={tagColor}
|
value={tagColor}
|
||||||
onChangeText={handleTagColorChange}
|
onChangeText={handleTagColorChange}
|
||||||
error={!!tagColorError}
|
error={!!tagColorError}
|
||||||
|
autoCorrect={false}
|
||||||
right={
|
right={
|
||||||
<TextInput.Icon
|
<TextInput.Icon
|
||||||
icon="palette"
|
icon="palette"
|
||||||
@@ -136,14 +97,16 @@ const AddTag = () => {
|
|||||||
{tagColorError}
|
{tagColorError}
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<View style={[styles.flex, styles.justifyEnd]}>
|
||||||
mode="contained"
|
<Button
|
||||||
icon="floppy"
|
mode="contained"
|
||||||
onPress={handleSave}
|
icon="floppy"
|
||||||
disabled={!!tagNameError || !!tagColorError}>
|
onPress={handleSave}
|
||||||
Save
|
disabled={!!tagNameError || !!tagColorError}>
|
||||||
</Button>
|
Save
|
||||||
</PaddedView>
|
</Button>
|
||||||
|
</View>
|
||||||
|
</RootScrollView>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -9,8 +9,8 @@ import {
|
|||||||
Searchbar,
|
Searchbar,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { PaddedView } from '../components';
|
import { RootScrollView } from '../components';
|
||||||
import styles, { verticalScale } from '../styles';
|
import styles from '../styles';
|
||||||
import { SORT, SORT_DIRECTION } from '../types';
|
import { SORT, SORT_DIRECTION } from '../types';
|
||||||
import { getSortIcon, getViewIcon } from '../utilities';
|
import { getSortIcon, getViewIcon } from '../utilities';
|
||||||
import {
|
import {
|
||||||
@@ -23,10 +23,11 @@ import {
|
|||||||
setFilter,
|
setFilter,
|
||||||
} from '../state';
|
} from '../state';
|
||||||
import { MEME_TYPE, memeTypePlural } from '../database';
|
import { MEME_TYPE, memeTypePlural } from '../database';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const dimensions = useDimensions();
|
||||||
const sort = useSelector((state: RootState) => state.home.sort);
|
const sort = useSelector((state: RootState) => state.home.sort);
|
||||||
const sortDirection = useSelector(
|
const sortDirection = useSelector(
|
||||||
(state: RootState) => state.home.sortDirection,
|
(state: RootState) => state.home.sortDirection,
|
||||||
@@ -63,7 +64,7 @@ const Home = () => {
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaddedView>
|
<RootScrollView padded>
|
||||||
<Searchbar placeholder="Search" value={search} onChangeText={setSearch} />
|
<Searchbar placeholder="Search" value={search} onChangeText={setSearch} />
|
||||||
<View style={[styles.flexRowSpaceBetween, styles.centeredVertical]}>
|
<View style={[styles.flexRowSpaceBetween, styles.centeredVertical]}>
|
||||||
<View style={[styles.flexRow, styles.centeredVertical]}>
|
<View style={[styles.flexRow, styles.centeredVertical]}>
|
||||||
@@ -96,13 +97,13 @@ const Home = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={getViewIcon(view)}
|
icon={getViewIcon(view)}
|
||||||
iconColor={theme.colors.primary}
|
iconColor={theme.colors.primary}
|
||||||
size={verticalScale(16)}
|
size={dimensions.static.verticalScale(16)}
|
||||||
onPress={() => dispatch(cycleView())}
|
onPress={() => dispatch(cycleView())}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={favoritesOnly ? 'heart' : 'heart-outline'}
|
icon={favoritesOnly ? 'heart' : 'heart-outline'}
|
||||||
iconColor={theme.colors.primary}
|
iconColor={theme.colors.primary}
|
||||||
size={verticalScale(16)}
|
size={dimensions.static.verticalScale(16)}
|
||||||
onPress={() => dispatch(toggleFavoritesOnly())}
|
onPress={() => dispatch(toggleFavoritesOnly())}
|
||||||
/>
|
/>
|
||||||
<Menu
|
<Menu
|
||||||
@@ -113,7 +114,7 @@ const Home = () => {
|
|||||||
onPress={() => setFilterMenuVisible(true)}
|
onPress={() => setFilterMenuVisible(true)}
|
||||||
icon={filter ? 'filter' : 'filter-outline'}
|
icon={filter ? 'filter' : 'filter-outline'}
|
||||||
iconColor={theme.colors.primary}
|
iconColor={theme.colors.primary}
|
||||||
size={verticalScale(16)}
|
size={dimensions.static.verticalScale(16)}
|
||||||
/>
|
/>
|
||||||
}>
|
}>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
@@ -138,7 +139,7 @@ const Home = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Divider />
|
<Divider />
|
||||||
</PaddedView>
|
</RootScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -4,18 +4,19 @@ import { Button, List, Snackbar, Switch, Text } from 'react-native-paper';
|
|||||||
import { useRealm } from '@realm/react';
|
import { useRealm } from '@realm/react';
|
||||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { PaddedView } from '../components';
|
import { RootScrollView } from '../components';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { Meme } from '../database';
|
import { Meme } from '../database';
|
||||||
import { RootState, updateNoMedia, updateStorageUri } from '../state';
|
import { RootState, updateNoMedia, updateStorageUri } from '../state';
|
||||||
import type {} from 'redux-thunk/extend-redux';
|
import type {} from 'redux-thunk/extend-redux';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const SettingsScreen = () => {
|
const SettingsScreen = () => {
|
||||||
const [optimizingDatabase, setOptimizingDatabase] = useState(false);
|
|
||||||
|
|
||||||
const noMedia = useSelector((state: RootState) => state.settings.noMedia);
|
const noMedia = useSelector((state: RootState) => state.settings.noMedia);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const dimensions = useDimensions();
|
||||||
|
|
||||||
|
const [optimizingDatabase, setOptimizingDatabase] = useState(false);
|
||||||
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
||||||
const [snackbarMessage, setSnackbarMessage] = useState('');
|
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||||
|
|
||||||
@@ -45,13 +46,15 @@ const SettingsScreen = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PaddedView>
|
<RootScrollView padded>
|
||||||
<View>
|
<View>
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Subheader>Database</List.Subheader>
|
<List.Subheader>Database</List.Subheader>
|
||||||
<Button
|
<Button
|
||||||
mode="elevated"
|
mode="elevated"
|
||||||
style={styles.marginBottom}
|
style={{
|
||||||
|
marginBottom: dimensions.responsive.verticalScale(15),
|
||||||
|
}}
|
||||||
loading={optimizingDatabase}
|
loading={optimizingDatabase}
|
||||||
onPress={optimizeDatabase}>
|
onPress={optimizeDatabase}>
|
||||||
Optimize Database Now
|
Optimize Database Now
|
||||||
@@ -61,7 +64,9 @@ const SettingsScreen = () => {
|
|||||||
<List.Subheader>Media Storage</List.Subheader>
|
<List.Subheader>Media Storage</List.Subheader>
|
||||||
<Button
|
<Button
|
||||||
mode="elevated"
|
mode="elevated"
|
||||||
style={styles.marginBottom}
|
style={{
|
||||||
|
marginBottom: dimensions.responsive.verticalScale(15),
|
||||||
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
const { uri } = await openDocumentTree(true);
|
const { uri } = await openDocumentTree(true);
|
||||||
void dispatch(updateStorageUri(uri));
|
void dispatch(updateStorageUri(uri));
|
||||||
@@ -72,7 +77,9 @@ const SettingsScreen = () => {
|
|||||||
style={[
|
style={[
|
||||||
styles.flexRowSpaceBetween,
|
styles.flexRowSpaceBetween,
|
||||||
styles.smallPaddingHorizontal,
|
styles.smallPaddingHorizontal,
|
||||||
styles.marginBottom,
|
{
|
||||||
|
marginBottom: dimensions.responsive.verticalScale(15),
|
||||||
|
},
|
||||||
]}>
|
]}>
|
||||||
<Text>Hide media from gallery</Text>
|
<Text>Hide media from gallery</Text>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -84,7 +91,7 @@ const SettingsScreen = () => {
|
|||||||
</View>
|
</View>
|
||||||
</List.Section>
|
</List.Section>
|
||||||
</View>
|
</View>
|
||||||
</PaddedView>
|
</RootScrollView>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
visible={snackbarVisible}
|
visible={snackbarVisible}
|
||||||
onDismiss={() => setSnackbarVisible(false)}
|
onDismiss={() => setSnackbarVisible(false)}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Button, Text } from 'react-native-paper';
|
import { Button, Text } from 'react-native-paper';
|
||||||
import { PaddedView } from '../components';
|
import { RootScrollView } from '../components';
|
||||||
import { useQuery, useRealm } from '@realm/react';
|
import { useQuery, useRealm } from '@realm/react';
|
||||||
import { Tag, deleteAllTags } from '../database';
|
import { Tag, deleteAllTags } from '../database';
|
||||||
|
|
||||||
@@ -10,14 +10,14 @@ const Tags = () => {
|
|||||||
const tags = useQuery<Tag>('Tag');
|
const tags = useQuery<Tag>('Tag');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaddedView centered>
|
<RootScrollView centered padded>
|
||||||
{tags.map(tag => (
|
{tags.map(tag => (
|
||||||
<Text key={tag.id.toHexString()} style={{ color: tag.color }}>
|
<Text key={tag.id.toHexString()} style={{ color: tag.color }}>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
<Button onPress={() => deleteAllTags(realm)}>Delete All Tags</Button>
|
<Button onPress={() => deleteAllTags(realm)}>Delete All Tags</Button>
|
||||||
</PaddedView>
|
</RootScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -2,13 +2,15 @@ import React from 'react';
|
|||||||
import { Button, Text } from 'react-native-paper';
|
import { Button, Text } from 'react-native-paper';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||||
import { PaddedView } from '../components';
|
import { RootView } from '../components';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { noOp } from '../utilities';
|
import { noOp } from '../utilities';
|
||||||
import { updateStorageUri } from '../state';
|
import { updateStorageUri } from '../state';
|
||||||
|
import { useDimensions } from '../contexts';
|
||||||
|
|
||||||
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const dimensions = useDimensions();
|
||||||
|
|
||||||
const selectStorageLocation = async () => {
|
const selectStorageLocation = async () => {
|
||||||
const uri = await openDocumentTree(true).catch(noOp);
|
const uri = await openDocumentTree(true).catch(noOp);
|
||||||
@@ -18,19 +20,26 @@ const Welcome = ({ onWelcomeComplete }: { onWelcomeComplete: () => void }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaddedView centered>
|
<RootView centered padded>
|
||||||
<Text
|
<Text
|
||||||
variant="displayMedium"
|
variant="displayMedium"
|
||||||
style={[styles.bigMarginBottom, styles.centerText]}>
|
style={[
|
||||||
|
{
|
||||||
|
marginBottom: dimensions.responsive.verticalScale(30),
|
||||||
|
},
|
||||||
|
styles.centerText,
|
||||||
|
]}>
|
||||||
Welcome to Terminally Online!
|
Welcome to Terminally Online!
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={selectStorageLocation}
|
onPress={selectStorageLocation}
|
||||||
style={styles.extremeMarginBottom}>
|
style={{
|
||||||
|
marginBottom: dimensions.responsive.verticalScale(100),
|
||||||
|
}}>
|
||||||
Select Storage Location
|
Select Storage Location
|
||||||
</Button>
|
</Button>
|
||||||
</PaddedView>
|
</RootView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,24 +1,8 @@
|
|||||||
import { StyleSheet, Dimensions } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
|
||||||
|
|
||||||
const guidelineBaseWidth = 350;
|
|
||||||
const guidelineBaseHeight = 680;
|
|
||||||
|
|
||||||
const horizontalScale = (size: number) => (width / guidelineBaseWidth) * size;
|
|
||||||
const verticalScale = (size: number) => (height / guidelineBaseHeight) * size;
|
|
||||||
const moderateScale = (size: number, factor = 0.5) =>
|
|
||||||
size + (horizontalScale(size) - size) * factor;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
marginBottom: {
|
smallPadding: {
|
||||||
marginBottom: verticalScale(15),
|
padding: '2.5%',
|
||||||
},
|
|
||||||
bigMarginBottom: {
|
|
||||||
marginBottom: verticalScale(30),
|
|
||||||
},
|
|
||||||
extremeMarginBottom: {
|
|
||||||
marginBottom: verticalScale(100),
|
|
||||||
},
|
},
|
||||||
padding: {
|
padding: {
|
||||||
padding: '5%',
|
padding: '5%',
|
||||||
@@ -27,19 +11,24 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: '2.5%',
|
paddingHorizontal: '2.5%',
|
||||||
},
|
},
|
||||||
centered: {
|
centered: {
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
centeredVertical: {
|
centeredVertical: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
|
centeredHorizontal: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
centerText: {
|
centerText: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
flex: {
|
flex: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
flexGrow: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
flexRow: {
|
flexRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
@@ -57,6 +46,12 @@ const styles = StyleSheet.create({
|
|||||||
flexRowReverse: {
|
flexRowReverse: {
|
||||||
flexDirection: 'row-reverse',
|
flexDirection: 'row-reverse',
|
||||||
},
|
},
|
||||||
|
justifyStart: {
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
},
|
||||||
|
justifyEnd: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { horizontalScale, verticalScale, moderateScale, styles as default };
|
export default styles;
|
||||||
|
Reference in New Issue
Block a user