@umituz/react-native-ai-generation-content 1.17.182 → 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 +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/remove-background/presentation/components/RemoveBackgroundFeature.tsx +48 -122
- 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
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FaceSwapFeature Component
|
|
3
3
|
* Self-contained face swap feature UI component
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses centralized DualImageFeatureLayout for consistent UX
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, {
|
|
8
|
-
import { View,
|
|
9
|
-
import { useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
|
+
import { View, Image, StyleSheet } from "react-native";
|
|
10
9
|
import { DualImagePicker } from "../../../../presentation/components/image-picker/DualImagePicker";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { DualImageFeatureLayout } from "../../../../presentation/layouts";
|
|
11
|
+
import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
|
|
13
12
|
import { useFaceSwapFeature } from "../hooks";
|
|
14
13
|
import type {
|
|
15
14
|
FaceSwapTranslations,
|
|
@@ -18,16 +17,17 @@ import type {
|
|
|
18
17
|
|
|
19
18
|
export interface FaceSwapFeatureProps {
|
|
20
19
|
config: FaceSwapFeatureConfig;
|
|
21
|
-
translations: FaceSwapTranslations
|
|
20
|
+
translations: FaceSwapTranslations & {
|
|
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
|
onSaveImage: (imageUrl: 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 FaceSwapFeature: React.FC<FaceSwapFeatureProps> = ({
|
|
@@ -39,10 +39,6 @@ export const FaceSwapFeature: React.FC<FaceSwapFeatureProps> = ({
|
|
|
39
39
|
onBeforeProcess,
|
|
40
40
|
renderProcessingModal,
|
|
41
41
|
}) => {
|
|
42
|
-
const tokens = useAppDesignTokens();
|
|
43
|
-
const { width: screenWidth, horizontalPadding } = useResponsive();
|
|
44
|
-
const imageSize = screenWidth - horizontalPadding * 2;
|
|
45
|
-
|
|
46
42
|
const feature = useFaceSwapFeature({
|
|
47
43
|
config,
|
|
48
44
|
onSelectSourceImage,
|
|
@@ -51,122 +47,53 @@ export const FaceSwapFeature: React.FC<FaceSwapFeatureProps> = ({
|
|
|
51
47
|
onBeforeProcess,
|
|
52
48
|
});
|
|
53
49
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
void feature.selectSourceImage();
|
|
64
|
-
}, [feature]);
|
|
65
|
-
|
|
66
|
-
const handleSelectTarget = useCallback(() => {
|
|
67
|
-
void feature.selectTargetImage();
|
|
68
|
-
}, [feature]);
|
|
69
|
-
|
|
70
|
-
if (feature.processedUrl) {
|
|
71
|
-
return (
|
|
72
|
-
<ScrollView
|
|
73
|
-
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
74
|
-
contentContainerStyle={styles.content}
|
|
75
|
-
showsVerticalScrollIndicator={false}
|
|
76
|
-
>
|
|
77
|
-
<AIGenerationResult
|
|
78
|
-
successText={translations.successText}
|
|
79
|
-
primaryAction={{
|
|
80
|
-
label: translations.saveButtonText,
|
|
81
|
-
onPress: handleSave,
|
|
82
|
-
}}
|
|
83
|
-
secondaryAction={{
|
|
84
|
-
label: translations.tryAnotherText,
|
|
85
|
-
onPress: feature.reset,
|
|
86
|
-
}}
|
|
87
|
-
>
|
|
88
|
-
<Image
|
|
89
|
-
source={{ uri: feature.processedUrl }}
|
|
90
|
-
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
91
|
-
resizeMode="contain"
|
|
92
|
-
/>
|
|
93
|
-
</AIGenerationResult>
|
|
94
|
-
</ScrollView>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
50
|
+
const modalTranslations = useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
title: translations.modalTitle || "Processing",
|
|
53
|
+
message: translations.modalMessage || "AI is swapping faces...",
|
|
54
|
+
hint: translations.modalHint || "This may take a moment",
|
|
55
|
+
backgroundHint: translations.modalBackgroundHint || "Continue in background",
|
|
56
|
+
}),
|
|
57
|
+
[translations],
|
|
58
|
+
);
|
|
97
59
|
|
|
98
60
|
return (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</View>
|
|
127
|
-
</AIGenerationForm>
|
|
128
|
-
</ScrollView>
|
|
129
|
-
|
|
130
|
-
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
131
|
-
</>
|
|
61
|
+
<DualImageFeatureLayout
|
|
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
|
+
renderResult={({ imageUrl, imageSize }) => (
|
|
81
|
+
<Image
|
|
82
|
+
source={{ uri: imageUrl }}
|
|
83
|
+
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
84
|
+
resizeMode="contain"
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
/>
|
|
132
88
|
);
|
|
133
89
|
};
|
|
134
90
|
|
|
135
91
|
const styles = StyleSheet.create({
|
|
136
|
-
container: {
|
|
137
|
-
flex: 1,
|
|
138
|
-
},
|
|
139
|
-
content: {
|
|
140
|
-
paddingVertical: 16,
|
|
141
|
-
},
|
|
142
|
-
description: {
|
|
143
|
-
textAlign: "center",
|
|
144
|
-
marginHorizontal: 24,
|
|
145
|
-
marginBottom: 24,
|
|
146
|
-
lineHeight: 24,
|
|
147
|
-
},
|
|
148
92
|
pickerContainer: {
|
|
149
93
|
marginHorizontal: 16,
|
|
150
94
|
marginBottom: 16,
|
|
151
95
|
},
|
|
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
|
});
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HDTouchUpFeature Component
|
|
3
3
|
* Self-contained HD touch up 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 { useHDTouchUpFeature } from "../hooks";
|
|
14
13
|
import type {
|
|
15
14
|
HDTouchUpTranslations,
|
|
@@ -18,15 +17,16 @@ import type {
|
|
|
18
17
|
|
|
19
18
|
export interface HDTouchUpFeatureProps {
|
|
20
19
|
config: HDTouchUpFeatureConfig;
|
|
21
|
-
translations: HDTouchUpTranslations
|
|
20
|
+
translations: HDTouchUpTranslations & {
|
|
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
|
-
/** Called before processing starts. Return false to cancel. */
|
|
25
28
|
onBeforeProcess?: () => Promise<boolean>;
|
|
26
|
-
renderProcessingModal?: (props:
|
|
27
|
-
visible: boolean;
|
|
28
|
-
progress: number;
|
|
29
|
-
}) => React.ReactNode;
|
|
29
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const HDTouchUpFeature: React.FC<HDTouchUpFeatureProps> = ({
|
|
@@ -37,10 +37,6 @@ export const HDTouchUpFeature: React.FC<HDTouchUpFeatureProps> = ({
|
|
|
37
37
|
onBeforeProcess,
|
|
38
38
|
renderProcessingModal,
|
|
39
39
|
}) => {
|
|
40
|
-
const tokens = useAppDesignTokens();
|
|
41
|
-
const { width: screenWidth, horizontalPadding } = useResponsive();
|
|
42
|
-
const imageSize = screenWidth - horizontalPadding * 2;
|
|
43
|
-
|
|
44
40
|
const feature = useHDTouchUpFeature({
|
|
45
41
|
config,
|
|
46
42
|
onSelectImage,
|
|
@@ -48,123 +44,55 @@ export const HDTouchUpFeature: React.FC<HDTouchUpFeatureProps> = ({
|
|
|
48
44
|
onBeforeProcess,
|
|
49
45
|
});
|
|
50
46
|
|
|
51
|
-
const
|
|
47
|
+
const modalTranslations = useMemo(
|
|
52
48
|
() => ({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
title: translations.modalTitle || "Processing",
|
|
50
|
+
message: translations.modalMessage || "AI is enhancing your photo...",
|
|
51
|
+
hint: translations.modalHint || "This may take a moment",
|
|
52
|
+
backgroundHint: translations.modalBackgroundHint || "Continue in background",
|
|
57
53
|
}),
|
|
58
54
|
[translations],
|
|
59
55
|
);
|
|
60
56
|
|
|
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
57
|
return (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
58
|
+
<SingleImageFeatureLayout
|
|
59
|
+
feature={feature}
|
|
60
|
+
translations={translations}
|
|
61
|
+
modalTranslations={modalTranslations}
|
|
62
|
+
renderProcessingModal={renderProcessingModal}
|
|
63
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
|
|
64
|
+
<PhotoUploadCard
|
|
65
|
+
imageUri={imageUri}
|
|
66
|
+
onPress={onSelect}
|
|
67
|
+
isValidating={isProcessing}
|
|
68
|
+
disabled={isDisabled}
|
|
111
69
|
translations={{
|
|
112
|
-
|
|
113
|
-
|
|
70
|
+
tapToUpload: translations.uploadTitle,
|
|
71
|
+
selectPhoto: translations.uploadSubtitle,
|
|
72
|
+
change: translations.uploadChange,
|
|
73
|
+
analyzing: translations.uploadAnalyzing,
|
|
114
74
|
}}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
133
|
-
</>
|
|
75
|
+
config={{
|
|
76
|
+
aspectRatio: 1,
|
|
77
|
+
borderRadius: 24,
|
|
78
|
+
showValidationStatus: false,
|
|
79
|
+
allowChange: true,
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
renderResult={({ imageUrl, imageSize }) => (
|
|
84
|
+
<Image
|
|
85
|
+
source={{ uri: imageUrl }}
|
|
86
|
+
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
87
|
+
resizeMode="contain"
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
134
91
|
);
|
|
135
92
|
};
|
|
136
93
|
|
|
137
94
|
const styles = StyleSheet.create({
|
|
138
|
-
container: {
|
|
139
|
-
flex: 1,
|
|
140
|
-
},
|
|
141
|
-
content: {
|
|
142
|
-
paddingVertical: 16,
|
|
143
|
-
},
|
|
144
|
-
description: {
|
|
145
|
-
textAlign: "center",
|
|
146
|
-
marginHorizontal: 24,
|
|
147
|
-
marginBottom: 24,
|
|
148
|
-
lineHeight: 24,
|
|
149
|
-
},
|
|
150
|
-
successText: {
|
|
151
|
-
textAlign: "center",
|
|
152
|
-
marginBottom: 24,
|
|
153
|
-
},
|
|
154
|
-
resultImageContainer: {
|
|
155
|
-
alignItems: "center",
|
|
156
|
-
marginHorizontal: 24,
|
|
157
|
-
marginBottom: 24,
|
|
158
|
-
},
|
|
159
95
|
resultImage: {
|
|
160
96
|
borderRadius: 16,
|
|
161
97
|
},
|
|
162
|
-
resultActions: {
|
|
163
|
-
marginHorizontal: 24,
|
|
164
|
-
gap: 12,
|
|
165
|
-
},
|
|
166
|
-
buttonContainer: {
|
|
167
|
-
marginHorizontal: 24,
|
|
168
|
-
marginTop: 8,
|
|
169
|
-
},
|
|
170
98
|
});
|
|
@@ -77,6 +77,14 @@ export interface BaseImageTranslations {
|
|
|
77
77
|
beforeLabel?: string;
|
|
78
78
|
afterLabel?: string;
|
|
79
79
|
compareHint?: string;
|
|
80
|
+
/** Modal title shown during processing */
|
|
81
|
+
modalTitle?: string;
|
|
82
|
+
/** Modal message shown during processing */
|
|
83
|
+
modalMessage?: string;
|
|
84
|
+
/** Modal hint/tip shown during processing */
|
|
85
|
+
modalHint?: string;
|
|
86
|
+
/** "Continue in background" text */
|
|
87
|
+
modalBackgroundHint?: string;
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/**
|
|
@@ -95,6 +103,14 @@ export interface BaseDualImageTranslations {
|
|
|
95
103
|
successText: string;
|
|
96
104
|
saveButtonText: string;
|
|
97
105
|
tryAnotherText: string;
|
|
106
|
+
/** Modal title shown during processing */
|
|
107
|
+
modalTitle?: string;
|
|
108
|
+
/** Modal message shown during processing */
|
|
109
|
+
modalMessage?: string;
|
|
110
|
+
/** Modal hint/tip shown during processing */
|
|
111
|
+
modalHint?: string;
|
|
112
|
+
/** "Continue in background" text */
|
|
113
|
+
modalBackgroundHint?: string;
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
/**
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RemoveBackgroundFeature Component
|
|
3
3
|
* Self-contained remove background 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 { useRemoveBackgroundFeature } from "../hooks";
|
|
14
13
|
import type {
|
|
15
14
|
RemoveBackgroundTranslations,
|
|
@@ -18,15 +17,16 @@ import type {
|
|
|
18
17
|
|
|
19
18
|
export interface RemoveBackgroundFeatureProps {
|
|
20
19
|
config: RemoveBackgroundFeatureConfig;
|
|
21
|
-
translations: RemoveBackgroundTranslations
|
|
20
|
+
translations: RemoveBackgroundTranslations & {
|
|
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
|
-
/** Called before processing starts. Return false to cancel. */
|
|
25
28
|
onBeforeProcess?: () => Promise<boolean>;
|
|
26
|
-
renderProcessingModal?: (props:
|
|
27
|
-
visible: boolean;
|
|
28
|
-
progress: number;
|
|
29
|
-
}) => React.ReactNode;
|
|
29
|
+
renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const RemoveBackgroundFeature: React.FC<RemoveBackgroundFeatureProps> = ({
|
|
@@ -37,10 +37,6 @@ export const RemoveBackgroundFeature: React.FC<RemoveBackgroundFeatureProps> = (
|
|
|
37
37
|
onBeforeProcess,
|
|
38
38
|
renderProcessingModal,
|
|
39
39
|
}) => {
|
|
40
|
-
const tokens = useAppDesignTokens();
|
|
41
|
-
const { width: screenWidth, horizontalPadding } = useResponsive();
|
|
42
|
-
const imageSize = screenWidth - horizontalPadding * 2;
|
|
43
|
-
|
|
44
40
|
const feature = useRemoveBackgroundFeature({
|
|
45
41
|
config,
|
|
46
42
|
onSelectImage,
|
|
@@ -48,125 +44,55 @@ export const RemoveBackgroundFeature: React.FC<RemoveBackgroundFeatureProps> = (
|
|
|
48
44
|
onBeforeProcess,
|
|
49
45
|
});
|
|
50
46
|
|
|
51
|
-
const
|
|
47
|
+
const modalTranslations = useMemo(
|
|
52
48
|
() => ({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
title: translations.modalTitle || "Processing",
|
|
50
|
+
message: translations.modalMessage || "AI is removing the background...",
|
|
51
|
+
hint: translations.modalHint || "This may take a moment",
|
|
52
|
+
backgroundHint: translations.modalBackgroundHint || "Continue in background",
|
|
57
53
|
}),
|
|
58
54
|
[translations],
|
|
59
55
|
);
|
|
60
56
|
|
|
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
57
|
return (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
58
|
+
<SingleImageFeatureLayout
|
|
59
|
+
feature={feature}
|
|
60
|
+
translations={translations}
|
|
61
|
+
modalTranslations={modalTranslations}
|
|
62
|
+
renderProcessingModal={renderProcessingModal}
|
|
63
|
+
renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
|
|
64
|
+
<PhotoUploadCard
|
|
65
|
+
imageUri={imageUri}
|
|
66
|
+
onPress={onSelect}
|
|
67
|
+
isValidating={isProcessing}
|
|
68
|
+
disabled={isDisabled}
|
|
112
69
|
translations={{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
70
|
+
tapToUpload: translations.uploadTitle,
|
|
71
|
+
selectPhoto: translations.uploadSubtitle,
|
|
72
|
+
change: translations.uploadChange,
|
|
73
|
+
analyzing: translations.uploadAnalyzing,
|
|
116
74
|
}}
|
|
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
|
-
</>
|
|
75
|
+
config={{
|
|
76
|
+
aspectRatio: 1,
|
|
77
|
+
borderRadius: 24,
|
|
78
|
+
showValidationStatus: false,
|
|
79
|
+
allowChange: true,
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
renderResult={({ imageUrl, imageSize }) => (
|
|
84
|
+
<Image
|
|
85
|
+
source={{ uri: imageUrl }}
|
|
86
|
+
style={[styles.resultImage, { width: imageSize, height: imageSize }]}
|
|
87
|
+
resizeMode="contain"
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
136
91
|
);
|
|
137
92
|
};
|
|
138
93
|
|
|
139
94
|
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
95
|
resultImage: {
|
|
162
96
|
borderRadius: 16,
|
|
163
97
|
},
|
|
164
|
-
resultActions: {
|
|
165
|
-
marginHorizontal: 24,
|
|
166
|
-
gap: 12,
|
|
167
|
-
},
|
|
168
|
-
buttonContainer: {
|
|
169
|
-
marginHorizontal: 24,
|
|
170
|
-
marginTop: 8,
|
|
171
|
-
},
|
|
172
98
|
});
|