@fadyshawky/react-native-magic 1.0.8 → 1.0.9

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 (60) hide show
  1. package/.vscode/settings.json +7 -0
  2. package/package.json +1 -1
  3. package/template/App.tsx +30 -9
  4. package/template/package-lock.json +170 -123
  5. package/template/package.json +1 -0
  6. package/template/src/common/ImageResources.g.ts +3 -5
  7. package/template/src/common/components/Background.tsx +66 -28
  8. package/template/src/common/components/Cards.tsx +116 -0
  9. package/template/src/common/components/Container.tsx +145 -0
  10. package/template/src/common/components/FlatListWrapper.tsx +1 -0
  11. package/template/src/common/components/ImageCropPickerButton.tsx +1 -1
  12. package/template/src/common/components/OTPInput.tsx +107 -0
  13. package/template/src/common/components/PhotoTakingButton.tsx +1 -4
  14. package/template/src/common/components/PrimaryButton.tsx +171 -157
  15. package/template/src/common/components/RTLAwareText.tsx +42 -0
  16. package/template/src/common/components/RTLAwareView.tsx +179 -0
  17. package/template/src/common/components/RadioButton.tsx +1 -3
  18. package/template/src/common/components/RadioIcon.tsx +1 -2
  19. package/template/src/common/components/SearchBar.tsx +179 -0
  20. package/template/src/common/components/Separator.tsx +7 -4
  21. package/template/src/common/components/TouchablePlatform.tsx +1 -3
  22. package/template/src/common/components/TryAgain.tsx +3 -3
  23. package/template/src/common/helpers/inAppReviewHelper.ts +0 -1
  24. package/template/src/common/helpers/stringsHelpers.ts +10 -0
  25. package/template/src/common/hooks/useFlatListActions.ts +1 -1
  26. package/template/src/common/localization/LocalizationProvider.tsx +152 -0
  27. package/template/src/common/localization/README.md +488 -0
  28. package/template/src/common/localization/localization.ts +12 -0
  29. package/template/src/common/localization/translations/profileLocalization.ts +24 -0
  30. package/template/src/common/validations/errorValidations.ts +1 -6
  31. package/template/src/common/validations/examples/TextInputWithValidation.tsx +229 -0
  32. package/template/src/common/validations/index.ts +28 -0
  33. package/template/src/common/validations/regex.js +83 -0
  34. package/template/src/common/validations/regexValidator.ts +240 -0
  35. package/template/src/common/validations/validationConstants.ts +2 -2
  36. package/template/src/core/api/errorHandler.ts +39 -0
  37. package/template/src/core/api/responseHandlers.ts +1 -26
  38. package/template/src/core/api/serverHeaders.ts +13 -23
  39. package/template/src/core/store/app/appSlice.ts +1 -2
  40. package/template/src/core/theme/ThemeProvider.tsx +63 -0
  41. package/template/src/core/theme/colors.ts +31 -42
  42. package/template/src/core/theme/commonConsts.ts +1 -1
  43. package/template/src/core/theme/commonStyles.ts +267 -210
  44. package/template/src/core/theme/fonts.ts +17 -1
  45. package/template/src/core/theme/scaling.ts +101 -0
  46. package/template/src/core/theme/themes.ts +214 -0
  47. package/template/src/core/theme/types.ts +51 -0
  48. package/template/src/navigation/AuthStack.tsx +25 -30
  49. package/template/src/navigation/HeaderComponents.tsx +18 -58
  50. package/template/src/navigation/MainNavigation.tsx +5 -6
  51. package/template/src/navigation/MainStack.tsx +3 -28
  52. package/template/src/navigation/RootNavigation.tsx +1 -7
  53. package/template/src/navigation/TabBar.tsx +2 -2
  54. package/template/src/navigation/TopTabBar.tsx +1 -1
  55. package/template/src/screens/Login/Login.tsx +3 -3
  56. package/template/src/screens/home/components/CarouselSection.tsx +7 -8
  57. package/template/src/screens/home/components/FeaturedCarousel.tsx +5 -6
  58. package/template/src/screens/registration/RegistrationScreen.tsx +2 -2
  59. package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +2 -2
  60. package/template/src/utils/stringBuilder.js +25 -0
