@umituz/react-native-design-system 2.6.78 → 2.6.80
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 +1 -1
- package/src/device/domain/entities/Device.ts +0 -109
- package/src/device/domain/entities/DeviceUtils.ts +95 -0
- package/src/device/domain/entities/__tests__/DeviceUtils.test.ts +2 -1
- package/src/device/index.ts +2 -1
- package/src/molecules/avatar/Avatar.utils.ts +5 -9
- package/src/molecules/countdown/components/Countdown.tsx +16 -13
- package/src/molecules/splash/components/SplashScreen.tsx +7 -34
- package/src/utilities/sharing/domain/entities/Share.ts +0 -106
- package/src/utilities/sharing/domain/entities/SharingUtils.ts +111 -0
- package/src/utilities/sharing/index.ts +2 -174
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.80",
|
|
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",
|
|
@@ -99,113 +99,4 @@ export const DEVICE_CONSTANTS = {
|
|
|
99
99
|
},
|
|
100
100
|
} as const;
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* Device utilities
|
|
104
|
-
*/
|
|
105
|
-
export class DeviceUtils {
|
|
106
|
-
/**
|
|
107
|
-
* Check if running on physical device (not simulator/emulator)
|
|
108
|
-
*/
|
|
109
|
-
static isPhysicalDevice(isDevice: boolean): boolean {
|
|
110
|
-
return isDevice;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Get device display name
|
|
115
|
-
*/
|
|
116
|
-
static getDeviceDisplayName(info: DeviceInfo): string {
|
|
117
|
-
if (info.deviceName) {
|
|
118
|
-
return info.deviceName;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (info.modelName) {
|
|
122
|
-
return info.modelName;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (info.brand && info.manufacturer) {
|
|
126
|
-
return `${info.brand} ${info.manufacturer}`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return 'Unknown Device';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get OS display string
|
|
134
|
-
*/
|
|
135
|
-
static getOSDisplayString(info: DeviceInfo): string {
|
|
136
|
-
if (info.osName && info.osVersion) {
|
|
137
|
-
return `${info.osName} ${info.osVersion}`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (info.osName) {
|
|
141
|
-
return info.osName;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return 'Unknown OS';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Get app version string
|
|
149
|
-
*/
|
|
150
|
-
static getAppVersionString(info: ApplicationInfo): string {
|
|
151
|
-
if (info.nativeApplicationVersion && info.nativeBuildVersion) {
|
|
152
|
-
return `${info.nativeApplicationVersion} (${info.nativeBuildVersion})`;
|
|
153
|
-
}
|
|
154
102
|
|
|
155
|
-
if (info.nativeApplicationVersion) {
|
|
156
|
-
return info.nativeApplicationVersion;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return 'Unknown Version';
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Check if device meets minimum requirements
|
|
164
|
-
*/
|
|
165
|
-
static meetsMinimumRequirements(info: DeviceInfo, minMemoryGB: number = 1): {
|
|
166
|
-
meets: boolean;
|
|
167
|
-
reasons: string[];
|
|
168
|
-
} {
|
|
169
|
-
const reasons: string[] = [];
|
|
170
|
-
|
|
171
|
-
if (!info.isDevice) {
|
|
172
|
-
reasons.push('Running on simulator/emulator');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (info.totalMemory) {
|
|
176
|
-
const memoryGB = info.totalMemory / (1024 * 1024 * 1024);
|
|
177
|
-
if (memoryGB < minMemoryGB) {
|
|
178
|
-
reasons.push(`Insufficient memory: ${memoryGB.toFixed(2)}GB (minimum: ${minMemoryGB}GB)`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (info.deviceYearClass && info.deviceYearClass < 2018) {
|
|
183
|
-
reasons.push(`Device too old: ${info.deviceYearClass} (minimum: 2018)`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
meets: reasons.length === 0,
|
|
188
|
-
reasons,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get device tier (low/mid/high) based on specs
|
|
194
|
-
*/
|
|
195
|
-
static getDeviceTier(info: DeviceInfo): 'low' | 'mid' | 'high' {
|
|
196
|
-
if (info.deviceYearClass) {
|
|
197
|
-
if (info.deviceYearClass >= 2022) return 'high';
|
|
198
|
-
if (info.deviceYearClass >= 2019) return 'mid';
|
|
199
|
-
return 'low';
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (info.totalMemory) {
|
|
203
|
-
const memoryGB = info.totalMemory / (1024 * 1024 * 1024);
|
|
204
|
-
if (memoryGB >= 6) return 'high';
|
|
205
|
-
if (memoryGB >= 3) return 'mid';
|
|
206
|
-
return 'low';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return 'mid';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Utilities
|
|
3
|
+
* Helper functions for processing device information
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DeviceInfo, ApplicationInfo } from './Device';
|
|
7
|
+
|
|
8
|
+
export class DeviceUtils {
|
|
9
|
+
/**
|
|
10
|
+
* Check if running on physical device (not simulator/emulator)
|
|
11
|
+
*/
|
|
12
|
+
static isPhysicalDevice(isDevice: boolean): boolean {
|
|
13
|
+
return isDevice;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get device display name
|
|
18
|
+
*/
|
|
19
|
+
static getDeviceDisplayName(info: DeviceInfo): string {
|
|
20
|
+
if (info.deviceName) return info.deviceName;
|
|
21
|
+
if (info.modelName) return info.modelName;
|
|
22
|
+
if (info.brand && info.manufacturer) return `${info.brand} ${info.manufacturer}`;
|
|
23
|
+
return 'Unknown Device';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get OS display string
|
|
28
|
+
*/
|
|
29
|
+
static getOSDisplayString(info: DeviceInfo): string {
|
|
30
|
+
if (info.osName && info.osVersion) {
|
|
31
|
+
return `${info.osName} ${info.osVersion}`;
|
|
32
|
+
}
|
|
33
|
+
return info.osName || 'Unknown OS';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get app version string
|
|
38
|
+
*/
|
|
39
|
+
static getAppVersionString(info: ApplicationInfo): string {
|
|
40
|
+
if (info.nativeApplicationVersion && info.nativeBuildVersion) {
|
|
41
|
+
return `${info.nativeApplicationVersion} (${info.nativeBuildVersion})`;
|
|
42
|
+
}
|
|
43
|
+
return info.nativeApplicationVersion || 'Unknown Version';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if device meets minimum requirements
|
|
48
|
+
*/
|
|
49
|
+
static meetsMinimumRequirements(info: DeviceInfo, minMemoryGB: number = 1): {
|
|
50
|
+
meets: boolean;
|
|
51
|
+
reasons: string[];
|
|
52
|
+
} {
|
|
53
|
+
const reasons: string[] = [];
|
|
54
|
+
|
|
55
|
+
if (!info.isDevice) {
|
|
56
|
+
reasons.push('Running on simulator/emulator');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (info.totalMemory) {
|
|
60
|
+
const memoryGB = info.totalMemory / (1024 * 1024 * 1024);
|
|
61
|
+
if (memoryGB < minMemoryGB) {
|
|
62
|
+
reasons.push(`Insufficient memory: ${memoryGB.toFixed(2)}GB (minimum: ${minMemoryGB}GB)`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (info.deviceYearClass && info.deviceYearClass < 2018) {
|
|
67
|
+
reasons.push(`Device too old: ${info.deviceYearClass} (minimum: 2018)`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
meets: reasons.length === 0,
|
|
72
|
+
reasons,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get device tier (low/mid/high) based on specs
|
|
78
|
+
*/
|
|
79
|
+
static getDeviceTier(info: DeviceInfo): 'low' | 'mid' | 'high' {
|
|
80
|
+
if (info.deviceYearClass) {
|
|
81
|
+
if (info.deviceYearClass >= 2022) return 'high';
|
|
82
|
+
if (info.deviceYearClass >= 2019) return 'mid';
|
|
83
|
+
return 'low';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (info.totalMemory) {
|
|
87
|
+
const memoryGB = info.totalMemory / (1024 * 1024 * 1024);
|
|
88
|
+
if (memoryGB >= 6) return 'high';
|
|
89
|
+
if (memoryGB >= 3) return 'mid';
|
|
90
|
+
return 'low';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return 'mid';
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/device/index.ts
CHANGED
|
@@ -60,9 +60,10 @@ export type {
|
|
|
60
60
|
|
|
61
61
|
export {
|
|
62
62
|
DEVICE_CONSTANTS,
|
|
63
|
-
DeviceUtils,
|
|
64
63
|
} from './domain/entities/Device';
|
|
65
64
|
|
|
65
|
+
export { DeviceUtils } from './domain/entities/DeviceUtils';
|
|
66
|
+
|
|
66
67
|
export { DeviceTypeUtils } from './domain/entities/DeviceTypeUtils';
|
|
67
68
|
export { DeviceMemoryUtils } from './domain/entities/DeviceMemoryUtils';
|
|
68
69
|
|
|
@@ -64,17 +64,17 @@ export class AvatarUtils {
|
|
|
64
64
|
* Same name always returns same color
|
|
65
65
|
*/
|
|
66
66
|
static getColorForName(name: string): string {
|
|
67
|
-
if (!name) return AVATAR_COLORS[0];
|
|
67
|
+
if (!name) return AVATAR_COLORS[0] ?? '#7E57C2';
|
|
68
68
|
|
|
69
69
|
const hash = this.hashString(name);
|
|
70
|
-
return AVATAR_COLORS[hash % AVATAR_COLORS.length];
|
|
70
|
+
return AVATAR_COLORS[hash % AVATAR_COLORS.length] ?? AVATAR_COLORS[0] ?? '#7E57C2';
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Get size config
|
|
75
75
|
*/
|
|
76
76
|
static getSizeConfig(size: AvatarSize): SizeConfig {
|
|
77
|
-
return SIZE_CONFIGS[size];
|
|
77
|
+
return SIZE_CONFIGS[size] ?? SIZE_CONFIGS.md;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -84,14 +84,14 @@ export class AvatarUtils {
|
|
|
84
84
|
if (shape === 'circle') {
|
|
85
85
|
return size / 2;
|
|
86
86
|
}
|
|
87
|
-
return SHAPE_CONFIGS[shape];
|
|
87
|
+
return SHAPE_CONFIGS[shape] ?? 0;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Get status color
|
|
92
92
|
*/
|
|
93
93
|
static getStatusColor(status: 'online' | 'offline' | 'away' | 'busy'): string {
|
|
94
|
-
return STATUS_COLORS[status];
|
|
94
|
+
return STATUS_COLORS[status] ?? STATUS_COLORS.offline;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -132,7 +132,3 @@ export class AvatarUtils {
|
|
|
132
132
|
return config.type === 'icon' && !!config.icon;
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
|
|
136
|
-
// Re-export types and constants for convenience
|
|
137
|
-
export type { AvatarSize, AvatarShape, AvatarType, AvatarConfig, SizeConfig } from './Avatar.types';
|
|
138
|
-
export { AVATAR_COLORS, STATUS_COLORS, SHAPE_CONFIGS, SIZE_CONFIGS, AVATAR_CONSTANTS } from './Avatar.constants';
|
|
@@ -42,7 +42,7 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
42
42
|
() => [target, ...alternateTargets],
|
|
43
43
|
[target, alternateTargets]
|
|
44
44
|
);
|
|
45
|
-
const currentTarget = allTargets[currentTargetIndex];
|
|
45
|
+
const currentTarget = allTargets[currentTargetIndex] ?? target;
|
|
46
46
|
|
|
47
47
|
const { timeRemaining, setTarget: updateTarget } = useCountdown(currentTarget, {
|
|
48
48
|
interval,
|
|
@@ -50,25 +50,28 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
React.useEffect(() => {
|
|
53
|
-
|
|
53
|
+
if (currentTarget) {
|
|
54
|
+
updateTarget(currentTarget);
|
|
55
|
+
}
|
|
54
56
|
}, [currentTarget, updateTarget]);
|
|
55
57
|
|
|
56
58
|
const handleToggle = () => {
|
|
57
59
|
const nextIndex = (currentTargetIndex + 1) % allTargets.length;
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
60
|
+
const nextTarget = allTargets[nextIndex];
|
|
61
|
+
if (nextTarget) {
|
|
62
|
+
setCurrentTargetIndex(nextIndex);
|
|
63
|
+
onTargetChange?.(nextTarget);
|
|
61
64
|
}
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
const defaultFormatLabel = (unit: 'days' | 'hours' | 'minutes' | 'seconds') => {
|
|
65
|
-
const labels = {
|
|
66
|
-
days: '
|
|
67
|
-
hours: '
|
|
68
|
-
minutes: '
|
|
69
|
-
seconds: '
|
|
68
|
+
const labels: Record<string, string> = {
|
|
69
|
+
days: '',
|
|
70
|
+
hours: '',
|
|
71
|
+
minutes: '',
|
|
72
|
+
seconds: '',
|
|
70
73
|
};
|
|
71
|
-
return labels[unit];
|
|
74
|
+
return labels[unit] ?? '';
|
|
72
75
|
};
|
|
73
76
|
|
|
74
77
|
const labelFormatter = formatLabel || defaultFormatLabel;
|
|
@@ -112,9 +115,9 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
112
115
|
|
|
113
116
|
return (
|
|
114
117
|
<View style={styles.container}>
|
|
115
|
-
{showLabel && (
|
|
118
|
+
{showLabel && currentTarget && (
|
|
116
119
|
<CountdownHeader
|
|
117
|
-
title={currentTarget.label || '
|
|
120
|
+
title={currentTarget.label || ''}
|
|
118
121
|
icon={currentTarget.icon as IconName}
|
|
119
122
|
showToggle={showToggle}
|
|
120
123
|
onToggle={handleToggle}
|
|
@@ -55,17 +55,7 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
55
55
|
iconPlaceholder: `${tokens.colors.textPrimary}30`, // 30% opacity
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
console.log('[SplashScreen] Component render:', {
|
|
60
|
-
visible,
|
|
61
|
-
appName,
|
|
62
|
-
tagline,
|
|
63
|
-
hasIcon: !!icon,
|
|
64
|
-
hasCustomColors: !!customColors,
|
|
65
|
-
resolvedColors: colors,
|
|
66
|
-
hasGradient: !!gradientColors,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
58
|
+
|
|
69
59
|
|
|
70
60
|
if (!visible) {
|
|
71
61
|
if (__DEV__) {
|
|
@@ -159,13 +149,8 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
|
|
|
159
149
|
};
|
|
160
150
|
|
|
161
151
|
const styles = StyleSheet.create({
|
|
162
|
-
container: {
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
content: {
|
|
166
|
-
flex: 1,
|
|
167
|
-
justifyContent: "space-between",
|
|
168
|
-
},
|
|
152
|
+
container: { flex: 1 },
|
|
153
|
+
content: { flex: 1, justifyContent: "space-between" },
|
|
169
154
|
center: {
|
|
170
155
|
flex: 1,
|
|
171
156
|
alignItems: "center",
|
|
@@ -183,26 +168,14 @@ const styles = StyleSheet.create({
|
|
|
183
168
|
borderRadius: SPLASH_CONSTANTS.ICON_PLACEHOLDER_SIZE / 2,
|
|
184
169
|
marginBottom: SPLASH_CONSTANTS.CONTENT_PADDING,
|
|
185
170
|
},
|
|
186
|
-
title: {
|
|
187
|
-
|
|
188
|
-
fontWeight: "800",
|
|
189
|
-
marginBottom: 8,
|
|
190
|
-
},
|
|
191
|
-
tagline: {
|
|
192
|
-
textAlign: "center",
|
|
193
|
-
opacity: 0.9,
|
|
194
|
-
},
|
|
171
|
+
title: { textAlign: "center", fontWeight: "800", marginBottom: 8 },
|
|
172
|
+
tagline: { textAlign: "center", opacity: 0.9 },
|
|
195
173
|
loadingContainer: {
|
|
196
174
|
marginTop: SPLASH_CONSTANTS.CONTENT_PADDING,
|
|
197
175
|
alignItems: "center",
|
|
198
176
|
justifyContent: "center",
|
|
199
177
|
minHeight: 40,
|
|
200
178
|
},
|
|
201
|
-
loadingIndicator: {
|
|
202
|
-
|
|
203
|
-
},
|
|
204
|
-
timeoutText: {
|
|
205
|
-
textAlign: "center",
|
|
206
|
-
marginTop: 16,
|
|
207
|
-
},
|
|
179
|
+
loadingIndicator: { opacity: 0.8 },
|
|
180
|
+
timeoutText: { textAlign: "center", marginTop: 16 },
|
|
208
181
|
});
|
|
@@ -101,110 +101,4 @@ export const SHARING_CONSTANTS = {
|
|
|
101
101
|
DEFAULT_MIME_TYPE: MIME_TYPES.OCTET_STREAM,
|
|
102
102
|
} as const;
|
|
103
103
|
|
|
104
|
-
/**
|
|
105
|
-
* Sharing utilities
|
|
106
|
-
*/
|
|
107
|
-
export class SharingUtils {
|
|
108
|
-
/**
|
|
109
|
-
* Get MIME type from file extension
|
|
110
|
-
*/
|
|
111
|
-
static getMimeTypeFromExtension(filename: string): string {
|
|
112
|
-
const extension = filename.split('.').pop()?.toLowerCase();
|
|
113
|
-
|
|
114
|
-
switch (extension) {
|
|
115
|
-
// Images
|
|
116
|
-
case 'jpg':
|
|
117
|
-
case 'jpeg':
|
|
118
|
-
return MIME_TYPES.IMAGE_JPEG;
|
|
119
|
-
case 'png':
|
|
120
|
-
return MIME_TYPES.IMAGE_PNG;
|
|
121
|
-
case 'gif':
|
|
122
|
-
return MIME_TYPES.IMAGE_GIF;
|
|
123
|
-
case 'webp':
|
|
124
|
-
return MIME_TYPES.IMAGE_WEBP;
|
|
125
|
-
|
|
126
|
-
// Videos
|
|
127
|
-
case 'mp4':
|
|
128
|
-
return MIME_TYPES.VIDEO_MP4;
|
|
129
|
-
case 'mov':
|
|
130
|
-
return MIME_TYPES.VIDEO_QUICKTIME;
|
|
131
|
-
case 'avi':
|
|
132
|
-
return MIME_TYPES.VIDEO_AVI;
|
|
133
|
-
|
|
134
|
-
// Audio
|
|
135
|
-
case 'mp3':
|
|
136
|
-
return MIME_TYPES.AUDIO_MP3;
|
|
137
|
-
case 'wav':
|
|
138
|
-
return MIME_TYPES.AUDIO_WAV;
|
|
139
|
-
case 'aac':
|
|
140
|
-
return MIME_TYPES.AUDIO_AAC;
|
|
141
|
-
|
|
142
|
-
// Documents
|
|
143
|
-
case 'pdf':
|
|
144
|
-
return MIME_TYPES.PDF;
|
|
145
|
-
case 'txt':
|
|
146
|
-
return MIME_TYPES.TEXT;
|
|
147
|
-
case 'json':
|
|
148
|
-
return MIME_TYPES.JSON;
|
|
149
|
-
case 'zip':
|
|
150
|
-
return MIME_TYPES.ZIP;
|
|
151
|
-
|
|
152
|
-
default:
|
|
153
|
-
return MIME_TYPES.OCTET_STREAM;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Get UTI from file extension (iOS)
|
|
159
|
-
*/
|
|
160
|
-
static getUTIFromExtension(filename: string): string {
|
|
161
|
-
const extension = filename.split('.').pop()?.toLowerCase();
|
|
162
|
-
|
|
163
|
-
switch (extension) {
|
|
164
|
-
// Images
|
|
165
|
-
case 'jpg':
|
|
166
|
-
case 'jpeg':
|
|
167
|
-
return UTI_TYPES.JPEG;
|
|
168
|
-
case 'png':
|
|
169
|
-
return UTI_TYPES.PNG;
|
|
170
|
-
case 'gif':
|
|
171
|
-
case 'webp':
|
|
172
|
-
return UTI_TYPES.IMAGE;
|
|
173
|
-
|
|
174
|
-
// Videos
|
|
175
|
-
case 'mp4':
|
|
176
|
-
case 'mov':
|
|
177
|
-
case 'avi':
|
|
178
|
-
return UTI_TYPES.VIDEO;
|
|
179
|
-
|
|
180
|
-
// Audio
|
|
181
|
-
case 'mp3':
|
|
182
|
-
return UTI_TYPES.MP3;
|
|
183
|
-
case 'wav':
|
|
184
|
-
case 'aac':
|
|
185
|
-
return UTI_TYPES.AUDIO;
|
|
186
|
-
|
|
187
|
-
// Documents
|
|
188
|
-
case 'pdf':
|
|
189
|
-
return UTI_TYPES.PDF;
|
|
190
|
-
case 'txt':
|
|
191
|
-
return UTI_TYPES.TEXT;
|
|
192
|
-
case 'json':
|
|
193
|
-
return UTI_TYPES.JSON;
|
|
194
|
-
|
|
195
|
-
default:
|
|
196
|
-
return UTI_TYPES.DATA;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
104
|
|
|
200
|
-
/**
|
|
201
|
-
* Prepare share options from filename
|
|
202
|
-
*/
|
|
203
|
-
static prepareShareOptions(filename: string, dialogTitle?: string): ShareOptions {
|
|
204
|
-
return {
|
|
205
|
-
dialogTitle: dialogTitle || SHARING_CONSTANTS.DEFAULT_DIALOG_TITLE,
|
|
206
|
-
mimeType: SharingUtils.getMimeTypeFromExtension(filename),
|
|
207
|
-
UTI: SharingUtils.getUTIFromExtension(filename),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sharing Utilities
|
|
3
|
+
* Helper functions for sharing functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { MIME_TYPES, UTI_TYPES, SHARING_CONSTANTS, ShareOptions } from './Share';
|
|
7
|
+
|
|
8
|
+
export class SharingUtils {
|
|
9
|
+
/**
|
|
10
|
+
* Get MIME type from file extension
|
|
11
|
+
*/
|
|
12
|
+
static getMimeTypeFromExtension(filename: string): string {
|
|
13
|
+
const extension = filename.split('.').pop()?.toLowerCase();
|
|
14
|
+
|
|
15
|
+
switch (extension) {
|
|
16
|
+
// Images
|
|
17
|
+
case 'jpg':
|
|
18
|
+
case 'jpeg':
|
|
19
|
+
return MIME_TYPES.IMAGE_JPEG;
|
|
20
|
+
case 'png':
|
|
21
|
+
return MIME_TYPES.IMAGE_PNG;
|
|
22
|
+
case 'gif':
|
|
23
|
+
return MIME_TYPES.IMAGE_GIF;
|
|
24
|
+
case 'webp':
|
|
25
|
+
return MIME_TYPES.IMAGE_WEBP;
|
|
26
|
+
|
|
27
|
+
// Videos
|
|
28
|
+
case 'mp4':
|
|
29
|
+
return MIME_TYPES.VIDEO_MP4;
|
|
30
|
+
case 'mov':
|
|
31
|
+
return MIME_TYPES.VIDEO_QUICKTIME;
|
|
32
|
+
case 'avi':
|
|
33
|
+
return MIME_TYPES.VIDEO_AVI;
|
|
34
|
+
|
|
35
|
+
// Audio
|
|
36
|
+
case 'mp3':
|
|
37
|
+
return MIME_TYPES.AUDIO_MP3;
|
|
38
|
+
case 'wav':
|
|
39
|
+
return MIME_TYPES.AUDIO_WAV;
|
|
40
|
+
case 'aac':
|
|
41
|
+
return MIME_TYPES.AUDIO_AAC;
|
|
42
|
+
|
|
43
|
+
// Documents
|
|
44
|
+
case 'pdf':
|
|
45
|
+
return MIME_TYPES.PDF;
|
|
46
|
+
case 'txt':
|
|
47
|
+
return MIME_TYPES.TEXT;
|
|
48
|
+
case 'json':
|
|
49
|
+
return MIME_TYPES.JSON;
|
|
50
|
+
case 'zip':
|
|
51
|
+
return MIME_TYPES.ZIP;
|
|
52
|
+
|
|
53
|
+
default:
|
|
54
|
+
return MIME_TYPES.OCTET_STREAM;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get UTI from file extension (iOS)
|
|
60
|
+
*/
|
|
61
|
+
static getUTIFromExtension(filename: string): string {
|
|
62
|
+
const extension = filename.split('.').pop()?.toLowerCase();
|
|
63
|
+
|
|
64
|
+
switch (extension) {
|
|
65
|
+
// Images
|
|
66
|
+
case 'jpg':
|
|
67
|
+
case 'jpeg':
|
|
68
|
+
return UTI_TYPES.JPEG;
|
|
69
|
+
case 'png':
|
|
70
|
+
return UTI_TYPES.PNG;
|
|
71
|
+
case 'gif':
|
|
72
|
+
case 'webp':
|
|
73
|
+
return UTI_TYPES.IMAGE;
|
|
74
|
+
|
|
75
|
+
// Videos
|
|
76
|
+
case 'mp4':
|
|
77
|
+
case 'mov':
|
|
78
|
+
case 'avi':
|
|
79
|
+
return UTI_TYPES.VIDEO;
|
|
80
|
+
|
|
81
|
+
// Audio
|
|
82
|
+
case 'mp3':
|
|
83
|
+
return UTI_TYPES.MP3;
|
|
84
|
+
case 'wav':
|
|
85
|
+
case 'aac':
|
|
86
|
+
return UTI_TYPES.AUDIO;
|
|
87
|
+
|
|
88
|
+
// Documents
|
|
89
|
+
case 'pdf':
|
|
90
|
+
return UTI_TYPES.PDF;
|
|
91
|
+
case 'txt':
|
|
92
|
+
return UTI_TYPES.TEXT;
|
|
93
|
+
case 'json':
|
|
94
|
+
return UTI_TYPES.JSON;
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
return UTI_TYPES.DATA;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Prepare share options from filename
|
|
103
|
+
*/
|
|
104
|
+
static prepareShareOptions(filename: string, dialogTitle?: string): ShareOptions {
|
|
105
|
+
return {
|
|
106
|
+
dialogTitle: dialogTitle || SHARING_CONSTANTS.DEFAULT_DIALOG_TITLE,
|
|
107
|
+
mimeType: SharingUtils.getMimeTypeFromExtension(filename),
|
|
108
|
+
UTI: SharingUtils.getUTIFromExtension(filename),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -1,179 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sharing Domain - Barrel Export
|
|
3
|
-
*
|
|
4
3
|
* Provides system share sheet functionality using expo-sharing.
|
|
5
|
-
* Optional domain - disabled by default (opt-in).
|
|
6
|
-
*
|
|
7
|
-
* @domain sharing
|
|
8
|
-
* @enabled false (Apps that need file sharing - Opt-in)
|
|
9
|
-
*
|
|
10
|
-
* ARCHITECTURE:
|
|
11
|
-
* - Domain Layer: Entities (share types, MIME types, utilities)
|
|
12
|
-
* - Infrastructure Layer: Services (SharingService)
|
|
13
|
-
* - Presentation Layer: Hooks (useSharing)
|
|
14
|
-
*
|
|
15
|
-
* DEPENDENCIES:
|
|
16
|
-
* - expo-sharing (system share sheet)
|
|
17
|
-
*
|
|
18
|
-
* FEATURES:
|
|
19
|
-
* - Share files via system share sheet
|
|
20
|
-
* - Automatic MIME type detection
|
|
21
|
-
* - Platform-specific options (UTI for iOS, dialogTitle for Android)
|
|
22
|
-
* - Share availability check
|
|
23
|
-
* - Multiple file sharing (sequential)
|
|
24
|
-
*
|
|
25
|
-
* USAGE:
|
|
26
|
-
*
|
|
27
|
-
* Basic Sharing:
|
|
28
|
-
* ```typescript
|
|
29
|
-
* import { useSharing } from '@umituz/react-native-sharing';
|
|
30
|
-
*
|
|
31
|
-
* const { share, isAvailable, isSharing } = useSharing();
|
|
32
|
-
*
|
|
33
|
-
* if (!isAvailable) {
|
|
34
|
-
* return <Text>Sharing not available</Text>;
|
|
35
|
-
* }
|
|
36
|
-
*
|
|
37
|
-
* const handleShare = async () => {
|
|
38
|
-
* const success = await share('file:///path/to/file.jpg', {
|
|
39
|
-
* dialogTitle: 'Share Photo',
|
|
40
|
-
* mimeType: 'image/jpeg',
|
|
41
|
-
* });
|
|
42
|
-
*
|
|
43
|
-
* if (success) {
|
|
44
|
-
* console.log('Shared successfully');
|
|
45
|
-
* }
|
|
46
|
-
* };
|
|
47
|
-
*
|
|
48
|
-
* <AtomicButton onPress={handleShare} loading={isSharing}>
|
|
49
|
-
* Share
|
|
50
|
-
* </AtomicButton>
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* Auto-Detect MIME Type:
|
|
54
|
-
* ```typescript
|
|
55
|
-
* import { useSharing } from '@umituz/react-native-sharing';
|
|
56
|
-
*
|
|
57
|
-
* const { shareWithAutoType } = useSharing();
|
|
58
|
-
*
|
|
59
|
-
* // MIME type auto-detected from .pdf extension
|
|
60
|
-
* await shareWithAutoType(
|
|
61
|
-
* 'file:///path/to/document.pdf',
|
|
62
|
-
* 'document.pdf',
|
|
63
|
-
* 'Share Document'
|
|
64
|
-
* );
|
|
65
|
-
*
|
|
66
|
-
* // MIME type auto-detected from .jpg extension
|
|
67
|
-
* await shareWithAutoType(
|
|
68
|
-
* 'file:///path/to/photo.jpg',
|
|
69
|
-
* 'photo.jpg',
|
|
70
|
-
* 'Share Photo'
|
|
71
|
-
* );
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* Share with media Integration:
|
|
75
|
-
* ```typescript
|
|
76
|
-
* import { useMedia } from '@umituz/react-native-media';
|
|
77
|
-
* import { useSharing } from '@umituz/react-native-sharing';
|
|
78
|
-
* import { FileSystemService } from '@umituz/react-native-filesystem';
|
|
79
|
-
*
|
|
80
|
-
* const { pickImage } = useMedia();
|
|
81
|
-
* const { shareWithAutoType } = useSharing();
|
|
82
|
-
*
|
|
83
|
-
* const handlePickAndShare = async () => {
|
|
84
|
-
* // Pick image
|
|
85
|
-
* const result = await pickImage();
|
|
86
|
-
* if (result.canceled || !result.assets?.[0]?.uri) return;
|
|
87
|
-
*
|
|
88
|
-
* // Save to filesystem
|
|
89
|
-
* const savedPath = await FileSystemService.copyToDocuments(
|
|
90
|
-
* result.assets[0].uri,
|
|
91
|
-
* 'shared_image.jpg'
|
|
92
|
-
* );
|
|
93
|
-
*
|
|
94
|
-
* if (!savedPath.success || !savedPath.uri) return;
|
|
95
|
-
*
|
|
96
|
-
* // Share via system sheet
|
|
97
|
-
* await shareWithAutoType(savedPath.uri, 'shared_image.jpg', 'Share Photo');
|
|
98
|
-
* };
|
|
99
|
-
* ```
|
|
100
|
-
*
|
|
101
|
-
* Share Multiple Files:
|
|
102
|
-
* ```typescript
|
|
103
|
-
* import { useSharing } from '@umituz/react-native-sharing';
|
|
104
|
-
*
|
|
105
|
-
* const { shareMultiple } = useSharing();
|
|
106
|
-
*
|
|
107
|
-
* const handleShareMultiple = async () => {
|
|
108
|
-
* const uris = [
|
|
109
|
-
* 'file:///path/to/file1.jpg',
|
|
110
|
-
* 'file:///path/to/file2.jpg',
|
|
111
|
-
* 'file:///path/to/file3.jpg',
|
|
112
|
-
* ];
|
|
113
|
-
*
|
|
114
|
-
* await shareMultiple(uris, {
|
|
115
|
-
* dialogTitle: 'Share Photos',
|
|
116
|
-
* mimeType: 'image/jpeg',
|
|
117
|
-
* });
|
|
118
|
-
* };
|
|
119
|
-
* ```
|
|
120
|
-
*
|
|
121
|
-
* MIME Type Utilities:
|
|
122
|
-
* ```typescript
|
|
123
|
-
* import { SharingUtils, MIME_TYPES } from '@umituz/react-native-sharing';
|
|
124
|
-
*
|
|
125
|
-
* // Get MIME type from extension
|
|
126
|
-
* const mimeType = SharingUtils.getMimeTypeFromExtension('document.pdf');
|
|
127
|
-
* // Returns: 'application/pdf'
|
|
128
|
-
*
|
|
129
|
-
* // Get UTI for iOS
|
|
130
|
-
* const uti = SharingUtils.getUTIFromExtension('photo.jpg');
|
|
131
|
-
* // Returns: 'public.jpeg'
|
|
132
|
-
*
|
|
133
|
-
* // Prepare full options
|
|
134
|
-
* const options = SharingUtils.prepareShareOptions('file.pdf', 'Share Document');
|
|
135
|
-
* // Returns: { dialogTitle, mimeType, UTI }
|
|
136
|
-
*
|
|
137
|
-
* // Use predefined MIME types
|
|
138
|
-
* await share(uri, { mimeType: MIME_TYPES.PDF });
|
|
139
|
-
* await share(uri, { mimeType: MIME_TYPES.IMAGE_JPEG });
|
|
140
|
-
* await share(uri, { mimeType: MIME_TYPES.VIDEO_MP4 });
|
|
141
|
-
* ```
|
|
142
|
-
*
|
|
143
|
-
* Direct Service Usage (Rare):
|
|
144
|
-
* ```typescript
|
|
145
|
-
* import { SharingService } from '@umituz/react-native-sharing';
|
|
146
|
-
*
|
|
147
|
-
* // Check availability
|
|
148
|
-
* const isAvailable = await SharingService.isAvailable();
|
|
149
|
-
*
|
|
150
|
-
* // Share file
|
|
151
|
-
* const result = await SharingService.shareFile(uri, options);
|
|
152
|
-
* if (result.success) {
|
|
153
|
-
* console.log('Shared');
|
|
154
|
-
* } else {
|
|
155
|
-
* console.error(result.error);
|
|
156
|
-
* }
|
|
157
|
-
* ```
|
|
158
|
-
*
|
|
159
|
-
* BENEFITS:
|
|
160
|
-
* - Native system share sheet (OS-provided UI)
|
|
161
|
-
* - Automatic MIME type detection from filename
|
|
162
|
-
* - Platform-specific options (iOS UTI, Android dialogTitle)
|
|
163
|
-
* - Share availability check before attempting
|
|
164
|
-
* - Type-safe share operations
|
|
165
|
-
* - Error handling with state management
|
|
166
|
-
* - Works with all file types
|
|
167
|
-
*
|
|
168
|
-
* USE CASES:
|
|
169
|
-
* - Share photos/videos from gallery
|
|
170
|
-
* - Export reports/documents
|
|
171
|
-
* - Share app-generated content
|
|
172
|
-
* - Social media sharing
|
|
173
|
-
* - File backup/export
|
|
174
|
-
* - Collaborative file sharing
|
|
175
|
-
*
|
|
176
|
-
* @see https://docs.expo.dev/versions/latest/sdk/sharing/
|
|
177
4
|
*/
|
|
178
5
|
|
|
179
6
|
// ============================================================================
|
|
@@ -189,9 +16,10 @@ export {
|
|
|
189
16
|
MIME_TYPES,
|
|
190
17
|
UTI_TYPES,
|
|
191
18
|
SHARING_CONSTANTS,
|
|
192
|
-
SharingUtils,
|
|
193
19
|
} from './domain/entities/Share';
|
|
194
20
|
|
|
21
|
+
export { SharingUtils } from './domain/entities/SharingUtils';
|
|
22
|
+
|
|
195
23
|
// ============================================================================
|
|
196
24
|
// INFRASTRUCTURE LAYER - SERVICES
|
|
197
25
|
// ============================================================================
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import * as Sharing from 'expo-sharing';
|
|
12
12
|
import { FileSystemService } from '@umituz/react-native-filesystem';
|
|
13
13
|
import type { ShareOptions, ShareResult } from '../../domain/entities/Share';
|
|
14
|
-
import { SharingUtils } from '../../domain/entities/
|
|
14
|
+
import { SharingUtils } from '../../domain/entities/SharingUtils';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Sharing service for sharing files via system share sheet
|