@umituz/react-native-design-system 2.3.0 → 2.3.2

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 (50) hide show
  1. package/package.json +16 -4
  2. package/src/atoms/AtomicBadge.tsx +121 -0
  3. package/src/atoms/AtomicInput.tsx +0 -1
  4. package/src/atoms/AtomicPicker.tsx +0 -1
  5. package/src/atoms/index.ts +8 -0
  6. package/src/atoms/picker/components/PickerChips.tsx +0 -1
  7. package/src/atoms/picker/components/PickerModal.tsx +1 -3
  8. package/src/atoms/picker/styles/pickerStyles.ts +1 -1
  9. package/src/device/domain/entities/Device.ts +207 -0
  10. package/src/device/domain/entities/DeviceMemoryUtils.ts +62 -0
  11. package/src/device/domain/entities/DeviceTypeUtils.ts +66 -0
  12. package/src/device/domain/entities/__tests__/DeviceMemoryUtils.test.ts +118 -0
  13. package/src/device/domain/entities/__tests__/DeviceTypeUtils.test.ts +104 -0
  14. package/src/device/domain/entities/__tests__/DeviceUtils.test.ts +167 -0
  15. package/src/device/index.ts +51 -0
  16. package/src/device/infrastructure/services/ApplicationInfoService.ts +86 -0
  17. package/src/device/infrastructure/services/DeviceCapabilityService.ts +60 -0
  18. package/src/device/infrastructure/services/DeviceIdService.ts +70 -0
  19. package/src/device/infrastructure/services/DeviceInfoService.ts +95 -0
  20. package/src/device/infrastructure/services/DeviceService.ts +104 -0
  21. package/src/device/infrastructure/services/PersistentDeviceIdService.ts +132 -0
  22. package/src/device/infrastructure/services/UserFriendlyIdService.ts +68 -0
  23. package/src/device/infrastructure/utils/__tests__/nativeModuleUtils.test.ts +158 -0
  24. package/src/device/infrastructure/utils/__tests__/stringUtils.test.ts +120 -0
  25. package/src/device/infrastructure/utils/nativeModuleUtils.ts +69 -0
  26. package/src/device/infrastructure/utils/stringUtils.ts +59 -0
  27. package/src/device/presentation/hooks/useAnonymousUser.ts +117 -0
  28. package/src/device/presentation/hooks/useDeviceInfo.ts +222 -0
  29. package/src/index.ts +4 -0
  30. package/src/molecules/ConfirmationModalContent.tsx +4 -4
  31. package/src/molecules/ConfirmationModalMain.tsx +1 -1
  32. package/src/molecules/ScreenHeader.tsx +2 -2
  33. package/src/molecules/confirmation-modal/components.tsx +1 -1
  34. package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +6 -7
  35. package/src/presentation/utils/variants/__tests__/core.test.ts +0 -1
  36. package/src/responsive/deviceDetection.ts +5 -5
  37. package/src/responsive/iPadBreakpoints.ts +55 -0
  38. package/src/responsive/iPadDetection.ts +48 -0
  39. package/src/responsive/iPadLayoutUtils.ts +95 -0
  40. package/src/responsive/iPadModalUtils.ts +98 -0
  41. package/src/responsive/index.ts +31 -0
  42. package/src/safe-area/__tests__/components/SafeAreaProvider.test.tsx +2 -2
  43. package/src/safe-area/__tests__/hooks/useContentSafeAreaPadding.test.tsx +2 -2
  44. package/src/safe-area/__tests__/hooks/useHeaderSafeAreaPadding.test.tsx +2 -2
  45. package/src/safe-area/__tests__/hooks/useSafeAreaInsets.test.tsx +2 -2
  46. package/src/safe-area/__tests__/hooks/useStatusBarSafeAreaPadding.test.tsx +2 -2
  47. package/src/safe-area/__tests__/integration/completeFlow.test.tsx +5 -4
  48. package/src/safe-area/__tests__/utils/testUtils.tsx +5 -4
  49. package/src/theme/infrastructure/stores/themeStore.ts +0 -2
  50. package/src/typography/presentation/utils/textColorUtils.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -18,6 +18,7 @@