@@ -0,0 +1,179 @@
1
+ import React from 'react';
2
+ import {
3
+ I18nManager,
4
+ KeyboardTypeOptions,
5
+ Platform,
6
+ StyleProp,
7
+ StyleSheet,
8
+ TextInput,
9
+ TextInputProps,
10
+ TextStyle,
11
+ TouchableOpacity,
12
+ ViewStyle,
13
+ } from 'react-native';
14
+ import Icon from 'react-native-vector-icons/Ionicons';
15
+ import {CommonSizes} from '../../core/theme/commonSizes';
16
+ import {createThemedStyles} from '../../core/theme/commonStyles';
17
+ import {useTheme} from '../../core/theme/ThemeProvider';
18
+ import {Languages} from '../localization/localization';
19
+ import {
20
+ useLocalization,
21
+ useRTL,
22
+ useTranslation,
23
+ } from '../localization/LocalizationProvider';
24
+ import {RTLAwareView} from './RTLAwareView';
25
+
26
+ interface SearchBarProps {
27
+ value: string;
28
+ onChangeText: (text: string) => void;
29
+ placeholder?: string;
30
+ style?: StyleProp<ViewStyle>;
31
+ inputStyle?: StyleProp<TextStyle>;
32
+ onClear?: () => void;
33
+ autoFocus?: boolean;
34
+ }
35
+
36
+ export const SearchBar: React.FC<SearchBarProps> = ({
37
+ value,
38
+ onChangeText,
39
+ placeholder,
40
+ style,
41
+ inputStyle,
42
+ onClear,
43
+ autoFocus = false,
44
+ }) => {
45
+ const {theme} = useTheme();
46
+ const t = useTranslation();
47
+ const isRTL = useRTL();
48
+ const {currentLanguage} = useLocalization();
49
+ const isArabic = currentLanguage === Languages.ar;
50
+
51
+ // Create a ref to the TextInput to control it programmatically if needed
52
+ const inputRef = React.useRef<TextInput>(null);
53
+
54
+ const handleClear = () => {
55
+ onChangeText('');
56
+ if (onClear) {
57
+ onClear();
58
+ }
59
+ };
60
+
61
+ const containerStyle: ViewStyle = {
62
+ backgroundColor: `${theme.colors.white}20`,
63
+ borderColor: theme.colors.mutedLavender30,
64
+ };
65
+
66
+ // Set keyboard language specific properties
67
+ const getKeyboardProps = (): Partial<TextInputProps> => {
68
+ if (Platform.OS === 'ios') {
69
+ // For iOS, we use primaryLanguage to hint at the keyboard language
70
+ return {
71
+ keyboardType: 'default' as KeyboardTypeOptions,
72
+ textContentType: 'none',
73
+ // primaryLanguage property is iOS specific but not directly supported by RN types
74
+ // This custom property helps indicate preferred keyboard language to iOS
75
+ ...(isArabic ? {primaryLanguage: 'ar'} : {primaryLanguage: 'en'}),
76
+ };
77
+ } else {
78
+ // Android keyboard language is system controlled, we can only hint at it
79
+ return {
80
+ keyboardType: 'default' as KeyboardTypeOptions,
81
+ };
82
+ }
83
+ };
84
+
85
+ // Switch to appropriate keyboard when language changes
86
+ React.useEffect(() => {
87
+ if (Platform.OS === 'android') {
88
+ // On Android, the keyboard language is managed system-wide
89
+ // We can only ensure our TextInput respects RTL settings
90
+ const shouldBeRTL = isArabic;
91
+ if (I18nManager.isRTL !== shouldBeRTL) {
92
+ }
93
+ }
94
+
95
+ // Reset input when language changes to ensure keyboard updates
96
+ if (inputRef.current && value.length > 0) {
97
+ // Force the keyboard to reload with new language by blurring/focusing
98
+ inputRef.current.blur();
99
+ setTimeout(() => {
100
+ if (inputRef.current) {
101
+ inputRef.current.focus();
102
+ }
103
+ }, 100);
104
+ }
105
+ }, [currentLanguage, value]);
106
+
107
+ return (
108
+ <RTLAwareView
109
+ style={[
110
+ styles.container,
111
+ containerStyle,
112
+ style as ViewStyle,
113
+ {
114
+ backgroundColor: theme.colors.surface,
115
+ ...createThemedStyles(theme).dropShadow,
116
+ },
117
+ ]}>
118
+ <Icon
119
+ name="search"
120
+ size={20}
121
+ color={theme.colors.mutedLavender}
122
+ style={styles.searchIcon}
123
+ />
124
+ <TextInput
125
+ ref={inputRef}
126
+ {...getKeyboardProps()}
127
+ style={[
128
+ styles.input,
129
+ theme.text.SearchBar,
130
+ {
131
+ textAlign: isRTL ? 'right' : 'left',
132
+ },
133
+ inputStyle as TextStyle,
134
+ ]}
135
+ placeholder={placeholder || t('search', 'common')}
136
+ placeholderTextColor={`${theme.colors.mutedLavender}80`}
137
+ value={value}
138
+ onChangeText={onChangeText}
139
+ autoCapitalize="none"
140
+ autoComplete="off"
141
+ autoCorrect={false}
142
+ autoFocus={autoFocus}
143
+ />
144
+ {value.length > 0 && (
145
+ <TouchableOpacity onPressIn={handleClear} style={styles.clearButton}>
146
+ <Icon
147
+ name="close-circle"
148
+ size={20}
149
+ color={theme.colors.mutedLavender}
150
+ />
151
+ </TouchableOpacity>
152
+ )}
153
+ </RTLAwareView>
154
+ );
155
+ };
156
+
157
+ const styles = StyleSheet.create({
158
+ container: {
159
+ flexDirection: 'row',
160
+ alignItems: 'center',
161
+ height: 48,
162
+ borderRadius: 16,
163
+ borderWidth: 1,
164
+ paddingHorizontal: CommonSizes.spacing.medium,
165
+ marginHorizontal: CommonSizes.spacing.large,
166
+ marginBottom: CommonSizes.spacing.large,
167
+ },
168
+ input: {
169
+ flex: 1,
170
+ paddingVertical: CommonSizes.spacing.medium,
171
+ paddingHorizontal: CommonSizes.spacing.small,
172
+ },
173
+ searchIcon: {
174
+ marginHorizontal: CommonSizes.spacing.small,
175
+ },
176
+ clearButton: {
177
+ padding: CommonSizes.spacing.small,
178
+ },
179
+ });
@@ -1,24 +1,27 @@
1
1
  import React, {FC, useMemo} from 'react';
