@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.
Files changed (91) hide show
  1. package/MMTemplate/.bundle/config +2 -0
  2. package/MMTemplate/.eslintrc.js +4 -0
  3. package/MMTemplate/.prettierrc.js +5 -0
  4. package/MMTemplate/.watchmanconfig +1 -0
  5. package/MMTemplate/App.tsx +41 -0
  6. package/MMTemplate/Gemfile +16 -0
  7. package/MMTemplate/README.md +109 -0
  8. package/MMTemplate/__tests__/App.test.tsx +13 -0
  9. package/MMTemplate/_gitignore +79 -0
  10. package/MMTemplate/android/app/build.gradle +119 -0
  11. package/MMTemplate/android/app/debug.keystore +0 -0
  12. package/MMTemplate/android/app/proguard-rules.pro +10 -0
  13. package/MMTemplate/android/app/src/main/AndroidManifest.xml +27 -0
  14. package/MMTemplate/android/app/src/main/java/com/mmtemplate/MainActivity.kt +22 -0
  15. package/MMTemplate/android/app/src/main/java/com/mmtemplate/MainApplication.kt +27 -0
  16. package/MMTemplate/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  17. package/MMTemplate/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  18. package/MMTemplate/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  19. package/MMTemplate/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  20. package/MMTemplate/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  21. package/MMTemplate/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  22. package/MMTemplate/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  23. package/MMTemplate/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  24. package/MMTemplate/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  25. package/MMTemplate/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  26. package/MMTemplate/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  27. package/MMTemplate/android/app/src/main/res/values/strings.xml +3 -0
  28. package/MMTemplate/android/app/src/main/res/values/styles.xml +9 -0
  29. package/MMTemplate/android/build.gradle +21 -0
  30. package/MMTemplate/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  31. package/MMTemplate/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  32. package/MMTemplate/android/gradle.properties +44 -0
  33. package/MMTemplate/android/gradlew +251 -0
  34. package/MMTemplate/android/gradlew.bat +99 -0
  35. package/MMTemplate/android/settings.gradle +6 -0
  36. package/MMTemplate/app.json +4 -0
  37. package/MMTemplate/babel.config.js +8 -0
  38. package/MMTemplate/index.js +9 -0
  39. package/MMTemplate/ios/.xcode.env +11 -0
  40. package/MMTemplate/ios/MMTemplate/AppDelegate.swift +48 -0
  41. package/MMTemplate/ios/MMTemplate/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  42. package/MMTemplate/ios/MMTemplate/Images.xcassets/Contents.json +6 -0
  43. package/MMTemplate/ios/MMTemplate/Info.plist +55 -0
  44. package/MMTemplate/ios/MMTemplate/LaunchScreen.storyboard +47 -0
  45. package/MMTemplate/ios/MMTemplate/PrivacyInfo.xcprivacy +37 -0
  46. package/MMTemplate/ios/MMTemplate.xcodeproj/project.pbxproj +480 -0
  47. package/MMTemplate/ios/MMTemplate.xcodeproj/xcshareddata/xcschemes/MMTemplate.xcscheme +88 -0
  48. package/MMTemplate/ios/Podfile +38 -0
  49. package/MMTemplate/jest.config.js +3 -0
  50. package/MMTemplate/metro.config.js +11 -0
  51. package/MMTemplate/package.json +55 -0
  52. package/MMTemplate/src/assets/images/index.ts +4 -0
  53. package/MMTemplate/src/assets/images/logo-dark.png +0 -0
  54. package/MMTemplate/src/assets/images/logo.png +0 -0
  55. package/MMTemplate/src/components/AnimationView.tsx +113 -0
  56. package/MMTemplate/src/components/CustomToast.tsx +46 -0
  57. package/MMTemplate/src/components/FeatureItem.tsx +59 -0
  58. package/MMTemplate/src/components/FullScreenContainer.tsx +60 -0
  59. package/MMTemplate/src/components/InfoCard.tsx +61 -0
  60. package/MMTemplate/src/components/LanguageSwitcher.tsx +80 -0
  61. package/MMTemplate/src/components/TextInput.tsx +74 -0
  62. package/MMTemplate/src/components/ThemeSwitcher.tsx +63 -0
  63. package/MMTemplate/src/context/AuthContext.tsx +76 -0
  64. package/MMTemplate/src/context/ThemeContext.tsx +67 -0
  65. package/MMTemplate/src/locales/en.json +83 -0
  66. package/MMTemplate/src/locales/hi.json +83 -0
  67. package/MMTemplate/src/mock/index.ts +5 -0
  68. package/MMTemplate/src/navigation/AppNavigator.tsx +45 -0
  69. package/MMTemplate/src/navigation/AppStack.tsx +27 -0
  70. package/MMTemplate/src/navigation/AuthCheck.tsx +50 -0
  71. package/MMTemplate/src/navigation/AuthStack.tsx +24 -0
  72. package/MMTemplate/src/navigation/MainTab.tsx +50 -0
  73. package/MMTemplate/src/navigation/routes.ts +17 -0
  74. package/MMTemplate/src/screens/HomeScreen.tsx +296 -0
  75. package/MMTemplate/src/screens/LoginScreen.tsx +199 -0
  76. package/MMTemplate/src/screens/ProfileScreen.tsx +171 -0
  77. package/MMTemplate/src/screens/SettingsScreen.tsx +96 -0
  78. package/MMTemplate/src/screens/WelcomeScreen.tsx +215 -0
  79. package/MMTemplate/src/theme/Colors.ts +25 -0
  80. package/MMTemplate/src/types/components.types.ts +13 -0
  81. package/MMTemplate/src/types/navigation.types.ts +35 -0
  82. package/MMTemplate/src/utils/i18n.ts +28 -0
  83. package/MMTemplate/src/utils/navigationUtils.ts +72 -0
  84. package/MMTemplate/src/utils/storageHelper.ts +51 -0
  85. package/MMTemplate/src/utils/utilsHelper.ts +34 -0
  86. package/MMTemplate/src/utils/validationSchemas.ts +58 -0
  87. package/MMTemplate/tsconfig.json +8 -0
  88. package/README.md +108 -0
  89. package/package.json +22 -0
  90. package/script.js +62 -0
  91. package/template.config.js +11 -0
@@ -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;