@umituz/react-native-design-system 2.3.14 → 2.3.16
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 +19 -2
- package/src/index.ts +105 -0
- package/src/layouts/ScreenLayout/ScreenLayout.example.tsx +2 -2
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +1 -1
- package/src/molecules/animation/core/AnimationCore.ts +29 -0
- package/src/molecules/animation/domain/entities/Animation.ts +81 -0
- package/src/molecules/animation/domain/entities/Fireworks.ts +44 -0
- package/src/molecules/animation/domain/entities/Theme.ts +76 -0
- package/src/molecules/animation/index.ts +146 -0
- package/src/molecules/animation/infrastructure/services/AnimationConfigService.ts +35 -0
- package/src/molecules/animation/infrastructure/services/SpringAnimationConfigService.ts +67 -0
- package/src/molecules/animation/infrastructure/services/TimingAnimationConfigService.ts +57 -0
- package/src/molecules/animation/infrastructure/services/__tests__/SpringAnimationConfigService.test.ts +114 -0
- package/src/molecules/animation/infrastructure/services/__tests__/TimingAnimationConfigService.test.ts +105 -0
- package/src/molecules/animation/presentation/components/Fireworks.tsx +126 -0
- package/src/molecules/animation/presentation/components/__tests__/Fireworks.test.tsx +189 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useAnimation.integration.test.ts +216 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useFireworks.test.ts +242 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useGesture.test.ts +111 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useSpringAnimation.test.ts +131 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTimingAnimation.test.ts +175 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTransformAnimation.test.ts +137 -0
- package/src/molecules/animation/presentation/hooks/useAnimation.ts +77 -0
- package/src/molecules/animation/presentation/hooks/useFireworks.ts +141 -0
- package/src/molecules/animation/presentation/hooks/useGesture.ts +61 -0
- package/src/molecules/animation/presentation/hooks/useGestureCreators.ts +163 -0
- package/src/molecules/animation/presentation/hooks/useGestureState.ts +53 -0
- package/src/molecules/animation/presentation/hooks/useIconAnimations.ts +119 -0
- package/src/molecules/animation/presentation/hooks/useModalAnimations.ts +124 -0
- package/src/molecules/animation/presentation/hooks/useReanimatedReady.ts +60 -0
- package/src/molecules/animation/presentation/hooks/useSpringAnimation.ts +69 -0
- package/src/molecules/animation/presentation/hooks/useTimingAnimation.ts +111 -0
- package/src/molecules/animation/presentation/hooks/useTransformAnimation.ts +57 -0
- package/src/molecules/animation/presentation/providers/AnimationThemeProvider.tsx +62 -0
- package/src/molecules/animation/presentation/providers/__tests__/AnimationThemeProvider.test.tsx +165 -0
- package/src/molecules/animation/types/global.d.ts +97 -0
- package/src/molecules/celebration/domain/entities/CelebrationConfig.ts +17 -0
- package/src/molecules/celebration/domain/entities/FireworksConfig.ts +32 -0
- package/src/molecules/celebration/index.ts +93 -0
- package/src/molecules/celebration/infrastructure/services/FireworksConfigService.ts +49 -0
- package/src/molecules/celebration/presentation/components/CelebrationFireworksOverlay.tsx +33 -0
- package/src/molecules/celebration/presentation/components/CelebrationModal.tsx +78 -0
- package/src/molecules/celebration/presentation/components/CelebrationModalContent.tsx +90 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationModalAnimation.ts +49 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationState.ts +45 -0
- package/src/molecules/celebration/presentation/styles/CelebrationModalStyles.ts +65 -0
- package/src/molecules/countdown/components/Countdown.tsx +128 -0
- package/src/molecules/countdown/components/CountdownHeader.tsx +84 -0
- package/src/molecules/countdown/components/TimeUnit.tsx +73 -0
- package/src/molecules/countdown/hooks/useCountdown.ts +107 -0
- package/src/molecules/countdown/index.ts +25 -0
- package/src/molecules/countdown/types/CountdownTypes.ts +31 -0
- package/src/molecules/countdown/utils/TimeCalculator.ts +46 -0
- package/src/molecules/emoji/domain/entities/Emoji.ts +129 -0
- package/src/molecules/emoji/index.ts +177 -0
- package/src/molecules/emoji/presentation/components/EmojiPicker.tsx +102 -0
- package/src/molecules/emoji/presentation/hooks/useEmojiPicker.ts +171 -0
- package/src/molecules/index.ts +21 -0
- package/src/molecules/long-press-menu/domain/entities/MenuAction.ts +37 -0
- package/src/molecules/long-press-menu/index.ts +16 -0
- package/src/molecules/navigation/StackNavigator.tsx +75 -0
- package/src/molecules/navigation/TabsNavigator.tsx +94 -0
- package/src/molecules/navigation/components/FabButton.tsx +45 -0
- package/src/molecules/navigation/components/TabLabel.tsx +47 -0
- package/src/molecules/navigation/createStackNavigator.ts +20 -0
- package/src/molecules/navigation/createTabNavigator.ts +20 -0
- package/src/molecules/navigation/hooks/useTabBarStyles.ts +54 -0
- package/src/molecules/navigation/index.ts +37 -0
- package/src/molecules/navigation/types.ts +118 -0
- package/src/molecules/navigation/utils/AppNavigation.ts +101 -0
- package/src/molecules/navigation/utils/IconRenderer.ts +50 -0
- package/src/molecules/navigation/utils/LabelProcessor.ts +70 -0
- package/src/molecules/navigation/utils/NavigationCleanup.ts +62 -0
- package/src/molecules/navigation/utils/NavigationTheme.ts +21 -0
- package/src/molecules/navigation/utils/NavigationValidator.ts +61 -0
- package/src/molecules/navigation/utils/ScreenFactory.ts +115 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.getIconName.test.ts +109 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.renderIcon.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processLabel.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processTitle.test.ts +59 -0
- package/src/molecules/navigation/utils/__tests__/NavigationCleanup.test.ts +271 -0
- package/src/molecules/navigation/utils/__tests__/NavigationValidator.test.ts +252 -0
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +194 -0
- package/src/molecules/swipe-actions/index.ts +6 -0
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +131 -0
- package/src/theme/hooks/useResponsiveDesignTokens.ts +1 -1
- package/src/utilities/clipboard/ClipboardUtils.ts +71 -0
- package/src/utilities/clipboard/index.ts +5 -0
- package/src/utilities/index.ts +6 -0
- package/src/utilities/sharing/domain/entities/Share.ts +210 -0
- package/src/utilities/sharing/index.ts +205 -0
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +165 -0
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +154 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe Actions Domain - Entity Layer
|
|
3
|
+
*
|
|
4
|
+
* Core swipe action types and configurations.
|
|
5
|
+
* Defines pre-built action types, colors, icons, and utilities.
|
|
6
|
+
*
|
|
7
|
+
* @domain swipe-actions
|
|
8
|
+
* @layer domain/entities
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { IconName } from '../../../../index';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pre-built swipe action types
|
|
15
|
+
*/
|
|
16
|
+
export type SwipeActionType =
|
|
17
|
+
| 'delete'
|
|
18
|
+
| 'archive'
|
|
19
|
+
| 'edit'
|
|
20
|
+
| 'share'
|
|
21
|
+
| 'favorite'
|
|
22
|
+
| 'more'
|
|
23
|
+
| 'custom';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Swipe action configuration
|
|
27
|
+
*/
|
|
28
|
+
export interface SwipeActionConfig {
|
|
29
|
+
/** Action type */
|
|
30
|
+
type: SwipeActionType;
|
|
31
|
+
/** Action label (optional, defaults from type) */
|
|
32
|
+
label?: string;
|
|
33
|
+
/** Icon name (optional, defaults from type) */
|
|
34
|
+
icon?: IconName;
|
|
35
|
+
/** Custom color (optional, defaults from type) */
|
|
36
|
+
color?: string;
|
|
37
|
+
/** Action handler */
|
|
38
|
+
onPress: () => void | Promise<void>;
|
|
39
|
+
/** Enable haptic feedback (default: true) */
|
|
40
|
+
enableHaptics?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Swipe direction
|
|
45
|
+
*/
|
|
46
|
+
export type SwipeDirection = 'left' | 'right';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Swipeable item configuration
|
|
50
|
+
*/
|
|
51
|
+
export interface SwipeableConfig {
|
|
52
|
+
/** Actions revealed when swiping right (left side) */
|
|
53
|
+
leftActions?: SwipeActionConfig[];
|
|
54
|
+
/** Actions revealed when swiping left (right side) */
|
|
55
|
+
rightActions?: SwipeActionConfig[];
|
|
56
|
+
/** Swipe threshold in points (default: 80) */
|
|
57
|
+
threshold?: number;
|
|
58
|
+
/** Disable overshoot animation (default: true) */
|
|
59
|
+
disableOvershoot?: boolean;
|
|
60
|
+
/** Friction value (default: 2) */
|
|
61
|
+
friction?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pre-built action type configurations
|
|
66
|
+
*/
|
|
67
|
+
export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
|
|
68
|
+
label: string;
|
|
69
|
+
icon: IconName;
|
|
70
|
+
colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
|
|
71
|
+
hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
|
|
72
|
+
}> = {
|
|
73
|
+
delete: {
|
|
74
|
+
label: 'Delete',
|
|
75
|
+
icon: 'Trash2',
|
|
76
|
+
colorKey: 'error',
|
|
77
|
+
hapticsIntensity: 'Heavy',
|
|
78
|
+
},
|
|
79
|
+
archive: {
|
|
80
|
+
label: 'Archive',
|
|
81
|
+
icon: 'Archive',
|
|
82
|
+
colorKey: 'success',
|
|
83
|
+
hapticsIntensity: 'Medium',
|
|
84
|
+
},
|
|
85
|
+
edit: {
|
|
86
|
+
label: 'Edit',
|
|
87
|
+
icon: 'Pencil',
|
|
88
|
+
colorKey: 'primary',
|
|
89
|
+
hapticsIntensity: 'Light',
|
|
90
|
+
},
|
|
91
|
+
share: {
|
|
92
|
+
label: 'Share',
|
|
93
|
+
icon: 'Share2',
|
|
94
|
+
colorKey: 'secondary',
|
|
95
|
+
hapticsIntensity: 'Light',
|
|
96
|
+
},
|
|
97
|
+
favorite: {
|
|
98
|
+
label: 'Favorite',
|
|
99
|
+
icon: 'Heart',
|
|
100
|
+
colorKey: 'warning',
|
|
101
|
+
hapticsIntensity: 'Light',
|
|
102
|
+
},
|
|
103
|
+
more: {
|
|
104
|
+
label: 'More',
|
|
105
|
+
icon: 'MoveHorizontal',
|
|
106
|
+
colorKey: 'textSecondary',
|
|
107
|
+
hapticsIntensity: 'Light',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Default swipe configuration
|
|
113
|
+
*/
|
|
114
|
+
export const DEFAULT_SWIPE_CONFIG: Required<Omit<SwipeableConfig, 'leftActions' | 'rightActions'>> = {
|
|
115
|
+
threshold: 80,
|
|
116
|
+
disableOvershoot: true,
|
|
117
|
+
friction: 2,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Swipe action utility functions
|
|
122
|
+
*/
|
|
123
|
+
export class SwipeActionUtils {
|
|
124
|
+
/**
|
|
125
|
+
* Gets preset configuration for action type
|
|
126
|
+
*/
|
|
127
|
+
static getPreset(type: SwipeActionType) {
|
|
128
|
+
if (type === 'custom') {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return ACTION_PRESETS[type];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validates swipe action configuration
|
|
136
|
+
*/
|
|
137
|
+
static validateAction(action: SwipeActionConfig): boolean {
|
|
138
|
+
// Must have onPress handler
|
|
139
|
+
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Custom actions must have label, icon, and color
|
|
144
|
+
if (action.type === 'custom') {
|
|
145
|
+
return !!(action.label && action.icon && action.color);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Gets action display label
|
|
153
|
+
*/
|
|
154
|
+
static getLabel(action: SwipeActionConfig): string {
|
|
155
|
+
if (action.label) {
|
|
156
|
+
return action.label;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const preset = this.getPreset(action.type);
|
|
160
|
+
return preset?.label || 'Action';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Gets action icon name
|
|
165
|
+
*/
|
|
166
|
+
static getIcon(action: SwipeActionConfig): IconName {
|
|
167
|
+
if (action.icon) {
|
|
168
|
+
return action.icon;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const preset = this.getPreset(action.type);
|
|
172
|
+
return preset?.icon || 'MoveHorizontal';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Gets action color key for theme
|
|
177
|
+
*/
|
|
178
|
+
static getColorKey(action: SwipeActionConfig): string | null {
|
|
179
|
+
if (action.color) {
|
|
180
|
+
return null; // Use custom color
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const preset = this.getPreset(action.type);
|
|
184
|
+
return preset?.colorKey || null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Gets haptics intensity for action
|
|
189
|
+
*/
|
|
190
|
+
static getHapticsIntensity(action: SwipeActionConfig): 'Light' | 'Medium' | 'Heavy' {
|
|
191
|
+
const preset = this.getPreset(action.type);
|
|
192
|
+
return preset?.hapticsIntensity || 'Light';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe Actions Domain - SwipeActionButton Component
|
|
3
|
+
*
|
|
4
|
+
* Individual swipe action button with icon and label.
|
|
5
|
+
* Rendered inside SwipeableItem when swiping.
|
|
6
|
+
*
|
|
7
|
+
* @domain swipe-actions
|
|
8
|
+
* @layer presentation/components
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { StyleSheet, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
13
|
+
import { AtomicText, AtomicIcon } from '../../../../index';
|
|
14
|
+
import { useAppDesignTokens } from '../../../../index';
|
|
15
|
+
import { HapticService } from '@umituz/react-native-haptics';
|
|
16
|
+
import type { SwipeActionConfig } from '../../domain/entities/SwipeAction';
|
|
17
|
+
import { SwipeActionUtils } from '../../domain/entities/SwipeAction';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SwipeActionButton component props
|
|
21
|
+
*/
|
|
22
|
+
export interface SwipeActionButtonProps {
|
|
23
|
+
/** Action configuration */
|
|
24
|
+
action: SwipeActionConfig;
|
|
25
|
+
/** Action position (index) */
|
|
26
|
+
position: number;
|
|
27
|
+
/** Total actions count */
|
|
28
|
+
totalActions: number;
|
|
29
|
+
/** Swipe direction */
|
|
30
|
+
direction: 'left' | 'right';
|
|
31
|
+
/** Custom container style */
|
|
32
|
+
style?: StyleProp<ViewStyle>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* SwipeActionButton Component
|
|
37
|
+
*
|
|
38
|
+
* Displays an individual swipe action with icon and label.
|
|
39
|
+
* Animates based on swipe progress.
|
|
40
|
+
*
|
|
41
|
+
* USAGE:
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // Used internally by SwipeableItem
|
|
44
|
+
* <SwipeActionButton
|
|
45
|
+
* action={deleteAction}
|
|
46
|
+
* progress={progressValue}
|
|
47
|
+
* drag={dragValue}
|
|
48
|
+
* position={0}
|
|
49
|
+
* totalActions={1}
|
|
50
|
+
* direction="right"
|
|
51
|
+
* />
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const SwipeActionButton: React.FC<SwipeActionButtonProps> = ({
|
|
55
|
+
action,
|
|
56
|
+
style,
|
|
57
|
+
}) => {
|
|
58
|
+
const tokens = useAppDesignTokens();
|
|
59
|
+
|
|
60
|
+
// Get action properties
|
|
61
|
+
const label = SwipeActionUtils.getLabel(action);
|
|
62
|
+
const iconName = SwipeActionUtils.getIcon(action);
|
|
63
|
+
const colorKey = SwipeActionUtils.getColorKey(action);
|
|
64
|
+
const customColor = action.color;
|
|
65
|
+
const enableHaptics = action.enableHaptics !== false;
|
|
66
|
+
|
|
67
|
+
// Get background color from theme or custom
|
|
68
|
+
const backgroundColor = customColor || (colorKey ? (tokens.colors[colorKey as keyof typeof tokens.colors] as string) : tokens.colors.primary);
|
|
69
|
+
|
|
70
|
+
const handlePress = async () => {
|
|
71
|
+
// Trigger haptic feedback
|
|
72
|
+
if (enableHaptics) {
|
|
73
|
+
const intensity = SwipeActionUtils.getHapticsIntensity(action);
|
|
74
|
+
if (intensity === 'Light') {
|
|
75
|
+
await HapticService.impact('Light');
|
|
76
|
+
} else if (intensity === 'Medium') {
|
|
77
|
+
await HapticService.impact('Medium');
|
|
78
|
+
} else {
|
|
79
|
+
await HapticService.impact('Heavy');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Execute action
|
|
84
|
+
await action.onPress();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<TouchableOpacity
|
|
89
|
+
style={[
|
|
90
|
+
styles.container,
|
|
91
|
+
{
|
|
92
|
+
backgroundColor,
|
|
93
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
94
|
+
minWidth: 80,
|
|
95
|
+
justifyContent: 'center',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
},
|
|
98
|
+
style,
|
|
99
|
+
]}
|
|
100
|
+
onPress={handlePress}
|
|
101
|
+
activeOpacity={0.7}
|
|
102
|
+
>
|
|
103
|
+
<View style={styles.content}>
|
|
104
|
+
<AtomicIcon
|
|
105
|
+
name={iconName}
|
|
106
|
+
size="md"
|
|
107
|
+
customColor="#FFFFFF"
|
|
108
|
+
/>
|
|
109
|
+
<AtomicText
|
|
110
|
+
type="bodySmall"
|
|
111
|
+
style={[styles.label, { color: '#FFFFFF' }]}
|
|
112
|
+
>
|
|
113
|
+
{label}
|
|
114
|
+
</AtomicText>
|
|
115
|
+
</View>
|
|
116
|
+
</TouchableOpacity>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const styles = StyleSheet.create({
|
|
121
|
+
container: {
|
|
122
|
+
height: '100%',
|
|
123
|
+
},
|
|
124
|
+
content: {
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
gap: 4,
|
|
127
|
+
},
|
|
128
|
+
label: {
|
|
129
|
+
textAlign: 'center',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
@@ -24,7 +24,7 @@ import { useResponsive } from '../../responsive/useResponsive';
|
|
|
24
24
|
*
|
|
25
25
|
* @example
|
|
26
26
|
* ```typescript
|
|
27
|
-
* import { useResponsiveDesignTokens } from '
|
|
27
|
+
* import { useResponsiveDesignTokens } from '../../index';
|
|
28
28
|
*
|
|
29
29
|
* const MyComponent = () => {
|
|
30
30
|
* const tokens = useResponsiveDesignTokens();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard Utilities
|
|
3
|
+
*
|
|
4
|
+
* Simple wrapper around expo-clipboard for copy/paste functionality
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { ClipboardUtils } from '@umituz/react-native-design-system';
|
|
9
|
+
*
|
|
10
|
+
* // Copy text
|
|
11
|
+
* await ClipboardUtils.copyToClipboard('Hello World');
|
|
12
|
+
*
|
|
13
|
+
* // Paste text
|
|
14
|
+
* const text = await ClipboardUtils.getFromClipboard();
|
|
15
|
+
*
|
|
16
|
+
* // Check if clipboard has content
|
|
17
|
+
* const hasContent = await ClipboardUtils.hasContent();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as Clipboard from 'expo-clipboard';
|
|
22
|
+
|
|
23
|
+
export class ClipboardUtils {
|
|
24
|
+
/**
|
|
25
|
+
* Copy text to clipboard
|
|
26
|
+
*/
|
|
27
|
+
static async copyToClipboard(text: string): Promise<void> {
|
|
28
|
+
try {
|
|
29
|
+
await Clipboard.setStringAsync(text);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('[ClipboardUtils] Failed to copy to clipboard:', error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get text from clipboard
|
|
38
|
+
*/
|
|
39
|
+
static async getFromClipboard(): Promise<string> {
|
|
40
|
+
try {
|
|
41
|
+
return await Clipboard.getStringAsync();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('[ClipboardUtils] Failed to get from clipboard:', error);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if clipboard has content
|
|
50
|
+
*/
|
|
51
|
+
static async hasContent(): Promise<boolean> {
|
|
52
|
+
try {
|
|
53
|
+
return await Clipboard.hasStringAsync();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('[ClipboardUtils] Failed to check clipboard:', error);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Clear clipboard
|
|
62
|
+
*/
|
|
63
|
+
static async clear(): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
await Clipboard.setStringAsync('');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('[ClipboardUtils] Failed to clear clipboard:', error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sharing Domain - Core Entities
|
|
3
|
+
*
|
|
4
|
+
* This file defines core types and interfaces for sharing functionality.
|
|
5
|
+
* Handles system share sheet using expo-sharing.
|
|
6
|
+
*
|
|
7
|
+
* @domain sharing
|
|
8
|
+
* @layer domain/entities
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Share options for sharing content
|
|
13
|
+
*/
|
|
14
|
+
export interface ShareOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Dialog title (Android only)
|
|
17
|
+
*/
|
|
18
|
+
dialogTitle?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* MIME type of the file being shared
|
|
22
|
+
*/
|
|
23
|
+
mimeType?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* UTI (Uniform Type Identifier) for the file (iOS only)
|
|
27
|
+
*/
|
|
28
|
+
UTI?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Share result
|
|
33
|
+
*/
|
|
34
|
+
export interface ShareResult {
|
|
35
|
+
success: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Common MIME types for sharing
|
|
41
|
+
*/
|
|
42
|
+
export const MIME_TYPES = {
|
|
43
|
+
// Images
|
|
44
|
+
IMAGE_JPEG: 'image/jpeg',
|
|
45
|
+
IMAGE_PNG: 'image/png',
|
|
46
|
+
IMAGE_GIF: 'image/gif',
|
|
47
|
+
IMAGE_WEBP: 'image/webp',
|
|
48
|
+
|
|
49
|
+
// Videos
|
|
50
|
+
VIDEO_MP4: 'video/mp4',
|
|
51
|
+
VIDEO_QUICKTIME: 'video/quicktime',
|
|
52
|
+
VIDEO_AVI: 'video/avi',
|
|
53
|
+
|
|
54
|
+
// Audio
|
|
55
|
+
AUDIO_MP3: 'audio/mpeg',
|
|
56
|
+
AUDIO_WAV: 'audio/wav',
|
|
57
|
+
AUDIO_AAC: 'audio/aac',
|
|
58
|
+
|
|
59
|
+
// Documents
|
|
60
|
+
PDF: 'application/pdf',
|
|
61
|
+
TEXT: 'text/plain',
|
|
62
|
+
JSON: 'application/json',
|
|
63
|
+
ZIP: 'application/zip',
|
|
64
|
+
|
|
65
|
+
// Generic
|
|
66
|
+
OCTET_STREAM: 'application/octet-stream',
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* iOS UTI (Uniform Type Identifiers)
|
|
71
|
+
*/
|
|
72
|
+
export const UTI_TYPES = {
|
|
73
|
+
// Images
|
|
74
|
+
IMAGE: 'public.image',
|
|
75
|
+
JPEG: 'public.jpeg',
|
|
76
|
+
PNG: 'public.png',
|
|
77
|
+
|
|
78
|
+
// Videos
|
|
79
|
+
VIDEO: 'public.video',
|
|
80
|
+
MOVIE: 'public.movie',
|
|
81
|
+
|
|
82
|
+
// Audio
|
|
83
|
+
AUDIO: 'public.audio',
|
|
84
|
+
MP3: 'public.mp3',
|
|
85
|
+
|
|
86
|
+
// Documents
|
|
87
|
+
PDF: 'com.adobe.pdf',
|
|
88
|
+
TEXT: 'public.text',
|
|
89
|
+
JSON: 'public.json',
|
|
90
|
+
|
|
91
|
+
// Generic
|
|
92
|
+
DATA: 'public.data',
|
|
93
|
+
CONTENT: 'public.content',
|
|
94
|
+
} as const;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Sharing constants
|
|
98
|
+
*/
|
|
99
|
+
export const SHARING_CONSTANTS = {
|
|
100
|
+
DEFAULT_DIALOG_TITLE: 'Share',
|
|
101
|
+
DEFAULT_MIME_TYPE: MIME_TYPES.OCTET_STREAM,
|
|
102
|
+
} as const;
|
|
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
|
+
|
|
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
|
+
}
|