@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.
- package/package.json +16 -4
- package/src/atoms/AtomicBadge.tsx +121 -0
- package/src/atoms/AtomicInput.tsx +0 -1
- package/src/atoms/AtomicPicker.tsx +0 -1
- package/src/atoms/index.ts +8 -0
- package/src/atoms/picker/components/PickerChips.tsx +0 -1
- package/src/atoms/picker/components/PickerModal.tsx +1 -3
- package/src/atoms/picker/styles/pickerStyles.ts +1 -1
- package/src/device/domain/entities/Device.ts +207 -0
- package/src/device/domain/entities/DeviceMemoryUtils.ts +62 -0
- package/src/device/domain/entities/DeviceTypeUtils.ts +66 -0
- package/src/device/domain/entities/__tests__/DeviceMemoryUtils.test.ts +118 -0
- package/src/device/domain/entities/__tests__/DeviceTypeUtils.test.ts +104 -0
- package/src/device/domain/entities/__tests__/DeviceUtils.test.ts +167 -0
- package/src/device/index.ts +51 -0
- package/src/device/infrastructure/services/ApplicationInfoService.ts +86 -0
- package/src/device/infrastructure/services/DeviceCapabilityService.ts +60 -0
- package/src/device/infrastructure/services/DeviceIdService.ts +70 -0
- package/src/device/infrastructure/services/DeviceInfoService.ts +95 -0
- package/src/device/infrastructure/services/DeviceService.ts +104 -0
- package/src/device/infrastructure/services/PersistentDeviceIdService.ts +132 -0
- package/src/device/infrastructure/services/UserFriendlyIdService.ts +68 -0
- package/src/device/infrastructure/utils/__tests__/nativeModuleUtils.test.ts +158 -0
- package/src/device/infrastructure/utils/__tests__/stringUtils.test.ts +120 -0
- package/src/device/infrastructure/utils/nativeModuleUtils.ts +69 -0
- package/src/device/infrastructure/utils/stringUtils.ts +59 -0
- package/src/device/presentation/hooks/useAnonymousUser.ts +117 -0
- package/src/device/presentation/hooks/useDeviceInfo.ts +222 -0
- package/src/index.ts +4 -0
- package/src/molecules/ConfirmationModalContent.tsx +4 -4
- package/src/molecules/ConfirmationModalMain.tsx +1 -1
- package/src/molecules/ScreenHeader.tsx +2 -2
- package/src/molecules/confirmation-modal/components.tsx +1 -1
- package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +6 -7
- package/src/presentation/utils/variants/__tests__/core.test.ts +0 -1
- package/src/responsive/deviceDetection.ts +5 -5
- package/src/responsive/iPadBreakpoints.ts +55 -0
- package/src/responsive/iPadDetection.ts +48 -0
- package/src/responsive/iPadLayoutUtils.ts +95 -0
- package/src/responsive/iPadModalUtils.ts +98 -0
- package/src/responsive/index.ts +31 -0
- package/src/safe-area/__tests__/components/SafeAreaProvider.test.tsx +2 -2
- package/src/safe-area/__tests__/hooks/useContentSafeAreaPadding.test.tsx +2 -2
- package/src/safe-area/__tests__/hooks/useHeaderSafeAreaPadding.test.tsx +2 -2
- package/src/safe-area/__tests__/hooks/useSafeAreaInsets.test.tsx +2 -2
- package/src/safe-area/__tests__/hooks/useStatusBarSafeAreaPadding.test.tsx +2 -2
- package/src/safe-area/__tests__/integration/completeFlow.test.tsx +5 -4
- package/src/safe-area/__tests__/utils/testUtils.tsx +5 -4
- package/src/theme/infrastructure/stores/themeStore.ts +0 -2
- package/src/typography/presentation/utils/textColorUtils.ts +0 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Memory Utils Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DeviceMemoryUtils } from '../DeviceMemoryUtils';
|
|
6
|
+
|
|
7
|
+
describe('DeviceMemoryUtils', () => {
|
|
8
|
+
describe('bytesToGB', () => {
|
|
9
|
+
it('should convert bytes to gigabytes', () => {
|
|
10
|
+
const bytes = 6442450944; // 6GB
|
|
11
|
+
const result = DeviceMemoryUtils.bytesToGB(bytes);
|
|
12
|
+
expect(result).toBe(6);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should handle decimal values', () => {
|
|
16
|
+
const bytes = 3221225472; // 3GB
|
|
17
|
+
const result = DeviceMemoryUtils.bytesToGB(bytes);
|
|
18
|
+
expect(result).toBe(3);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('bytesToMB', () => {
|
|
23
|
+
it('should convert bytes to megabytes', () => {
|
|
24
|
+
const bytes = 1048576; // 1MB
|
|
25
|
+
const result = DeviceMemoryUtils.bytesToMB(bytes);
|
|
26
|
+
expect(result).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should handle larger values', () => {
|
|
30
|
+
const bytes = 6442450944; // 6GB = 6144MB
|
|
31
|
+
const result = DeviceMemoryUtils.bytesToMB(bytes);
|
|
32
|
+
expect(result).toBe(6144);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('formatMemorySize', () => {
|
|
37
|
+
it('should format bytes in GB when >= 1GB', () => {
|
|
38
|
+
const bytes = 6442450944; // 6GB
|
|
39
|
+
const result = DeviceMemoryUtils.formatMemorySize(bytes);
|
|
40
|
+
expect(result).toBe('6.00 GB');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should format bytes in MB when < 1GB', () => {
|
|
44
|
+
const bytes = 524288000; // ~500MB
|
|
45
|
+
const result = DeviceMemoryUtils.formatMemorySize(bytes);
|
|
46
|
+
expect(result).toBe('500.00 MB');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return Unknown for null bytes', () => {
|
|
50
|
+
const result = DeviceMemoryUtils.formatMemorySize(null);
|
|
51
|
+
expect(result).toBe('Unknown');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should format with 2 decimal places', () => {
|
|
55
|
+
const bytes = 15032385536; // 14GB
|
|
56
|
+
const result = DeviceMemoryUtils.formatMemorySize(bytes);
|
|
57
|
+
expect(result).toBe('14.00 GB');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('hasSufficientMemory', () => {
|
|
62
|
+
it('should return true when memory is sufficient', () => {
|
|
63
|
+
const totalMemory = 6442450944; // 6GB
|
|
64
|
+
const result = DeviceMemoryUtils.hasSufficientMemory(totalMemory, 4);
|
|
65
|
+
expect(result).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return false when memory is insufficient', () => {
|
|
69
|
+
const totalMemory = 2147483648; // 2GB
|
|
70
|
+
const result = DeviceMemoryUtils.hasSufficientMemory(totalMemory, 4);
|
|
71
|
+
expect(result).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return false for null memory', () => {
|
|
75
|
+
const result = DeviceMemoryUtils.hasSufficientMemory(null, 4);
|
|
76
|
+
expect(result).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle exact match', () => {
|
|
80
|
+
const totalMemory = 4294967296; // 4GB
|
|
81
|
+
const result = DeviceMemoryUtils.hasSufficientMemory(totalMemory, 4);
|
|
82
|
+
expect(result).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('getMemoryTier', () => {
|
|
87
|
+
it('should return high for >= 6GB', () => {
|
|
88
|
+
const totalMemory = 6442450944; // 6GB
|
|
89
|
+
const result = DeviceMemoryUtils.getMemoryTier(totalMemory);
|
|
90
|
+
expect(result).toBe('high');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return mid for >= 3GB and < 6GB', () => {
|
|
94
|
+
const totalMemory = 4294967296; // 4GB
|
|
95
|
+
const result = DeviceMemoryUtils.getMemoryTier(totalMemory);
|
|
96
|
+
expect(result).toBe('mid');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return low for < 3GB', () => {
|
|
100
|
+
const totalMemory = 2147483648; // 2GB
|
|
101
|
+
const result = DeviceMemoryUtils.getMemoryTier(totalMemory);
|
|
102
|
+
expect(result).toBe('low');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return mid for null memory', () => {
|
|
106
|
+
const result = DeviceMemoryUtils.getMemoryTier(null);
|
|
107
|
+
expect(result).toBe('mid');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle boundary values', () => {
|
|
111
|
+
const exactly3GB = 3221225472; // 3GB
|
|
112
|
+
const exactly6GB = 6442450944; // 6GB
|
|
113
|
+
|
|
114
|
+
expect(DeviceMemoryUtils.getMemoryTier(exactly3GB)).toBe('mid');
|
|
115
|
+
expect(DeviceMemoryUtils.getMemoryTier(exactly6GB)).toBe('high');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Type Utils Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DeviceTypeUtils } from '../DeviceTypeUtils';
|
|
6
|
+
import { DEVICE_CONSTANTS } from '../Device';
|
|
7
|
+
|
|
8
|
+
describe('DeviceTypeUtils', () => {
|
|
9
|
+
describe('isTablet', () => {
|
|
10
|
+
it('should return true for tablet device', () => {
|
|
11
|
+
expect(DeviceTypeUtils.isTablet(DEVICE_CONSTANTS.DEVICE_TYPE.TABLET)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return false for phone device', () => {
|
|
15
|
+
expect(DeviceTypeUtils.isTablet(DEVICE_CONSTANTS.DEVICE_TYPE.PHONE)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return false for null device type', () => {
|
|
19
|
+
expect(DeviceTypeUtils.isTablet(null)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('isPhone', () => {
|
|
24
|
+
it('should return true for phone device', () => {
|
|
25
|
+
expect(DeviceTypeUtils.isPhone(DEVICE_CONSTANTS.DEVICE_TYPE.PHONE)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return false for tablet device', () => {
|
|
29
|
+
expect(DeviceTypeUtils.isPhone(DEVICE_CONSTANTS.DEVICE_TYPE.TABLET)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return false for null device type', () => {
|
|
33
|
+
expect(DeviceTypeUtils.isPhone(null)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('isDesktop', () => {
|
|
38
|
+
it('should return true for desktop device', () => {
|
|
39
|
+
expect(DeviceTypeUtils.isDesktop(DEVICE_CONSTANTS.DEVICE_TYPE.DESKTOP)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return false for phone device', () => {
|
|
43
|
+
expect(DeviceTypeUtils.isDesktop(DEVICE_CONSTANTS.DEVICE_TYPE.PHONE)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return false for null device type', () => {
|
|
47
|
+
expect(DeviceTypeUtils.isDesktop(null)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('isTV', () => {
|
|
52
|
+
it('should return true for TV device', () => {
|
|
53
|
+
expect(DeviceTypeUtils.isTV(DEVICE_CONSTANTS.DEVICE_TYPE.TV)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return false for phone device', () => {
|
|
57
|
+
expect(DeviceTypeUtils.isTV(DEVICE_CONSTANTS.DEVICE_TYPE.PHONE)).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return false for null device type', () => {
|
|
61
|
+
expect(DeviceTypeUtils.isTV(null)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('isUnknown', () => {
|
|
66
|
+
it('should return true for unknown device', () => {
|
|
67
|
+
expect(DeviceTypeUtils.isUnknown(DEVICE_CONSTANTS.DEVICE_TYPE.UNKNOWN)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should return false for phone device', () => {
|
|
71
|
+
expect(DeviceTypeUtils.isUnknown(DEVICE_CONSTANTS.DEVICE_TYPE.PHONE)).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return false for null device type', () => {
|
|
75
|
+
expect(DeviceTypeUtils.isUnknown(null)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('getDeviceTypeName', () => {
|
|
80
|
+
it('should return Phone for phone device', () => {
|
|
81
|
+
expect(DeviceTypeUtils.getDeviceTypeName(DEVICE_CONSTANTS.DEVICE_TYPE.PHONE)).toBe('Phone');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return Tablet for tablet device', () => {
|
|
85
|
+
expect(DeviceTypeUtils.getDeviceTypeName(DEVICE_CONSTANTS.DEVICE_TYPE.TABLET)).toBe('Tablet');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return Desktop for desktop device', () => {
|
|
89
|
+
expect(DeviceTypeUtils.getDeviceTypeName(DEVICE_CONSTANTS.DEVICE_TYPE.DESKTOP)).toBe('Desktop');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return TV for TV device', () => {
|
|
93
|
+
expect(DeviceTypeUtils.getDeviceTypeName(DEVICE_CONSTANTS.DEVICE_TYPE.TV)).toBe('TV');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return Unknown for unknown device', () => {
|
|
97
|
+
expect(DeviceTypeUtils.getDeviceTypeName(DEVICE_CONSTANTS.DEVICE_TYPE.UNKNOWN)).toBe('Unknown');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should return Unknown for null device type', () => {
|
|
101
|
+
expect(DeviceTypeUtils.getDeviceTypeName(null)).toBe('Unknown');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Utils Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DeviceUtils, DEVICE_CONSTANTS } from '../Device';
|
|
6
|
+
|
|
7
|
+
describe('DeviceUtils', () => {
|
|
8
|
+
const mockDeviceInfo = {
|
|
9
|
+
brand: 'Apple',
|
|
10
|
+
manufacturer: 'Apple',
|
|
11
|
+
modelName: 'iPhone 14',
|
|
12
|
+
modelId: 'iPhone14,1',
|
|
13
|
+
deviceName: 'Test iPhone',
|
|
14
|
+
deviceYearClass: 2022,
|
|
15
|
+
deviceType: DEVICE_CONSTANTS.DEVICE_TYPE.PHONE,
|
|
16
|
+
isDevice: true,
|
|
17
|
+
osName: 'iOS',
|
|
18
|
+
osVersion: '16.0',
|
|
19
|
+
osBuildId: '20A362',
|
|
20
|
+
platformApiLevel: null,
|
|
21
|
+
totalMemory: 6442450944, // 6GB
|
|
22
|
+
platform: 'ios' as const,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const mockAppInfo = {
|
|
26
|
+
applicationName: 'Test App',
|
|
27
|
+
applicationId: 'com.test.app',
|
|
28
|
+
nativeApplicationVersion: '1.0.0',
|
|
29
|
+
nativeBuildVersion: '100',
|
|
30
|
+
installTime: new Date('2023-01-01'),
|
|
31
|
+
lastUpdateTime: new Date('2023-01-15'),
|
|
32
|
+
androidId: null,
|
|
33
|
+
iosIdForVendor: 'test-ios-id',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('isPhysicalDevice', () => {
|
|
37
|
+
it('should return true for physical device', () => {
|
|
38
|
+
expect(DeviceUtils.isPhysicalDevice(true)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return false for simulator', () => {
|
|
42
|
+
expect(DeviceUtils.isPhysicalDevice(false)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('getDeviceDisplayName', () => {
|
|
47
|
+
it('should return device name when available', () => {
|
|
48
|
+
const result = DeviceUtils.getDeviceDisplayName(mockDeviceInfo);
|
|
49
|
+
expect(result).toBe('Test iPhone');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return model name when device name is null', () => {
|
|
53
|
+
const info = { ...mockDeviceInfo, deviceName: null };
|
|
54
|
+
const result = DeviceUtils.getDeviceDisplayName(info);
|
|
55
|
+
expect(result).toBe('iPhone 14');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return brand and manufacturer when model name is null', () => {
|
|
59
|
+
const info = { ...mockDeviceInfo, deviceName: null, modelName: null };
|
|
60
|
+
const result = DeviceUtils.getDeviceDisplayName(info);
|
|
61
|
+
expect(result).toBe('Apple Apple');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return Unknown Device when all info is null', () => {
|
|
65
|
+
const info = { ...mockDeviceInfo, deviceName: null, modelName: null, brand: null, manufacturer: null };
|
|
66
|
+
const result = DeviceUtils.getDeviceDisplayName(info);
|
|
67
|
+
expect(result).toBe('Unknown Device');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('getOSDisplayString', () => {
|
|
72
|
+
it('should return OS name and version', () => {
|
|
73
|
+
const result = DeviceUtils.getOSDisplayString(mockDeviceInfo);
|
|
74
|
+
expect(result).toBe('iOS 16.0');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return OS name only when version is null', () => {
|
|
78
|
+
const info = { ...mockDeviceInfo, osVersion: null };
|
|
79
|
+
const result = DeviceUtils.getOSDisplayString(info);
|
|
80
|
+
expect(result).toBe('iOS');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return Unknown OS when OS name is null', () => {
|
|
84
|
+
const info = { ...mockDeviceInfo, osName: null };
|
|
85
|
+
const result = DeviceUtils.getOSDisplayString(info);
|
|
86
|
+
expect(result).toBe('Unknown OS');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('getAppVersionString', () => {
|
|
91
|
+
it('should return version and build number', () => {
|
|
92
|
+
const result = DeviceUtils.getAppVersionString(mockAppInfo);
|
|
93
|
+
expect(result).toBe('1.0.0 (100)');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return version only when build is null', () => {
|
|
97
|
+
const info = { ...mockAppInfo, nativeBuildVersion: null };
|
|
98
|
+
const result = DeviceUtils.getAppVersionString(info);
|
|
99
|
+
expect(result).toBe('1.0.0');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should return Unknown Version when version is null', () => {
|
|
103
|
+
const info = { ...mockAppInfo, nativeApplicationVersion: null };
|
|
104
|
+
const result = DeviceUtils.getAppVersionString(info);
|
|
105
|
+
expect(result).toBe('Unknown Version');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('meetsMinimumRequirements', () => {
|
|
110
|
+
it('should pass for device meeting all requirements', () => {
|
|
111
|
+
const result = DeviceUtils.meetsMinimumRequirements(mockDeviceInfo, 4);
|
|
112
|
+
expect(result.meets).toBe(true);
|
|
113
|
+
expect(result.reasons).toHaveLength(0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should fail for simulator', () => {
|
|
117
|
+
const info = { ...mockDeviceInfo, isDevice: false };
|
|
118
|
+
const result = DeviceUtils.meetsMinimumRequirements(info, 1);
|
|
119
|
+
expect(result.meets).toBe(false);
|
|
120
|
+
expect(result.reasons).toContain('Running on simulator/emulator');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should fail for insufficient memory', () => {
|
|
124
|
+
const result = DeviceUtils.meetsMinimumRequirements(mockDeviceInfo, 8);
|
|
125
|
+
expect(result.meets).toBe(false);
|
|
126
|
+
expect(result.reasons[0]).toContain('Insufficient memory');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should fail for old device', () => {
|
|
130
|
+
const info = { ...mockDeviceInfo, deviceYearClass: 2016 };
|
|
131
|
+
const result = DeviceUtils.meetsMinimumRequirements(info, 1);
|
|
132
|
+
expect(result.meets).toBe(false);
|
|
133
|
+
expect(result.reasons[0]).toContain('Device too old: 2016');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('getDeviceTier', () => {
|
|
138
|
+
it('should return high for new device', () => {
|
|
139
|
+
const result = DeviceUtils.getDeviceTier(mockDeviceInfo);
|
|
140
|
+
expect(result).toBe('high');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return mid for 2020 device', () => {
|
|
144
|
+
const info = { ...mockDeviceInfo, deviceYearClass: 2020 };
|
|
145
|
+
const result = DeviceUtils.getDeviceTier(info);
|
|
146
|
+
expect(result).toBe('mid');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should return low for old device', () => {
|
|
150
|
+
const info = { ...mockDeviceInfo, deviceYearClass: 2017 };
|
|
151
|
+
const result = DeviceUtils.getDeviceTier(info);
|
|
152
|
+
expect(result).toBe('low');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should classify by memory when year is null', () => {
|
|
156
|
+
const info = { ...mockDeviceInfo, deviceYearClass: null, totalMemory: 8589934592 }; // 8GB
|
|
157
|
+
const result = DeviceUtils.getDeviceTier(info);
|
|
158
|
+
expect(result).toBe('high');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should return mid when no info available', () => {
|
|
162
|
+
const info = { ...mockDeviceInfo, deviceYearClass: null, totalMemory: null };
|
|
163
|
+
const result = DeviceUtils.getDeviceTier(info);
|
|
164
|
+
expect(result).toBe('mid');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Module - Public API
|
|
3
|
+
*
|
|
4
|
+
* Device and application information utilities.
|
|
5
|
+
* Provides device detection, capabilities, and system info.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Domain entities
|
|
9
|
+
export type {
|
|
10
|
+
DeviceInfo,
|
|
11
|
+
ApplicationInfo,
|
|
12
|
+
SystemInfo,
|
|
13
|
+
DeviceType,
|
|
14
|
+
} from './domain/entities/Device';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
DEVICE_CONSTANTS,
|
|
18
|
+
DeviceUtils,
|
|
19
|
+
} from './domain/entities/Device';
|
|
20
|
+
|
|
21
|
+
export { DeviceTypeUtils } from './domain/entities/DeviceTypeUtils';
|
|
22
|
+
export { DeviceMemoryUtils } from './domain/entities/DeviceMemoryUtils';
|
|
23
|
+
|
|
24
|
+
// Infrastructure services
|
|
25
|
+
export { DeviceService } from './infrastructure/services/DeviceService';
|
|
26
|
+
export { UserFriendlyIdService } from './infrastructure/services/UserFriendlyIdService';
|
|
27
|
+
import { PersistentDeviceIdService } from './infrastructure/services/PersistentDeviceIdService';
|
|
28
|
+
export { PersistentDeviceIdService };
|
|
29
|
+
|
|
30
|
+
// Presentation hooks
|
|
31
|
+
export {
|
|
32
|
+
useDeviceInfo,
|
|
33
|
+
useDeviceCapabilities,
|
|
34
|
+
useDeviceId,
|
|
35
|
+
} from './presentation/hooks/useDeviceInfo';
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
useAnonymousUser,
|
|
39
|
+
} from './presentation/hooks/useAnonymousUser';
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
AnonymousUser,
|
|
43
|
+
UseAnonymousUserOptions,
|
|
44
|
+
} from './presentation/hooks/useAnonymousUser';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get anonymous user ID for services
|
|
48
|
+
*/
|
|
49
|
+
export async function getAnonymousUserId(): Promise<string> {
|
|
50
|
+
return PersistentDeviceIdService.getDeviceId();
|
|
51
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Info Service
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Get application information from native modules
|
|
5
|
+
* Follows SOLID principles - only handles application info retrieval
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as Application from 'expo-application';
|
|
9
|
+
import { Platform } from 'react-native';
|
|
10
|
+
import type { ApplicationInfo } from '../../domain/entities/Device';
|
|
11
|
+
import { safeAccess, withTimeout } from '../utils/nativeModuleUtils';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service for retrieving application information
|
|
15
|
+
*/
|
|
16
|
+
export class ApplicationInfoService {
|
|
17
|
+
/**
|
|
18
|
+
* Get application information
|
|
19
|
+
* SAFE: Returns minimal info if native modules are not ready
|
|
20
|
+
*/
|
|
21
|
+
static async getApplicationInfo(): Promise<ApplicationInfo> {
|
|
22
|
+
try {
|
|
23
|
+
const [installTime, lastUpdateTime] = await Promise.all([
|
|
24
|
+
withTimeout(() => Application.getInstallationTimeAsync(), 1000),
|
|
25
|
+
withTimeout(() => Application.getLastUpdateTimeAsync(), 1000),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const applicationName = safeAccess(() => Application.applicationName, 'Unknown');
|
|
29
|
+
const applicationId = safeAccess(() => Application.applicationId, 'Unknown');
|
|
30
|
+
const nativeApplicationVersion = safeAccess(
|
|
31
|
+
() => Application.nativeApplicationVersion,
|
|
32
|
+
null,
|
|
33
|
+
);
|
|
34
|
+
const nativeBuildVersion = safeAccess(
|
|
35
|
+
() => Application.nativeBuildVersion,
|
|
36
|
+
null,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
let androidId: string | null = null;
|
|
40
|
+
let iosIdForVendor: string | null = null;
|
|
41
|
+
|
|
42
|
+
if (Platform.OS === 'android') {
|
|
43
|
+
const result = await withTimeout(async () => Application.getAndroidId(), 1000);
|
|
44
|
+
androidId = result || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Platform.OS === 'ios') {
|
|
48
|
+
const result = await withTimeout(
|
|
49
|
+
async () => Application.getIosIdForVendorAsync(),
|
|
50
|
+
1000,
|
|
51
|
+
);
|
|
52
|
+
iosIdForVendor = result || null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
applicationName: applicationName || 'Unknown',
|
|
57
|
+
applicationId: applicationId || 'Unknown',
|
|
58
|
+
nativeApplicationVersion,
|
|
59
|
+
nativeBuildVersion,
|
|
60
|
+
installTime,
|
|
61
|
+
lastUpdateTime,
|
|
62
|
+
androidId,
|
|
63
|
+
iosIdForVendor,
|
|
64
|
+
};
|
|
65
|
+
} catch {
|
|
66
|
+
return this.getMinimalApplicationInfo();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get minimal application info (fallback)
|
|
72
|
+
*/
|
|
73
|
+
private static getMinimalApplicationInfo(): ApplicationInfo {
|
|
74
|
+
return {
|
|
75
|
+
applicationName: 'Unknown',
|
|
76
|
+
applicationId: 'Unknown',
|
|
77
|
+
nativeApplicationVersion: null,
|
|
78
|
+
nativeBuildVersion: null,
|
|
79
|
+
installTime: null,
|
|
80
|
+
lastUpdateTime: null,
|
|
81
|
+
androidId: null,
|
|
82
|
+
iosIdForVendor: null,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Capability Service
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Check device capabilities and features
|
|
5
|
+
* Follows SOLID principles - only handles capability checks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as Device from 'expo-device';
|
|
9
|
+
import { Platform } from 'react-native';
|
|
10
|
+
import { DeviceInfoService } from './DeviceInfoService';
|
|
11
|
+
import { safeAccess } from '../utils/nativeModuleUtils';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service for checking device capabilities
|
|
15
|
+
*/
|
|
16
|
+
export class DeviceCapabilityService {
|
|
17
|
+
/**
|
|
18
|
+
* Check if device supports specific features
|
|
19
|
+
*/
|
|
20
|
+
static async getDeviceCapabilities(): Promise<{
|
|
21
|
+
isDevice: boolean;
|
|
22
|
+
isTablet: boolean;
|
|
23
|
+
hasNotch: boolean;
|
|
24
|
+
totalMemoryGB: number | null;
|
|
25
|
+
}> {
|
|
26
|
+
const info = await DeviceInfoService.getDeviceInfo();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
isDevice: info.isDevice,
|
|
30
|
+
isTablet: info.deviceType === Device.DeviceType.TABLET,
|
|
31
|
+
hasNotch: await this.hasNotch(),
|
|
32
|
+
totalMemoryGB: info.totalMemory
|
|
33
|
+
? info.totalMemory / (1024 * 1024 * 1024)
|
|
34
|
+
: null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if device has notch/dynamic island
|
|
40
|
+
*/
|
|
41
|
+
static async hasNotch(): Promise<boolean> {
|
|
42
|
+
try {
|
|
43
|
+
if (Platform.OS !== 'ios') {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const modelName = safeAccess(() => Device.modelName?.toLowerCase() || '', '');
|
|
48
|
+
|
|
49
|
+
// iPhone X and newer (with notch or dynamic island)
|
|
50
|
+
return (
|
|
51
|
+
modelName.includes('iphone x') ||
|
|
52
|
+
modelName.includes('iphone 1') || // 11, 12, 13, 14, 15
|
|
53
|
+
modelName.includes('pro')
|
|
54
|
+
);
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device ID Service
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Get device unique identifiers
|
|
5
|
+
* Follows SOLID principles - only handles device ID retrieval
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as Application from 'expo-application';
|
|
9
|
+
import { Platform } from 'react-native';
|
|
10
|
+
import { withTimeout } from '../utils/nativeModuleUtils';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Service for retrieving device unique identifiers
|
|
14
|
+
*/
|
|
15
|
+
export class DeviceIdService {
|
|
16
|
+
/**
|
|
17
|
+
* Get device unique identifier (platform-specific)
|
|
18
|
+
*
|
|
19
|
+
* WARNING: Use with caution - user privacy considerations!
|
|
20
|
+
* Android: androidId (can be reset)
|
|
21
|
+
* iOS: iosIdForVendor (changes on reinstall)
|
|
22
|
+
* Web: null (not supported)
|
|
23
|
+
*
|
|
24
|
+
* SAFE: Returns null if native modules are not ready
|
|
25
|
+
*/
|
|
26
|
+
static async getDeviceId(): Promise<string | null> {
|
|
27
|
+
try {
|
|
28
|
+
let deviceId: string | null = null;
|
|
29
|
+
|
|
30
|
+
if (Platform.OS === 'android') {
|
|
31
|
+
const result = await withTimeout(async () => Application.getAndroidId(), 1000);
|
|
32
|
+
deviceId = result || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Platform.OS === 'ios') {
|
|
36
|
+
const result = await withTimeout(
|
|
37
|
+
async () => Application.getIosIdForVendorAsync(),
|
|
38
|
+
1000,
|
|
39
|
+
);
|
|
40
|
+
deviceId = result || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return deviceId;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get offline user ID with fallback (iOS/Android only)
|
|
51
|
+
*
|
|
52
|
+
* For offline apps that need a persistent user ID:
|
|
53
|
+
* 1. Try to get platform-specific device ID (iOS/Android)
|
|
54
|
+
* 2. Fallback to a default offline user ID if device ID not available
|
|
55
|
+
*
|
|
56
|
+
* NOTE: Returns null for web platform (not supported)
|
|
57
|
+
*/
|
|
58
|
+
static async getOfflineUserId(fallbackId: string = 'offline_user'): Promise<string | null> {
|
|
59
|
+
if (Platform.OS === 'web') {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const deviceId = await this.getDeviceId();
|
|
64
|
+
if (deviceId) {
|
|
65
|
+
return `offline_${deviceId}`;
|
|
66
|
+
}
|
|
67
|
+
return fallbackId;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|