18
18
  "./typography": "./src/typography/index.ts",
19
19
  "./responsive": "./src/responsive/index.ts",
20
20
  "./safe-area": "./src/safe-area/index.ts",
21
+ "./device": "./src/device/index.ts",
21
22
  "./package.json": "./package.json"
22
23
  },
23
24
  "scripts": {
@@ -60,7 +61,9 @@
60
61
  "react-native-reanimated": ">=3.16.0",
61
62
  "react-native-safe-area-context": ">=5.0.0",
62
63
  "react-native-svg": ">=15.0.0",
63
- "zustand": ">=5.0.0"
64
+ "zustand": ">=5.0.0",
65
+ "expo-device": ">=5.0.0",
66
+ "expo-application": ">=5.0.0"
64
67
  },
65
68
  "peerDependenciesMeta": {
66
69
  "expo-linear-gradient": {
@@ -71,6 +74,12 @@
71
74
  },
72
75
  "@gorhom/bottom-sheet": {
73
76
  "optional": true
77
+ },
78
+ "expo-device": {
79
+ "optional": true
80
+ },
81
+ "expo-application": {
82
+ "optional": true
74
83
  }
75
84
  },
76
85
  "devDependencies": {
@@ -95,7 +104,10 @@
95
104
  "react-native-safe-area-context": "^5.6.0",
96
105
  "react-native-svg": "15.12.1",
97
106
  "typescript": "~5.9.2",
98
- "zustand": "^5.0.2"
107
+ "zustand": "^5.0.2",
108
+ "expo-device": "~7.0.2",
109
+ "expo-application": "~5.9.1",
110
+ "expo-localization": "~16.0.1"
99
111
  },
100
112
  "publishConfig": {
101
113
  "access": "public"
@@ -105,4 +117,4 @@
105
117
  "README.md",
106
118
  "LICENSE"
107
119
  ]
