@modhamanish/rn-mm-template 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MMTemplate/.eslintrc.js +95 -1
- package/MMTemplate/.husky/pre-commit +2 -0
- package/MMTemplate/.prettierrc.js +2 -0
- package/MMTemplate/App.tsx +27 -21
- package/MMTemplate/README.md +76 -35
- package/MMTemplate/__tests__/App.test.tsx +5 -3
- package/MMTemplate/babel.config.js +24 -1
- package/MMTemplate/index.js +1 -0
- package/MMTemplate/package.json +22 -2
- package/MMTemplate/src/components/AnimationView.tsx +54 -69
- package/MMTemplate/src/components/AppText.tsx +7 -4
- package/MMTemplate/src/components/CustomAlert.tsx +203 -0
- package/MMTemplate/src/components/CustomToast.tsx +2 -1
- package/MMTemplate/src/components/ErrorBoundaryFallback.tsx +127 -0
- package/MMTemplate/src/components/FeatureItem.tsx +8 -6
- package/MMTemplate/src/components/FullScreenContainer.tsx +9 -7
- package/MMTemplate/src/components/InfoCard.tsx +8 -6
- package/MMTemplate/src/components/LanguageSwitcher.tsx +8 -6
- package/MMTemplate/src/components/TextInput.tsx +25 -15
- package/MMTemplate/src/components/ThemeSwitcher.tsx +4 -3
- package/MMTemplate/src/context/AuthContext.tsx +5 -4
- package/MMTemplate/src/context/ThemeContext.tsx +10 -19
- package/MMTemplate/src/locales/en.json +4 -1
- package/MMTemplate/src/locales/hi.json +4 -1
- package/MMTemplate/src/navigation/AppNavigator.tsx +7 -8
- package/MMTemplate/src/navigation/AppStack.tsx +6 -6
- package/MMTemplate/src/navigation/AuthCheck.tsx +11 -10
- package/MMTemplate/src/navigation/AuthStack.tsx +6 -4
- package/MMTemplate/src/navigation/MainTab.tsx +19 -13
- package/MMTemplate/src/screens/AddNoteScreen.tsx +20 -14
- package/MMTemplate/src/screens/HomeScreen.tsx +21 -15
- package/MMTemplate/src/screens/LoginScreen.tsx +21 -18
- package/MMTemplate/src/screens/NoteScreen.tsx +18 -16
- package/MMTemplate/src/screens/ProfileScreen.tsx +16 -13
- package/MMTemplate/src/screens/SettingsScreen.tsx +14 -11
- package/MMTemplate/src/screens/WelcomeScreen.tsx +19 -13
- package/MMTemplate/src/services/axiosInstance.ts +7 -5
- package/MMTemplate/src/services/note.query.ts +7 -5
- package/MMTemplate/src/theme/{Colors.ts → colors.ts} +6 -0
- package/MMTemplate/src/types/components.types.ts +38 -0
- package/MMTemplate/src/types/navigation.types.ts +1 -1
- package/MMTemplate/src/utils/i18n.ts +8 -6
- package/MMTemplate/src/utils/navigationUtils.ts +2 -1
- package/MMTemplate/src/utils/storageHelper.ts +8 -8
- package/MMTemplate/src/utils/utilsHelper.ts +1 -0
- package/MMTemplate/tsconfig.json +27 -0
- package/README.md +3 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { FC, memo } from 'react';
|
|
2
2
|
import { Text, TextStyle } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import { AppTextProps, AppTextSize } from '
|
|
3
|
+
|
|
4
|
+
import { AppTextProps, AppTextSize } from '@app-types/components.types';
|
|
5
|
+
import { useTheme } from '@context/ThemeContext';
|
|
5
6
|
|
|
6
7
|
const FONTS: Record<string, string> | undefined = {
|
|
7
8
|
// Define custom font families here. If empty or undefined, the component will use default system fonts with font weights.
|
|
@@ -41,7 +42,9 @@ const AppText: FC<AppTextProps> = ({
|
|
|
41
42
|
return SIZES[s as string] || SIZES.normal;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
const getTransform = (
|
|
45
|
+
const getTransform = (
|
|
46
|
+
t?: string,
|
|
47
|
+
): 'uppercase' | 'lowercase' | 'capitalize' | undefined => {
|
|
45
48
|
if (!t) return undefined;
|
|
46
49
|
switch (t) {
|
|
47
50
|
case 'capital':
|
|
@@ -54,7 +57,7 @@ const AppText: FC<AppTextProps> = ({
|
|
|
54
57
|
case 'capitalize':
|
|
55
58
|
return 'capitalize';
|
|
56
59
|
default:
|
|
57
|
-
return
|
|
60
|
+
return undefined;
|
|
58
61
|
}
|
|
59
62
|
};
|
|
60
63
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import React, { FC, memo, useState, useMemo } from 'react';
|
|
2
|
+
import { Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import AppText from '@components/AppText';
|
|
7
|
+
import { useTheme } from '@src/context/ThemeContext';
|
|
8
|
+
import { ThemeType } from '@src/theme/colors';
|
|
9
|
+
import { AlertOptionsProps } from '@src/types/components.types';
|
|
10
|
+
import { hexWithOpacity } from '@src/utils/utilsHelper';
|
|
11
|
+
|
|
12
|
+
let externalShow: ((opts: AlertOptionsProps) => void) | null = null;
|
|
13
|
+
|
|
14
|
+
const CustomAlert: FC = () => {
|
|
15
|
+
const [stack, setStack] = useState<AlertOptionsProps[]>([]);
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
const { colors } = theme;
|
|
19
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
20
|
+
|
|
21
|
+
const show = (opts: AlertOptionsProps) => {
|
|
22
|
+
const item: AlertOptionsProps = {
|
|
23
|
+
title: opts.title || '',
|
|
24
|
+
message: opts.message || '',
|
|
25
|
+
onConfirm: opts.onConfirm,
|
|
26
|
+
onCancel: opts.onCancel,
|
|
27
|
+
onClose: opts.onClose,
|
|
28
|
+
confirmText: opts.confirmText || t('common.ok'),
|
|
29
|
+
cancelText: opts.cancelText || t('common.cancel'),
|
|
30
|
+
boxStyle: opts.boxStyle,
|
|
31
|
+
titleStyle: opts.titleStyle,
|
|
32
|
+
messageStyle: opts.messageStyle,
|
|
33
|
+
confirmButtonStyle: opts.confirmButtonStyle,
|
|
34
|
+
cancelButtonStyle: opts.cancelButtonStyle,
|
|
35
|
+
confirmTextStyle: opts.confirmTextStyle,
|
|
36
|
+
cancelTextStyle: opts.cancelTextStyle,
|
|
37
|
+
isCancellable: opts.isCancellable || false,
|
|
38
|
+
isError: opts.isError || false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
setStack(prev => [...prev, item]);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const hideTop = () => {
|
|
45
|
+
setStack(prev => prev.slice(0, -1));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
externalShow = show;
|
|
49
|
+
|
|
50
|
+
if (stack.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const item = stack[stack.length - 1];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Modal visible transparent animationType="fade">
|
|
58
|
+
<TouchableOpacity
|
|
59
|
+
style={styles.overlay}
|
|
60
|
+
activeOpacity={1}
|
|
61
|
+
onPress={() => {
|
|
62
|
+
if (item.isCancellable || (!item.onCancel && !item.onConfirm)) {
|
|
63
|
+
hideTop();
|
|
64
|
+
item.onClose && item.onClose();
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<TouchableOpacity
|
|
69
|
+
activeOpacity={1}
|
|
70
|
+
onPress={() => {}}
|
|
71
|
+
style={[styles.box, item.boxStyle]}
|
|
72
|
+
>
|
|
73
|
+
{!!item.title && (
|
|
74
|
+
<AppText
|
|
75
|
+
variant="semiBold"
|
|
76
|
+
size="xlarge"
|
|
77
|
+
style={[
|
|
78
|
+
styles.title,
|
|
79
|
+
{ color: item.isError ? colors.error : colors.primary },
|
|
80
|
+
item.titleStyle,
|
|
81
|
+
]}
|
|
82
|
+
>
|
|
83
|
+
{item.title}
|
|
84
|
+
</AppText>
|
|
85
|
+
)}
|
|
86
|
+
{!!item.message && (
|
|
87
|
+
<AppText
|
|
88
|
+
variant="regular"
|
|
89
|
+
size="normal"
|
|
90
|
+
color={colors.textColor}
|
|
91
|
+
style={[styles.message, item.messageStyle]}
|
|
92
|
+
>
|
|
93
|
+
{item.message}
|
|
94
|
+
</AppText>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{item.onCancel || item.onConfirm ? (
|
|
98
|
+
<View style={styles.btnRow}>
|
|
99
|
+
{Boolean(item.onCancel || item.isCancellable) && (
|
|
100
|
+
<TouchableOpacity
|
|
101
|
+
style={[
|
|
102
|
+
styles.btn,
|
|
103
|
+
{ backgroundColor: colors.backgroundColor },
|
|
104
|
+
item.cancelButtonStyle,
|
|
105
|
+
]}
|
|
106
|
+
onPress={() => {
|
|
107
|
+
hideTop();
|
|
108
|
+
item.onCancel && item.onCancel();
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<AppText
|
|
112
|
+
variant="medium"
|
|
113
|
+
color={colors.textColor}
|
|
114
|
+
style={item.cancelTextStyle}
|
|
115
|
+
>
|
|
116
|
+
{item.cancelText}
|
|
117
|
+
</AppText>
|
|
118
|
+
</TouchableOpacity>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{item.onConfirm && (
|
|
122
|
+
<TouchableOpacity
|
|
123
|
+
style={[
|
|
124
|
+
styles.btn,
|
|
125
|
+
{
|
|
126
|
+
backgroundColor: item.isError
|
|
127
|
+
? colors.error
|
|
128
|
+
: colors.primary,
|
|
129
|
+
},
|
|
130
|
+
item.confirmButtonStyle,
|
|
131
|
+
]}
|
|
132
|
+
onPress={() => {
|
|
133
|
+
hideTop();
|
|
134
|
+
item.onConfirm && item.onConfirm();
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<AppText
|
|
138
|
+
variant="medium"
|
|
139
|
+
color={colors.white}
|
|
140
|
+
style={item.confirmTextStyle}
|
|
141
|
+
>
|
|
142
|
+
{item.confirmText}
|
|
143
|
+
</AppText>
|
|
144
|
+
</TouchableOpacity>
|
|
145
|
+
)}
|
|
146
|
+
</View>
|
|
147
|
+
) : null}
|
|
148
|
+
</TouchableOpacity>
|
|
149
|
+
</TouchableOpacity>
|
|
150
|
+
</Modal>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const MemoAlert = memo(CustomAlert);
|
|
155
|
+
|
|
156
|
+
(
|
|
157
|
+
MemoAlert as typeof MemoAlert & { show: (opts: AlertOptionsProps) => void }
|
|
158
|
+
).show = (opts: AlertOptionsProps) => externalShow && externalShow(opts);
|
|
159
|
+
|
|
160
|
+
export default MemoAlert as typeof MemoAlert & {
|
|
161
|
+
show: (opts: AlertOptionsProps) => void;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const getStyles = ({ colors }: ThemeType) =>
|
|
165
|
+
StyleSheet.create({
|
|
166
|
+
overlay: {
|
|
167
|
+
flex: 1,
|
|
168
|
+
backgroundColor: hexWithOpacity(colors.black, 50),
|
|
169
|
+
justifyContent: 'center',
|
|
170
|
+
},
|
|
171
|
+
box: {
|
|
172
|
+
margin: 24,
|
|
173
|
+
padding: 24,
|
|
174
|
+
borderRadius: 16,
|
|
175
|
+
borderWidth: 1,
|
|
176
|
+
backgroundColor: colors.backgroundColor,
|
|
177
|
+
borderColor: colors.borderColor,
|
|
178
|
+
shadowColor: colors.textColor,
|
|
179
|
+
shadowOffset: { width: 0, height: 4 },
|
|
180
|
+
shadowOpacity: 0.1,
|
|
181
|
+
shadowRadius: 12,
|
|
182
|
+
elevation: 5,
|
|
183
|
+
},
|
|
184
|
+
title: {
|
|
185
|
+
marginBottom: 12,
|
|
186
|
+
},
|
|
187
|
+
message: {
|
|
188
|
+
lineHeight: 20,
|
|
189
|
+
},
|
|
190
|
+
btnRow: {
|
|
191
|
+
marginTop: 24,
|
|
192
|
+
flexDirection: 'row',
|
|
193
|
+
justifyContent: 'flex-end',
|
|
194
|
+
},
|
|
195
|
+
btn: {
|
|
196
|
+
paddingVertical: 10,
|
|
197
|
+
paddingHorizontal: 20,
|
|
198
|
+
marginLeft: 12,
|
|
199
|
+
borderRadius: 12,
|
|
200
|
+
minWidth: 80,
|
|
201
|
+
alignItems: 'center',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { StyleSheet, View, TouchableOpacity, ScrollView } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import AppText from '@components/AppText';
|
|
7
|
+
import FullScreenContainer from '@components/FullScreenContainer';
|
|
8
|
+
import { useTheme } from '@context/ThemeContext';
|
|
9
|
+
import { ThemeType } from '@src/theme/colors';
|
|
10
|
+
import { hexWithOpacity } from '@utils/utilsHelper';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
error: Error;
|
|
14
|
+
resetError: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ErrorBoundaryFallback = (props: Props) => {
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
const theme = useTheme();
|
|
20
|
+
const { colors } = theme;
|
|
21
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<FullScreenContainer style={styles.container}>
|
|
25
|
+
<View style={styles.content}>
|
|
26
|
+
<View style={styles.iconContainer}>
|
|
27
|
+
<AppText style={styles.icon}>⚠️</AppText>
|
|
28
|
+
</View>
|
|
29
|
+
|
|
30
|
+
<AppText variant="h1" style={styles.title}>
|
|
31
|
+
{t('common.errorOccurred')}
|
|
32
|
+
</AppText>
|
|
33
|
+
|
|
34
|
+
<AppText variant="regular" style={styles.subtitle}>
|
|
35
|
+
{t('common.errorDescription')}
|
|
36
|
+
</AppText>
|
|
37
|
+
|
|
38
|
+
<View style={styles.errorBox}>
|
|
39
|
+
<ScrollView bounces={false}>
|
|
40
|
+
<AppText
|
|
41
|
+
size="small"
|
|
42
|
+
color={colors.primary}
|
|
43
|
+
style={styles.errorText}
|
|
44
|
+
>
|
|
45
|
+
{props.error.toString()}
|
|
46
|
+
</AppText>
|
|
47
|
+
</ScrollView>
|
|
48
|
+
</View>
|
|
49
|
+
|
|
50
|
+
<TouchableOpacity
|
|
51
|
+
style={styles.button}
|
|
52
|
+
onPress={props.resetError}
|
|
53
|
+
activeOpacity={0.8}
|
|
54
|
+
>
|
|
55
|
+
<AppText variant="semiBold" color={colors.white}>
|
|
56
|
+
{t('common.tryAgain')}
|
|
57
|
+
</AppText>
|
|
58
|
+
</TouchableOpacity>
|
|
59
|
+
</View>
|
|
60
|
+
</FullScreenContainer>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const getStyles = (theme: ThemeType) => {
|
|
65
|
+
const { colors } = theme;
|
|
66
|
+
return StyleSheet.create({
|
|
67
|
+
container: {
|
|
68
|
+
flex: 1,
|
|
69
|
+
justifyContent: 'center',
|
|
70
|
+
padding: 24,
|
|
71
|
+
},
|
|
72
|
+
content: {
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
},
|
|
75
|
+
iconContainer: {
|
|
76
|
+
width: 80,
|
|
77
|
+
height: 80,
|
|
78
|
+
borderRadius: 40,
|
|
79
|
+
justifyContent: 'center',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
marginBottom: 24,
|
|
82
|
+
backgroundColor: hexWithOpacity(colors.primary, 15),
|
|
83
|
+
},
|
|
84
|
+
icon: {
|
|
85
|
+
fontSize: 40,
|
|
86
|
+
},
|
|
87
|
+
title: {
|
|
88
|
+
textAlign: 'center',
|
|
89
|
+
marginBottom: 12,
|
|
90
|
+
},
|
|
91
|
+
subtitle: {
|
|
92
|
+
textAlign: 'center',
|
|
93
|
+
marginBottom: 32,
|
|
94
|
+
lineHeight: 22,
|
|
95
|
+
color: hexWithOpacity(colors.textColor, 80),
|
|
96
|
+
},
|
|
97
|
+
errorBox: {
|
|
98
|
+
width: '100%',
|
|
99
|
+
maxHeight: 200,
|
|
100
|
+
borderRadius: 12,
|
|
101
|
+
borderWidth: 1,
|
|
102
|
+
padding: 16,
|
|
103
|
+
marginBottom: 32,
|
|
104
|
+
backgroundColor: colors.backgroundColor,
|
|
105
|
+
borderColor: colors.borderColor,
|
|
106
|
+
},
|
|
107
|
+
errorText: {
|
|
108
|
+
lineHeight: 18,
|
|
109
|
+
fontFamily: 'monospace',
|
|
110
|
+
},
|
|
111
|
+
button: {
|
|
112
|
+
width: '100%',
|
|
113
|
+
height: 56,
|
|
114
|
+
borderRadius: 16,
|
|
115
|
+
justifyContent: 'center',
|
|
116
|
+
alignItems: 'center',
|
|
117
|
+
shadowColor: colors.black,
|
|
118
|
+
shadowOffset: { width: 0, height: 4 },
|
|
119
|
+
shadowOpacity: 0.1,
|
|
120
|
+
shadowRadius: 8,
|
|
121
|
+
elevation: 4,
|
|
122
|
+
backgroundColor: colors.primary,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default ErrorBoundaryFallback;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { View, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
|
|
4
|
+
import AppText from '@components/AppText';
|
|
5
|
+
import { useTheme } from '@context/ThemeContext';
|
|
6
|
+
import { ThemeType } from '@src/theme/colors';
|
|
7
|
+
import { hexWithOpacity } from '@utils/utilsHelper';
|
|
6
8
|
|
|
7
9
|
interface FeatureItemProps {
|
|
8
10
|
icon: string;
|
|
@@ -16,7 +18,7 @@ const FeatureItem: React.FC<FeatureItemProps> = ({
|
|
|
16
18
|
description,
|
|
17
19
|
}) => {
|
|
18
20
|
const theme = useTheme();
|
|
19
|
-
const styles = getStyles(theme);
|
|
21
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
20
22
|
|
|
21
23
|
return (
|
|
22
24
|
<View style={styles.container}>
|
|
@@ -56,7 +58,7 @@ const getStyles = ({ colors }: ThemeType) =>
|
|
|
56
58
|
marginBottom: 2,
|
|
57
59
|
},
|
|
58
60
|
description: {
|
|
59
|
-
color: colors.textColor
|
|
61
|
+
color: hexWithOpacity(colors.textColor, 80),
|
|
60
62
|
lineHeight: 18,
|
|
61
63
|
},
|
|
62
64
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import React, { FC, memo, useMemo } from 'react';
|
|
1
2
|
import { StatusBar, StyleSheet } from 'react-native';
|
|
2
|
-
|
|
3
|
-
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
3
|
+
|
|
4
4
|
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import {
|
|
5
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
6
|
+
|
|
7
|
+
import { FullScreenContainerProps } from '@app-types/components.types';
|
|
8
|
+
import { useTheme } from '@context/ThemeContext';
|
|
9
|
+
import { ThemeType } from '@src/theme/colors';
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
const FullScreenContainer: FC<FullScreenContainerProps> = ({
|
|
10
12
|
children,
|
|
11
13
|
style,
|
|
12
14
|
edges = ['top'],
|
|
@@ -16,7 +18,7 @@ export const FullScreenContainer: FC<FullScreenContainerProps> = ({
|
|
|
16
18
|
isKeyboardAvoidingView = false,
|
|
17
19
|
}) => {
|
|
18
20
|
const theme = useTheme();
|
|
19
|
-
const styles = getStyles(theme);
|
|
21
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
20
22
|
|
|
21
23
|
const appBarStyle =
|
|
22
24
|
barStyle ??
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { View, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
|
|
4
|
+
import AppText from '@components/AppText';
|
|
5
|
+
import { useTheme } from '@context/ThemeContext';
|
|
6
|
+
import { ThemeType } from '@src/theme/colors';
|
|
7
|
+
import { hexWithOpacity } from '@utils/utilsHelper';
|
|
6
8
|
|
|
7
9
|
interface InfoCardProps {
|
|
8
10
|
title: string;
|
|
@@ -12,7 +14,7 @@ interface InfoCardProps {
|
|
|
12
14
|
|
|
13
15
|
const InfoCard: React.FC<InfoCardProps> = ({ title, children, icon }) => {
|
|
14
16
|
const theme = useTheme();
|
|
15
|
-
const styles = getStyles(theme);
|
|
17
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
16
18
|
|
|
17
19
|
return (
|
|
18
20
|
<View style={styles.card}>
|
|
@@ -41,7 +43,7 @@ const getStyles = ({ colors }: ThemeType) =>
|
|
|
41
43
|
padding: 16,
|
|
42
44
|
marginBottom: 16,
|
|
43
45
|
borderWidth: 1,
|
|
44
|
-
borderColor: colors.primary
|
|
46
|
+
borderColor: hexWithOpacity(colors.primary, 12),
|
|
45
47
|
shadowColor: colors.textColor,
|
|
46
48
|
shadowOffset: { width: 0, height: 2 },
|
|
47
49
|
shadowOpacity: 0.1,
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
|
|
6
|
+
import AppText from '@components/AppText';
|
|
7
|
+
import { useTheme } from '@context/ThemeContext';
|
|
8
|
+
import { ThemeType } from '@src/theme/colors';
|
|
9
|
+
import StorageHelper from '@utils/storageHelper';
|
|
8
10
|
|
|
9
11
|
const LanguageSwitcher = () => {
|
|
10
12
|
const { i18n } = useTranslation();
|
|
11
13
|
const theme = useTheme();
|
|
12
|
-
const styles = getStyles(theme);
|
|
14
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
13
15
|
|
|
14
16
|
const changeLanguage = (lng: string) => {
|
|
15
17
|
i18n.changeLanguage(lng);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
TextInput as RNTextInput,
|
|
4
4
|
TextInputProps,
|
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
StyleProp,
|
|
8
8
|
TextStyle,
|
|
9
9
|
ViewStyle,
|
|
10
|
+
LayoutChangeEvent,
|
|
11
|
+
FocusEvent,
|
|
12
|
+
BlurEvent,
|
|
10
13
|
} from 'react-native';
|
|
14
|
+
|
|
11
15
|
import Animated, {
|
|
12
16
|
useSharedValue,
|
|
13
17
|
useAnimatedStyle,
|
|
@@ -15,9 +19,11 @@ import Animated, {
|
|
|
15
19
|
interpolate,
|
|
16
20
|
Extrapolate,
|
|
17
21
|
} from 'react-native-reanimated';
|
|
18
|
-
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
22
|
+
|
|
23
|
+
import AppText from '@components/AppText';
|
|
24
|
+
import { useTheme } from '@context/ThemeContext';
|
|
25
|
+
import { ThemeType } from '@src/theme/colors';
|
|
26
|
+
import { hexWithOpacity } from '@utils/utilsHelper';
|
|
21
27
|
|
|
22
28
|
interface CustomTextInputProps extends TextInputProps {
|
|
23
29
|
label?: string;
|
|
@@ -59,7 +65,7 @@ const TextInput: React.FC<CustomTextInputProps> = ({
|
|
|
59
65
|
...props
|
|
60
66
|
}) => {
|
|
61
67
|
const theme = useTheme();
|
|
62
|
-
const styles = getStyles(theme);
|
|
68
|
+
const styles = useMemo(() => getStyles(theme), [theme]);
|
|
63
69
|
const [isFocused, setIsFocused] = useState(false);
|
|
64
70
|
const [leftWidth, setLeftWidth] = useState(0);
|
|
65
71
|
|
|
@@ -115,21 +121,25 @@ const TextInput: React.FC<CustomTextInputProps> = ({
|
|
|
115
121
|
? hasError
|
|
116
122
|
? theme.colors.primary
|
|
117
123
|
: theme.colors.primary
|
|
118
|
-
: theme.colors.textColor
|
|
124
|
+
: hexWithOpacity(theme.colors.textColor, 50),
|
|
119
125
|
};
|
|
120
126
|
});
|
|
121
127
|
|
|
122
|
-
const handleFocus = (e:
|
|
128
|
+
const handleFocus = (e: FocusEvent) => {
|
|
123
129
|
setIsFocused(true);
|
|
124
|
-
onFocus
|
|
130
|
+
if (onFocus) {
|
|
131
|
+
onFocus(e);
|
|
132
|
+
}
|
|
125
133
|
};
|
|
126
134
|
|
|
127
|
-
const handleBlur = (e:
|
|
135
|
+
const handleBlur = (e: BlurEvent) => {
|
|
128
136
|
setIsFocused(false);
|
|
129
|
-
onBlur
|
|
137
|
+
if (onBlur) {
|
|
138
|
+
onBlur(e);
|
|
139
|
+
}
|
|
130
140
|
};
|
|
131
141
|
|
|
132
|
-
const onLeftLayout = (event:
|
|
142
|
+
const onLeftLayout = (event: LayoutChangeEvent) => {
|
|
133
143
|
setLeftWidth(event.nativeEvent.layout.width);
|
|
134
144
|
};
|
|
135
145
|
|
|
@@ -171,7 +181,7 @@ const TextInput: React.FC<CustomTextInputProps> = ({
|
|
|
171
181
|
<RNTextInput
|
|
172
182
|
style={[styles.input, style]}
|
|
173
183
|
placeholder={showPlaceholder ? placeholder : ''}
|
|
174
|
-
placeholderTextColor={theme.colors.textColor
|
|
184
|
+
placeholderTextColor={hexWithOpacity(theme.colors.textColor, 37)}
|
|
175
185
|
onFocus={handleFocus}
|
|
176
186
|
onBlur={handleBlur}
|
|
177
187
|
value={value}
|
|
@@ -213,7 +223,7 @@ const getStyles = ({ colors }: ThemeType) =>
|
|
|
213
223
|
alignItems: 'center',
|
|
214
224
|
backgroundColor: colors.backgroundColor,
|
|
215
225
|
borderWidth: 1,
|
|
216
|
-
borderColor: colors.textColor
|
|
226
|
+
borderColor: hexWithOpacity(colors.textColor, 18),
|
|
217
227
|
borderRadius: 12,
|
|
218
228
|
paddingHorizontal: 12,
|
|
219
229
|
minHeight: 56,
|
|
@@ -251,11 +261,11 @@ const getStyles = ({ colors }: ThemeType) =>
|
|
|
251
261
|
},
|
|
252
262
|
prefix: {
|
|
253
263
|
marginRight: 4,
|
|
254
|
-
color: colors.textColor
|
|
264
|
+
color: hexWithOpacity(colors.textColor, 50),
|
|
255
265
|
},
|
|
256
266
|
suffix: {
|
|
257
267
|
marginLeft: 4,
|
|
258
|
-
color: colors.textColor
|
|
268
|
+
color: hexWithOpacity(colors.textColor, 50),
|
|
259
269
|
},
|
|
260
270
|
errorText: {
|
|
261
271
|
color: colors.primary,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
|
|
4
|
+
import AppText from '@components/AppText';
|
|
5
|
+
import { useTheme } from '@context/ThemeContext';
|
|
6
|
+
import { ThemeType } from '@src/theme/colors';
|
|
6
7
|
|
|
7
8
|
const ThemeSwitcher = () => {
|
|
8
9
|
const { colors, currentTheme, toggleTheme } = useTheme();
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { userMockData } from '../mock';
|
|
2
|
-
import Routes from '../navigation/routes';
|
|
3
|
-
import { resetAndNavigate } from '../utils/navigationUtils';
|
|
4
|
-
import StorageHelper from '../utils/storageHelper';
|
|
5
1
|
import {
|
|
6
2
|
createContext,
|
|
7
3
|
FC,
|
|
@@ -12,6 +8,11 @@ import {
|
|
|
12
8
|
useState,
|
|
13
9
|
} from 'react';
|
|
14
10
|
|
|
11
|
+
import { userMockData } from '@mock';
|
|
12
|
+
import Routes from '@navigation/routes';
|
|
13
|
+
import { resetAndNavigate } from '@utils/navigationUtils';
|
|
14
|
+
import StorageHelper from '@utils/storageHelper';
|
|
15
|
+
|
|
15
16
|
type UserType = typeof userMockData;
|
|
16
17
|
|
|
17
18
|
type AuthContextType = {
|
|
@@ -4,12 +4,13 @@ import {
|
|
|
4
4
|
ReactNode,
|
|
5
5
|
useCallback,
|
|
6
6
|
useContext,
|
|
7
|
-
useEffect,
|
|
8
7
|
useState,
|
|
9
8
|
} from 'react';
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
import { darkTheme, lightTheme, ThemeType } from '@src/theme/colors';
|
|
13
|
+
import storageHelper from '@utils/storageHelper';
|
|
13
14
|
|
|
14
15
|
export type ThemeContextType = {
|
|
15
16
|
colors: ThemeType['colors'];
|
|
@@ -26,24 +27,14 @@ type ThemeProviderProps = {
|
|
|
26
27
|
|
|
27
28
|
export const ThemeProvider: FC<ThemeProviderProps> = ({ children }) => {
|
|
28
29
|
const safeAreaInsets = useSafeAreaInsets();
|
|
29
|
-
const [currentTheme, setCurrentTheme] =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
lightTheme.colors,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
30
|
+
const [currentTheme, setCurrentTheme] = useState<
|
|
31
|
+
ThemeContextType['currentTheme']
|
|
32
|
+
>(() => {
|
|
36
33
|
const theme = storageHelper.getItem(storageHelper.STORAGE_KEYS.THEME);
|
|
37
|
-
|
|
38
|
-
}
|
|
34
|
+
return theme === 'dark' ? 'dark' : 'light';
|
|
35
|
+
});
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
if (currentTheme === 'dark') {
|
|
42
|
-
setColors(darkTheme.colors);
|
|
43
|
-
} else {
|
|
44
|
-
setColors(lightTheme.colors);
|
|
45
|
-
}
|
|
46
|
-
}, [currentTheme]);
|
|
37
|
+
const colors = currentTheme === 'dark' ? darkTheme.colors : lightTheme.colors;
|
|
47
38
|
|
|
48
39
|
const toggleTheme = useCallback(() => {
|
|
49
40
|
setCurrentTheme(prev => {
|