@umituz/react-native-design-system 2.6.77 → 2.6.79
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/atoms/AtomicIcon.tsx +2 -19
- package/src/atoms/AtomicIcon.types.ts +21 -0
- 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/alerts/AlertToast.tsx +21 -37
- package/src/molecules/avatar/Avatar.tsx +0 -23
- 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.79",
|
|
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",
|
package/src/atoms/AtomicIcon.tsx
CHANGED
|
@@ -13,32 +13,15 @@ import { useAppDesignTokens } from '../theme';
|
|
|
13
13
|
import {
|
|
14
14
|
type IconSize as BaseIconSize,
|
|
15
15
|
type IconName,
|
|
16
|
+
type IconColor,
|
|
16
17
|
} from "./AtomicIcon.types";
|
|
17
18
|
|
|
18
19
|
// Re-export IconSize for convenience
|
|
19
20
|
export type IconSize = BaseIconSize;
|
|
20
|
-
export type { IconName };
|
|
21
|
+
export type { IconName, IconColor };
|
|
21
22
|
|
|
22
23
|
const FALLBACK_ICON = "help-circle-outline";
|
|
23
24
|
|
|
24
|
-
// Semantic color names that map to theme tokens
|
|
25
|
-
export type IconColor =
|
|
26
|
-
| "primary"
|
|
27
|
-
| "secondary"
|
|
28
|
-
| "success"
|
|
29
|
-
| "warning"
|
|
30
|
-
| "error"
|
|
31
|
-
| "info"
|
|
32
|
-
| "onSurface"
|
|
33
|
-
| "surfaceVariant"
|
|
34
|
-
| "onPrimary"
|
|
35
|
-
| "onSecondary"
|
|
36
|
-
| "textInverse"
|
|
37
|
-
| "textPrimary"
|
|
38
|
-
| "textSecondary"
|
|
39
|
-
| "textTertiary"
|
|
40
|
-
| "onSurfaceVariant";
|
|
41
|
-
|
|
42
25
|
export interface AtomicIconProps {
|
|
43
26
|
/** Icon name (Ionicons) */
|
|
44
27
|
name?: IconName;
|
|
@@ -20,11 +20,32 @@ export type IconName = IoniconsName | string;
|
|
|
20
20
|
*/
|
|
21
21
|
export type IconSizePreset = "xs" | "sm" | "md" | "lg" | "xl" | "xxl";
|
|
22
22
|
|
|
23
|
+
|
|
23
24
|
/**
|
|
24
25
|
* Icon size - preset name or custom number in pixels
|
|
25
26
|
*/
|
|
26
27
|
export type IconSize = IconSizePreset | number;
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Semantic color names that map to theme tokens
|
|
31
|
+
*/
|
|
32
|
+
export type IconColor =
|
|
33
|
+
| "primary"
|
|
34
|
+
| "secondary"
|
|
35
|
+
| "success"
|
|
36
|
+
| "warning"
|
|
37
|
+
| "error"
|
|
38
|
+
| "info"
|
|
39
|
+
| "onSurface"
|
|
40
|
+
| "surfaceVariant"
|
|
41
|
+
| "onPrimary"
|
|
42
|
+
| "onSecondary"
|
|
43
|
+
| "textInverse"
|
|
44
|
+
| "textPrimary"
|
|
45
|
+
| "textSecondary"
|
|
46
|
+
| "textTertiary"
|
|
47
|
+
| "onSurfaceVariant";
|
|
48
|
+
|
|
28
49
|
/**
|
|
29
50
|
* Icon size mapping to pixels
|
|
30
51
|
*/
|
|
@@ -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
|
|
|
@@ -28,48 +28,32 @@ export function AlertToast({ alert }: AlertToastProps) {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const getBackgroundColor = (type: AlertType): string => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
case AlertType.INFO:
|
|
39
|
-
return tokens.colors.info;
|
|
40
|
-
default:
|
|
41
|
-
return tokens.colors.backgroundSecondary;
|
|
42
|
-
}
|
|
31
|
+
const colors = {
|
|
32
|
+
[AlertType.SUCCESS]: tokens.colors.success,
|
|
33
|
+
[AlertType.ERROR]: tokens.colors.error,
|
|
34
|
+
[AlertType.WARNING]: tokens.colors.warning,
|
|
35
|
+
[AlertType.INFO]: tokens.colors.info,
|
|
36
|
+
};
|
|
37
|
+
return colors[type] || tokens.colors.backgroundSecondary;
|
|
43
38
|
};
|
|
44
39
|
|
|
45
|
-
const getActionButtonStyle = (style
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
borderWidth: 1,
|
|
53
|
-
borderColor: tokens.colors.textInverse,
|
|
54
|
-
};
|
|
55
|
-
case 'destructive':
|
|
56
|
-
return { backgroundColor: tokens.colors.error };
|
|
57
|
-
default:
|
|
58
|
-
return { backgroundColor: tokens.colors.backgroundSecondary };
|
|
40
|
+
const getActionButtonStyle = (style?: 'primary' | 'secondary' | 'destructive'): StyleProp<ViewStyle> => {
|
|
41
|
+
if (style === 'secondary') {
|
|
42
|
+
return {
|
|
43
|
+
backgroundColor: undefined,
|
|
44
|
+
borderWidth: 1,
|
|
45
|
+
borderColor: tokens.colors.textInverse,
|
|
46
|
+
};
|
|
59
47
|
}
|
|
48
|
+
const colors = {
|
|
49
|
+
primary: tokens.colors.backgroundPrimary,
|
|
50
|
+
destructive: tokens.colors.error,
|
|
51
|
+
};
|
|
52
|
+
return { backgroundColor: colors[style as keyof typeof colors] || tokens.colors.backgroundSecondary };
|
|
60
53
|
};
|
|
61
54
|
|
|
62
|
-
const getActionTextColor = (style
|
|
63
|
-
|
|
64
|
-
case 'primary':
|
|
65
|
-
return tokens.colors.textPrimary;
|
|
66
|
-
case 'secondary':
|
|
67
|
-
return tokens.colors.textInverse;
|
|
68
|
-
case 'destructive':
|
|
69
|
-
return tokens.colors.textInverse;
|
|
70
|
-
default:
|
|
71
|
-
return tokens.colors.textPrimary;
|
|
72
|
-
}
|
|
55
|
+
const getActionTextColor = (style?: 'primary' | 'secondary' | 'destructive'): string => {
|
|
56
|
+
return style === 'primary' ? tokens.colors.textPrimary : tokens.colors.textInverse;
|
|
73
57
|
};
|
|
74
58
|
|
|
75
59
|
const backgroundColor = getBackgroundColor(alert.type);
|
|
@@ -46,33 +46,10 @@ export interface AvatarProps {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Avatar Component
|
|
49
|
-
*
|
|
50
49
|
* Displays user avatars with automatic fallback hierarchy:
|
|
51
50
|
* 1. Image (if uri provided)
|
|
52
51
|
* 2. Initials (if name provided)
|
|
53
52
|
* 3. Icon (fallback)
|
|
54
|
-
*
|
|
55
|
-
* USAGE:
|
|
56
|
-
* ```typescript
|
|
57
|
-
* // Image avatar
|
|
58
|
-
* <Avatar uri="https://..." name="Ümit Uz" size="lg" />
|
|
59
|
-
*
|
|
60
|
-
* // Initials avatar (no image)
|
|
61
|
-
* <Avatar name="Ümit Uz" size="md" />
|
|
62
|
-
*
|
|
63
|
-
* // Icon avatar (fallback)
|
|
64
|
-
* <Avatar size="sm" />
|
|
65
|
-
*
|
|
66
|
-
* // With status indicator
|
|
67
|
-
* <Avatar
|
|
68
|
-
* name="Ümit Uz"
|
|
69
|
-
* showStatus
|
|
70
|
-
* status="online"
|
|
71
|
-
* />
|
|
72
|
-
*
|
|
73
|
-
* // Custom shape
|
|
74
|
-
* <Avatar name="John Doe" shape="rounded" />
|
|
75
|
-
* ```
|
|
76
53
|
*/
|
|
77
54
|
export const Avatar: React.FC<AvatarProps> = ({
|
|
78
55
|
uri,
|
|
@@ -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
|