@umituz/react-native-ai-generation-content 1.15.2 → 1.16.0
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/upscaling/domain/types/upscale.types.ts +3 -0
- package/src/features/upscaling/index.ts +3 -0
- package/src/features/upscaling/presentation/components/ComparisonSlider.tsx +206 -0
- package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +10 -3
- package/src/features/upscaling/presentation/components/UpscaleResultView.tsx +27 -23
- package/src/features/upscaling/presentation/components/index.ts +2 -0
- package/src/features/upscaling/presentation/hooks/useUpscaleFeature.ts +2 -1
package/package.json
CHANGED
|
@@ -38,3 +38,6 @@ export type {
|
|
|
38
38
|
UpscaleFeatureProps,
|
|
39
39
|
UpscaleResultViewProps,
|
|
40
40
|
} from "./presentation";
|
|
41
|
+
|
|
42
|
+
// ComparisonSlider is available from ./presentation/components if needed directly
|
|
43
|
+
// but not re-exported to avoid conflict with background feature's ComparisonSlider
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison Slider Component
|
|
3
|
+
* Before/After comparison slider for upscaled images
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { memo, useState, useRef, useMemo } from "react";
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
Image,
|
|
12
|
+
PanResponder,
|
|
13
|
+
Dimensions,
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import {
|
|
16
|
+
AtomicText,
|
|
17
|
+
useAppDesignTokens,
|
|
18
|
+
} from "@umituz/react-native-design-system";
|
|
19
|
+
|
|
20
|
+
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
|
21
|
+
|
|
22
|
+
export interface ComparisonSliderProps {
|
|
23
|
+
originalUri: string;
|
|
24
|
+
processedUri: string;
|
|
25
|
+
beforeLabel?: string;
|
|
26
|
+
afterLabel?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const ComparisonSlider: React.FC<ComparisonSliderProps> = memo(
|
|
30
|
+
function ComparisonSlider({
|
|
31
|
+
originalUri,
|
|
32
|
+
processedUri,
|
|
33
|
+
beforeLabel = "Before",
|
|
34
|
+
afterLabel = "After",
|
|
35
|
+
}) {
|
|
36
|
+
const tokens = useAppDesignTokens();
|
|
37
|
+
const [sliderPosition, setSliderPosition] = useState(50);
|
|
38
|
+
const containerWidth = useRef(SCREEN_WIDTH - 48);
|
|
39
|
+
|
|
40
|
+
const panResponder = useRef(
|
|
41
|
+
PanResponder.create({
|
|
42
|
+
onStartShouldSetPanResponder: () => true,
|
|
43
|
+
onMoveShouldSetPanResponder: () => true,
|
|
44
|
+
onPanResponderMove: (_, gestureState) => {
|
|
45
|
+
const newPosition =
|
|
46
|
+
((gestureState.moveX - 24) / containerWidth.current) * 100;
|
|
47
|
+
setSliderPosition(Math.max(5, Math.min(95, newPosition)));
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
).current;
|
|
51
|
+
|
|
52
|
+
const themedStyles = useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
StyleSheet.create({
|
|
55
|
+
container: {
|
|
56
|
+
width: "100%",
|
|
57
|
+
aspectRatio: 1,
|
|
58
|
+
borderRadius: 20,
|
|
59
|
+
overflow: "hidden",
|
|
60
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
61
|
+
},
|
|
62
|
+
originalContainer: {
|
|
63
|
+
position: "absolute",
|
|
64
|
+
top: 0,
|
|
65
|
+
left: 0,
|
|
66
|
+
bottom: 0,
|
|
67
|
+
overflow: "hidden",
|
|
68
|
+
borderRightWidth: 3,
|
|
69
|
+
borderRightColor: tokens.colors.surface,
|
|
70
|
+
},
|
|
71
|
+
sliderHandle: {
|
|
72
|
+
position: "absolute",
|
|
73
|
+
top: "50%",
|
|
74
|
+
left: -20,
|
|
75
|
+
width: 40,
|
|
76
|
+
height: 40,
|
|
77
|
+
borderRadius: 20,
|
|
78
|
+
justifyContent: "center",
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
marginTop: -20,
|
|
81
|
+
backgroundColor: tokens.colors.surface,
|
|
82
|
+
borderWidth: 3,
|
|
83
|
+
borderColor: tokens.colors.primary,
|
|
84
|
+
},
|
|
85
|
+
labelLeft: {
|
|
86
|
+
backgroundColor: tokens.colors.surface,
|
|
87
|
+
},
|
|
88
|
+
labelRight: {
|
|
89
|
+
backgroundColor: tokens.colors.primary,
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
[tokens]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<View
|
|
97
|
+
style={themedStyles.container}
|
|
98
|
+
onLayout={(e) => {
|
|
99
|
+
containerWidth.current = e.nativeEvent.layout.width;
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<View style={styles.imageContainer}>
|
|
103
|
+
<Image
|
|
104
|
+
source={{ uri: processedUri }}
|
|
105
|
+
style={styles.image}
|
|
106
|
+
resizeMode="cover"
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<View
|
|
110
|
+
style={[
|
|
111
|
+
themedStyles.originalContainer,
|
|
112
|
+
{ width: `${sliderPosition}%` },
|
|
113
|
+
]}
|
|
114
|
+
>
|
|
115
|
+
<Image
|
|
116
|
+
source={{ uri: originalUri }}
|
|
117
|
+
style={[styles.image, { width: containerWidth.current }]}
|
|
118
|
+
resizeMode="cover"
|
|
119
|
+
/>
|
|
120
|
+
</View>
|
|
121
|
+
|
|
122
|
+
<View
|
|
123
|
+
style={[styles.sliderLine, { left: `${sliderPosition}%` }]}
|
|
124
|
+
{...panResponder.panHandlers}
|
|
125
|
+
>
|
|
126
|
+
<View style={themedStyles.sliderHandle}>
|
|
127
|
+
<View style={styles.handleBars}>
|
|
128
|
+
<View
|
|
129
|
+
style={[
|
|
130
|
+
styles.handleBar,
|
|
131
|
+
{ backgroundColor: tokens.colors.primary },
|
|
132
|
+
]}
|
|
133
|
+
/>
|
|
134
|
+
<View
|
|
135
|
+
style={[
|
|
136
|
+
styles.handleBar,
|
|
137
|
+
{ backgroundColor: tokens.colors.primary },
|
|
138
|
+
]}
|
|
139
|
+
/>
|
|
140
|
+
</View>
|
|
141
|
+
</View>
|
|
142
|
+
</View>
|
|
143
|
+
|
|
144
|
+
<View
|
|
145
|
+
style={[styles.label, styles.labelLeft, themedStyles.labelLeft]}
|
|
146
|
+
>
|
|
147
|
+
<AtomicText type="labelSmall" style={{ color: tokens.colors.text }}>
|
|
148
|
+
{beforeLabel}
|
|
149
|
+
</AtomicText>
|
|
150
|
+
</View>
|
|
151
|
+
|
|
152
|
+
<View
|
|
153
|
+
style={[styles.label, styles.labelRight, themedStyles.labelRight]}
|
|
154
|
+
>
|
|
155
|
+
<AtomicText
|
|
156
|
+
type="labelSmall"
|
|
157
|
+
style={{ color: tokens.colors.onPrimary }}
|
|
158
|
+
>
|
|
159
|
+
{afterLabel}
|
|
160
|
+
</AtomicText>
|
|
161
|
+
</View>
|
|
162
|
+
</View>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const styles = StyleSheet.create({
|
|
169
|
+
imageContainer: {
|
|
170
|
+
flex: 1,
|
|
171
|
+
position: "relative",
|
|
172
|
+
},
|
|
173
|
+
image: {
|
|
174
|
+
width: "100%",
|
|
175
|
+
height: "100%",
|
|
176
|
+
},
|
|
177
|
+
sliderLine: {
|
|
178
|
+
position: "absolute",
|
|
179
|
+
top: 0,
|
|
180
|
+
bottom: 0,
|
|
181
|
+
width: 3,
|
|
182
|
+
marginLeft: -1.5,
|
|
183
|
+
},
|
|
184
|
+
handleBars: {
|
|
185
|
+
flexDirection: "row",
|
|
186
|
+
gap: 4,
|
|
187
|
+
},
|
|
188
|
+
handleBar: {
|
|
189
|
+
width: 3,
|
|
190
|
+
height: 16,
|
|
191
|
+
borderRadius: 2,
|
|
192
|
+
},
|
|
193
|
+
label: {
|
|
194
|
+
position: "absolute",
|
|
195
|
+
top: 12,
|
|
196
|
+
paddingHorizontal: 12,
|
|
197
|
+
paddingVertical: 6,
|
|
198
|
+
borderRadius: 16,
|
|
199
|
+
},
|
|
200
|
+
labelLeft: {
|
|
201
|
+
left: 12,
|
|
202
|
+
},
|
|
203
|
+
labelRight: {
|
|
204
|
+
right: 12,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
@@ -64,7 +64,7 @@ export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
|
64
64
|
onProcess();
|
|
65
65
|
}, [onProcess]);
|
|
66
66
|
|
|
67
|
-
if (processedUrl) {
|
|
67
|
+
if (processedUrl && imageUri) {
|
|
68
68
|
return (
|
|
69
69
|
<ScrollView
|
|
70
70
|
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
@@ -72,8 +72,15 @@ export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
|
72
72
|
showsVerticalScrollIndicator={false}
|
|
73
73
|
>
|
|
74
74
|
<UpscaleResultView
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
originalUri={imageUri}
|
|
76
|
+
processedUri={processedUrl}
|
|
77
|
+
translations={{
|
|
78
|
+
successText: translations.successText,
|
|
79
|
+
saveButtonText: translations.saveButtonText,
|
|
80
|
+
tryAnotherText: translations.tryAnotherText,
|
|
81
|
+
beforeLabel: translations.beforeLabel,
|
|
82
|
+
afterLabel: translations.afterLabel,
|
|
83
|
+
}}
|
|
77
84
|
onSave={onSave}
|
|
78
85
|
onReset={onReset}
|
|
79
86
|
/>
|
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UpscaleResultView Component
|
|
3
|
-
* Displays the upscaled image result
|
|
3
|
+
* Displays the upscaled image result with before/after comparison
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View,
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
AtomicText,
|
|
10
10
|
AtomicButton,
|
|
11
11
|
useAppDesignTokens,
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { ComparisonSlider } from "./ComparisonSlider";
|
|
13
14
|
import type { UpscaleTranslations } from "../../domain/types";
|
|
14
15
|
|
|
15
16
|
export interface UpscaleResultViewProps {
|
|
16
|
-
|
|
17
|
+
originalUri: string;
|
|
18
|
+
processedUri: string;
|
|
17
19
|
translations: Pick<
|
|
18
20
|
UpscaleTranslations,
|
|
19
|
-
|
|
21
|
+
| "successText"
|
|
22
|
+
| "saveButtonText"
|
|
23
|
+
| "tryAnotherText"
|
|
24
|
+
| "beforeLabel"
|
|
25
|
+
| "afterLabel"
|
|
20
26
|
>;
|
|
21
27
|
onSave: () => void;
|
|
22
28
|
onReset: () => void;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export const UpscaleResultView: React.FC<UpscaleResultViewProps> = ({
|
|
26
|
-
|
|
32
|
+
originalUri,
|
|
33
|
+
processedUri,
|
|
27
34
|
translations,
|
|
28
35
|
onSave,
|
|
29
36
|
onReset,
|
|
@@ -39,18 +46,19 @@ export const UpscaleResultView: React.FC<UpscaleResultViewProps> = ({
|
|
|
39
46
|
{translations.successText}
|
|
40
47
|
</AtomicText>
|
|
41
48
|
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
<ComparisonSlider
|
|
50
|
+
originalUri={originalUri}
|
|
51
|
+
processedUri={processedUri}
|
|
52
|
+
beforeLabel={translations.beforeLabel}
|
|
53
|
+
afterLabel={translations.afterLabel}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<AtomicText
|
|
57
|
+
type="bodySmall"
|
|
58
|
+
style={[styles.hint, { color: tokens.colors.textSecondary }]}
|
|
47
59
|
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
style={styles.image}
|
|
51
|
-
resizeMode="contain"
|
|
52
|
-
/>
|
|
53
|
-
</View>
|
|
60
|
+
Drag slider to compare
|
|
61
|
+
</AtomicText>
|
|
54
62
|
|
|
55
63
|
<View style={styles.actions}>
|
|
56
64
|
<AtomicButton
|
|
@@ -79,15 +87,11 @@ const styles = StyleSheet.create({
|
|
|
79
87
|
textAlign: "center",
|
|
80
88
|
marginBottom: 24,
|
|
81
89
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
hint: {
|
|
91
|
+
textAlign: "center",
|
|
92
|
+
marginTop: 12,
|
|
85
93
|
marginBottom: 24,
|
|
86
94
|
},
|
|
87
|
-
image: {
|
|
88
|
-
width: "100%",
|
|
89
|
-
aspectRatio: 1,
|
|
90
|
-
},
|
|
91
95
|
actions: {
|
|
92
96
|
gap: 12,
|
|
93
97
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { UpscaleFeature } from "./UpscaleFeature";
|
|
2
2
|
export { UpscaleResultView } from "./UpscaleResultView";
|
|
3
|
+
export { ComparisonSlider } from "./ComparisonSlider";
|
|
3
4
|
export type { UpscaleFeatureProps } from "./UpscaleFeature";
|
|
4
5
|
export type { UpscaleResultViewProps } from "./UpscaleResultView";
|
|
6
|
+
export type { ComparisonSliderProps } from "./ComparisonSlider";
|
|
@@ -88,10 +88,11 @@ export function useUpscaleFeature(
|
|
|
88
88
|
);
|
|
89
89
|
|
|
90
90
|
if (result.success && result.imageUrl) {
|
|
91
|
+
const url = result.imageUrl;
|
|
91
92
|
setState((prev) => ({
|
|
92
93
|
...prev,
|
|
93
94
|
isProcessing: false,
|
|
94
|
-
processedUrl:
|
|
95
|
+
processedUrl: url,
|
|
95
96
|
progress: 100,
|
|
96
97
|
}));
|
|
97
98
|
config?.onProcessingComplete?.(result);
|