@umituz/react-native-ai-generation-content 1.17.8 → 1.17.10
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/hd-touch-up/domain/index.ts +1 -0
- package/src/features/hd-touch-up/domain/types/hd-touch-up.types.ts +69 -0
- package/src/features/hd-touch-up/domain/types/index.ts +10 -0
- package/src/features/hd-touch-up/index.ts +31 -0
- package/src/features/hd-touch-up/infrastructure/index.ts +1 -0
- package/src/features/hd-touch-up/infrastructure/services/hd-touch-up-executor.ts +97 -0
- package/src/features/hd-touch-up/infrastructure/services/index.ts +2 -0
- package/src/features/hd-touch-up/presentation/components/HDTouchUpFeature.tsx +199 -0
- package/src/features/hd-touch-up/presentation/components/index.ts +2 -0
- package/src/features/hd-touch-up/presentation/hooks/index.ts +5 -0
- package/src/features/hd-touch-up/presentation/hooks/useHDTouchUpFeature.ts +137 -0
- package/src/features/hd-touch-up/presentation/index.ts +2 -0
- package/src/features/remove-object/index.ts +4 -0
- package/src/features/remove-object/presentation/components/RemoveObjectFeature.tsx +249 -0
- package/src/features/remove-object/presentation/components/index.ts +2 -0
- package/src/features/remove-object/presentation/index.ts +1 -0
- package/src/index.ts +6 -0
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HD Touch Up Feature Types
|
|
3
|
+
* Request, Result, Config types for HD enhancement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface HDTouchUpOptions {
|
|
7
|
+
enhanceQuality?: boolean;
|
|
8
|
+
upscaleFactor?: 2 | 4;
|
|
9
|
+
sharpen?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HDTouchUpRequest {
|
|
13
|
+
imageUri: string;
|
|
14
|
+
imageBase64?: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
options?: HDTouchUpOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HDTouchUpResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
imageUrl?: string;
|
|
22
|
+
imageBase64?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
requestId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface HDTouchUpFeatureState {
|
|
28
|
+
imageUri: string | null;
|
|
29
|
+
processedUrl: string | null;
|
|
30
|
+
isProcessing: boolean;
|
|
31
|
+
progress: number;
|
|
32
|
+
error: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HDTouchUpTranslations {
|
|
36
|
+
uploadTitle: string;
|
|
37
|
+
uploadSubtitle: string;
|
|
38
|
+
uploadChange: string;
|
|
39
|
+
uploadAnalyzing: string;
|
|
40
|
+
description: string;
|
|
41
|
+
processingText: string;
|
|
42
|
+
processButtonText: string;
|
|
43
|
+
successText: string;
|
|
44
|
+
saveButtonText: string;
|
|
45
|
+
tryAnotherText: string;
|
|
46
|
+
beforeLabel?: string;
|
|
47
|
+
afterLabel?: string;
|
|
48
|
+
compareHint?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type HDTouchUpInputBuilder = (
|
|
52
|
+
base64: string,
|
|
53
|
+
options?: HDTouchUpOptions,
|
|
54
|
+
) => Record<string, unknown>;
|
|
55
|
+
|
|
56
|
+
export type HDTouchUpResultExtractor = (result: unknown) => string | undefined;
|
|
57
|
+
|
|
58
|
+
export interface HDTouchUpFeatureConfig {
|
|
59
|
+
providerId?: string;
|
|
60
|
+
creditCost?: number;
|
|
61
|
+
model: string;
|
|
62
|
+
buildInput: HDTouchUpInputBuilder;
|
|
63
|
+
extractResult?: HDTouchUpResultExtractor;
|
|
64
|
+
prepareImage: (imageUri: string) => Promise<string>;
|
|
65
|
+
onImageSelect?: (uri: string) => void;
|
|
66
|
+
onProcessingStart?: () => void;
|
|
67
|
+
onProcessingComplete?: (result: HDTouchUpResult) => void;
|
|
68
|
+
onError?: (error: string) => void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HD Touch Up Feature
|
|
3
|
+
* Provider-agnostic HD enhancement feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Domain Types
|
|
7
|
+
export type {
|
|
8
|
+
HDTouchUpOptions,
|
|
9
|
+
HDTouchUpRequest,
|
|
10
|
+
HDTouchUpResult,
|
|
11
|
+
HDTouchUpFeatureState,
|
|
12
|
+
HDTouchUpTranslations,
|
|
13
|
+
HDTouchUpFeatureConfig,
|
|
14
|
+
HDTouchUpInputBuilder,
|
|
15
|
+
HDTouchUpResultExtractor,
|
|
16
|
+
} from "./domain";
|
|
17
|
+
|
|
18
|
+
// Infrastructure Services
|
|
19
|
+
export { executeHDTouchUp, hasHDTouchUpSupport } from "./infrastructure";
|
|
20
|
+
export type { ExecuteHDTouchUpOptions } from "./infrastructure";
|
|
21
|
+
|
|
22
|
+
// Presentation Hooks
|
|
23
|
+
export { useHDTouchUpFeature } from "./presentation";
|
|
24
|
+
export type {
|
|
25
|
+
UseHDTouchUpFeatureProps,
|
|
26
|
+
UseHDTouchUpFeatureReturn,
|
|
27
|
+
} from "./presentation";
|
|
28
|
+
|
|
29
|
+
// Presentation Components
|
|
30
|
+
export { HDTouchUpFeature } from "./presentation";
|
|
31
|
+
export type { HDTouchUpFeatureProps } from "./presentation";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./services";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HD Touch Up Executor
|
|
3
|
+
* Executes HD enhancement using the configured provider
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { providerRegistry } from "../../../../infrastructure/services";
|
|
7
|
+
import type {
|
|
8
|
+
HDTouchUpRequest,
|
|
9
|
+
HDTouchUpResult,
|
|
10
|
+
HDTouchUpInputBuilder,
|
|
11
|
+
HDTouchUpResultExtractor,
|
|
12
|
+
} from "../../domain/types";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
export interface ExecuteHDTouchUpOptions {
|
|
17
|
+
model: string;
|
|
18
|
+
buildInput: HDTouchUpInputBuilder;
|
|
19
|
+
extractResult?: HDTouchUpResultExtractor;
|
|
20
|
+
onProgress?: (progress: number) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function executeHDTouchUp(
|
|
24
|
+
request: HDTouchUpRequest,
|
|
25
|
+
options: ExecuteHDTouchUpOptions,
|
|
26
|
+
): Promise<HDTouchUpResult> {
|
|
27
|
+
const { model, buildInput, extractResult, onProgress } = options;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const provider = providerRegistry.getActiveProvider();
|
|
31
|
+
if (!provider) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: "No AI provider configured",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (__DEV__) {
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.log("[HDTouchUp] Starting HD enhancement with model:", model);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onProgress?.(10);
|
|
44
|
+
|
|
45
|
+
const imageBase64 = request.imageBase64;
|
|
46
|
+
if (!imageBase64) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: "Image base64 is required",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const input = buildInput(imageBase64, request.options);
|
|
54
|
+
|
|
55
|
+
onProgress?.(30);
|
|
56
|
+
|
|
57
|
+
const result = await provider.generate(model, input, request.userId);
|
|
58
|
+
|
|
59
|
+
onProgress?.(80);
|
|
60
|
+
|
|
61
|
+
const imageUrl = extractResult
|
|
62
|
+
? extractResult(result)
|
|
63
|
+
: (result as { imageUrl?: string })?.imageUrl ||
|
|
64
|
+
(result as { image?: string })?.image ||
|
|
65
|
+
(result as { output?: string })?.output;
|
|
66
|
+
|
|
67
|
+
onProgress?.(100);
|
|
68
|
+
|
|
69
|
+
if (imageUrl) {
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
imageUrl,
|
|
73
|
+
requestId: (result as { requestId?: string })?.requestId,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: "No image URL in response",
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
if (__DEV__) {
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.error("[HDTouchUp] Error:", message);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: message,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function hasHDTouchUpSupport(): boolean {
|
|
95
|
+
const provider = providerRegistry.getActiveProvider();
|
|
96
|
+
return !!provider;
|
|
97
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HDTouchUpFeature Component
|
|
3
|
+
* Self-contained HD touch up 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 { useHDTouchUpFeature } from "../hooks";
|
|
17
|
+
import type {
|
|
18
|
+
HDTouchUpTranslations,
|
|
19
|
+
HDTouchUpFeatureConfig,
|
|
20
|
+
} from "../../domain/types";
|
|
21
|
+
|
|
22
|
+
export interface HDTouchUpFeatureProps {
|
|
23
|
+
config: HDTouchUpFeatureConfig;
|
|
24
|
+
userId: string;
|
|
25
|
+
translations: HDTouchUpTranslations;
|
|
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 HDTouchUpFeature: React.FC<HDTouchUpFeatureProps> = ({
|
|
35
|
+
config,
|
|
36
|
+
userId,
|
|
37
|
+
translations,
|
|
38
|
+
onSelectImage,
|
|
39
|
+
onSaveImage,
|
|
40
|
+
renderProcessingModal,
|
|
41
|
+
}) => {
|
|
42
|
+
const tokens = useAppDesignTokens();
|
|
43
|
+
|
|
44
|
+
const feature = useHDTouchUpFeature({
|
|
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
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useHDTouchUpFeature Hook
|
|
3
|
+
* Manages HD touch up feature state and actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import { executeHDTouchUp } from "../../infrastructure/services";
|
|
8
|
+
import type {
|
|
9
|
+
HDTouchUpFeatureState,
|
|
10
|
+
HDTouchUpFeatureConfig,
|
|
11
|
+
HDTouchUpResult,
|
|
12
|
+
} from "../../domain/types";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
export interface UseHDTouchUpFeatureProps {
|
|
17
|
+
config: HDTouchUpFeatureConfig;
|
|
18
|
+
userId: string;
|
|
19
|
+
onSelectImage: () => Promise<string | null>;
|
|
20
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseHDTouchUpFeatureReturn extends HDTouchUpFeatureState {
|
|
24
|
+
selectImage: () => Promise<void>;
|
|
25
|
+
process: () => Promise<void>;
|
|
26
|
+
save: () => Promise<void>;
|
|
27
|
+
reset: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const initialState: HDTouchUpFeatureState = {
|
|
31
|
+
imageUri: null,
|
|
32
|
+
processedUrl: null,
|
|
33
|
+
isProcessing: false,
|
|
34
|
+
progress: 0,
|
|
35
|
+
error: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function useHDTouchUpFeature(
|
|
39
|
+
props: UseHDTouchUpFeatureProps,
|
|
40
|
+
): UseHDTouchUpFeatureReturn {
|
|
41
|
+
const { config, userId, onSelectImage, onSaveImage } = props;
|
|
42
|
+
const [state, setState] = useState<HDTouchUpFeatureState>(initialState);
|
|
43
|
+
|
|
44
|
+
const selectImage = useCallback(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const uri = await onSelectImage();
|
|
47
|
+
if (uri) {
|
|
48
|
+
setState((prev) => ({ ...prev, imageUri: uri, error: null }));
|
|
49
|
+
config.onImageSelect?.(uri);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
54
|
+
}
|
|
55
|
+
}, [onSelectImage, config]);
|
|
56
|
+
|
|
57
|
+
const handleProgress = useCallback((progress: number) => {
|
|
58
|
+
setState((prev) => ({ ...prev, progress }));
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const process = useCallback(async () => {
|
|
62
|
+
if (!state.imageUri) return;
|
|
63
|
+
|
|
64
|
+
setState((prev) => ({
|
|
65
|
+
...prev,
|
|
66
|
+
isProcessing: true,
|
|
67
|
+
progress: 0,
|
|
68
|
+
error: null,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
config.onProcessingStart?.();
|
|
72
|
+
|
|
73
|
+
if (__DEV__) {
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log("[useHDTouchUpFeature] Starting HD enhancement");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const imageBase64 = await config.prepareImage(state.imageUri);
|
|
79
|
+
|
|
80
|
+
const result: HDTouchUpResult = await executeHDTouchUp(
|
|
81
|
+
{
|
|
82
|
+
imageUri: state.imageUri,
|
|
83
|
+
imageBase64,
|
|
84
|
+
userId,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
model: config.model,
|
|
88
|
+
buildInput: config.buildInput,
|
|
89
|
+
extractResult: config.extractResult,
|
|
90
|
+
onProgress: handleProgress,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (result.success && result.imageUrl) {
|
|
95
|
+
const url = result.imageUrl;
|
|
96
|
+
setState((prev) => ({
|
|
97
|
+
...prev,
|
|
98
|
+
isProcessing: false,
|
|
99
|
+
processedUrl: url,
|
|
100
|
+
progress: 100,
|
|
101
|
+
}));
|
|
102
|
+
config.onProcessingComplete?.(result);
|
|
103
|
+
} else {
|
|
104
|
+
const errorMessage = result.error || "Processing failed";
|
|
105
|
+
setState((prev) => ({
|
|
106
|
+
...prev,
|
|
107
|
+
isProcessing: false,
|
|
108
|
+
error: errorMessage,
|
|
109
|
+
progress: 0,
|
|
110
|
+
}));
|
|
111
|
+
config.onError?.(errorMessage);
|
|
112
|
+
}
|
|
113
|
+
}, [state.imageUri, userId, config, handleProgress]);
|
|
114
|
+
|
|
115
|
+
const save = useCallback(async () => {
|
|
116
|
+
if (!state.processedUrl) return;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await onSaveImage(state.processedUrl);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
123
|
+
}
|
|
124
|
+
}, [state.processedUrl, onSaveImage]);
|
|
125
|
+
|
|
126
|
+
const reset = useCallback(() => {
|
|
127
|
+
setState(initialState);
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
selectImage,
|
|
133
|
+
process,
|
|
134
|
+
save,
|
|
135
|
+
reset,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemoveObjectFeature Component
|
|
3
|
+
* Self-contained remove object 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, TextInput } 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 { useRemoveObjectFeature } from "../hooks";
|
|
17
|
+
import type {
|
|
18
|
+
RemoveObjectTranslations,
|
|
19
|
+
RemoveObjectFeatureConfig,
|
|
20
|
+
} from "../../domain/types";
|
|
21
|
+
|
|
22
|
+
export interface RemoveObjectFeatureProps {
|
|
23
|
+
config: RemoveObjectFeatureConfig;
|
|
24
|
+
userId: string;
|
|
25
|
+
translations: RemoveObjectTranslations;
|
|
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 RemoveObjectFeature: React.FC<RemoveObjectFeatureProps> = ({
|
|
35
|
+
config,
|
|
36
|
+
userId,
|
|
37
|
+
translations,
|
|
38
|
+
onSelectImage,
|
|
39
|
+
onSaveImage,
|
|
40
|
+
renderProcessingModal,
|
|
41
|
+
}) => {
|
|
42
|
+
const tokens = useAppDesignTokens();
|
|
43
|
+
const feature = useRemoveObjectFeature({
|
|
44
|
+
config,
|
|
45
|
+
userId,
|
|
46
|
+
onSelectImage,
|
|
47
|
+
onSaveImage,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const photoTranslations = useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
tapToUpload: translations.uploadTitle,
|
|
53
|
+
selectPhoto: translations.uploadSubtitle,
|
|
54
|
+
change: translations.uploadChange,
|
|
55
|
+
analyzing: translations.uploadAnalyzing,
|
|
56
|
+
}),
|
|
57
|
+
[translations],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const handleProcess = useCallback(() => {
|
|
61
|
+
void feature.process();
|
|
62
|
+
}, [feature]);
|
|
63
|
+
|
|
64
|
+
const handleSave = useCallback(() => {
|
|
65
|
+
void feature.save();
|
|
66
|
+
}, [feature]);
|
|
67
|
+
|
|
68
|
+
const handleSelectImage = useCallback(() => {
|
|
69
|
+
void feature.selectImage();
|
|
70
|
+
}, [feature]);
|
|
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
|
+
<PhotoUploadCard
|
|
130
|
+
imageUri={feature.imageUri}
|
|
131
|
+
onPress={handleSelectImage}
|
|
132
|
+
isValidating={feature.isProcessing}
|
|
133
|
+
disabled={feature.isProcessing}
|
|
134
|
+
translations={photoTranslations}
|
|
135
|
+
config={{
|
|
136
|
+
aspectRatio: 1,
|
|
137
|
+
borderRadius: 24,
|
|
138
|
+
showValidationStatus: false,
|
|
139
|
+
allowChange: true,
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
<View style={styles.promptContainer}>
|
|
144
|
+
<AtomicText
|
|
145
|
+
type="labelMedium"
|
|
146
|
+
style={[styles.promptLabel, { color: tokens.colors.textPrimary }]}
|
|
147
|
+
>
|
|
148
|
+
{translations.maskTitle}
|
|
149
|
+
</AtomicText>
|
|
150
|
+
<TextInput
|
|
151
|
+
style={[
|
|
152
|
+
styles.promptInput,
|
|
153
|
+
{
|
|
154
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
155
|
+
color: tokens.colors.textPrimary,
|
|
156
|
+
borderColor: tokens.colors.border,
|
|
157
|
+
},
|
|
158
|
+
]}
|
|
159
|
+
value={feature.prompt}
|
|
160
|
+
onChangeText={feature.setPrompt}
|
|
161
|
+
placeholder={translations.promptPlaceholder}
|
|
162
|
+
placeholderTextColor={tokens.colors.textTertiary}
|
|
163
|
+
multiline
|
|
164
|
+
numberOfLines={3}
|
|
165
|
+
editable={!feature.isProcessing}
|
|
166
|
+
/>
|
|
167
|
+
<AtomicText
|
|
168
|
+
type="bodySmall"
|
|
169
|
+
style={[styles.promptHint, { color: tokens.colors.textTertiary }]}
|
|
170
|
+
>
|
|
171
|
+
{translations.maskSubtitle}
|
|
172
|
+
</AtomicText>
|
|
173
|
+
</View>
|
|
174
|
+
|
|
175
|
+
<ErrorDisplay error={feature.error} />
|
|
176
|
+
|
|
177
|
+
<View style={styles.buttonContainer}>
|
|
178
|
+
<AtomicButton
|
|
179
|
+
title={
|
|
180
|
+
feature.isProcessing
|
|
181
|
+
? translations.processingText
|
|
182
|
+
: translations.processButtonText
|
|
183
|
+
}
|
|
184
|
+
onPress={handleProcess}
|
|
185
|
+
disabled={!feature.imageUri || feature.isProcessing}
|
|
186
|
+
variant="primary"
|
|
187
|
+
size="lg"
|
|
188
|
+
/>
|
|
189
|
+
</View>
|
|
190
|
+
</ScrollView>
|
|
191
|
+
|
|
192
|
+
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const styles = StyleSheet.create({
|
|
198
|
+
container: {
|
|
199
|
+
flex: 1,
|
|
200
|
+
},
|
|
201
|
+
content: {
|
|
202
|
+
paddingVertical: 16,
|
|
203
|
+
},
|
|
204
|
+
description: {
|
|
205
|
+
textAlign: "center",
|
|
206
|
+
marginHorizontal: 24,
|
|
207
|
+
marginBottom: 24,
|
|
208
|
+
lineHeight: 24,
|
|
209
|
+
},
|
|
210
|
+
promptContainer: {
|
|
211
|
+
marginHorizontal: 24,
|
|
212
|
+
marginTop: 16,
|
|
213
|
+
marginBottom: 8,
|
|
214
|
+
},
|
|
215
|
+
promptLabel: {
|
|
216
|
+
marginBottom: 8,
|
|
217
|
+
},
|
|
218
|
+
promptInput: {
|
|
219
|
+
borderWidth: 1,
|
|
220
|
+
borderRadius: 12,
|
|
221
|
+
padding: 12,
|
|
222
|
+
minHeight: 80,
|
|
223
|
+
textAlignVertical: "top",
|
|
224
|
+
fontSize: 16,
|
|
225
|
+
},
|
|
226
|
+
promptHint: {
|
|
227
|
+
marginTop: 8,
|
|
228
|
+
},
|
|
229
|
+
successText: {
|
|
230
|
+
textAlign: "center",
|
|
231
|
+
marginBottom: 24,
|
|
232
|
+
},
|
|
233
|
+
resultImageContainer: {
|
|
234
|
+
alignItems: "center",
|
|
235
|
+
marginHorizontal: 24,
|
|
236
|
+
marginBottom: 24,
|
|
237
|
+
},
|
|
238
|
+
resultImage: {
|
|
239
|
+
borderRadius: 16,
|
|
240
|
+
},
|
|
241
|
+
resultActions: {
|
|
242
|
+
marginHorizontal: 24,
|
|
243
|
+
gap: 12,
|
|
244
|
+
},
|
|
245
|
+
buttonContainer: {
|
|
246
|
+
marginHorizontal: 24,
|
|
247
|
+
marginTop: 8,
|
|
248
|
+
},
|
|
249
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -403,3 +403,9 @@ export * from "./features/image-to-video";
|
|
|
403
403
|
|
|
404
404
|
export * from "./features/text-to-voice";
|
|
405
405
|
|
|
406
|
+
// =============================================================================
|
|
407
|
+
// FEATURES - HD Touch Up
|
|
408
|
+
// =============================================================================
|
|
409
|
+
|
|
410
|
+
export * from "./features/hd-touch-up";
|
|
411
|
+
|