108
- }
120
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * AtomicBadge Component
3
+ * Reusable badge for labels, status indicators, and tags
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet, type StyleProp, type ViewStyle, type TextStyle } from "react-native";
8
+ import { AtomicText } from "./AtomicText";
9
+ import { AtomicIcon, type IconName } from "./AtomicIcon";
10
+ import { useAppDesignTokens } from "../theme";
11
+
12
+ export type BadgeVariant = "primary" | "secondary" | "success" | "warning" | "error" | "info";
13
+ export type BadgeSize = "sm" | "md" | "lg";
14
+
15
+ export interface AtomicBadgeProps {
16
+ /** Badge text content */
17
+ text: string;
18
+ /** Visual variant */
19
+ variant?: BadgeVariant;
20
+ /** Size preset */
21
+ size?: BadgeSize;
22
+ /** Optional icon name (Ionicons) */
23
+ icon?: IconName;
24
+ /** Icon position */
25
+ iconPosition?: "left" | "right";
26
+ /** Custom container style */
27
+ style?: StyleProp<ViewStyle>;
28
+ /** Custom text style */
29
+ textStyle?: StyleProp<TextStyle>;
30
+ /** Test ID for testing */
31
+ testID?: string;
32
+ }
33
+
34
+ const SIZE_CONFIG = {
35
+ sm: { paddingH: 6, paddingV: 2, fontSize: 10, iconSize: 10, gap: 3, radius: 4 },
36
+ md: { paddingH: 8, paddingV: 4, fontSize: 11, iconSize: 12, gap: 4, radius: 6 },
37
+ lg: { paddingH: 12, paddingV: 6, fontSize: 13, iconSize: 14, gap: 5, radius: 8 },
38
+ };
39
+
40
+ export const AtomicBadge: React.FC<AtomicBadgeProps> = React.memo(({
41
+ text,
42
+ variant = "primary",
43
+ size = "md",
44
+ icon,
45
+ iconPosition = "left",
46
+ style,
47
+ textStyle,
48
+ testID,
49
+ }) => {
50
+ const tokens = useAppDesignTokens();
51
+ const sizeConfig = SIZE_CONFIG[size];
52
+
53
+ const getVariantColors = () => {
54
+ switch (variant) {
55
+ case "primary":
56
+ return { bg: tokens.colors.primaryLight, text: tokens.colors.primary };
57
+ case "secondary":
58
+ return { bg: tokens.colors.surfaceSecondary, text: tokens.colors.textSecondary };
59
+ case "success":
60
+ return { bg: tokens.colors.successLight, text: tokens.colors.success };
61
+ case "warning":
62
+ return { bg: tokens.colors.warningLight, text: tokens.colors.warning };
63
+ case "error":
64
+ return { bg: tokens.colors.errorLight, text: tokens.colors.error };
65
+ case "info":
66
+ return { bg: tokens.colors.infoLight, text: tokens.colors.info };
67
+ default:
68
+ return { bg: tokens.colors.primaryLight, text: tokens.colors.primary };
69
+ }
70
+ };
71
+
72
+ const colors = getVariantColors();
73
+
74
+ const containerStyle: StyleProp<ViewStyle> = [
75
+ styles.container,
76
+ {
77
+ backgroundColor: colors.bg,
78
+ paddingHorizontal: sizeConfig.paddingH,
79
+ paddingVertical: sizeConfig.paddingV,
80
+ borderRadius: sizeConfig.radius,
81
+ gap: sizeConfig.gap,
82
+ flexDirection: iconPosition === "right" ? "row-reverse" : "row",
83
+ },
84
+ style,
85
+ ];
86
+
87
+ return (
88
+ <View style={containerStyle} testID={testID}>
89
+ {icon && (
90
+ <AtomicIcon
91
+ name={icon}
92
+ customSize={sizeConfig.iconSize}
93
+ customColor={colors.text}
94
+ />
95
+ )}
96
+ <AtomicText
97
+ type="labelSmall"
98
+ style={[
99
+ {
100
+ color: colors.text,
101
+ fontSize: sizeConfig.fontSize,
102
+ fontWeight: "700",
103
+ },
104
+ textStyle,
105
+ ]}
106
+ >
107
+ {text}
108
+ </AtomicText>
109
+ </View>
110
+ );
111
+ });
112
+
113
+ AtomicBadge.displayName = "AtomicBadge";
114
+
115
+ const styles = StyleSheet.create({
116
+ container: {
117
+ alignSelf: "flex-start",
118
+ alignItems: "center",
119
+ justifyContent: "center",
120
+ },
121
+ });
@@ -107,7 +107,6 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
107
107
  isFocused,
108
108
  isPasswordVisible,
109
109
  characterCount,
110
- isAtMaxLength: _isAtMaxLength,
111
110
  setIsFocused,
112
111
  handleTextChange,
113
112
  togglePasswordVisibility,
@@ -80,7 +80,6 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
80
80
  searchable = false,
81
81
  clearable = false,
82
82
  autoClose = true,
83
- color: _color = 'primary',
84
83
  size = 'md',
85
84
  modalTitle,
86
85
  emptyMessage = 'No options available',
@@ -79,3 +79,11 @@ export {
79
79
  type SkeletonConfig,
80
80
  SKELETON_PATTERNS,
81
81
  } from './skeleton';
