@modhamanish/rn-mm-template 1.0.1
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/.bundle/config +2 -0
- package/MMTemplate/.eslintrc.js +4 -0
- package/MMTemplate/.prettierrc.js +5 -0
- package/MMTemplate/.watchmanconfig +1 -0
- package/MMTemplate/App.tsx +41 -0
- package/MMTemplate/Gemfile +16 -0
- package/MMTemplate/README.md +109 -0
- package/MMTemplate/__tests__/App.test.tsx +13 -0
- package/MMTemplate/_gitignore +79 -0
- package/MMTemplate/android/app/build.gradle +119 -0
- package/MMTemplate/android/app/debug.keystore +0 -0
- package/MMTemplate/android/app/proguard-rules.pro +10 -0
- package/MMTemplate/android/app/src/main/AndroidManifest.xml +27 -0
- package/MMTemplate/android/app/src/main/java/com/mmtemplate/MainActivity.kt +22 -0
- package/MMTemplate/android/app/src/main/java/com/mmtemplate/MainApplication.kt +27 -0
- package/MMTemplate/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/MMTemplate/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/MMTemplate/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/MMTemplate/android/app/src/main/res/values/strings.xml +3 -0
- package/MMTemplate/android/app/src/main/res/values/styles.xml +9 -0
- package/MMTemplate/android/build.gradle +21 -0
- package/MMTemplate/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/MMTemplate/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/MMTemplate/android/gradle.properties +44 -0
- package/MMTemplate/android/gradlew +251 -0
- package/MMTemplate/android/gradlew.bat +99 -0
- package/MMTemplate/android/settings.gradle +6 -0
- package/MMTemplate/app.json +4 -0
- package/MMTemplate/babel.config.js +8 -0
- package/MMTemplate/index.js +9 -0
- package/MMTemplate/ios/.xcode.env +11 -0
- package/MMTemplate/ios/MMTemplate/AppDelegate.swift +48 -0
- package/MMTemplate/ios/MMTemplate/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/MMTemplate/ios/MMTemplate/Images.xcassets/Contents.json +6 -0
- package/MMTemplate/ios/MMTemplate/Info.plist +55 -0
- package/MMTemplate/ios/MMTemplate/LaunchScreen.storyboard +47 -0
- package/MMTemplate/ios/MMTemplate/PrivacyInfo.xcprivacy +37 -0
- package/MMTemplate/ios/MMTemplate.xcodeproj/project.pbxproj +480 -0
- package/MMTemplate/ios/MMTemplate.xcodeproj/xcshareddata/xcschemes/MMTemplate.xcscheme +88 -0
- package/MMTemplate/ios/Podfile +38 -0
- package/MMTemplate/jest.config.js +3 -0
- package/MMTemplate/metro.config.js +11 -0
- package/MMTemplate/package.json +55 -0
- package/MMTemplate/src/assets/images/index.ts +4 -0
- package/MMTemplate/src/assets/images/logo-dark.png +0 -0
- package/MMTemplate/src/assets/images/logo.png +0 -0
- package/MMTemplate/src/components/AnimationView.tsx +113 -0
- package/MMTemplate/src/components/CustomToast.tsx +46 -0
- package/MMTemplate/src/components/FeatureItem.tsx +59 -0
- package/MMTemplate/src/components/FullScreenContainer.tsx +60 -0
- package/MMTemplate/src/components/InfoCard.tsx +61 -0
- package/MMTemplate/src/components/LanguageSwitcher.tsx +80 -0
- package/MMTemplate/src/components/TextInput.tsx +74 -0
- package/MMTemplate/src/components/ThemeSwitcher.tsx +63 -0
- package/MMTemplate/src/context/AuthContext.tsx +76 -0
- package/MMTemplate/src/context/ThemeContext.tsx +67 -0
- package/MMTemplate/src/locales/en.json +83 -0
- package/MMTemplate/src/locales/hi.json +83 -0
- package/MMTemplate/src/mock/index.ts +5 -0
- package/MMTemplate/src/navigation/AppNavigator.tsx +45 -0
- package/MMTemplate/src/navigation/AppStack.tsx +27 -0
- package/MMTemplate/src/navigation/AuthCheck.tsx +50 -0
- package/MMTemplate/src/navigation/AuthStack.tsx +24 -0
- package/MMTemplate/src/navigation/MainTab.tsx +50 -0
- package/MMTemplate/src/navigation/routes.ts +17 -0
- package/MMTemplate/src/screens/HomeScreen.tsx +296 -0
- package/MMTemplate/src/screens/LoginScreen.tsx +199 -0
- package/MMTemplate/src/screens/ProfileScreen.tsx +171 -0
- package/MMTemplate/src/screens/SettingsScreen.tsx +96 -0
- package/MMTemplate/src/screens/WelcomeScreen.tsx +215 -0
- package/MMTemplate/src/theme/Colors.ts +25 -0
- package/MMTemplate/src/types/components.types.ts +13 -0
- package/MMTemplate/src/types/navigation.types.ts +35 -0
- package/MMTemplate/src/utils/i18n.ts +28 -0
- package/MMTemplate/src/utils/navigationUtils.ts +72 -0
- package/MMTemplate/src/utils/storageHelper.ts +51 -0
- package/MMTemplate/src/utils/utilsHelper.ts +34 -0
- package/MMTemplate/src/utils/validationSchemas.ts +58 -0
- package/MMTemplate/tsconfig.json +8 -0
- package/README.md +108 -0
- package/package.json +22 -0
- package/script.js +62 -0
- package/template.config.js +11 -0
|
Binary file
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React, { memo, useEffect } from 'react';
|
|
2
|
+
import Animated, {
|
|
3
|
+
Easing,
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withDelay,
|
|
7
|
+
withTiming,
|
|
8
|
+
} from 'react-native-reanimated';
|
|
9
|
+
|
|
10
|
+
const AnimationView = ({
|
|
11
|
+
children,
|
|
12
|
+
animType,
|
|
13
|
+
duration = 500,
|
|
14
|
+
delay = 0,
|
|
15
|
+
rotateValue = 360,
|
|
16
|
+
style,
|
|
17
|
+
}: {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
animType:
|
|
20
|
+
| 'FadeIn'
|
|
21
|
+
| 'FadeOut'
|
|
22
|
+
| 'ZoomIn'
|
|
23
|
+
| 'ZoomOut'
|
|
24
|
+
| 'RotateIn'
|
|
25
|
+
| 'RotateOut'
|
|
26
|
+
| 'SlideInDown';
|
|
27
|
+
duration?: number;
|
|
28
|
+
delay?: number;
|
|
29
|
+
rotateValue?: number;
|
|
30
|
+
style?: any;
|
|
31
|
+
}) => {
|
|
32
|
+
const fadeAnim = useSharedValue(0);
|
|
33
|
+
const zoomAnim = useSharedValue(0);
|
|
34
|
+
const rotateAnim = useSharedValue(0);
|
|
35
|
+
const translateAnim = useSharedValue(-100);
|
|
36
|
+
|
|
37
|
+
const animStyle = useAnimatedStyle(() => {
|
|
38
|
+
switch (animType) {
|
|
39
|
+
case 'FadeIn':
|
|
40
|
+
return {
|
|
41
|
+
opacity: fadeAnim.value,
|
|
42
|
+
};
|
|
43
|
+
case 'FadeOut':
|
|
44
|
+
return {
|
|
45
|
+
opacity: fadeAnim.value,
|
|
46
|
+
};
|
|
47
|
+
case 'ZoomIn':
|
|
48
|
+
return {
|
|
49
|
+
transform: [{ scale: zoomAnim.value }],
|
|
50
|
+
};
|
|
51
|
+
case 'ZoomOut':
|
|
52
|
+
return {
|
|
53
|
+
transform: [{ scale: zoomAnim.value }],
|
|
54
|
+
};
|
|
55
|
+
case 'RotateIn':
|
|
56
|
+
return {
|
|
57
|
+
transform: [{ rotate: rotateAnim.value + 'deg' }],
|
|
58
|
+
};
|
|
59
|
+
case 'RotateOut':
|
|
60
|
+
return {
|
|
61
|
+
transform: [{ rotate: rotateAnim.value + 'deg' }],
|
|
62
|
+
};
|
|
63
|
+
case 'SlideInDown':
|
|
64
|
+
return {
|
|
65
|
+
transform: [{ translateY: translateAnim.value }],
|
|
66
|
+
};
|
|
67
|
+
default:
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (animType === 'FadeIn') {
|
|
74
|
+
fadeAnim.value = withDelay(delay, withTiming(1, { duration: duration }));
|
|
75
|
+
} else if (animType === 'FadeOut') {
|
|
76
|
+
fadeAnim.value = withDelay(delay, withTiming(0, { duration: duration }));
|
|
77
|
+
} else if (animType === 'ZoomIn') {
|
|
78
|
+
zoomAnim.value = withDelay(
|
|
79
|
+
delay,
|
|
80
|
+
withTiming(1, { duration: duration, easing: Easing.elastic(1) }),
|
|
81
|
+
);
|
|
82
|
+
} else if (animType === 'ZoomOut') {
|
|
83
|
+
zoomAnim.value = withDelay(
|
|
84
|
+
delay,
|
|
85
|
+
withTiming(0, { duration: duration, easing: Easing.elastic(1) }),
|
|
86
|
+
);
|
|
87
|
+
} else if (animType === 'RotateIn') {
|
|
88
|
+
rotateAnim.value = withDelay(
|
|
89
|
+
delay,
|
|
90
|
+
withTiming(rotateValue, {
|
|
91
|
+
duration: duration,
|
|
92
|
+
easing: Easing.elastic(1),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
} else if (animType === 'RotateOut') {
|
|
96
|
+
rotateAnim.value = withDelay(
|
|
97
|
+
delay,
|
|
98
|
+
withTiming(rotateValue, {
|
|
99
|
+
duration: duration,
|
|
100
|
+
easing: Easing.elastic(1),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
} else if (animType === 'SlideInDown') {
|
|
104
|
+
translateAnim.value = withDelay(
|
|
105
|
+
delay,
|
|
106
|
+
withTiming(0, { duration: duration, easing: Easing.elastic(1) }),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}, [animType, duration, delay, rotateValue]);
|
|
110
|
+
return <Animated.View style={[animStyle, style]}>{children}</Animated.View>;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default memo(AnimationView);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type CustomToastProps = {
|
|
5
|
+
text1?: string;
|
|
6
|
+
onPress?: () => void;
|
|
7
|
+
type: 'success' | 'error';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const CustomToast = ({ text1, onPress, type }: CustomToastProps) => {
|
|
11
|
+
const backgroundColor = type === 'success' ? '#4CAF50' : '#ff3f3f';
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<View style={[styles.container, { backgroundColor }]}>
|
|
15
|
+
<Text style={styles.text}>{text1}</Text>
|
|
16
|
+
{onPress && (
|
|
17
|
+
<TouchableOpacity onPress={onPress}>
|
|
18
|
+
<Text style={styles.buttonText}>OK</Text>
|
|
19
|
+
</TouchableOpacity>
|
|
20
|
+
)}
|
|
21
|
+
</View>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const styles = StyleSheet.create({
|
|
26
|
+
container: {
|
|
27
|
+
flexDirection: 'row',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
paddingHorizontal: 12,
|
|
30
|
+
paddingVertical: 15,
|
|
31
|
+
width: '90%',
|
|
32
|
+
marginTop: 20,
|
|
33
|
+
borderRadius: 5,
|
|
34
|
+
gap: 12,
|
|
35
|
+
},
|
|
36
|
+
text: {
|
|
37
|
+
flex: 1,
|
|
38
|
+
color: 'white',
|
|
39
|
+
fontWeight: '500',
|
|
40
|
+
},
|
|
41
|
+
buttonText: {
|
|
42
|
+
color: 'white',
|
|
43
|
+
fontSize: 16,
|
|
44
|
+
fontWeight: '600',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { useTheme } from '../context/ThemeContext';
|
|
4
|
+
import { ThemeType } from '../theme/Colors';
|
|
5
|
+
|
|
6
|
+
interface FeatureItemProps {
|
|
7
|
+
icon: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const FeatureItem: React.FC<FeatureItemProps> = ({
|
|
13
|
+
icon,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
}) => {
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
const styles = getStyles(theme);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<View style={styles.container}>
|
|
22
|
+
<Text style={styles.icon}>{icon}</Text>
|
|
23
|
+
<View style={styles.textContainer}>
|
|
24
|
+
<Text style={styles.title}>{title}</Text>
|
|
25
|
+
<Text style={styles.description}>{description}</Text>
|
|
26
|
+
</View>
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default FeatureItem;
|
|
32
|
+
|
|
33
|
+
const getStyles = ({ colors }: ThemeType) =>
|
|
34
|
+
StyleSheet.create({
|
|
35
|
+
container: {
|
|
36
|
+
flexDirection: 'row',
|
|
37
|
+
alignItems: 'flex-start',
|
|
38
|
+
marginBottom: 12,
|
|
39
|
+
},
|
|
40
|
+
icon: {
|
|
41
|
+
fontSize: 20,
|
|
42
|
+
marginRight: 12,
|
|
43
|
+
marginTop: 2,
|
|
44
|
+
},
|
|
45
|
+
textContainer: {
|
|
46
|
+
flex: 1,
|
|
47
|
+
},
|
|
48
|
+
title: {
|
|
49
|
+
fontSize: 15,
|
|
50
|
+
fontWeight: '600',
|
|
51
|
+
color: colors.textColor,
|
|
52
|
+
marginBottom: 2,
|
|
53
|
+
},
|
|
54
|
+
description: {
|
|
55
|
+
fontSize: 13,
|
|
56
|
+
color: colors.textColor + 'CC',
|
|
57
|
+
lineHeight: 18,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { StatusBar, StyleSheet } from 'react-native';
|
|
2
|
+
import React, { FC, memo } from 'react';
|
|
3
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
4
|
+
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
|
|
5
|
+
import { FullScreenContainerProps } from '../types/components.types';
|
|
6
|
+
import { useTheme } from '../context/ThemeContext';
|
|
7
|
+
import { ThemeType } from '../theme/Colors';
|
|
8
|
+
|
|
9
|
+
export const FullScreenContainer: FC<FullScreenContainerProps> = ({
|
|
10
|
+
children,
|
|
11
|
+
style,
|
|
12
|
+
edges = ['top'],
|
|
13
|
+
barStyle,
|
|
14
|
+
statusBarHidden = false,
|
|
15
|
+
keyboardAvoidingViewStyle,
|
|
16
|
+
isKeyboardAvoidingView = false,
|
|
17
|
+
}) => {
|
|
18
|
+
const theme = useTheme();
|
|
19
|
+
const styles = getStyles(theme);
|
|
20
|
+
|
|
21
|
+
const appBarStyle =
|
|
22
|
+
barStyle ??
|
|
23
|
+
(theme.currentTheme === 'dark' ? 'light-content' : 'dark-content');
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<SafeAreaView edges={edges} style={[styles.container, style]}>
|
|
27
|
+
<StatusBar
|
|
28
|
+
animated
|
|
29
|
+
hidden={statusBarHidden}
|
|
30
|
+
translucent
|
|
31
|
+
backgroundColor={'transparent'}
|
|
32
|
+
barStyle={appBarStyle}
|
|
33
|
+
/>
|
|
34
|
+
{isKeyboardAvoidingView ? (
|
|
35
|
+
<KeyboardAvoidingView
|
|
36
|
+
enabled={isKeyboardAvoidingView}
|
|
37
|
+
behavior={'padding'}
|
|
38
|
+
style={[styles.keyboardAvoidingViewStyle, keyboardAvoidingViewStyle]}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</KeyboardAvoidingView>
|
|
42
|
+
) : (
|
|
43
|
+
children
|
|
44
|
+
)}
|
|
45
|
+
</SafeAreaView>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default memo(FullScreenContainer);
|
|
50
|
+
|
|
51
|
+
const getStyles = ({ colors }: ThemeType) =>
|
|
52
|
+
StyleSheet.create({
|
|
53
|
+
container: {
|
|
54
|
+
flex: 1,
|
|
55
|
+
backgroundColor: colors.backgroundColor,
|
|
56
|
+
},
|
|
57
|
+
keyboardAvoidingViewStyle: {
|
|
58
|
+
flex: 1,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { useTheme } from '../context/ThemeContext';
|
|
4
|
+
import { ThemeType } from '../theme/Colors';
|
|
5
|
+
|
|
6
|
+
interface InfoCardProps {
|
|
7
|
+
title: string;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
icon?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const InfoCard: React.FC<InfoCardProps> = ({ title, children, icon }) => {
|
|
13
|
+
const theme = useTheme();
|
|
14
|
+
const styles = getStyles(theme);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<View style={styles.card}>
|
|
18
|
+
<View style={styles.header}>
|
|
19
|
+
{icon && <Text style={styles.icon}>{icon}</Text>}
|
|
20
|
+
<Text style={styles.title}>{title}</Text>
|
|
21
|
+
</View>
|
|
22
|
+
<View style={styles.content}>{children}</View>
|
|
23
|
+
</View>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default InfoCard;
|
|
28
|
+
|
|
29
|
+
const getStyles = ({ colors }: ThemeType) =>
|
|
30
|
+
StyleSheet.create({
|
|
31
|
+
card: {
|
|
32
|
+
backgroundColor: colors.backgroundColor,
|
|
33
|
+
borderRadius: 12,
|
|
34
|
+
padding: 16,
|
|
35
|
+
marginBottom: 16,
|
|
36
|
+
borderWidth: 1,
|
|
37
|
+
borderColor: colors.primary + '20',
|
|
38
|
+
shadowColor: colors.textColor,
|
|
39
|
+
shadowOffset: { width: 0, height: 2 },
|
|
40
|
+
shadowOpacity: 0.1,
|
|
41
|
+
shadowRadius: 4,
|
|
42
|
+
elevation: 3,
|
|
43
|
+
},
|
|
44
|
+
header: {
|
|
45
|
+
flexDirection: 'row',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
marginBottom: 12,
|
|
48
|
+
},
|
|
49
|
+
icon: {
|
|
50
|
+
fontSize: 24,
|
|
51
|
+
marginRight: 8,
|
|
52
|
+
},
|
|
53
|
+
title: {
|
|
54
|
+
fontSize: 18,
|
|
55
|
+
fontWeight: '700',
|
|
56
|
+
color: colors.primary,
|
|
57
|
+
},
|
|
58
|
+
content: {
|
|
59
|
+
gap: 8,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { useTheme } from '../context/ThemeContext';
|
|
5
|
+
import { ThemeType } from '../theme/Colors';
|
|
6
|
+
import StorageHelper from '../utils/storageHelper';
|
|
7
|
+
|
|
8
|
+
const LanguageSwitcher = () => {
|
|
9
|
+
const { i18n } = useTranslation();
|
|
10
|
+
const theme = useTheme();
|
|
11
|
+
const styles = getStyles(theme);
|
|
12
|
+
|
|
13
|
+
const changeLanguage = (lng: string) => {
|
|
14
|
+
i18n.changeLanguage(lng);
|
|
15
|
+
StorageHelper.saveItem(StorageHelper.STORAGE_KEYS.LANGUAGE, lng);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<View style={styles.container}>
|
|
20
|
+
<TouchableOpacity
|
|
21
|
+
style={[styles.button, i18n.language === 'en' && styles.activeButton]}
|
|
22
|
+
onPress={() => changeLanguage('en')}
|
|
23
|
+
>
|
|
24
|
+
<Text
|
|
25
|
+
style={[
|
|
26
|
+
styles.buttonText,
|
|
27
|
+
i18n.language === 'en' && styles.activeButtonText,
|
|
28
|
+
]}
|
|
29
|
+
>
|
|
30
|
+
🇬🇧 English
|
|
31
|
+
</Text>
|
|
32
|
+
</TouchableOpacity>
|
|
33
|
+
<TouchableOpacity
|
|
34
|
+
style={[styles.button, i18n.language === 'hi' && styles.activeButton]}
|
|
35
|
+
onPress={() => changeLanguage('hi')}
|
|
36
|
+
>
|
|
37
|
+
<Text
|
|
38
|
+
style={[
|
|
39
|
+
styles.buttonText,
|
|
40
|
+
i18n.language === 'hi' && styles.activeButtonText,
|
|
41
|
+
]}
|
|
42
|
+
>
|
|
43
|
+
🇮🇳 हिंदी
|
|
44
|
+
</Text>
|
|
45
|
+
</TouchableOpacity>
|
|
46
|
+
</View>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default LanguageSwitcher;
|
|
51
|
+
|
|
52
|
+
const getStyles = ({ colors }: ThemeType) =>
|
|
53
|
+
StyleSheet.create({
|
|
54
|
+
container: {
|
|
55
|
+
flexDirection: 'row',
|
|
56
|
+
gap: 12,
|
|
57
|
+
marginBottom: 20,
|
|
58
|
+
},
|
|
59
|
+
button: {
|
|
60
|
+
flex: 1,
|
|
61
|
+
paddingVertical: 10,
|
|
62
|
+
paddingHorizontal: 16,
|
|
63
|
+
borderRadius: 8,
|
|
64
|
+
borderWidth: 1,
|
|
65
|
+
borderColor: colors.primary,
|
|
66
|
+
backgroundColor: 'transparent',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
},
|
|
69
|
+
activeButton: {
|
|
70
|
+
backgroundColor: colors.primary,
|
|
71
|
+
},
|
|
72
|
+
buttonText: {
|
|
73
|
+
fontSize: 14,
|
|
74
|
+
fontWeight: '600',
|
|
75
|
+
color: colors.primary,
|
|
76
|
+
},
|
|
77
|
+
activeButtonText: {
|
|
78
|
+
color: colors.white,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
TextInput as RNTextInput,
|
|
4
|
+
TextInputProps,
|
|
5
|
+
View,
|
|
6
|
+
Text,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { useTheme } from '../context/ThemeContext';
|
|
10
|
+
import { ThemeType } from '../theme/Colors';
|
|
11
|
+
|
|
12
|
+
interface CustomTextInputProps extends TextInputProps {
|
|
13
|
+
label?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
touched?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TextInput: React.FC<CustomTextInputProps> = ({
|
|
19
|
+
label,
|
|
20
|
+
error,
|
|
21
|
+
touched,
|
|
22
|
+
style,
|
|
23
|
+
...props
|
|
24
|
+
}) => {
|
|
25
|
+
const theme = useTheme();
|
|
26
|
+
const styles = getStyles(theme);
|
|
27
|
+
|
|
28
|
+
const hasError = touched && error;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<View style={styles.container}>
|
|
32
|
+
{label && <Text style={styles.label}>{label}</Text>}
|
|
33
|
+
<RNTextInput
|
|
34
|
+
style={[styles.input, hasError && styles.inputError, style]}
|
|
35
|
+
placeholderTextColor={theme.colors.textColor + '60'}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
{hasError && <Text style={styles.errorText}>{error}</Text>}
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default TextInput;
|
|
44
|
+
|
|
45
|
+
const getStyles = ({ colors }: ThemeType) =>
|
|
46
|
+
StyleSheet.create({
|
|
47
|
+
container: {
|
|
48
|
+
marginBottom: 20,
|
|
49
|
+
},
|
|
50
|
+
label: {
|
|
51
|
+
fontSize: 14,
|
|
52
|
+
fontWeight: '600',
|
|
53
|
+
color: colors.textColor,
|
|
54
|
+
marginBottom: 8,
|
|
55
|
+
},
|
|
56
|
+
input: {
|
|
57
|
+
backgroundColor: colors.backgroundColor,
|
|
58
|
+
borderWidth: 1,
|
|
59
|
+
borderColor: colors.textColor + '30',
|
|
60
|
+
borderRadius: 12,
|
|
61
|
+
padding: 16,
|
|
62
|
+
fontSize: 16,
|
|
63
|
+
color: colors.textColor,
|
|
64
|
+
},
|
|
65
|
+
inputError: {
|
|
66
|
+
borderColor: colors.primary,
|
|
67
|
+
},
|
|
68
|
+
errorText: {
|
|
69
|
+
color: colors.primary,
|
|
70
|
+
fontSize: 12,
|
|
71
|
+
marginTop: 4,
|
|
72
|
+
marginLeft: 4,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
+
import { useTheme } from '../context/ThemeContext';
|
|
4
|
+
import { ThemeType } from '../theme/Colors';
|
|
5
|
+
|
|
6
|
+
const ThemeSwitcher = () => {
|
|
7
|
+
const { colors, currentTheme, toggleTheme } = useTheme();
|
|
8
|
+
const styles = getStyles({ colors });
|
|
9
|
+
|
|
10
|
+
const isDark = currentTheme === 'dark';
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<View style={styles.container}>
|
|
14
|
+
<TouchableOpacity
|
|
15
|
+
style={[styles.button, !isDark && styles.activeButton]}
|
|
16
|
+
onPress={() => !isDark || toggleTheme()}
|
|
17
|
+
>
|
|
18
|
+
<Text style={[styles.buttonText, !isDark && styles.activeButtonText]}>
|
|
19
|
+
☀️ Light
|
|
20
|
+
</Text>
|
|
21
|
+
</TouchableOpacity>
|
|
22
|
+
<TouchableOpacity
|
|
23
|
+
style={[styles.button, isDark && styles.activeButton]}
|
|
24
|
+
onPress={() => isDark || toggleTheme()}
|
|
25
|
+
>
|
|
26
|
+
<Text style={[styles.buttonText, isDark && styles.activeButtonText]}>
|
|
27
|
+
🌙 Dark
|
|
28
|
+
</Text>
|
|
29
|
+
</TouchableOpacity>
|
|
30
|
+
</View>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default ThemeSwitcher;
|
|
35
|
+
|
|
36
|
+
const getStyles = ({ colors }: ThemeType) =>
|
|
37
|
+
StyleSheet.create({
|
|
38
|
+
container: {
|
|
39
|
+
flexDirection: 'row',
|
|
40
|
+
gap: 12,
|
|
41
|
+
},
|
|
42
|
+
button: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
paddingVertical: 10,
|
|
45
|
+
paddingHorizontal: 16,
|
|
46
|
+
borderRadius: 8,
|
|
47
|
+
borderWidth: 1,
|
|
48
|
+
borderColor: colors.primary,
|
|
49
|
+
backgroundColor: 'transparent',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
},
|
|
52
|
+
activeButton: {
|
|
53
|
+
backgroundColor: colors.primary,
|
|
54
|
+
},
|
|
55
|
+
buttonText: {
|
|
56
|
+
fontSize: 14,
|
|
57
|
+
fontWeight: '600',
|
|
58
|
+
color: colors.primary,
|
|
59
|
+
},
|
|
60
|
+
activeButtonText: {
|
|
61
|
+
color: colors.white,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { userMockData } from '../mock';
|
|
2
|
+
import Routes from '../navigation/routes';
|
|
3
|
+
import { resetAndNavigate } from '../utils/navigationUtils';
|
|
4
|
+
import StorageHelper from '../utils/storageHelper';
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
FC,
|
|
8
|
+
ReactNode,
|
|
9
|
+
useCallback,
|
|
10
|
+
useContext,
|
|
11
|
+
useEffect,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
14
|
+
|
|
15
|
+
type UserType = typeof userMockData;
|
|
16
|
+
|
|
17
|
+
type AuthContextType = {
|
|
18
|
+
user?: UserType;
|
|
19
|
+
updateUser: (user: UserType) => void;
|
|
20
|
+
handleLogout: () => void;
|
|
21
|
+
isUserLoggedIn: () => boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
25
|
+
|
|
26
|
+
type AuthProviderProps = {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
};
|
|
29
|
+
export const AuthProvider: FC<AuthProviderProps> = ({ children }) => {
|
|
30
|
+
const [user, setUser] = useState<UserType>();
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const userItem = StorageHelper.getItem(StorageHelper.STORAGE_KEYS.USER);
|
|
34
|
+
if (userItem) {
|
|
35
|
+
setUser(JSON.parse(userItem));
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const isUserLoggedIn = useCallback(() => {
|
|
40
|
+
const userItem = StorageHelper.getItem(StorageHelper.STORAGE_KEYS.USER);
|
|
41
|
+
if (userItem) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const updateUser = useCallback((_user: UserType) => {
|
|
48
|
+
setUser(_user);
|
|
49
|
+
StorageHelper.saveItem(
|
|
50
|
+
StorageHelper.STORAGE_KEYS.USER,
|
|
51
|
+
JSON.stringify(_user),
|
|
52
|
+
);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const handleLogout = useCallback(() => {
|
|
56
|
+
StorageHelper.removeItem(StorageHelper.STORAGE_KEYS.USER);
|
|
57
|
+
setUser(undefined);
|
|
58
|
+
resetAndNavigate(Routes.AuthStack);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<AuthContext.Provider
|
|
63
|
+
value={{ user, updateUser, handleLogout, isUserLoggedIn }}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</AuthContext.Provider>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const useAuth = () =>
|
|
71
|
+
useContext(AuthContext) ?? {
|
|
72
|
+
user: undefined,
|
|
73
|
+
updateUser: () => {},
|
|
74
|
+
handleLogout: () => {},
|
|
75
|
+
isUserLoggedIn: () => false,
|
|
76
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
FC,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { darkTheme, lightTheme, ThemeType } from '../theme/Colors';
|
|
11
|
+
import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
12
|
+
|
|
13
|
+
export type ThemeContextType = {
|
|
14
|
+
colors: ThemeType['colors'];
|
|
15
|
+
currentTheme?: 'dark' | 'light';
|
|
16
|
+
toggleTheme: () => void;
|
|
17
|
+
safeAreaInsets: EdgeInsets;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const ThemeContext = createContext<ThemeContextType | null>(null);
|
|
21
|
+
|
|
22
|
+
type ThemeProviderProps = {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const ThemeProvider: FC<ThemeProviderProps> = ({ children }) => {
|
|
27
|
+
const safeAreaInsets = useSafeAreaInsets();
|
|
28
|
+
const [currentTheme, setCurrentTheme] =
|
|
29
|
+
useState<ThemeContextType['currentTheme']>();
|
|
30
|
+
const [colors, setColors] = useState<ThemeContextType['colors']>(
|
|
31
|
+
lightTheme.colors,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (currentTheme === 'dark') {
|
|
36
|
+
setColors(darkTheme.colors);
|
|
37
|
+
} else {
|
|
38
|
+
setColors(lightTheme.colors);
|
|
39
|
+
}
|
|
40
|
+
}, [currentTheme]);
|
|
41
|
+
|
|
42
|
+
const toggleTheme = useCallback(() => {
|
|
43
|
+
setCurrentTheme(prev => (prev === 'dark' ? 'light' : 'dark'));
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<ThemeContext.Provider
|
|
48
|
+
value={{
|
|
49
|
+
currentTheme,
|
|
50
|
+
colors,
|
|
51
|
+
toggleTheme,
|
|
52
|
+
safeAreaInsets,
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{children}
|
|
56
|
+
</ThemeContext.Provider>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const defaultThemeContext: ThemeContextType = {
|
|
61
|
+
colors: lightTheme.colors,
|
|
62
|
+
currentTheme: 'light',
|
|
63
|
+
toggleTheme: () => {},
|
|
64
|
+
safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const useTheme = () => useContext(ThemeContext) ?? defaultThemeContext;
|