2
2
  import {StyleSheet, View, ViewStyle} from 'react-native';
3
- import {Colors} from '../../core/theme/colors';
4
3
  import {hairlineWidth} from '../../core/theme/commonConsts';
5
4
  import {CommonSizes} from '../../core/theme/commonSizes';
6
-
5
+ import {useTheme} from '../../core/theme/ThemeProvider';
7
6
  interface IProps {
8
7
  isFull?: boolean;
9
8
  }
10
9
 
11
10
  export const Separator: FC<IProps> = ({isFull = true}) => {
11
+ const {theme} = useTheme();
12
12
  const containerStyle = useMemo(() => {
13
13
  return isFull ? styles.fullContainer : styles.container;
14
14
  }, [isFull]);
15
15
 
16
- return <View style={containerStyle} />;
16
+ return (
17
+ <View
18
+ style={[containerStyle, {backgroundColor: theme.colors.indigoBlue}]}
19
+ />
20
+ );
17
21
  };
18
22
 
19
23
  const sharedStyle: ViewStyle = {
20
24
  height: hairlineWidth,
21
- backgroundColor: Colors.gray,
22
25
  };
23
26
 
24
27
  const styles = StyleSheet.create({
@@ -7,7 +7,6 @@ import {
7
7
  PressableStateCallbackType,
8
8
  ViewStyle,
9
9
  } from 'react-native';
10
- import {Colors} from '../../core/theme/colors';
11
10
  import {isAndroid} from '../../core/theme/commonConsts';
12
11
 
13
12
  interface IProps extends PressableProps {
@@ -16,7 +15,7 @@ interface IProps extends PressableProps {
16
15
  }
17
16
 
18
17
  export const TouchablePlatform: FC<IProps> = memo(
19
- ({children, highlightColor = Colors.white, style, ...props}) => {
18
+ ({children, highlightColor, style, ...props}) => {
20
19
  const pressableStyle = useCallback(
21
20
  (state: PressableStateCallbackType) => {
22
21
  if (isAndroid) {
@@ -56,6 +55,5 @@ export const TouchablePlatform: FC<IProps> = memo(
56
55
  );
57
56
 
58
57
  const androidRippleConfig: PressableAndroidRippleConfig = {
59
- color: Colors.white,
60
58
  borderless: false,
61
59
  };
@@ -6,7 +6,7 @@ import {
6
6
  TouchableOpacity,
7
7
  View,
8
8
  } from 'react-native';
9
- import {Colors, NewColors} from '../../core/theme/colors';
9
+ import {Colors} from '../../core/theme/colors';
10
10
  import {CommonSizes} from '../../core/theme/commonSizes';
11
11
  import {CommonStyles} from '../../core/theme/commonStyles';
12
12
  import {localization} from '../localization/localization';
@@ -23,7 +23,7 @@ export const TryAgain: FC<IProps> = memo(
23
23
  style={{...CommonStyles.flexCenter, backgroundColor: 'transparent'}}>
24
24
  <Text style={styles.title}>{errorText}</Text>
25
25
  {onPress != null && (
26
- <TouchableOpacity onPress={onPress}>
26
+ <TouchableOpacity onPressIn={onPress}>
27
27
  <Text style={styles.description}>
28
28
  {localization.errors.tryAgain}
29
29
  </Text>
@@ -42,7 +42,7 @@ const styles = StyleSheet.create({
42
42
  } as TextStyle,
43
43
  description: {
44
44
  ...CommonStyles.normalText,
45
- color: NewColors.blueNormalActive,
45
+ color: Colors.blueNormalActive,
46
46
  textAlign: 'center',
47
47
  textDecorationLine: 'underline',
48
48
  } as TextStyle,
@@ -25,7 +25,6 @@ export function showInAppReview(
25
25
  failAction?.(error);
26
26
  });
27
27
  } else {
28
- console.error('Review is not available for this device/account');
29
28
  onReviewNotAvailable?.();
30
29
  }
31
30
  }
@@ -13,3 +13,13 @@ export function removeEmojis(text: string): string {
13
13
  '',
14
14
  );
15
15
  }
16
+
17
+ export function normalizeAndTrimWhitespace(text: string): string {
18
+ // 1. Remove leading whitespace with trimStart()
19
+ // 2. Remove trailing whitespace with trimEnd()
20
+ // 3. Replace multiple whitespaces (including tabs, newlines) with a single space
21
+ return text
22
+ ?.trimStart() // Remove leading whitespace
23
+ ?.trimEnd() // Remove trailing whitespace
24
+ ?.replace(/\s+/g, ' '); // Replace multiple whitespaces with single space
25
+ }
@@ -1,5 +1,5 @@
1
1
  import {useCallback} from 'react';
2
- import {LoadState} from '../../types';
2
+ import {LoadState} from '../../../types';
3
3
  import {AsyncThunk} from '@reduxjs/toolkit';
4
4
  import {useAppDispatch} from '../../core/store/reduxHelpers';
5
5
 
@@ -0,0 +1,152 @@
1
+ import React, {createContext, useContext, useState, useEffect} from 'react';
2
+ import {I18nManager, Platform, NativeModules} from 'react-native';
3
+ import RNRestart from 'react-native-restart';
4
+ import {
5
+ localization,
6
+ Languages,
7
+ setLanguage as setLanguageUtil,
8
+ getInterfaceLanguage,
9
+ DEFAULT_LANGUAGE,
10
+ } from './localization';
11
+ import {useAppDispatch, useAppSelector} from '../../core/store/reduxHelpers';
12
+ import {setLanguage as setLanguageAction} from '../../core/store/app/appSlice';
13
+
14
+ interface LocalizationContextType {
15
+ currentLanguage: Languages;
16
+ changeLanguage: (language: Languages) => void;
17
+ t: (key: string, section?: keyof typeof localization) => string;
18
+ isRTL: boolean;
19
+ }
20
+
21
+ const LocalizationContext = createContext<LocalizationContextType | undefined>(
22
+ undefined,
23
+ );
24
+
25
+ interface LocalizationProviderProps {
26
+ children: React.ReactNode;
27
+ initialLanguage?: Languages;
28
+ }
29
+
30
+ export const LocalizationProvider: React.FC<LocalizationProviderProps> = ({
31
+ children,
32
+ initialLanguage,
33
+ }) => {
34
+ const dispatch = useAppDispatch();
35
+ const {language: storedLanguage, isRTL: storedIsRTL} = useAppSelector(
36
+ state => state.app,
37
+ );
38
+
39
+ const [currentLanguage, setCurrentLanguage] = useState<Languages>(
40
+ storedLanguage ||
41
+ initialLanguage ||
42
+ (getInterfaceLanguage() as Languages) ||
43
+ DEFAULT_LANGUAGE,
44
+ );
45
+ const [isRTL, setIsRTL] = useState<boolean>(
46
+ storedIsRTL !== undefined ? storedIsRTL : currentLanguage === Languages.ar,
47
+ );
48
+
49
+ useEffect(() => {
50
+ setLanguageUtil(currentLanguage);
51
+ setIsRTL(currentLanguage === Languages.ar);
52
+
53
+ // Force app restart on Android when changing RTL/LTR
54
+ if (Platform.OS === 'android') {
55
+ const shouldBeRTL = currentLanguage === Languages.ar;
56
+ if (I18nManager.isRTL !== shouldBeRTL) {
57
+ I18nManager.allowRTL(shouldBeRTL);
58
+ I18nManager.forceRTL(shouldBeRTL);
59
+ }
60
+ }
61
+ }, [currentLanguage]);
62
+
63
+ const changeLanguage = (language: Languages) => {
64
+ // Only restart if the language is actually changing
65
+ if (language !== currentLanguage) {
66
+ setCurrentLanguage(language);
67
+ dispatch(setLanguageAction(language));
68
+
69
+ // Set RTL configuration before restart
70
+ const shouldBeRTL = language === Languages.ar;
71
+ if (I18nManager.isRTL !== shouldBeRTL) {
72
+ I18nManager.allowRTL(shouldBeRTL);
73
+ I18nManager.forceRTL(shouldBeRTL);
74
+
75
+ // Restart the app to apply RTL/LTR changes properly
76
+ // Small delay to ensure settings are applied
77
+ setTimeout(() => {
78
+ try {
79
+ // Check if RNRestart is available
80
+ if (RNRestart && typeof RNRestart.restart === 'function') {
81
+ RNRestart.restart();
82
+ } else if (Platform.OS === 'android') {
83
+ // Fallback for Android using DevSettings
84
+ const DevSettings = NativeModules.DevSettings;
85
+ if (DevSettings && DevSettings.reload) {
86
+ DevSettings.reload();
87
+ }
88
+ }
89
+ } catch (error) {
90
+ console.error('Failed to restart the app:', error);
91
+ }
92
+ }, 100);
93
+ }
94
+ }
95
+ };
96
+
97
+ // Helper function to get translations
98
+ const t = (
99
+ key: string,
100
+ section: keyof typeof localization = 'common',
101
+ ): string => {
102
+ try {
103
+ const keys = key.split('.');
104
+ let result: any = localization[section];
105
+
106
+ // If the key has dot notation (e.g., 'registration.title'), navigate through the object
107
+ if (keys.length > 1) {
108
+ for (const k of keys) {
109
+ result = result[k];
110
+ }
111
+ return result || key;
112
+ }
113
+
114
+ // Simple key
115
+ return result[key] || key;
116
+ } catch (error) {
117
+ console.warn(
118
+ `Translation not found for key: ${key} in section: ${section}`,
119
+ );
120
+ return key;
121
+ }
122
+ };
123
+
124
+ return (
125
+ <LocalizationContext.Provider
126
+ value={{currentLanguage, changeLanguage, t, isRTL}}>
127
+ {children}
128
+ </LocalizationContext.Provider>
129
+ );
130
+ };
131
+
132
+ export const useLocalization = () => {
133
+ const context = useContext(LocalizationContext);
134
+ if (!context) {
135
+ throw new Error(
136
+ 'useLocalization must be used within a LocalizationProvider',
137
+ );
138
+ }
139
+ return context;
140
+ };
141
+
142
+ // Shorthand hook for translations
143
+ export const useTranslation = () => {
144
+ const {t} = useLocalization();
145
+ return t;
146
+ };
147
+
148
+ // Hook to get RTL status
149
+ export const useRTL = () => {
150
+ const {isRTL} = useLocalization();
151
+ return isRTL;
152
+ };