82
+
83
+ // Badge
84
+ export {
85
+ AtomicBadge,
86
+ type AtomicBadgeProps,
87
+ type BadgeVariant,
88
+ type BadgeSize,
89
+ } from './AtomicBadge';
@@ -26,7 +26,6 @@ interface PickerChipsProps {
26
26
  export const PickerChips: React.FC<PickerChipsProps> = React.memo(({
27
27
  selectedOptions,
28
28
  onRemoveChip,
29
- testID: _testID,
30
29
  }) => {
31
30
  const tokens = useAppDesignTokens();
32
31
 
@@ -59,7 +59,6 @@ interface PickerModalProps {
59
59
  export const PickerModal: React.FC<PickerModalProps> = React.memo(({
60
60
  visible,
61
61
  onClose,
62
- options: _options,
63
62
  selectedValues,
64
63
  onSelect,
65
64
  title,
@@ -67,7 +66,6 @@ export const PickerModal: React.FC<PickerModalProps> = React.memo(({
67
66
  searchQuery,
68
67
  onSearchChange,
69
68
  filteredOptions,
70
- multiple: _multiple = false,
71
69
  emptyMessage = 'No options available',
72
70
  searchPlaceholder = 'Search...',
73
71
  closeAccessibilityLabel = 'Close picker',
@@ -76,7 +74,7 @@ export const PickerModal: React.FC<PickerModalProps> = React.memo(({
76
74
  const tokens = useAppDesignTokens();
77
75
  const insets = useSafeAreaInsets();
78
76
 
79
- const modalOverlayStyles = getModalOverlayStyles(tokens);
77
+ const modalOverlayStyles = getModalOverlayStyles();
80
78
  const modalContainerStyles = getModalContainerStyles(tokens, 0);
81
79
  const modalHeaderStyles = getModalHeaderStyles(tokens);
82
80
  const modalTitleStyles = getModalTitleStyles(tokens);
@@ -110,7 +110,7 @@ export const getPickerErrorStyles = (tokens: DesignTokens): TextStyle => ({
110
110
  });
111
111
 
112
112
  // Modal styles
113
- export const getModalOverlayStyles = (_tokens: DesignTokens): ViewStyle => ({
113
+ export const getModalOverlayStyles = (): ViewStyle => ({
114
114
  flex: 1,
115
115
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
116
116
  justifyContent: 'flex-end',
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Device Domain - Core Entities
3
+ *
4
+ * This file defines core types and interfaces for device information.
5
+ * Handles device details, app info using expo-device and expo-application.
6
+ *
7
+ * @domain device
8
+ * @layer domain/entities
9
+ */
10
+
11
+ import type * as DeviceModule from 'expo-device';
12
+
13
+ /**
14
+ * Re-export Device types from expo-device
15
+ */
16
+ export type DeviceType = DeviceModule.DeviceType;
17
+
18
+ /**
19
+ * Device information interface
20
+ */
21
+ export interface DeviceInfo {
22
+ // Device identification
23
+ brand: string | null;
24
+ manufacturer: string | null;
25
+ modelName: string | null;
26
+ modelId: string | null;
27
+ deviceName: string | null;
28
+ deviceYearClass: number | null;
29
+
30
+ // Device type
31
+ deviceType: DeviceType | null;
32
+ isDevice: boolean;
33
+
34
+ // OS information
35
+ osName: string | null;
36
+ osVersion: string | null;
37
+ osBuildId: string | null;
38
+ platformApiLevel: number | null;
39
+
40
+ // Memory
41
+ totalMemory: number | null;
42
+
43
+ // Platform
44
+ platform: 'ios' | 'android' | 'web';
45
+
46
+ // Localization
47
+ timezone: string | null;
48
+ region: string | null;
49
+ }
50
+
51
+ /**
52
+ * Application information interface
53
+ */
54
+ export interface ApplicationInfo {
55
+ // App identification
56
+ applicationName: string;
57
+ applicationId: string;
58
+ nativeApplicationVersion: string | null;
59
+ nativeBuildVersion: string | null;
60
+
61
+ // Installation
62
+ installTime: Date | null;
63
+ lastUpdateTime: Date | null;
64
+
65
+ // Platform-specific
66
+ androidId: string | null; // Android only
67
+ iosIdForVendor: string | null; // iOS only
68
+ }
69
+
70
+ /**
71
+ * Combined device and app info
72
+ */
73
+ export interface SystemInfo {
74
+ device: DeviceInfo;
75
+ application: ApplicationInfo;
76
+ timestamp: number;
77
+ userId?: string;
78
+ }
79
+
80
+ /**
81
+ * Device constants
82
+ */
83
+ export const DEVICE_CONSTANTS = {
84
+ DEVICE_TYPE: {
85
+ UNKNOWN: 0,
86
+ PHONE: 1,
87
+ TABLET: 2,
88
+ DESKTOP: 3,
89
+ TV: 4,
90
+ },
91
+ PLATFORM: {
92
+ IOS: 'ios',
93
+ ANDROID: 'android',
94
+ WEB: 'web',
95
+ },
96
+ } as const;
97
+
98
+ /**
99
+ * Device utilities
100
+ */
101
+ export class DeviceUtils {
102
+ /**
103
+ * Check if running on physical device (not simulator/emulator)
104
+ */
105
+ static isPhysicalDevice(isDevice: boolean): boolean {
106
+ return isDevice;
107
+ }
108
+
109
+ /**
110
+ * Get device display name
111
+ */
112
+ static getDeviceDisplayName(info: DeviceInfo): string {
113
+ if (info.deviceName) {
114
+ return info.deviceName;
115
+ }
116
+
117
+ if (info.modelName) {
118
+ return info.modelName;
119
+ }
120
+
121
+ if (info.brand && info.manufacturer) {
122
+ return `${info.brand} ${info.manufacturer}`;
123
+ }
124
+
125
+ return 'Unknown Device';
126
+ }
127
+
128
+ /**
129
+ * Get OS display string
130
+ */
131
+ static getOSDisplayString(info: DeviceInfo): string {
132
+ if (info.osName && info.osVersion) {
133
+ return `${info.osName} ${info.osVersion}`;
134
+ }
135
+
136
+ if (info.osName) {
137
+ return info.osName;
138
+ }
139
+
140
+ return 'Unknown OS';
141
+ }
142
+
143
+ /**
144
+ * Get app version string
145
+ */
146
+ static getAppVersionString(info: ApplicationInfo): string {
147
+ if (info.nativeApplicationVersion && info.nativeBuildVersion) {
148
+ return `${info.nativeApplicationVersion} (${info.nativeBuildVersion})`;
149
+ }
150
+
151
+ if (info.nativeApplicationVersion) {
152
+ return info.nativeApplicationVersion;
153
+ }
154
+
155
+ return 'Unknown Version';
156
+ }
157
+
158
+ /**
159
+ * Check if device meets minimum requirements
160
+ */
161
+ static meetsMinimumRequirements(info: DeviceInfo, minMemoryGB: number = 1): {
162
+ meets: boolean;
163
+ reasons: string[];
164
+ } {
165
+ const reasons: string[] = [];
166
+
167
+ if (!info.isDevice) {
168
+ reasons.push('Running on simulator/emulator');
169
+ }
170
+
171
+ if (info.totalMemory) {
172
+ const memoryGB = info.totalMemory / (1024 * 1024 * 1024);
173
+ if (memoryGB < minMemoryGB) {
174
+ reasons.push(`Insufficient memory: ${memoryGB.toFixed(2)}GB (minimum: ${minMemoryGB}GB)`);
175
+ }
176
+ }
177
+
178
+ if (info.deviceYearClass && info.deviceYearClass < 2018) {
179
+ reasons.push(`Device too old: ${info.deviceYearClass} (minimum: 2018)`);
180
+ }
181
+
182
+ return {
183
+ meets: reasons.length === 0,
184
+ reasons,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Get device tier (low/mid/high) based on specs
190
+ */
191
+ static getDeviceTier(info: DeviceInfo): 'low' | 'mid' | 'high' {
192
+ if (info.deviceYearClass) {
193
+ if (info.deviceYearClass >= 2022) return 'high';
194
+ if (info.deviceYearClass >= 2019) return 'mid';
195
+ return 'low';
196
+ }
197
+
198
+ if (info.totalMemory) {
199
+ const memoryGB = info.totalMemory / (1024 * 1024 * 1024);
200
+ if (memoryGB >= 6) return 'high';
201
+ if (memoryGB >= 3) return 'mid';
202
+ return 'low';
203
+ }
204
+
205
+ return 'mid';
206
+ }
207
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Device Memory Utilities
3
+ *
4
+ * Utility functions for device memory calculations and formatting
5
+ */
6
+
7
+ /**
8
+ * Device memory utilities
9
+ */
10
+ export class DeviceMemoryUtils {
11
+ /**
12
+ * Convert bytes to gigabytes
13
+ */
14
+ static bytesToGB(bytes: number): number {
15
+ return bytes / (1024 * 1024 * 1024);
16
+ }
17
+
18
+ /**
19
+ * Convert bytes to megabytes
20
+ */
21
+ static bytesToMB(bytes: number): number {
22
+ return bytes / (1024 * 1024);
23
+ }
24
+
25
+ /**
26
+ * Format memory size to human readable string
27
+ */
28
+ static formatMemorySize(bytes: number | null): string {
29
+ if (!bytes) return 'Unknown';
30
+
31
+ const gb = this.bytesToGB(bytes);
32
+ if (gb >= 1) {
33
+ return `${gb.toFixed(2)} GB`;
34
+ }
35
+
36
+ const mb = this.bytesToMB(bytes);
37
+ return `${mb.toFixed(2)} MB`;
38
+ }
39
+
40
+ /**
41
+ * Check if memory is sufficient for requirements
42
+ */
43
+ static hasSufficientMemory(
44
+ totalMemory: number | null,
45
+ requiredGB: number
46
+ ): boolean {
47
+ if (!totalMemory) return false;
48
+ return this.bytesToGB(totalMemory) >= requiredGB;
49
+ }
50
+
51
+ /**
52
+ * Get memory tier classification
53
+ */
54
+ static getMemoryTier(totalMemory: number | null): 'low' | 'mid' | 'high' {
55
+ if (!totalMemory) return 'mid';
56
+
57
+ const gb = this.bytesToGB(totalMemory);
58
+ if (gb >= 6) return 'high';
59
+ if (gb >= 3) return 'mid';
60
+ return 'low';
61
+ }
62
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Device Type Utilities
3
+ *
4
+ * Utility functions for device type detection
5
+ */
6
+
7
+ import type { DeviceType } from './Device';
8
+ import { DEVICE_CONSTANTS } from './Device';
9
+
10
+ /**
11
+ * Device type utilities
12
+ */
13
+ export class DeviceTypeUtils {
14
+ /**
15
+ * Check if device is a tablet
16
+ */
17
+ static isTablet(deviceType: DeviceType | null): boolean {
18
+ return deviceType === DEVICE_CONSTANTS.DEVICE_TYPE.TABLET;
19
+ }
20
+
21
+ /**
22
+ * Check if device is a phone
23
+ */
24
+ static isPhone(deviceType: DeviceType | null): boolean {
25
+ return deviceType === DEVICE_CONSTANTS.DEVICE_TYPE.PHONE;
26
+ }
27
+
28
+ /**
29
+ * Check if device is desktop
30
+ */
31
+ static isDesktop(deviceType: DeviceType | null): boolean {
32
+ return deviceType === DEVICE_CONSTANTS.DEVICE_TYPE.DESKTOP;
33
+ }
34
+
35
+ /**
36
+ * Check if device is TV
37
+ */
38
+ static isTV(deviceType: DeviceType | null): boolean {
39
+ return deviceType === DEVICE_CONSTANTS.DEVICE_TYPE.TV;
40
+ }
41
+
42
+ /**
43
+ * Check if device type is unknown
44
+ */
45
+ static isUnknown(deviceType: DeviceType | null): boolean {
46
+ return deviceType === DEVICE_CONSTANTS.DEVICE_TYPE.UNKNOWN;
47
+ }
48
+
49
+ /**
50
+ * Get device type name
51
+ */
52
+ static getDeviceTypeName(deviceType: DeviceType | null): string {
53
+ switch (deviceType) {
54
+ case DEVICE_CONSTANTS.DEVICE_TYPE.PHONE:
55
+ return 'Phone';
56
+ case DEVICE_CONSTANTS.DEVICE_TYPE.TABLET:
57
+ return 'Tablet';
58
+ case DEVICE_CONSTANTS.DEVICE_TYPE.DESKTOP:
59
+ return 'Desktop';
60
+ case DEVICE_CONSTANTS.DEVICE_TYPE.TV:
61
+ return 'TV';
62
+ default:
63
+ return 'Unknown';
64
+ }
65
+ }
66
+ }