@umituz/react-native-ai-generation-content 1.17.181 → 1.17.183
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/features/ai-hug/presentation/components/AIHugFeature.tsx +41 -94
- package/src/features/ai-kiss/presentation/components/AIKissFeature.tsx +41 -88
- package/src/features/anime-selfie/presentation/components/AnimeSelfieFeature.tsx +48 -121
- package/src/features/face-swap/presentation/components/FaceSwapFeature.tsx +48 -121
- package/src/features/hd-touch-up/presentation/components/HDTouchUpFeature.tsx +48 -120
- package/src/features/image-to-image/domain/types/base.types.ts +16 -0
- package/src/features/remove-background/presentation/components/RemoveBackgroundFeature.tsx +48 -122
- package/src/features/shared/dual-image-video/domain/types/dual-image-video.types.ts +8 -0
- package/src/index.ts +8 -0
- package/src/infrastructure/services/image-feature-executor.service.ts +18 -3
- package/src/presentation/layouts/DualImageFeatureLayout.tsx +149 -0
- package/src/presentation/layouts/DualImageVideoFeatureLayout.tsx +143 -0
- package/src/presentation/layouts/SingleImageFeatureLayout.tsx +143 -0
- package/src/presentation/layouts/index.ts +21 -0
- package/src/presentation/layouts/types.ts +166 -0
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AIHugFeature Component
|
|
3
3
|
* Self-contained AI hug video feature UI component
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses centralized DualImageVideoFeatureLayout for consistent UX
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, {
|
|
8
|
-
import { View,
|
|
9
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
|
+
import { View, StyleSheet } from "react-native";
|
|
10
9
|
import { DualImagePicker } from "../../../../presentation/components/image-picker/DualImagePicker";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
13
12
|
import { useAIHugFeature } from "../hooks";
|
|
14
13
|
import type {
|
|
15
14
|
AIHugTranslations,
|
|
@@ -17,23 +16,18 @@ import type {
|
|
|
17
16
|
} from "../../domain/types";
|
|
18
17
|
|
|
19
18
|
export interface AIHugFeatureProps {
|
|
20
|
-
/** Feature configuration with provider-specific settings */
|
|
21
19
|
config: AIHugFeatureConfig;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
translations: AIHugTranslations & {
|
|
21
|
+
modalTitle?: string;
|
|
22
|
+
modalMessage?: string;
|
|
23
|
+
modalHint?: string;
|
|
24
|
+
modalBackgroundHint?: string;
|
|
25
|
+
};
|
|
25
26
|
onSelectSourceImage: () => Promise<string | null>;
|
|
26
|
-
/** Target image picker callback */
|
|
27
27
|
onSelectTargetImage: () => Promise<string | null>;
|
|
28
|
-
/** Save video callback */
|
|
29
28
|
onSaveVideo: (videoUrl: string) => Promise<void>;
|
|
30
|
-
/** Called before processing starts. Return false to cancel. */
|
|
31
29
|
onBeforeProcess?: () => Promise<boolean>;
|
|
32
|
-
|
|
33
|
-
renderProcessingModal?: (props: {
|
|
34
|
-
visible: boolean;
|
|
35
|
-
progress: number;
|
|
36
|
-
}) => React.ReactNode;
|
|
30
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
|
|
@@ -45,8 +39,6 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
|
|
|
45
39
|
onBeforeProcess,
|
|
46
40
|
renderProcessingModal,
|
|
47
41
|
}) => {
|
|
48
|
-
const tokens = useAppDesignTokens();
|
|
49
|
-
|
|
50
42
|
const feature = useAIHugFeature({
|
|
51
43
|
config,
|
|
52
44
|
onSelectSourceImage,
|
|
@@ -55,86 +47,41 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
|
|
|
55
47
|
onBeforeProcess,
|
|
56
48
|
});
|
|
57
49
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
void feature.selectSourceImage();
|
|
68
|
-
}, [feature]);
|
|
69
|
-
|
|
70
|
-
const handleSelectTarget = useCallback(() => {
|
|
71
|
-
void feature.selectTargetImage();
|
|
72
|
-
}, [feature]);
|
|
73
|
-
|
|
74
|
-
if (feature.processedVideoUrl) {
|
|
75
|
-
return (
|
|
76
|
-
<ScrollView
|
|
77
|
-
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
78
|
-
contentContainerStyle={styles.content}
|
|
79
|
-
showsVerticalScrollIndicator={false}
|
|
80
|
-
>
|
|
81
|
-
<AIGenerationResult
|
|
82
|
-
successText={translations.successText}
|
|
83
|
-
primaryAction={{
|
|
84
|
-
label: translations.saveButtonText,
|
|
85
|
-
onPress: handleSave,
|
|
86
|
-
}}
|
|
87
|
-
secondaryAction={{
|
|
88
|
-
label: translations.tryAnotherText,
|
|
89
|
-
onPress: feature.reset,
|
|
90
|
-
}}
|
|
91
|
-
/>
|
|
92
|
-
</ScrollView>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
50
|
+
const modalTranslations = useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
title: translations.modalTitle || "Creating your video",
|
|
53
|
+
message: translations.modalMessage || "AI is working its magic...",
|
|
54
|
+
hint: translations.modalHint || "This may take a moment",
|
|
55
|
+
backgroundHint: translations.modalBackgroundHint || "Continue in background",
|
|
56
|
+
}),
|
|
57
|
+
[translations],
|
|
58
|
+
);
|
|
95
59
|
|
|
96
60
|
return (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
onSelectTarget={handleSelectTarget}
|
|
118
|
-
sourcePlaceholder={translations.sourceUploadTitle}
|
|
119
|
-
targetPlaceholder={translations.targetUploadTitle}
|
|
120
|
-
layout="horizontal"
|
|
121
|
-
/>
|
|
122
|
-
</View>
|
|
123
|
-
</AIGenerationForm>
|
|
124
|
-
</ScrollView>
|
|
125
|
-
|
|
126
|
-
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
127
|
-
</>
|
|
61
|
+
<DualImageVideoFeatureLayout
|
|
62
|
+
feature={feature}
|
|
63
|
+
translations={translations}
|
|
64
|
+
modalTranslations={modalTranslations}
|
|
65
|
+
renderProcessingModal={renderProcessingModal}
|
|
66
|
+
renderInput={({ sourceImageUri, targetImageUri, onSelectSource, onSelectTarget, isDisabled }) => (
|
|
67
|
+
<View style={styles.pickerContainer}>
|
|
68
|
+
<DualImagePicker
|
|
69
|
+
sourceImageUri={sourceImageUri}
|
|
70
|
+
targetImageUri={targetImageUri}
|
|
71
|
+
isDisabled={isDisabled}
|
|
72
|
+
onSelectSource={onSelectSource}
|
|
73
|
+
onSelectTarget={onSelectTarget}
|
|
74
|
+
sourcePlaceholder={translations.sourceUploadTitle}
|
|
75
|
+
targetPlaceholder={translations.targetUploadTitle}
|
|
76
|
+
layout="horizontal"
|
|
77
|
+
/>
|
|
78
|
+
</View>
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
128
81
|
);
|
|
129
82
|
};
|
|
130
83
|
|
|
131
84
|
const styles = StyleSheet.create({
|
|
132
|
-
container: {
|
|
133
|
-
flex: 1,
|
|
134
|
-
},
|
|
135
|
-
content: {
|
|
136
|
-
paddingVertical: 16,
|
|
137
|
-
},
|
|
138
85
|
pickerContainer: {
|
|
139
86
|
marginHorizontal: 16,
|
|
140
87
|
marginBottom: 16,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AIKissFeature Component
|
|
3
3
|
* Self-contained AI kiss video feature UI component
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses centralized DualImageVideoFeatureLayout for consistent UX
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, {
|
|
8
|
-
import { View,
|
|
9
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
|
+
import { View, StyleSheet } from "react-native";
|
|
10
9
|
import { DualImagePicker } from "../../../../presentation/components/image-picker/DualImagePicker";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
13
12
|
import { useAIKissFeature } from "../hooks";
|
|
14
13
|
import type {
|
|
15
14
|
AIKissTranslations,
|
|
@@ -18,16 +17,17 @@ import type {
|
|
|
18
17
|
|
|
19
18
|
export interface AIKissFeatureProps {
|
|
20
19
|
config: AIKissFeatureConfig;
|
|
21
|
-
translations: AIKissTranslations
|
|
20
|
+
translations: AIKissTranslations & {
|
|
21
|
+
modalTitle?: string;
|
|
22
|
+
modalMessage?: string;
|
|
23
|
+
modalHint?: string;
|
|
24
|
+
modalBackgroundHint?: string;
|
|
25
|
+
};
|
|
22
26
|
onSelectSourceImage: () => Promise<string | null>;
|
|
23
27
|
onSelectTargetImage: () => Promise<string | null>;
|
|
24
28
|
onSaveVideo: (videoUrl: string) => Promise<void>;
|
|
25
|
-
/** Called before processing starts. Return false to cancel. */
|
|
26
29
|
onBeforeProcess?: () => Promise<boolean>;
|
|
27
|
-
renderProcessingModal?: (props:
|
|
28
|
-
visible: boolean;
|
|
29
|
-
progress: number;
|
|
30
|
-
}) => React.ReactNode;
|
|
30
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
|
|
@@ -39,8 +39,6 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
|
|
|
39
39
|
onBeforeProcess,
|
|
40
40
|
renderProcessingModal,
|
|
41
41
|
}) => {
|
|
42
|
-
const tokens = useAppDesignTokens();
|
|
43
|
-
|
|
44
42
|
const feature = useAIKissFeature({
|
|
45
43
|
config,
|
|
46
44
|
onSelectSourceImage,
|
|
@@ -49,86 +47,41 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
|
|
|
49
47
|
onBeforeProcess,
|
|
50
48
|
});
|
|
51
49
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
void feature.selectSourceImage();
|
|
62
|
-
}, [feature]);
|
|
63
|
-
|
|
64
|
-
const handleSelectTarget = useCallback(() => {
|
|
65
|
-
void feature.selectTargetImage();
|
|
66
|
-
}, [feature]);
|
|
67
|
-
|
|
68
|
-
if (feature.processedVideoUrl) {
|
|
69
|
-
return (
|
|
70
|
-
<ScrollView
|
|
71
|
-
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
72
|
-
contentContainerStyle={styles.content}
|
|
73
|
-
showsVerticalScrollIndicator={false}
|
|
74
|
-
>
|
|
75
|
-
<AIGenerationResult
|
|
76
|
-
successText={translations.successText}
|
|
77
|
-
primaryAction={{
|
|
78
|
-
label: translations.saveButtonText,
|
|
79
|
-
onPress: handleSave,
|
|
80
|
-
}}
|
|
81
|
-
secondaryAction={{
|
|
82
|
-
label: translations.tryAnotherText,
|
|
83
|
-
onPress: feature.reset,
|
|
84
|
-
}}
|
|
85
|
-
/>
|
|
86
|
-
</ScrollView>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
50
|
+
const modalTranslations = useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
title: translations.modalTitle || "Creating your video",
|
|
53
|
+
message: translations.modalMessage || "AI is working its magic...",
|
|
54
|
+
hint: translations.modalHint || "This may take a moment",
|
|
55
|
+
backgroundHint: translations.modalBackgroundHint || "Continue in background",
|
|
56
|
+
}),
|
|
57
|
+
[translations],
|
|
58
|
+
);
|
|
89
59
|
|
|
90
60
|
return (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
onSelectTarget={handleSelectTarget}
|
|
112
|
-
sourcePlaceholder={translations.sourceUploadTitle}
|
|
113
|
-
targetPlaceholder={translations.targetUploadTitle}
|
|
114
|
-
layout="horizontal"
|
|
115
|
-
/>
|
|
116
|
-
</View>
|
|
117
|
-
</AIGenerationForm>
|
|
118
|
-
</ScrollView>
|
|
119
|
-
|
|
120
|
-
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
121
|
-
</>
|
|
61
|
+
<DualImageVideoFeatureLayout
|
|
62
|
+
feature={feature}
|
|
63
|
+
translations={translations}
|
|
64
|
+
modalTranslations={modalTranslations}
|
|
65
|
+
renderProcessingModal={renderProcessingModal}
|
|
66
|
+
renderInput={({ sourceImageUri, targetImageUri, onSelectSource, onSelectTarget, isDisabled }) => (
|
|
67
|
+
<View style={styles.pickerContainer}>
|
|
68
|
+
<DualImagePicker
|
|
69
|
+
sourceImageUri={sourceImageUri}
|
|
70
|
+
targetImageUri={targetImageUri}
|
|
71
|
+
isDisabled={isDisabled}
|
|
72
|
+
onSelectSource={onSelectSource}
|
|
73
|
+
onSelectTarget={onSelectTarget}
|
|
74
|
+
sourcePlaceholder={translations.sourceUploadTitle}
|
|
75
|
+
targetPlaceholder={translations.targetUploadTitle}
|
|
76
|
+
layout="horizontal"
|
|
77
|
+
/>
|
|
78
|
+
</View>
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
122
81
|
);
|
|
123
82
|
};
|
|
124
83
|
|
|
125
84
|
const styles = StyleSheet.create({
|
|
126
|
-
container: {
|
|
127
|
-
flex: 1,
|
|
128
|
-
},
|
|
129
|
-
content: {
|
|
130
|
-
paddingVertical: 16,
|
|
131
|
-
},
|
|
132
85
|
pickerContainer: {
|
|
133
86
|
marginHorizontal: 16,
|
|
134
87
|
marginBottom: 16,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AnimeSelfieFeature Component
|
|
3
3
|
* Self-contained anime selfie feature UI component
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses centralized SingleImageFeatureLayout for consistent UX
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, {
|
|
8
|
-
import {
|
|
9
|
-
import { useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
|
+
import { Image, StyleSheet } from "react-native";
|
|
10
9
|
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { SingleImageFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
13
12
|
import { useAnimeSelfieFeature } from "../hooks";
|
|
14
13
|
import type {
|
|
15
14
|
AnimeSelfieTranslations,
|
|
@@ -18,15 +17,17 @@ import type {
|
|
|
18
17
|
|
|
19
18
|
export interface AnimeSelfieFeatureProps {
|
|
20
19
|
config: AnimeSelfieFeatureConfig;
|
|
21
|
-
translations: AnimeSelfieTranslations
|
|
20
|
+
translations: AnimeSelfieTranslations & {
|
|
21
|
+
modalTitle?: string;
|
|
22
|
+
modalMessage?: string;
|
|
23
|
+
modalHint?: string;
|
|
24
|
+
modalBackgroundHint?: string;
|
|
25
|
+
};
|
|
22
26
|
onSelectImage: () => Promise<string | null>;
|
|
23
27
|
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
24
28
|
/** Called before processing starts. Return false to cancel. */
|
|
25
29
|
onBeforeProcess?: () => Promise<boolean>;
|
|
26
|
-
renderProcessingModal?: (props:
|
|
27
|
-
visible: boolean;
|
|
28
|
-
progress: number;
|
|
29
|
-
}) => React.ReactNode;
|
|
30
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export const AnimeSelfieFeature: React.FC<AnimeSelfieFeatureProps> = ({
|
|
@@ -37,10 +38,6 @@ export const AnimeSelfieFeature: React.FC<AnimeSelfieFeatureProps> = ({
|
|
|
37
38
|
onBeforeProcess,
|
|
38
39
|
renderProcessingModal,
|
|
39
40
|
}) => {
|
|
40
|
-
const tokens = useAppDesignTokens();
|
|
41
|
-
const { width: screenWidth, horizontalPadding } = useResponsive();
|
|
42
|
-
const imageSize = screenWidth - horizontalPadding * 2;
|
|
43
|
-
|
|
44
41
|
const feature = useAnimeSelfieFeature({
|
|
45
42
|
config,
|
|
46
43
|
onSelectImage,
|
|
@@ -48,125 +45,55 @@ export const AnimeSelfieFeature: React.FC<AnimeSelfieFeatureProps> = ({
|
|
|
48
45
|
onBeforeProcess,
|
|
49
46
|
});
|
|
50
47
|
|
|
51
|
-
const
|
|
48
|
+
const modalTranslations = useMemo(
|
|
52
49
|
() => ({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
title: translations.modalTitle || "Processing",
|
|
51
|
+
message: translations.modalMessage || "AI is transforming your photo...",
|
|
52
|
+
hint: translations.modalHint || "This may take a moment",
|
|
53
|
+
backgroundHint: translations.modalBackgroundHint || "Continue in background",
|
|
57
54
|
}),
|
|
58
55
|
[translations],
|
|
59
56
|
);
|
|
60
57
|
|
|
61
|
-
const handleProcess = useCallback(() => {
|
|
62
|
-
void feature.process();
|
|
63
|
-
}, [feature]);
|
|
64
|
-
|
|
65
|
-
const handleSave = useCallback(() => {
|
|
66
|
-
void feature.save();
|
|
67
|
-
}, [feature]);
|
|
68
|
-
|
|
69
|
-
const handleSelectImage = useCallback(() => {
|
|
70
|
-
void feature.selectImage();
|
|
71
|
-
}, [feature]);
|
|
72
|
-
|
|
73
|
-
if (feature.processedUrl) {
|
|
74
|
-
return (
|
|
75
|
-
<ScrollView
|
|
76
|
-
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
77
|
-
contentContainerStyle={styles.content}
|
|
78
|
-
showsVerticalScrollIndicator={false}
|
|
79
|
-
>
|
|
80
|
-
<AIGenerationResult
|
|
81
|
-
successText={translations.successText}
|
|
82
|
-
primaryAction={{
|
|
83
|
-
label: translations.saveButtonText,
|
|
84
|
-
onPress: handleSave,
|
|
85
|
-
}}
|
|
86
|
-
secondaryAction={{
|
|
87
|
-
label: translations.tryAnotherText,
|
|
88
|
-
onPress: feature.reset,
|
|
89
|
-
}}
|
|
90
|
-
>
|
|
91
|
-
<Image
|
|
92
|
-
source={{ uri: feature.processedUrl }}
|
|
93
|
-
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
94
|
-
resizeMode="contain"
|
|
95
|
-
/>
|
|
96
|
-
</AIGenerationResult>
|
|
97
|
-
</ScrollView>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
58
|
return (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
59
|
+
<SingleImageFeatureLayout
|
|
60
|
+
feature={feature}
|
|
61
|
+
translations={translations}
|
|
62
|
+
modalTranslations={modalTranslations}
|
|
63
|
+
renderProcessingModal={renderProcessingModal}
|
|
64
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
|
|
65
|
+
<PhotoUploadCard
|
|
66
|
+
imageUri={imageUri}
|
|
67
|
+
onPress={onSelect}
|
|
68
|
+
isValidating={isProcessing}
|
|
69
|
+
disabled={isDisabled}
|
|
112
70
|
translations={{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
71
|
+
tapToUpload: translations.uploadTitle,
|
|
72
|
+
selectPhoto: translations.uploadSubtitle,
|
|
73
|
+
change: translations.uploadChange,
|
|
74
|
+
analyzing: translations.uploadAnalyzing,
|
|
116
75
|
}}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
135
|
-
</>
|
|
76
|
+
config={{
|
|
77
|
+
aspectRatio: 1,
|
|
78
|
+
borderRadius: 24,
|
|
79
|
+
showValidationStatus: false,
|
|
80
|
+
allowChange: true,
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
renderResult={({ imageUrl, imageSize }) => (
|
|
85
|
+
<Image
|
|
86
|
+
source={{ uri: imageUrl }}
|
|
87
|
+
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
88
|
+
resizeMode="contain"
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
/>
|
|
136
92
|
);
|
|
137
93
|
};
|
|
138
94
|
|
|
139
95
|
const styles = StyleSheet.create({
|
|
140
|
-
container: {
|
|
141
|
-
flex: 1,
|
|
142
|
-
},
|
|
143
|
-
content: {
|
|
144
|
-
paddingVertical: 16,
|
|
145
|
-
},
|
|
146
|
-
description: {
|
|
147
|
-
textAlign: "center",
|
|
148
|
-
marginHorizontal: 24,
|
|
149
|
-
marginBottom: 24,
|
|
150
|
-
lineHeight: 24,
|
|
151
|
-
},
|
|
152
|
-
successText: {
|
|
153
|
-
textAlign: "center",
|
|
154
|
-
marginBottom: 24,
|
|
155
|
-
},
|
|
156
|
-
resultImageContainer: {
|
|
157
|
-
alignItems: "center",
|
|
158
|
-
marginHorizontal: 24,
|
|
159
|
-
marginBottom: 24,
|
|
160
|
-
},
|
|
161
96
|
resultImage: {
|
|
162
97
|
borderRadius: 16,
|
|
163
98
|
},
|
|
164
|
-
resultActions: {
|
|
165
|
-
marginHorizontal: 24,
|
|
166
|
-
gap: 12,
|
|
167
|
-
},
|
|
168
|
-
buttonContainer: {
|
|
169
|
-
marginHorizontal: 24,
|
|
170
|
-
marginTop: 8,
|
|
171
|
-
},
|
|
172
99
|
});
|