@umituz/react-native-ai-generation-content 1.17.182 → 1.17.184
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 +40 -111
- package/src/features/ai-kiss/presentation/components/AIKissFeature.tsx +40 -105
- 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/photo-restoration/presentation/components/PhotoRestoreFeature.tsx +49 -110
- package/src/features/remove-background/presentation/components/RemoveBackgroundFeature.tsx +48 -122
- package/src/features/remove-object/presentation/components/RemoveObjectFeature.tsx +47 -117
- package/src/features/replace-background/presentation/components/ReplaceBackgroundFeature.tsx +48 -121
- package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +50 -120
- package/src/index.ts +13 -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 +163 -0
- package/src/presentation/layouts/SingleImageWithPromptFeatureLayout.tsx +153 -0
- package/src/presentation/layouts/index.ts +26 -0
- package/src/presentation/layouts/types.ts +228 -0
package/package.json
CHANGED
|
@@ -1,16 +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 {
|
|
13
|
-
import { GenerationProgressModal } from "../../../../presentation/components/GenerationProgressModal";
|
|
10
|
+
import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
14
12
|
import { useAIHugFeature } from "../hooks";
|
|
15
13
|
import type {
|
|
16
14
|
AIHugTranslations,
|
|
@@ -18,23 +16,18 @@ import type {
|
|
|
18
16
|
} from "../../domain/types";
|
|
19
17
|
|
|
20
18
|
export interface AIHugFeatureProps {
|
|
21
|
-
/** Feature configuration with provider-specific settings */
|
|
22
19
|
config: AIHugFeatureConfig;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
translations: AIHugTranslations & {
|
|
21
|
+
modalTitle?: string;
|
|
22
|
+
modalMessage?: string;
|
|
23
|
+
modalHint?: string;
|
|
24
|
+
modalBackgroundHint?: string;
|
|
25
|
+
};
|
|
26
26
|
onSelectSourceImage: () => Promise<string | null>;
|
|
27
|
-
/** Target image picker callback */
|
|
28
27
|
onSelectTargetImage: () => Promise<string | null>;
|
|
29
|
-
/** Save video callback */
|
|
30
28
|
onSaveVideo: (videoUrl: string) => Promise<void>;
|
|
31
|
-
/** Called before processing starts. Return false to cancel. */
|
|
32
29
|
onBeforeProcess?: () => Promise<boolean>;
|
|
33
|
-
|
|
34
|
-
renderProcessingModal?: (props: {
|
|
35
|
-
visible: boolean;
|
|
36
|
-
progress: number;
|
|
37
|
-
}) => React.ReactNode;
|
|
30
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
38
31
|
}
|
|
39
32
|
|
|
40
33
|
export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
|
|
@@ -46,8 +39,6 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
|
|
|
46
39
|
onBeforeProcess,
|
|
47
40
|
renderProcessingModal,
|
|
48
41
|
}) => {
|
|
49
|
-
const tokens = useAppDesignTokens();
|
|
50
|
-
|
|
51
42
|
const feature = useAIHugFeature({
|
|
52
43
|
config,
|
|
53
44
|
onSelectSourceImage,
|
|
@@ -56,103 +47,41 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
|
|
|
56
47
|
onBeforeProcess,
|
|
57
48
|
});
|
|
58
49
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const handleSelectSource = useCallback(() => {
|
|
68
|
-
void feature.selectSourceImage();
|
|
69
|
-
}, [feature]);
|
|
70
|
-
|
|
71
|
-
const handleSelectTarget = useCallback(() => {
|
|
72
|
-
void feature.selectTargetImage();
|
|
73
|
-
}, [feature]);
|
|
74
|
-
|
|
75
|
-
if (feature.processedVideoUrl) {
|
|
76
|
-
return (
|
|
77
|
-
<ScrollView
|
|
78
|
-
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
79
|
-
contentContainerStyle={styles.content}
|
|
80
|
-
showsVerticalScrollIndicator={false}
|
|
81
|
-
>
|
|
82
|
-
<AIGenerationResult
|
|
83
|
-
successText={translations.successText}
|
|
84
|
-
primaryAction={{
|
|
85
|
-
label: translations.saveButtonText,
|
|
86
|
-
onPress: handleSave,
|
|
87
|
-
}}
|
|
88
|
-
secondaryAction={{
|
|
89
|
-
label: translations.tryAnotherText,
|
|
90
|
-
onPress: feature.reset,
|
|
91
|
-
}}
|
|
92
|
-
/>
|
|
93
|
-
</ScrollView>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const defaultModal = (
|
|
98
|
-
<GenerationProgressModal
|
|
99
|
-
visible={feature.isProcessing}
|
|
100
|
-
progress={feature.progress}
|
|
101
|
-
icon="sparkles"
|
|
102
|
-
title={translations.modalTitle || "Creating your video"}
|
|
103
|
-
message={translations.modalMessage || "AI is working its magic..."}
|
|
104
|
-
hint={translations.modalHint || "This may take a moment"}
|
|
105
|
-
backgroundHint={translations.modalBackgroundHint || "Continue in background"}
|
|
106
|
-
onClose={() => {
|
|
107
|
-
// Allow continuing in background - just close modal
|
|
108
|
-
}}
|
|
109
|
-
/>
|
|
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],
|
|
110
58
|
);
|
|
111
59
|
|
|
112
60
|
return (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
onSelectTarget={handleSelectTarget}
|
|
134
|
-
sourcePlaceholder={translations.sourceUploadTitle}
|
|
135
|
-
targetPlaceholder={translations.targetUploadTitle}
|
|
136
|
-
layout="horizontal"
|
|
137
|
-
/>
|
|
138
|
-
</View>
|
|
139
|
-
</AIGenerationForm>
|
|
140
|
-
</ScrollView>
|
|
141
|
-
|
|
142
|
-
{renderProcessingModal
|
|
143
|
-
? renderProcessingModal({ visible: feature.isProcessing, progress: feature.progress })
|
|
144
|
-
: defaultModal}
|
|
145
|
-
</>
|
|
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
|
+
/>
|
|
146
81
|
);
|
|
147
82
|
};
|
|
148
83
|
|
|
149
84
|
const styles = StyleSheet.create({
|
|
150
|
-
container: {
|
|
151
|
-
flex: 1,
|
|
152
|
-
},
|
|
153
|
-
content: {
|
|
154
|
-
paddingVertical: 16,
|
|
155
|
-
},
|
|
156
85
|
pickerContainer: {
|
|
157
86
|
marginHorizontal: 16,
|
|
158
87
|
marginBottom: 16,
|
|
@@ -1,16 +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 {
|
|
13
|
-
import { GenerationProgressModal } from "../../../../presentation/components/GenerationProgressModal";
|
|
10
|
+
import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
14
12
|
import { useAIKissFeature } from "../hooks";
|
|
15
13
|
import type {
|
|
16
14
|
AIKissTranslations,
|
|
@@ -19,16 +17,17 @@ import type {
|
|
|
19
17
|
|
|
20
18
|
export interface AIKissFeatureProps {
|
|
21
19
|
config: AIKissFeatureConfig;
|
|
22
|
-
translations: AIKissTranslations
|
|
20
|
+
translations: AIKissTranslations & {
|
|
21
|
+
modalTitle?: string;
|
|
22
|
+
modalMessage?: string;
|
|
23
|
+
modalHint?: string;
|
|
24
|
+
modalBackgroundHint?: string;
|
|
25
|
+
};
|
|
23
26
|
onSelectSourceImage: () => Promise<string | null>;
|
|
24
27
|
onSelectTargetImage: () => Promise<string | null>;
|
|
25
28
|
onSaveVideo: (videoUrl: string) => Promise<void>;
|
|
26
|
-
/** Called before processing starts. Return false to cancel. */
|
|
27
29
|
onBeforeProcess?: () => Promise<boolean>;
|
|
28
|
-
renderProcessingModal?: (props:
|
|
29
|
-
visible: boolean;
|
|
30
|
-
progress: number;
|
|
31
|
-
}) => React.ReactNode;
|
|
30
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
|
|
@@ -40,8 +39,6 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
|
|
|
40
39
|
onBeforeProcess,
|
|
41
40
|
renderProcessingModal,
|
|
42
41
|
}) => {
|
|
43
|
-
const tokens = useAppDesignTokens();
|
|
44
|
-
|
|
45
42
|
const feature = useAIKissFeature({
|
|
46
43
|
config,
|
|
47
44
|
onSelectSourceImage,
|
|
@@ -50,103 +47,41 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
|
|
|
50
47
|
onBeforeProcess,
|
|
51
48
|
});
|
|
52
49
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const handleSelectSource = useCallback(() => {
|
|
62
|
-
void feature.selectSourceImage();
|
|
63
|
-
}, [feature]);
|
|
64
|
-
|
|
65
|
-
const handleSelectTarget = useCallback(() => {
|
|
66
|
-
void feature.selectTargetImage();
|
|
67
|
-
}, [feature]);
|
|
68
|
-
|
|
69
|
-
if (feature.processedVideoUrl) {
|
|
70
|
-
return (
|
|
71
|
-
<ScrollView
|
|
72
|
-
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
73
|
-
contentContainerStyle={styles.content}
|
|
74
|
-
showsVerticalScrollIndicator={false}
|
|
75
|
-
>
|
|
76
|
-
<AIGenerationResult
|
|
77
|
-
successText={translations.successText}
|
|
78
|
-
primaryAction={{
|
|
79
|
-
label: translations.saveButtonText,
|
|
80
|
-
onPress: handleSave,
|
|
81
|
-
}}
|
|
82
|
-
secondaryAction={{
|
|
83
|
-
label: translations.tryAnotherText,
|
|
84
|
-
onPress: feature.reset,
|
|
85
|
-
}}
|
|
86
|
-
/>
|
|
87
|
-
</ScrollView>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const defaultModal = (
|
|
92
|
-
<GenerationProgressModal
|
|
93
|
-
visible={feature.isProcessing}
|
|
94
|
-
progress={feature.progress}
|
|
95
|
-
icon="sparkles"
|
|
96
|
-
title={translations.modalTitle || "Creating your video"}
|
|
97
|
-
message={translations.modalMessage || "AI is working its magic..."}
|
|
98
|
-
hint={translations.modalHint || "This may take a moment"}
|
|
99
|
-
backgroundHint={translations.modalBackgroundHint || "Continue in background"}
|
|
100
|
-
onClose={() => {
|
|
101
|
-
// Allow continuing in background - just close modal
|
|
102
|
-
}}
|
|
103
|
-
/>
|
|
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],
|
|
104
58
|
);
|
|
105
59
|
|
|
106
60
|
return (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
onSelectTarget={handleSelectTarget}
|
|
128
|
-
sourcePlaceholder={translations.sourceUploadTitle}
|
|
129
|
-
targetPlaceholder={translations.targetUploadTitle}
|
|
130
|
-
layout="horizontal"
|
|
131
|
-
/>
|
|
132
|
-
</View>
|
|
133
|
-
</AIGenerationForm>
|
|
134
|
-
</ScrollView>
|
|
135
|
-
|
|
136
|
-
{renderProcessingModal
|
|
137
|
-
? renderProcessingModal({ visible: feature.isProcessing, progress: feature.progress })
|
|
138
|
-
: defaultModal}
|
|
139
|
-
</>
|
|
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
|
+
/>
|
|
140
81
|
);
|
|
141
82
|
};
|
|
142
83
|
|
|
143
84
|
const styles = StyleSheet.create({
|
|
144
|
-
container: {
|
|
145
|
-
flex: 1,
|
|
146
|
-
},
|
|
147
|
-
content: {
|
|
148
|
-
paddingVertical: 16,
|
|
149
|
-
},
|
|
150
85
|
pickerContainer: {
|
|
151
86
|
marginHorizontal: 16,
|
|
152
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
|
});
|