@umituz/react-native-ai-generation-content 1.17.6 → 1.17.8
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/index.ts +4 -0
- package/src/features/ai-hug/presentation/components/AIHugFeature.tsx +209 -0
- package/src/features/ai-hug/presentation/components/index.ts +2 -0
- package/src/features/ai-hug/presentation/index.ts +1 -0
- package/src/features/ai-kiss/index.ts +4 -0
- package/src/features/ai-kiss/presentation/components/AIKissFeature.tsx +202 -0
- package/src/features/ai-kiss/presentation/components/index.ts +2 -0
- package/src/features/ai-kiss/presentation/index.ts +1 -0
- package/src/features/anime-selfie/index.ts +4 -0
- package/src/features/anime-selfie/presentation/components/AnimeSelfieFeature.tsx +199 -0
- package/src/features/anime-selfie/presentation/components/index.ts +2 -0
- package/src/features/anime-selfie/presentation/index.ts +1 -0
- package/src/features/face-swap/index.ts +4 -0
- package/src/features/face-swap/presentation/components/FaceSwapFeature.tsx +202 -0
- package/src/features/face-swap/presentation/components/index.ts +2 -0
- package/src/features/face-swap/presentation/index.ts +1 -0
- package/src/features/photo-restoration/presentation/components/PhotoRestoreFeature.tsx +48 -36
- package/src/features/remove-background/index.ts +4 -0
- package/src/features/remove-background/presentation/components/RemoveBackgroundFeature.tsx +199 -0
- package/src/features/remove-background/presentation/components/index.ts +2 -0
- package/src/features/remove-background/presentation/index.ts +1 -0
- package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +48 -36
- package/src/presentation/components/photo-step/PhotoStep.tsx +2 -4
- package/src/presentation/types/flow-config.types.ts +1 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FaceSwapFeature Component
|
|
3
|
+
* Self-contained face swap feature UI component
|
|
4
|
+
* Uses hook internally, only requires config and translations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useCallback } from "react";
|
|
8
|
+
import { View, ScrollView, StyleSheet, Image, Dimensions } from "react-native";
|
|
9
|
+
import {
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
AtomicText,
|
|
12
|
+
AtomicButton,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
import { DualImagePicker } from "../../../../presentation/components/image-picker/DualImagePicker";
|
|
15
|
+
import { ErrorDisplay } from "../../../../presentation/components/display/ErrorDisplay";
|
|
16
|
+
import { useFaceSwapFeature } from "../hooks";
|
|
17
|
+
import type {
|
|
18
|
+
FaceSwapTranslations,
|
|
19
|
+
FaceSwapFeatureConfig,
|
|
20
|
+
} from "../../domain/types";
|
|
21
|
+
|
|
22
|
+
export interface FaceSwapFeatureProps {
|
|
23
|
+
config: FaceSwapFeatureConfig;
|
|
24
|
+
userId: string;
|
|
25
|
+
translations: FaceSwapTranslations;
|
|
26
|
+
onSelectSourceImage: () => Promise<string | null>;
|
|
27
|
+
onSelectTargetImage: () => Promise<string | null>;
|
|
28
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
29
|
+
renderProcessingModal?: (props: {
|
|
30
|
+
visible: boolean;
|
|
31
|
+
progress: number;
|
|
32
|
+
}) => React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const FaceSwapFeature: React.FC<FaceSwapFeatureProps> = ({
|
|
36
|
+
config,
|
|
37
|
+
userId,
|
|
38
|
+
translations,
|
|
39
|
+
onSelectSourceImage,
|
|
40
|
+
onSelectTargetImage,
|
|
41
|
+
onSaveImage,
|
|
42
|
+
renderProcessingModal,
|
|
43
|
+
}) => {
|
|
44
|
+
const tokens = useAppDesignTokens();
|
|
45
|
+
|
|
46
|
+
const feature = useFaceSwapFeature({
|
|
47
|
+
config,
|
|
48
|
+
userId,
|
|
49
|
+
onSelectSourceImage,
|
|
50
|
+
onSelectTargetImage,
|
|
51
|
+
onSaveImage,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const handleProcess = useCallback(() => {
|
|
55
|
+
void feature.process();
|
|
56
|
+
}, [feature]);
|
|
57
|
+
|
|
58
|
+
const handleSave = useCallback(() => {
|
|
59
|
+
void feature.save();
|
|
60
|
+
}, [feature]);
|
|
61
|
+
|
|
62
|
+
const handleSelectSource = useCallback(() => {
|
|
63
|
+
void feature.selectSourceImage();
|
|
64
|
+
}, [feature]);
|
|
65
|
+
|
|
66
|
+
const handleSelectTarget = useCallback(() => {
|
|
67
|
+
void feature.selectTargetImage();
|
|
68
|
+
}, [feature]);
|
|
69
|
+
|
|
70
|
+
const canProcess = feature.sourceImageUri && feature.targetImageUri && !feature.isProcessing;
|
|
71
|
+
|
|
72
|
+
if (feature.processedUrl) {
|
|
73
|
+
const screenWidth = Dimensions.get("window").width;
|
|
74
|
+
const imageSize = screenWidth - 48;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<ScrollView
|
|
78
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
79
|
+
contentContainerStyle={styles.content}
|
|
80
|
+
showsVerticalScrollIndicator={false}
|
|
81
|
+
>
|
|
82
|
+
<AtomicText
|
|
83
|
+
type="headlineMedium"
|
|
84
|
+
style={[styles.successText, { color: tokens.colors.success }]}
|
|
85
|
+
>
|
|
86
|
+
{translations.successText}
|
|
87
|
+
</AtomicText>
|
|
88
|
+
|
|
89
|
+
<View style={styles.resultImageContainer}>
|
|
90
|
+
<Image
|
|
91
|
+
source={{ uri: feature.processedUrl }}
|
|
92
|
+
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
93
|
+
resizeMode="contain"
|
|
94
|
+
/>
|
|
95
|
+
</View>
|
|
96
|
+
|
|
97
|
+
<View style={styles.resultActions}>
|
|
98
|
+
<AtomicButton
|
|
99
|
+
title={translations.saveButtonText}
|
|
100
|
+
onPress={handleSave}
|
|
101
|
+
variant="primary"
|
|
102
|
+
size="lg"
|
|
103
|
+
/>
|
|
104
|
+
<AtomicButton
|
|
105
|
+
title={translations.tryAnotherText}
|
|
106
|
+
onPress={feature.reset}
|
|
107
|
+
variant="secondary"
|
|
108
|
+
size="lg"
|
|
109
|
+
/>
|
|
110
|
+
</View>
|
|
111
|
+
</ScrollView>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
<ScrollView
|
|
118
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
119
|
+
contentContainerStyle={styles.content}
|
|
120
|
+
showsVerticalScrollIndicator={false}
|
|
121
|
+
>
|
|
122
|
+
<AtomicText
|
|
123
|
+
type="bodyLarge"
|
|
124
|
+
style={[styles.description, { color: tokens.colors.textSecondary }]}
|
|
125
|
+
>
|
|
126
|
+
{translations.description}
|
|
127
|
+
</AtomicText>
|
|
128
|
+
|
|
129
|
+
<View style={styles.pickerContainer}>
|
|
130
|
+
<DualImagePicker
|
|
131
|
+
sourceImageUri={feature.sourceImageUri}
|
|
132
|
+
targetImageUri={feature.targetImageUri}
|
|
133
|
+
isDisabled={feature.isProcessing}
|
|
134
|
+
onSelectSource={handleSelectSource}
|
|
135
|
+
onSelectTarget={handleSelectTarget}
|
|
136
|
+
sourcePlaceholder={translations.sourceUploadTitle}
|
|
137
|
+
targetPlaceholder={translations.targetUploadTitle}
|
|
138
|
+
layout="horizontal"
|
|
139
|
+
variant="portrait"
|
|
140
|
+
/>
|
|
141
|
+
</View>
|
|
142
|
+
|
|
143
|
+
<ErrorDisplay error={feature.error} />
|
|
144
|
+
|
|
145
|
+
<View style={styles.buttonContainer}>
|
|
146
|
+
<AtomicButton
|
|
147
|
+
title={
|
|
148
|
+
feature.isProcessing
|
|
149
|
+
? translations.processingText
|
|
150
|
+
: translations.processButtonText
|
|
151
|
+
}
|
|
152
|
+
onPress={handleProcess}
|
|
153
|
+
disabled={!canProcess}
|
|
154
|
+
variant="primary"
|
|
155
|
+
size="lg"
|
|
156
|
+
/>
|
|
157
|
+
</View>
|
|
158
|
+
</ScrollView>
|
|
159
|
+
|
|
160
|
+
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const styles = StyleSheet.create({
|
|
166
|
+
container: {
|
|
167
|
+
flex: 1,
|
|
168
|
+
},
|
|
169
|
+
content: {
|
|
170
|
+
paddingVertical: 16,
|
|
171
|
+
},
|
|
172
|
+
description: {
|
|
173
|
+
textAlign: "center",
|
|
174
|
+
marginHorizontal: 24,
|
|
175
|
+
marginBottom: 24,
|
|
176
|
+
lineHeight: 24,
|
|
177
|
+
},
|
|
178
|
+
pickerContainer: {
|
|
179
|
+
marginHorizontal: 16,
|
|
180
|
+
marginBottom: 16,
|
|
181
|
+
},
|
|
182
|
+
successText: {
|
|
183
|
+
textAlign: "center",
|
|
184
|
+
marginBottom: 24,
|
|
185
|
+
},
|
|
186
|
+
resultImageContainer: {
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
marginHorizontal: 24,
|
|
189
|
+
marginBottom: 24,
|
|
190
|
+
},
|
|
191
|
+
resultImage: {
|
|
192
|
+
borderRadius: 16,
|
|
193
|
+
},
|
|
194
|
+
resultActions: {
|
|
195
|
+
marginHorizontal: 24,
|
|
196
|
+
gap: 12,
|
|
197
|
+
},
|
|
198
|
+
buttonContainer: {
|
|
199
|
+
marginHorizontal: 24,
|
|
200
|
+
marginTop: 8,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PhotoRestoreFeature Component
|
|
3
|
-
*
|
|
3
|
+
* Self-contained photo restore feature UI component
|
|
4
|
+
* Uses hook internally, only requires config and translations
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import React, { useCallback, useMemo } from "react";
|
|
7
8
|
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
9
|
import {
|
|
9
10
|
useAppDesignTokens,
|
|
10
|
-
PhotoUploadCard,
|
|
11
11
|
AtomicText,
|
|
12
12
|
AtomicButton,
|
|
13
13
|
} from "@umituz/react-native-design-system";
|
|
14
|
+
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
14
15
|
import { PhotoRestoreResultView } from "./PhotoRestoreResultView";
|
|
16
|
+
import { usePhotoRestoreFeature } from "../hooks";
|
|
15
17
|
import type {
|
|
16
18
|
PhotoRestoreTranslations,
|
|
17
19
|
PhotoRestoreFeatureConfig,
|
|
18
20
|
} from "../../domain/types";
|
|
19
21
|
|
|
20
22
|
export interface PhotoRestoreFeatureProps {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
/** Feature configuration with provider-specific settings */
|
|
24
|
+
config: PhotoRestoreFeatureConfig;
|
|
25
|
+
/** User ID for the generation request */
|
|
26
|
+
userId: string;
|
|
27
|
+
/** Translations for all UI text */
|
|
26
28
|
translations: PhotoRestoreTranslations;
|
|
27
|
-
|
|
28
|
-
onSelectImage: () =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/** Image picker callback */
|
|
30
|
+
onSelectImage: () => Promise<string | null>;
|
|
31
|
+
/** Save image callback */
|
|
32
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
33
|
+
/** Optional custom processing modal renderer */
|
|
32
34
|
renderProcessingModal?: (props: {
|
|
33
35
|
visible: boolean;
|
|
34
36
|
progress: number;
|
|
@@ -36,20 +38,22 @@ export interface PhotoRestoreFeatureProps {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
isProcessing,
|
|
42
|
-
progress,
|
|
43
|
-
error,
|
|
41
|
+
config,
|
|
42
|
+
userId,
|
|
44
43
|
translations,
|
|
45
44
|
onSelectImage,
|
|
46
|
-
|
|
47
|
-
onSave,
|
|
48
|
-
onReset,
|
|
45
|
+
onSaveImage,
|
|
49
46
|
renderProcessingModal,
|
|
50
47
|
}) => {
|
|
51
48
|
const tokens = useAppDesignTokens();
|
|
52
49
|
|
|
50
|
+
const feature = usePhotoRestoreFeature({
|
|
51
|
+
config,
|
|
52
|
+
userId,
|
|
53
|
+
onSelectImage,
|
|
54
|
+
onSaveImage,
|
|
55
|
+
});
|
|
56
|
+
|
|
53
57
|
const photoTranslations = useMemo(
|
|
54
58
|
() => ({
|
|
55
59
|
tapToUpload: translations.uploadTitle,
|
|
@@ -61,10 +65,18 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
61
65
|
);
|
|
62
66
|
|
|
63
67
|
const handleProcess = useCallback(() => {
|
|
64
|
-
|
|
65
|
-
}, [
|
|
68
|
+
void feature.process();
|
|
69
|
+
}, [feature]);
|
|
70
|
+
|
|
71
|
+
const handleSave = useCallback(() => {
|
|
72
|
+
void feature.save();
|
|
73
|
+
}, [feature]);
|
|
74
|
+
|
|
75
|
+
const handleSelectImage = useCallback(() => {
|
|
76
|
+
void feature.selectImage();
|
|
77
|
+
}, [feature]);
|
|
66
78
|
|
|
67
|
-
if (processedUrl && imageUri) {
|
|
79
|
+
if (feature.processedUrl && feature.imageUri) {
|
|
68
80
|
return (
|
|
69
81
|
<ScrollView
|
|
70
82
|
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
@@ -72,8 +84,8 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
72
84
|
showsVerticalScrollIndicator={false}
|
|
73
85
|
>
|
|
74
86
|
<PhotoRestoreResultView
|
|
75
|
-
originalUri={imageUri}
|
|
76
|
-
processedUri={processedUrl}
|
|
87
|
+
originalUri={feature.imageUri}
|
|
88
|
+
processedUri={feature.processedUrl}
|
|
77
89
|
translations={{
|
|
78
90
|
successText: translations.successText,
|
|
79
91
|
saveButtonText: translations.saveButtonText,
|
|
@@ -81,8 +93,8 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
81
93
|
beforeLabel: translations.beforeLabel,
|
|
82
94
|
afterLabel: translations.afterLabel,
|
|
83
95
|
}}
|
|
84
|
-
onSave={
|
|
85
|
-
onReset={
|
|
96
|
+
onSave={handleSave}
|
|
97
|
+
onReset={feature.reset}
|
|
86
98
|
/>
|
|
87
99
|
</ScrollView>
|
|
88
100
|
);
|
|
@@ -103,10 +115,10 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
103
115
|
</AtomicText>
|
|
104
116
|
|
|
105
117
|
<PhotoUploadCard
|
|
106
|
-
imageUri={imageUri}
|
|
107
|
-
onPress={
|
|
108
|
-
isValidating={isProcessing}
|
|
109
|
-
disabled={isProcessing}
|
|
118
|
+
imageUri={feature.imageUri}
|
|
119
|
+
onPress={handleSelectImage}
|
|
120
|
+
isValidating={feature.isProcessing}
|
|
121
|
+
disabled={feature.isProcessing}
|
|
110
122
|
translations={photoTranslations}
|
|
111
123
|
config={{
|
|
112
124
|
aspectRatio: 1,
|
|
@@ -116,7 +128,7 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
116
128
|
}}
|
|
117
129
|
/>
|
|
118
130
|
|
|
119
|
-
{error && (
|
|
131
|
+
{feature.error && (
|
|
120
132
|
<View
|
|
121
133
|
style={[
|
|
122
134
|
styles.errorContainer,
|
|
@@ -124,7 +136,7 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
124
136
|
]}
|
|
125
137
|
>
|
|
126
138
|
<AtomicText type="bodyMedium" style={{ color: tokens.colors.error }}>
|
|
127
|
-
{error}
|
|
139
|
+
{feature.error}
|
|
128
140
|
</AtomicText>
|
|
129
141
|
</View>
|
|
130
142
|
)}
|
|
@@ -132,19 +144,19 @@ export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
|
132
144
|
<View style={styles.buttonContainer}>
|
|
133
145
|
<AtomicButton
|
|
134
146
|
title={
|
|
135
|
-
isProcessing
|
|
147
|
+
feature.isProcessing
|
|
136
148
|
? translations.processingText
|
|
137
149
|
: translations.processButtonText
|
|
138
150
|
}
|
|
139
151
|
onPress={handleProcess}
|
|
140
|
-
disabled={!imageUri || isProcessing}
|
|
152
|
+
disabled={!feature.imageUri || feature.isProcessing}
|
|
141
153
|
variant="primary"
|
|
142
154
|
size="lg"
|
|
143
155
|
/>
|
|
144
156
|
</View>
|
|
145
157
|
</ScrollView>
|
|
146
158
|
|
|
147
|
-
{renderProcessingModal?.({ visible: isProcessing, progress })}
|
|
159
|
+
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
148
160
|
</>
|
|
149
161
|
);
|
|
150
162
|
};
|
|
@@ -25,3 +25,7 @@ export type {
|
|
|
25
25
|
UseRemoveBackgroundFeatureProps,
|
|
26
26
|
UseRemoveBackgroundFeatureReturn,
|
|
27
27
|
} from "./presentation";
|
|
28
|
+
|
|
29
|
+
// Presentation Components
|
|
30
|
+
export { RemoveBackgroundFeature } from "./presentation";
|
|
31
|
+
export type { RemoveBackgroundFeatureProps } from "./presentation";
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemoveBackgroundFeature Component
|
|
3
|
+
* Self-contained remove background feature UI component
|
|
4
|
+
* Uses hook internally, only requires config and translations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useCallback, useMemo } from "react";
|
|
8
|
+
import { View, ScrollView, StyleSheet, Image, Dimensions } from "react-native";
|
|
9
|
+
import {
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
AtomicText,
|
|
12
|
+
AtomicButton,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
|
|
15
|
+
import { ErrorDisplay } from "../../../../presentation/components/display/ErrorDisplay";
|
|
16
|
+
import { useRemoveBackgroundFeature } from "../hooks";
|
|
17
|
+
import type {
|
|
18
|
+
RemoveBackgroundTranslations,
|
|
19
|
+
RemoveBackgroundFeatureConfig,
|
|
20
|
+
} from "../../domain/types";
|
|
21
|
+
|
|
22
|
+
export interface RemoveBackgroundFeatureProps {
|
|
23
|
+
config: RemoveBackgroundFeatureConfig;
|
|
24
|
+
userId: string;
|
|
25
|
+
translations: RemoveBackgroundTranslations;
|
|
26
|
+
onSelectImage: () => Promise<string | null>;
|
|
27
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
28
|
+
renderProcessingModal?: (props: {
|
|
29
|
+
visible: boolean;
|
|
30
|
+
progress: number;
|
|
31
|
+
}) => React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const RemoveBackgroundFeature: React.FC<RemoveBackgroundFeatureProps> = ({
|
|
35
|
+
config,
|
|
36
|
+
userId,
|
|
37
|
+
translations,
|
|
38
|
+
onSelectImage,
|
|
39
|
+
onSaveImage,
|
|
40
|
+
renderProcessingModal,
|
|
41
|
+
}) => {
|
|
42
|
+
const tokens = useAppDesignTokens();
|
|
43
|
+
|
|
44
|
+
const feature = useRemoveBackgroundFeature({
|
|
45
|
+
config,
|
|
46
|
+
userId,
|
|
47
|
+
onSelectImage,
|
|
48
|
+
onSaveImage,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const photoTranslations = useMemo(
|
|
52
|
+
() => ({
|
|
53
|
+
tapToUpload: translations.uploadTitle,
|
|
54
|
+
selectPhoto: translations.uploadSubtitle,
|
|
55
|
+
change: translations.uploadChange,
|
|
56
|
+
analyzing: translations.uploadAnalyzing,
|
|
57
|
+
}),
|
|
58
|
+
[translations],
|
|
59
|
+
);
|
|
60
|
+
|
|
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
|
+
const screenWidth = Dimensions.get("window").width;
|
|
75
|
+
const imageSize = screenWidth - 48;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<ScrollView
|
|
79
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
80
|
+
contentContainerStyle={styles.content}
|
|
81
|
+
showsVerticalScrollIndicator={false}
|
|
82
|
+
>
|
|
83
|
+
<AtomicText
|
|
84
|
+
type="headlineMedium"
|
|
85
|
+
style={[styles.successText, { color: tokens.colors.success }]}
|
|
86
|
+
>
|
|
87
|
+
{translations.successText}
|
|
88
|
+
</AtomicText>
|
|
89
|
+
|
|
90
|
+
<View style={styles.resultImageContainer}>
|
|
91
|
+
<Image
|
|
92
|
+
source={{ uri: feature.processedUrl }}
|
|
93
|
+
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
94
|
+
resizeMode="contain"
|
|
95
|
+
/>
|
|
96
|
+
</View>
|
|
97
|
+
|
|
98
|
+
<View style={styles.resultActions}>
|
|
99
|
+
<AtomicButton
|
|
100
|
+
title={translations.saveButtonText}
|
|
101
|
+
onPress={handleSave}
|
|
102
|
+
variant="primary"
|
|
103
|
+
size="lg"
|
|
104
|
+
/>
|
|
105
|
+
<AtomicButton
|
|
106
|
+
title={translations.tryAnotherText}
|
|
107
|
+
onPress={feature.reset}
|
|
108
|
+
variant="secondary"
|
|
109
|
+
size="lg"
|
|
110
|
+
/>
|
|
111
|
+
</View>
|
|
112
|
+
</ScrollView>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<>
|
|
118
|
+
<ScrollView
|
|
119
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
120
|
+
contentContainerStyle={styles.content}
|
|
121
|
+
showsVerticalScrollIndicator={false}
|
|
122
|
+
>
|
|
123
|
+
<AtomicText
|
|
124
|
+
type="bodyLarge"
|
|
125
|
+
style={[styles.description, { color: tokens.colors.textSecondary }]}
|
|
126
|
+
>
|
|
127
|
+
{translations.description}
|
|
128
|
+
</AtomicText>
|
|
129
|
+
|
|
130
|
+
<PhotoUploadCard
|
|
131
|
+
imageUri={feature.imageUri}
|
|
132
|
+
onPress={handleSelectImage}
|
|
133
|
+
isValidating={feature.isProcessing}
|
|
134
|
+
disabled={feature.isProcessing}
|
|
135
|
+
translations={photoTranslations}
|
|
136
|
+
config={{
|
|
137
|
+
aspectRatio: 1,
|
|
138
|
+
borderRadius: 24,
|
|
139
|
+
showValidationStatus: false,
|
|
140
|
+
allowChange: true,
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
<ErrorDisplay error={feature.error} />
|
|
145
|
+
|
|
146
|
+
<View style={styles.buttonContainer}>
|
|
147
|
+
<AtomicButton
|
|
148
|
+
title={
|
|
149
|
+
feature.isProcessing
|
|
150
|
+
? translations.processingText
|
|
151
|
+
: translations.processButtonText
|
|
152
|
+
}
|
|
153
|
+
onPress={handleProcess}
|
|
154
|
+
disabled={!feature.imageUri || feature.isProcessing}
|
|
155
|
+
variant="primary"
|
|
156
|
+
size="lg"
|
|
157
|
+
/>
|
|
158
|
+
</View>
|
|
159
|
+
</ScrollView>
|
|
160
|
+
|
|
161
|
+
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
162
|
+
</>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const styles = StyleSheet.create({
|
|
167
|
+
container: {
|
|
168
|
+
flex: 1,
|
|
169
|
+
},
|
|
170
|
+
content: {
|
|
171
|
+
paddingVertical: 16,
|
|
172
|
+
},
|
|
173
|
+
description: {
|
|
174
|
+
textAlign: "center",
|
|
175
|
+
marginHorizontal: 24,
|
|
176
|
+
marginBottom: 24,
|
|
177
|
+
lineHeight: 24,
|
|
178
|
+
},
|
|
179
|
+
successText: {
|
|
180
|
+
textAlign: "center",
|
|
181
|
+
marginBottom: 24,
|
|
182
|
+
},
|
|
183
|
+
resultImageContainer: {
|
|
184
|
+
alignItems: "center",
|
|
185
|
+
marginHorizontal: 24,
|
|
186
|
+
marginBottom: 24,
|
|
187
|
+
},
|
|
188
|
+
resultImage: {
|
|
189
|
+
borderRadius: 16,
|
|
190
|
+
},
|
|
191
|
+
resultActions: {
|
|
192
|
+
marginHorizontal: 24,
|
|
193
|
+
gap: 12,
|
|
194
|
+
},
|
|
195
|
+
buttonContainer: {
|
|
196
|
+
marginHorizontal: 24,
|
|
197
|
+
marginTop: 8,
|
|
198
|
+
},
|
|
199
|
+
});
|