@umituz/react-native-design-system 4.25.7 → 4.25.9
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/AtomicAvatar.tsx +0 -5
- package/src/atoms/AtomicInput.tsx +0 -2
- package/src/atoms/AtomicProgress.tsx +0 -5
- package/src/atoms/AtomicTextArea.tsx +0 -4
- package/src/atoms/card/AtomicCard.tsx +111 -54
- package/src/atoms/input/types.ts +0 -2
- package/src/atoms/skeleton/AtomicSkeleton.tsx +2 -2
- package/src/image/presentation/components/ImageGallery.tsx +16 -15
- package/src/image/presentation/components/editor/StickerPickerSheet.tsx +4 -7
- package/src/index.ts +12 -55
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +0 -2
- package/src/loading/presentation/providers/LoadingProvider.tsx +13 -4
- package/src/molecules/SearchBar/SearchBar.tsx +0 -2
- package/src/molecules/SearchBar/SearchSuggestions.tsx +1 -1
- package/src/molecules/SearchBar/types.ts +0 -1
- package/src/molecules/StepHeader/StepHeader.tsx +1 -1
- package/src/molecules/avatar/Avatar.tsx +76 -71
- package/src/molecules/avatar/AvatarGroup.tsx +1 -1
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +2 -3
- package/src/molecules/calendar/presentation/components/CalendarWeekdayHeader.tsx +1 -1
- package/src/molecules/countdown/components/Countdown.tsx +14 -11
- package/src/molecules/info-grid/InfoGrid.tsx +2 -2
- package/src/onboarding/presentation/components/BackgroundImageCollage.tsx +2 -2
- package/src/onboarding/presentation/components/OnboardingBackground.tsx +63 -49
- package/src/onboarding/presentation/components/OnboardingSlide.tsx +2 -2
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +3 -1
- package/src/gallery/gallery-download.service.ts +0 -69
- package/src/gallery/gallery-save.service.ts +0 -80
- package/src/gallery/index.ts +0 -3
- package/src/gallery/types.ts +0 -11
- package/src/image/domain/entities/EditorTypes.ts +0 -23
- package/src/image/domain/entities/editor/EditorConfigTypes.ts +0 -35
- package/src/image/domain/entities/editor/EditorElementTypes.ts +0 -60
- package/src/image/domain/entities/editor/EditorFilterTypes.ts +0 -9
- package/src/image/domain/entities/editor/EditorLayerTypes.ts +0 -34
- package/src/image/domain/entities/editor/EditorStateTypes.ts +0 -35
- package/src/image/domain/entities/editor/EditorToolTypes.ts +0 -33
- package/src/image/infrastructure/services/ImageEditorService.ts +0 -134
- package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +0 -120
- package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +0 -63
- package/src/image/infrastructure/utils/LayerManager.ts +0 -65
- package/src/media/infrastructure/hooks/useGenericMediaGeneration.ts +0 -170
- package/src/molecules/ConfirmationModal.tsx +0 -42
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.types.ts +0 -64
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.utils.ts +0 -56
- package/src/molecules/calendar/infrastructure/storage/EventActions.ts +0 -140
- package/src/molecules/calendar/infrastructure/storage/NavigationActions.ts +0 -118
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +0 -185
- package/src/molecules/confirmation-modal/index.ts +0 -7
- package/src/molecules/listitem/index.ts +0 -6
- package/src/molecules/navigation/components/index.ts +0 -4
- package/src/molecules/navigation/utils/NavigationTheme.ts +0 -21
- package/src/presentation/utils/variants/compound.ts +0 -34
- package/src/services/api/ApiClient.ts +0 -180
- package/src/services/api/index.ts +0 -9
- package/src/services/api/types/ApiTypes.ts +0 -50
- package/src/services/api/utils/requestBuilder.ts +0 -92
- package/src/services/api/utils/responseHandler.ts +0 -130
- package/src/storage/cache/index.ts +0 -28
- package/src/theme/core/tokens/BorderRadius.ts +0 -16
- package/src/utilities/clipboard/ClipboardUtils.ts +0 -67
- package/src/utilities/clipboard/index.ts +0 -5
- package/src/utilities/index.ts +0 -6
- package/src/utilities/sharing/domain/entities/Share.ts +0 -104
- package/src/utilities/sharing/domain/entities/SharingUtils.ts +0 -111
- package/src/utilities/sharing/index.ts +0 -33
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +0 -165
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +0 -116
- package/src/utils/colorMapper.ts +0 -193
- package/src/utils/errors/adapters/CacheErrorAdapter.ts +0 -68
- package/src/utils/errors/adapters/ImageErrorAdapter.ts +0 -91
- package/src/utils/errors/adapters/StorageErrorAdapter.ts +0 -107
- package/src/utils/formatHelper.ts +0 -16
- package/src/utils/formatters/dateFormatter.ts +0 -64
- package/src/utils/formatters/numberFormatter.ts +0 -130
- package/src/utils/index.ts +0 -16
- package/src/utils/styleComposer.ts +0 -94
- package/src/utils/validationHelper.ts +0 -16
- package/src/utils/validators/dataValidators.ts +0 -111
- package/src/utils/validators/numericValidators.ts +0 -106
- package/src/utils/validators/stringValidators.ts +0 -85
|
@@ -13,41 +13,86 @@ import type { AvatarSize, AvatarShape } from './Avatar.types';
|
|
|
13
13
|
import { SIZE_CONFIGS, AVATAR_CONSTANTS } from './Avatar.constants';
|
|
14
14
|
import { AvatarUtils } from './Avatar.utils';
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Avatar component props
|
|
18
|
-
*/
|
|
19
16
|
export interface AvatarProps {
|
|
20
|
-
/** Image URI */
|
|
21
17
|
uri?: string;
|
|
22
|
-
/** User name for initials */
|
|
23
18
|
name?: string;
|
|
24
|
-
/** Icon name (fallback when no image/name) */
|
|
25
19
|
icon?: string;
|
|
26
|
-
/** Size preset */
|
|
27
20
|
size?: AvatarSize;
|
|
28
|
-
/** Shape */
|
|
29
21
|
shape?: AvatarShape;
|
|
30
|
-
/** Custom background color */
|
|
31
22
|
backgroundColor?: string;
|
|
32
|
-
/** Show status indicator */
|
|
33
23
|
showStatus?: boolean;
|
|
34
|
-
/** Status (online/offline/away/busy) */
|
|
35
24
|
status?: 'online' | 'offline' | 'away' | 'busy';
|
|
36
|
-
/** Custom container style */
|
|
37
25
|
style?: StyleProp<ViewStyle>;
|
|
38
|
-
/** Custom image style */
|
|
39
26
|
imageStyle?: StyleProp<ImageStyle>;
|
|
40
|
-
/** OnPress handler */
|
|
41
27
|
onPress?: () => void;
|
|
42
28
|
}
|
|
43
29
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
interface AvatarContentProps {
|
|
31
|
+
hasImage: boolean;
|
|
32
|
+
hasName: boolean;
|
|
33
|
+
uri?: string;
|
|
34
|
+
initials: string;
|
|
35
|
+
icon: string;
|
|
36
|
+
config: typeof SIZE_CONFIGS[AvatarSize];
|
|
37
|
+
borderRadius: number;
|
|
38
|
+
imageStyle?: StyleProp<ImageStyle>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const AvatarContent: React.FC<AvatarContentProps> = ({
|
|
42
|
+
hasImage,
|
|
43
|
+
hasName,
|
|
44
|
+
uri,
|
|
45
|
+
initials,
|
|
46
|
+
icon,
|
|
47
|
+
config,
|
|
48
|
+
borderRadius,
|
|
49
|
+
imageStyle,
|
|
50
|
+
}) => {
|
|
51
|
+
const tokens = useAppDesignTokens();
|
|
52
|
+
|
|
53
|
+
if (hasImage) {
|
|
54
|
+
return (
|
|
55
|
+
<Image
|
|
56
|
+
source={{ uri }}
|
|
57
|
+
style={[
|
|
58
|
+
styles.image,
|
|
59
|
+
{
|
|
60
|
+
width: config.size,
|
|
61
|
+
height: config.size,
|
|
62
|
+
borderRadius,
|
|
63
|
+
},
|
|
64
|
+
imageStyle,
|
|
65
|
+
]}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (hasName) {
|
|
71
|
+
return (
|
|
72
|
+
<AtomicText
|
|
73
|
+
type="bodyMedium"
|
|
74
|
+
style={[
|
|
75
|
+
styles.initials,
|
|
76
|
+
{
|
|
77
|
+
fontSize: config.fontSize,
|
|
78
|
+
color: tokens.colors.textInverse,
|
|
79
|
+
},
|
|
80
|
+
]}
|
|
81
|
+
>
|
|
82
|
+
{initials}
|
|
83
|
+
</AtomicText>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<AtomicIcon
|
|
89
|
+
name={icon}
|
|
90
|
+
customSize={config.iconSize}
|
|
91
|
+
customColor={tokens.colors.textInverse}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
51
96
|
export const Avatar: React.FC<AvatarProps> = ({
|
|
52
97
|
uri,
|
|
53
98
|
name,
|
|
@@ -64,7 +109,6 @@ export const Avatar: React.FC<AvatarProps> = ({
|
|
|
64
109
|
const tokens = useAppDesignTokens();
|
|
65
110
|
const config = useMemo(() => SIZE_CONFIGS[size], [size]);
|
|
66
111
|
|
|
67
|
-
// Determine avatar type and content
|
|
68
112
|
const hasImage = !!uri;
|
|
69
113
|
const hasName = !!name;
|
|
70
114
|
const initials = useMemo(
|
|
@@ -80,57 +124,11 @@ export const Avatar: React.FC<AvatarProps> = ({
|
|
|
80
124
|
[shape, config.size]
|
|
81
125
|
);
|
|
82
126
|
|
|
83
|
-
// Status indicator position
|
|
84
127
|
const statusPosition = useMemo(() => ({
|
|
85
128
|
bottom: 0,
|
|
86
129
|
right: 0,
|
|
87
130
|
}), []);
|
|
88
131
|
|
|
89
|
-
const renderContent = () => {
|
|
90
|
-
if (hasImage) {
|
|
91
|
-
return (
|
|
92
|
-
<Image
|
|
93
|
-
source={{ uri }}
|
|
94
|
-
style={[
|
|
95
|
-
styles.image,
|
|
96
|
-
{
|
|
97
|
-
width: config.size,
|
|
98
|
-
height: config.size,
|
|
99
|
-
borderRadius,
|
|
100
|
-
},
|
|
101
|
-
imageStyle,
|
|
102
|
-
]}
|
|
103
|
-
/>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (hasName) {
|
|
108
|
-
return (
|
|
109
|
-
<AtomicText
|
|
110
|
-
type="bodyMedium"
|
|
111
|
-
style={[
|
|
112
|
-
styles.initials,
|
|
113
|
-
{
|
|
114
|
-
fontSize: config.fontSize,
|
|
115
|
-
color: tokens.colors.textInverse,
|
|
116
|
-
},
|
|
117
|
-
]}
|
|
118
|
-
>
|
|
119
|
-
{initials}
|
|
120
|
-
</AtomicText>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Fallback to icon
|
|
125
|
-
return (
|
|
126
|
-
<AtomicIcon
|
|
127
|
-
name={icon}
|
|
128
|
-
customSize={config.iconSize}
|
|
129
|
-
customColor={tokens.colors.textInverse}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
132
|
const AvatarWrapper = onPress ? TouchableOpacity : View;
|
|
135
133
|
|
|
136
134
|
return (
|
|
@@ -151,9 +149,17 @@ export const Avatar: React.FC<AvatarProps> = ({
|
|
|
151
149
|
accessibilityLabel={name || 'User avatar'}
|
|
152
150
|
accessible={true}
|
|
153
151
|
>
|
|
154
|
-
|
|
152
|
+
<AvatarContent
|
|
153
|
+
hasImage={hasImage}
|
|
154
|
+
hasName={hasName}
|
|
155
|
+
uri={uri}
|
|
156
|
+
initials={initials}
|
|
157
|
+
icon={icon}
|
|
158
|
+
config={config}
|
|
159
|
+
borderRadius={borderRadius}
|
|
160
|
+
imageStyle={imageStyle}
|
|
161
|
+
/>
|
|
155
162
|
|
|
156
|
-
{/* Status Indicator */}
|
|
157
163
|
{showStatus && (
|
|
158
164
|
<View
|
|
159
165
|
style={[
|
|
@@ -194,4 +200,3 @@ const styles = StyleSheet.create({
|
|
|
194
200
|
position: 'absolute',
|
|
195
201
|
},
|
|
196
202
|
});
|
|
197
|
-
|
|
@@ -60,7 +60,7 @@ export const AvatarGroup: React.FC<AvatarGroupProps> = ({
|
|
|
60
60
|
<View style={[styles.container, style]}>
|
|
61
61
|
{visibleItems.map((item, index) => (
|
|
62
62
|
<View
|
|
63
|
-
key={
|
|
63
|
+
key={item.uri || item.name || item.icon}
|
|
64
64
|
style={[
|
|
65
65
|
styles.avatarWrapper,
|
|
66
66
|
index > 0 && { marginLeft: spacing },
|
|
@@ -40,7 +40,6 @@ export const CalendarDayCell: React.FC<CalendarDayCellProps> = ({
|
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<TouchableOpacity
|
|
43
|
-
key={index}
|
|
44
43
|
style={[
|
|
45
44
|
calendarStyles.dayCell,
|
|
46
45
|
{
|
|
@@ -75,9 +74,9 @@ export const CalendarDayCell: React.FC<CalendarDayCellProps> = ({
|
|
|
75
74
|
<View style={[calendarStyles.eventDot, { backgroundColor: tokens.colors.success }]} />
|
|
76
75
|
)}
|
|
77
76
|
|
|
78
|
-
{visibleEvents.map((event
|
|
77
|
+
{visibleEvents.map((event) => (
|
|
79
78
|
<View
|
|
80
|
-
key={
|
|
79
|
+
key={event.id}
|
|
81
80
|
style={[
|
|
82
81
|
calendarStyles.eventDot,
|
|
83
82
|
{
|
|
@@ -17,7 +17,7 @@ export const CalendarWeekdayHeader: React.FC<CalendarWeekdayHeaderProps> = ({
|
|
|
17
17
|
return (
|
|
18
18
|
<View style={calendarStyles.weekdayHeader}>
|
|
19
19
|
{weekdayNames.map((day, index) => (
|
|
20
|
-
<View key={
|
|
20
|
+
<View key={day} style={calendarStyles.weekdayCell}>
|
|
21
21
|
<AtomicText type="bodySmall" color="secondary" style={calendarStyles.weekdayText}>
|
|
22
22
|
{day}
|
|
23
23
|
</AtomicText>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useState
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import { View, StyleSheet } from 'react-native';
|
|
3
3
|
import { useAppDesignTokens } from '../../../theme';
|
|
4
4
|
import { useCountdown } from '../hooks/useCountdown';
|
|
@@ -7,6 +7,9 @@ import { TimeUnit } from './TimeUnit';
|
|
|
7
7
|
import type { CountdownTarget, CountdownDisplayConfig } from '../types/CountdownTypes';
|
|
8
8
|
import type { IconName } from '../../../atoms';
|
|
9
9
|
|
|
10
|
+
const EMPTY_TARGETS: CountdownTarget[] = [];
|
|
11
|
+
const DEFAULT_DISPLAY_CONFIG: CountdownDisplayConfig = {};
|
|
12
|
+
|
|
10
13
|
export interface CountdownProps {
|
|
11
14
|
target: CountdownTarget;
|
|
12
15
|
alternateTargets?: CountdownTarget[];
|
|
@@ -19,8 +22,8 @@ export interface CountdownProps {
|
|
|
19
22
|
|
|
20
23
|
export const Countdown: React.FC<CountdownProps> = ({
|
|
21
24
|
target,
|
|
22
|
-
alternateTargets =
|
|
23
|
-
displayConfig =
|
|
25
|
+
alternateTargets = EMPTY_TARGETS,
|
|
26
|
+
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
|
24
27
|
interval = 1000,
|
|
25
28
|
onExpire,
|
|
26
29
|
onTargetChange,
|
|
@@ -49,17 +52,12 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
49
52
|
onExpire,
|
|
50
53
|
});
|
|
51
54
|
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (currentTarget) {
|
|
54
|
-
updateTarget(currentTarget);
|
|
55
|
-
}
|
|
56
|
-
}, [currentTarget, updateTarget]);
|
|
57
|
-
|
|
58
55
|
const handleToggle = () => {
|
|
59
56
|
const nextIndex = (currentTargetIndex + 1) % allTargets.length;
|
|
60
57
|
const nextTarget = allTargets[nextIndex];
|
|
61
58
|
if (nextTarget) {
|
|
62
59
|
setCurrentTargetIndex(nextIndex);
|
|
60
|
+
updateTarget(nextTarget);
|
|
63
61
|
onTargetChange?.(nextTarget);
|
|
64
62
|
}
|
|
65
63
|
};
|
|
@@ -78,6 +76,7 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
78
76
|
|
|
79
77
|
const timeUnits = useMemo(() => {
|
|
80
78
|
interface CountdownUnit {
|
|
79
|
+
key: string;
|
|
81
80
|
value: number;
|
|
82
81
|
label: string;
|
|
83
82
|
}
|
|
@@ -87,24 +86,28 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
87
86
|
|
|
88
87
|
if (shouldShowDays) {
|
|
89
88
|
units.push({
|
|
89
|
+
key: 'days',
|
|
90
90
|
value: timeRemaining.days,
|
|
91
91
|
label: labelFormatter('days', timeRemaining.days)
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
if (showHours) {
|
|
95
95
|
units.push({
|
|
96
|
+
key: 'hours',
|
|
96
97
|
value: timeRemaining.hours,
|
|
97
98
|
label: labelFormatter('hours', timeRemaining.hours)
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
101
|
if (showMinutes) {
|
|
101
102
|
units.push({
|
|
103
|
+
key: 'minutes',
|
|
102
104
|
value: timeRemaining.minutes,
|
|
103
105
|
label: labelFormatter('minutes', timeRemaining.minutes)
|
|
104
106
|
});
|
|
105
107
|
}
|
|
106
108
|
if (showSeconds) {
|
|
107
109
|
units.push({
|
|
110
|
+
key: 'seconds',
|
|
108
111
|
value: timeRemaining.seconds,
|
|
109
112
|
label: labelFormatter('seconds', timeRemaining.seconds)
|
|
110
113
|
});
|
|
@@ -125,9 +128,9 @@ export const Countdown: React.FC<CountdownProps> = ({
|
|
|
125
128
|
)}
|
|
126
129
|
|
|
127
130
|
<View style={[styles.grid, { gap: tokens.spacing.sm }]}>
|
|
128
|
-
{timeUnits.map((unit
|
|
131
|
+
{timeUnits.map((unit) => (
|
|
129
132
|
<TimeUnit
|
|
130
|
-
key={
|
|
133
|
+
key={unit.key}
|
|
131
134
|
value={unit.value}
|
|
132
135
|
label={unit.label}
|
|
133
136
|
size={size}
|
|
@@ -84,8 +84,8 @@ export const InfoGrid: React.FC<InfoGridProps> = ({
|
|
|
84
84
|
)}
|
|
85
85
|
|
|
86
86
|
<View style={styles.grid}>
|
|
87
|
-
{items.map((item
|
|
88
|
-
<View key={
|
|
87
|
+
{items.map((item) => (
|
|
88
|
+
<View key={item.text} style={[styles.item, itemStyle]}>
|
|
89
89
|
{item.icon && (
|
|
90
90
|
<View style={styles.iconContainer}>
|
|
91
91
|
<AtomicIcon name={item.icon} size="xs" color="primary" />
|
|
@@ -76,9 +76,9 @@ export const BackgroundImageCollage: React.FC<BackgroundImageCollageProps> = ({
|
|
|
76
76
|
|
|
77
77
|
return (
|
|
78
78
|
<View style={[StyleSheet.absoluteFill, { opacity }]} pointerEvents="none">
|
|
79
|
-
{imageLayouts.map((item
|
|
79
|
+
{imageLayouts.map((item) => (
|
|
80
80
|
<Image
|
|
81
|
-
key={
|
|
81
|
+
key={String(item.source)}
|
|
82
82
|
source={item.source}
|
|
83
83
|
style={item.style}
|
|
84
84
|
contentFit="cover"
|
|
@@ -10,6 +10,64 @@ import { BackgroundVideo } from "./BackgroundVideo";
|
|
|
10
10
|
import { BackgroundImageCollage } from "./BackgroundImageCollage";
|
|
11
11
|
import { AtomicImage } from "../../../atoms/image/AtomicImage";
|
|
12
12
|
|
|
13
|
+
interface BackgroundContentProps {
|
|
14
|
+
slide: OnboardingSlide;
|
|
15
|
+
useCustomBackground: boolean;
|
|
16
|
+
overlayOpacity: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const BackgroundContent: React.FC<BackgroundContentProps> = ({
|
|
20
|
+
slide,
|
|
21
|
+
useCustomBackground,
|
|
22
|
+
overlayOpacity,
|
|
23
|
+
}) => {
|
|
24
|
+
if (slide.backgroundVideo) {
|
|
25
|
+
return (
|
|
26
|
+
<BackgroundVideo
|
|
27
|
+
source={slide.backgroundVideo}
|
|
28
|
+
overlayOpacity={overlayOpacity}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (slide.backgroundImages && slide.backgroundImages.length > 0) {
|
|
34
|
+
return (
|
|
35
|
+
<BackgroundImageCollage
|
|
36
|
+
images={slide.backgroundImages}
|
|
37
|
+
layout={slide.backgroundImagesLayout || "grid"}
|
|
38
|
+
columns={slide.backgroundImagesColumns}
|
|
39
|
+
gap={slide.backgroundImagesGap}
|
|
40
|
+
borderRadius={slide.backgroundImagesBorderRadius}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (slide.backgroundImage) {
|
|
46
|
+
return (
|
|
47
|
+
<AtomicImage
|
|
48
|
+
source={slide.backgroundImage}
|
|
49
|
+
style={StyleSheet.absoluteFill}
|
|
50
|
+
contentFit="cover"
|
|
51
|
+
cachePolicy="memory-disk"
|
|
52
|
+
priority="high"
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (useCustomBackground && slide.backgroundColor) {
|
|
58
|
+
return (
|
|
59
|
+
<View
|
|
60
|
+
style={[
|
|
61
|
+
StyleSheet.absoluteFill,
|
|
62
|
+
{ backgroundColor: slide.backgroundColor }
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
};
|
|
70
|
+
|
|
13
71
|
interface OnboardingBackgroundProps {
|
|
14
72
|
currentSlide: OnboardingSlide | undefined;
|
|
15
73
|
useCustomBackground: boolean;
|
|
@@ -25,57 +83,13 @@ export const OnboardingBackground: React.FC<OnboardingBackgroundProps> = ({
|
|
|
25
83
|
}) => {
|
|
26
84
|
if (!currentSlide) return null;
|
|
27
85
|
|
|
28
|
-
const renderContent = () => {
|
|
29
|
-
if (currentSlide.backgroundVideo) {
|
|
30
|
-
return (
|
|
31
|
-
<BackgroundVideo
|
|
32
|
-
source={currentSlide.backgroundVideo}
|
|
33
|
-
overlayOpacity={overlayOpacity}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (currentSlide.backgroundImages && currentSlide.backgroundImages.length > 0) {
|
|
39
|
-
return (
|
|
40
|
-
<BackgroundImageCollage
|
|
41
|
-
images={currentSlide.backgroundImages}
|
|
42
|
-
layout={currentSlide.backgroundImagesLayout || "grid"}
|
|
43
|
-
columns={currentSlide.backgroundImagesColumns}
|
|
44
|
-
gap={currentSlide.backgroundImagesGap}
|
|
45
|
-
borderRadius={currentSlide.backgroundImagesBorderRadius}
|
|
46
|
-
/>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (currentSlide.backgroundImage) {
|
|
51
|
-
return (
|
|
52
|
-
<AtomicImage
|
|
53
|
-
source={currentSlide.backgroundImage}
|
|
54
|
-
style={StyleSheet.absoluteFill}
|
|
55
|
-
contentFit="cover"
|
|
56
|
-
cachePolicy="memory-disk"
|
|
57
|
-
priority="high"
|
|
58
|
-
/>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (useCustomBackground && currentSlide.backgroundColor) {
|
|
63
|
-
return (
|
|
64
|
-
<View
|
|
65
|
-
style={[
|
|
66
|
-
StyleSheet.absoluteFill,
|
|
67
|
-
{ backgroundColor: currentSlide.backgroundColor }
|
|
68
|
-
]}
|
|
69
|
-
/>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return null;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
86
|
return (
|
|
77
87
|
<View style={StyleSheet.absoluteFill} pointerEvents="none">
|
|
78
|
-
|
|
88
|
+
<BackgroundContent
|
|
89
|
+
slide={currentSlide}
|
|
90
|
+
useCustomBackground={useCustomBackground}
|
|
91
|
+
overlayOpacity={overlayOpacity}
|
|
92
|
+
/>
|
|
79
93
|
<View
|
|
80
94
|
style={[
|
|
81
95
|
StyleSheet.absoluteFill,
|
|
@@ -64,9 +64,9 @@ export const OnboardingSlide = ({
|
|
|
64
64
|
|
|
65
65
|
{slide.features && slide.features.length > 0 && (
|
|
66
66
|
<View style={styles.features}>
|
|
67
|
-
{slide.features.map((feature
|
|
67
|
+
{slide.features.map((feature) => (
|
|
68
68
|
<View
|
|
69
|
-
key={
|
|
69
|
+
key={feature}
|
|
70
70
|
style={[
|
|
71
71
|
styles.featureItem,
|
|
72
72
|
{ backgroundColor: colors.featureItemBg },
|
|
@@ -14,6 +14,8 @@ import type { IconRenderer, IconNames } from '../../../atoms/icon/iconStore';
|
|
|
14
14
|
const SplashScreen = lazy(() => import('../../../molecules/splash').then(m => ({ default: m.SplashScreen })));
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
const EMPTY_FONTS: Record<string, any> = {};
|
|
18
|
+
|
|
17
19
|
interface DesignSystemProviderProps {
|
|
18
20
|
children: ReactNode;
|
|
19
21
|
customColors?: CustomThemeColors;
|
|
@@ -44,7 +46,7 @@ export const DesignSystemProvider: React.FC<DesignSystemProviderProps> = ({
|
|
|
44
46
|
iconNames,
|
|
45
47
|
}) => {
|
|
46
48
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
47
|
-
const [fontsLoaded, fontError] =
|
|
49
|
+
const [fontsLoaded, fontError] = useFonts(fonts ?? EMPTY_FONTS);
|
|
48
50
|
|
|
49
51
|
const initialize = useTheme((state) => state.initialize);
|
|
50
52
|
const setCustomColors = useTheme((state) => state.setCustomColors);
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gallery Download Service
|
|
3
|
-
* Single Responsibility: Download remote media to local storage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { FileSystemService } from "../filesystem";
|
|
7
|
-
import { validateImageUri, getFileExtension } from "../image";
|
|
8
|
-
import { timezoneService } from "../timezone";
|
|
9
|
-
import type { DownloadMediaResult } from "./types";
|
|
10
|
-
|
|
11
|
-
const generateFilename = (uri: string, prefix: string): string => {
|
|
12
|
-
const extension = getFileExtension(uri) || "jpg";
|
|
13
|
-
const timestamp = timezoneService.formatDateToString(new Date());
|
|
14
|
-
const randomId = Math.random().toString(36).substring(2, 10);
|
|
15
|
-
return `${prefix}_${timestamp}_${randomId}.${extension}`;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
class GalleryDownloadService {
|
|
19
|
-
async downloadMedia(
|
|
20
|
-
mediaUri: string,
|
|
21
|
-
prefix: string = "media",
|
|
22
|
-
): Promise<DownloadMediaResult> {
|
|
23
|
-
try {
|
|
24
|
-
const validationResult = validateImageUri(mediaUri, "Media");
|
|
25
|
-
if (!validationResult.isValid) {
|
|
26
|
-
return {
|
|
27
|
-
success: false,
|
|
28
|
-
error: validationResult.error || "Invalid media file",
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const filename = generateFilename(mediaUri, prefix);
|
|
33
|
-
const documentDir = FileSystemService.getDocumentDirectory();
|
|
34
|
-
const fileUri = `${documentDir}${filename}`;
|
|
35
|
-
|
|
36
|
-
const downloadResult = await FileSystemService.downloadFile(
|
|
37
|
-
mediaUri,
|
|
38
|
-
fileUri,
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
if (!downloadResult.success || !downloadResult.uri) {
|
|
42
|
-
return {
|
|
43
|
-
success: false,
|
|
44
|
-
error: downloadResult.error || "Download failed",
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
success: true,
|
|
50
|
-
localUri: downloadResult.uri,
|
|
51
|
-
};
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return {
|
|
54
|
-
success: false,
|
|
55
|
-
error: error instanceof Error ? error.message : "Download failed",
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
isRemoteUrl(uri: string): boolean {
|
|
61
|
-
return uri.startsWith("http://") || uri.startsWith("https://");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async cleanupFile(fileUri: string): Promise<void> {
|
|
65
|
-
await FileSystemService.deleteFile(fileUri);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const galleryDownloadService = new GalleryDownloadService();
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gallery Save Service
|
|
3
|
-
* Single Responsibility: Save media to device gallery
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as MediaLibrary from "expo-media-library";
|
|
7
|
-
import { validateImageUri } from "../image";
|
|
8
|
-
import { galleryDownloadService } from "./gallery-download.service";
|
|
9
|
-
import type { SaveMediaResult } from "./types";
|
|
10
|
-
|
|
11
|
-
const requestMediaPermissions = async (): Promise<boolean> => {
|
|
12
|
-
try {
|
|
13
|
-
const { status } = await MediaLibrary.requestPermissionsAsync();
|
|
14
|
-
return status === "granted";
|
|
15
|
-
} catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
class GallerySaveService {
|
|
21
|
-
async saveToGallery(
|
|
22
|
-
mediaUri: string,
|
|
23
|
-
prefix?: string,
|
|
24
|
-
): Promise<SaveMediaResult> {
|
|
25
|
-
try {
|
|
26
|
-
const validationResult = validateImageUri(mediaUri, "Media");
|
|
27
|
-
if (!validationResult.isValid) {
|
|
28
|
-
return {
|
|
29
|
-
success: false,
|
|
30
|
-
error: validationResult.error || "Invalid media file",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const hasPermission = await requestMediaPermissions();
|
|
35
|
-
if (!hasPermission) {
|
|
36
|
-
return {
|
|
37
|
-
success: false,
|
|
38
|
-
error: "Media library permission denied",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let localUri = mediaUri;
|
|
43
|
-
const isRemote = galleryDownloadService.isRemoteUrl(mediaUri);
|
|
44
|
-
|
|
45
|
-
if (isRemote) {
|
|
46
|
-
const downloadResult = await galleryDownloadService.downloadMedia(
|
|
47
|
-
mediaUri,
|
|
48
|
-
prefix,
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
if (!downloadResult.success || !downloadResult.localUri) {
|
|
52
|
-
return {
|
|
53
|
-
success: false,
|
|
54
|
-
error: downloadResult.error || "Download failed",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
localUri = downloadResult.localUri;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const asset = await MediaLibrary.createAssetAsync(localUri);
|
|
62
|
-
|
|
63
|
-
if (isRemote && localUri) {
|
|
64
|
-
await galleryDownloadService.cleanupFile(localUri);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
success: true,
|
|
69
|
-
fileUri: asset.uri,
|
|
70
|
-
};
|
|
71
|
-
} catch (error) {
|
|
72
|
-
return {
|
|
73
|
-
success: false,
|
|
74
|
-
error: error instanceof Error ? error.message : "Save failed",
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const gallerySaveService = new GallerySaveService();
|
package/src/gallery/index.ts
DELETED