@umituz/react-native-ai-generation-content 1.17.144 → 1.17.145
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/replace-background/index.ts +5 -50
- package/src/features/replace-background/presentation/components/index.ts +0 -12
- package/src/features/replace-background/presentation/hooks/index.ts +0 -3
- package/src/infrastructure/utils/index.ts +1 -0
- package/src/infrastructure/utils/result-validator.util.ts +0 -214
- package/src/infrastructure/utils/url-extractor.util.ts +209 -0
- package/src/presentation/hooks/flow-state.utils.ts +101 -0
- package/src/presentation/hooks/useGenerationFlow.ts +47 -178
- package/src/presentation/types/flow-config.types.ts +5 -99
- package/src/presentation/types/flow-default-configs.ts +106 -0
- package/src/features/replace-background/domain/entities/background.types.ts +0 -77
- package/src/features/replace-background/domain/entities/component.types.ts +0 -87
- package/src/features/replace-background/domain/entities/config.types.ts +0 -41
- package/src/features/replace-background/domain/entities/index.ts +0 -30
- package/src/features/replace-background/infrastructure/constants/index.ts +0 -5
- package/src/features/replace-background/infrastructure/constants/prompts.constants.ts +0 -15
- package/src/features/replace-background/infrastructure/index.ts +0 -5
- package/src/features/replace-background/presentation/components/BackgroundFeature.tsx +0 -143
- package/src/features/replace-background/presentation/components/ComparisonSlider.tsx +0 -187
- package/src/features/replace-background/presentation/components/ErrorDisplay.tsx +0 -60
- package/src/features/replace-background/presentation/components/FeatureHeader.tsx +0 -80
- package/src/features/replace-background/presentation/components/GenerateButton.tsx +0 -85
- package/src/features/replace-background/presentation/components/ImagePicker.tsx +0 -136
- package/src/features/replace-background/presentation/components/ModeSelector.tsx +0 -78
- package/src/features/replace-background/presentation/components/PromptInput.tsx +0 -142
- package/src/features/replace-background/presentation/components/ResultDisplay.tsx +0 -122
- package/src/features/replace-background/presentation/hooks/useBackgroundFeature.ts +0 -119
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration Types
|
|
3
|
-
* @description Configuration interfaces for background feature
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
BackgroundProcessRequest,
|
|
8
|
-
BackgroundProcessResult,
|
|
9
|
-
StudioMode,
|
|
10
|
-
} from "./background.types";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Process request callback parameters
|
|
14
|
-
*/
|
|
15
|
-
export interface ProcessRequestParams extends BackgroundProcessRequest {
|
|
16
|
-
readonly onProgress?: (progress: number) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Background feature configuration
|
|
21
|
-
*/
|
|
22
|
-
export interface BackgroundFeatureConfig {
|
|
23
|
-
readonly onProcess: (
|
|
24
|
-
params: ProcessRequestParams
|
|
25
|
-
) => Promise<BackgroundProcessResult>;
|
|
26
|
-
readonly onSave?: (imageUrl: string) => Promise<void>;
|
|
27
|
-
readonly onError?: (error: Error) => void;
|
|
28
|
-
readonly onSuccess?: (result: BackgroundProcessResult) => void;
|
|
29
|
-
readonly defaultMode?: StudioMode;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Hook configuration
|
|
34
|
-
*/
|
|
35
|
-
export interface UseBackgroundFeatureConfig {
|
|
36
|
-
readonly processRequest: (
|
|
37
|
-
params: ProcessRequestParams
|
|
38
|
-
) => Promise<BackgroundProcessResult>;
|
|
39
|
-
readonly onSelectImage?: () => Promise<string | null>;
|
|
40
|
-
readonly defaultMode?: StudioMode;
|
|
41
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domain Entities Export
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export type {
|
|
6
|
-
BackgroundProcessRequest,
|
|
7
|
-
BackgroundProcessResult,
|
|
8
|
-
BackgroundFeatureState,
|
|
9
|
-
SamplePrompt,
|
|
10
|
-
StudioMode,
|
|
11
|
-
StudioModeConfig,
|
|
12
|
-
ComparisonState,
|
|
13
|
-
} from "./background.types";
|
|
14
|
-
|
|
15
|
-
export type {
|
|
16
|
-
ImagePickerProps,
|
|
17
|
-
PromptInputProps,
|
|
18
|
-
GenerateButtonProps,
|
|
19
|
-
ResultDisplayProps,
|
|
20
|
-
ErrorDisplayProps,
|
|
21
|
-
FeatureHeaderProps,
|
|
22
|
-
ModeSelectorProps,
|
|
23
|
-
ComparisonSliderProps,
|
|
24
|
-
} from "./component.types";
|
|
25
|
-
|
|
26
|
-
export type {
|
|
27
|
-
ProcessRequestParams,
|
|
28
|
-
BackgroundFeatureConfig,
|
|
29
|
-
UseBackgroundFeatureConfig,
|
|
30
|
-
} from "./config.types";
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default Sample Prompts
|
|
3
|
-
* @description Default prompt suggestions for background replacement
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { SamplePrompt } from "../../domain/entities";
|
|
7
|
-
|
|
8
|
-
export const DEFAULT_SAMPLE_PROMPTS: readonly SamplePrompt[] = [
|
|
9
|
-
{ id: "beach", text: "Beach sunset with palm trees" },
|
|
10
|
-
{ id: "office", text: "Modern office with city view" },
|
|
11
|
-
{ id: "mountain", text: "Mountain landscape with snow" },
|
|
12
|
-
{ id: "living-room", text: "Cozy living room interior" },
|
|
13
|
-
{ id: "garden", text: "Japanese garden with cherry blossoms" },
|
|
14
|
-
{ id: "cityscape", text: "Futuristic cityscape at night" },
|
|
15
|
-
] as const;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Background Feature Component
|
|
3
|
-
* @description Main feature component composing all sub-components
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { useCallback, useState } from "react";
|
|
8
|
-
import { ScrollView, StyleSheet } from "react-native";
|
|
9
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
10
|
-
import type {
|
|
11
|
-
BackgroundFeatureConfig,
|
|
12
|
-
SamplePrompt,
|
|
13
|
-
} from "../../domain/entities";
|
|
14
|
-
import { ImagePicker } from "./ImagePicker";
|
|
15
|
-
import { PromptInput } from "./PromptInput";
|
|
16
|
-
import { GenerateButton } from "./GenerateButton";
|
|
17
|
-
import { ResultDisplay } from "./ResultDisplay";
|
|
18
|
-
import { ErrorDisplay } from "./ErrorDisplay";
|
|
19
|
-
import { FeatureHeader } from "./FeatureHeader";
|
|
20
|
-
import { useBackgroundFeature } from "../hooks";
|
|
21
|
-
import type { ImageSourcePropType } from "react-native";
|
|
22
|
-
|
|
23
|
-
export interface BackgroundFeatureProps {
|
|
24
|
-
readonly config: BackgroundFeatureConfig;
|
|
25
|
-
readonly onSelectImage: () => Promise<string | null>;
|
|
26
|
-
readonly heroImage?: ImageSourcePropType;
|
|
27
|
-
readonly description?: string;
|
|
28
|
-
readonly promptLabel?: string;
|
|
29
|
-
readonly promptPlaceholder?: string;
|
|
30
|
-
readonly samplePrompts?: readonly SamplePrompt[];
|
|
31
|
-
readonly samplePromptsLabel?: string;
|
|
32
|
-
readonly generateButtonText?: string;
|
|
33
|
-
readonly saveButtonText?: string;
|
|
34
|
-
readonly resetButtonText?: string;
|
|
35
|
-
readonly processingText?: string;
|
|
36
|
-
readonly placeholderText?: string;
|
|
37
|
-
readonly renderProcessingModal?: (props: { visible: boolean; progress: number; title?: string }) => React.ReactNode;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const BackgroundFeature: React.FC<BackgroundFeatureProps> = ({
|
|
41
|
-
config,
|
|
42
|
-
onSelectImage,
|
|
43
|
-
heroImage,
|
|
44
|
-
description,
|
|
45
|
-
promptLabel,
|
|
46
|
-
promptPlaceholder,
|
|
47
|
-
samplePrompts,
|
|
48
|
-
samplePromptsLabel,
|
|
49
|
-
generateButtonText,
|
|
50
|
-
saveButtonText,
|
|
51
|
-
resetButtonText,
|
|
52
|
-
processingText,
|
|
53
|
-
placeholderText,
|
|
54
|
-
renderProcessingModal,
|
|
55
|
-
}) => {
|
|
56
|
-
const tokens = useAppDesignTokens();
|
|
57
|
-
const [prompt, setPrompt] = useState("");
|
|
58
|
-
|
|
59
|
-
const feature = useBackgroundFeature({
|
|
60
|
-
processRequest: config.onProcess,
|
|
61
|
-
onSelectImage,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const handleProcess = useCallback(async () => {
|
|
65
|
-
await feature.process(prompt);
|
|
66
|
-
if (feature.processedUrl && config.onSuccess) {
|
|
67
|
-
config.onSuccess({
|
|
68
|
-
success: true,
|
|
69
|
-
imageUrl: feature.processedUrl,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}, [feature, prompt, config]);
|
|
73
|
-
|
|
74
|
-
const handleSave = useCallback(async () => {
|
|
75
|
-
if (feature.processedUrl && config.onSave) {
|
|
76
|
-
await config.onSave(feature.processedUrl);
|
|
77
|
-
}
|
|
78
|
-
}, [feature.processedUrl, config]);
|
|
79
|
-
|
|
80
|
-
const handleReset = useCallback(() => {
|
|
81
|
-
feature.reset();
|
|
82
|
-
setPrompt("");
|
|
83
|
-
}, [feature]);
|
|
84
|
-
|
|
85
|
-
const isDisabled = !feature.imageUri || !prompt.trim();
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<>
|
|
89
|
-
<ScrollView
|
|
90
|
-
style={[styles.container, { padding: tokens.spacing.lg }]}
|
|
91
|
-
showsVerticalScrollIndicator={false}
|
|
92
|
-
>
|
|
93
|
-
<FeatureHeader
|
|
94
|
-
heroImage={heroImage}
|
|
95
|
-
description={description}
|
|
96
|
-
/>
|
|
97
|
-
|
|
98
|
-
<ImagePicker
|
|
99
|
-
imageUri={feature.imageUri}
|
|
100
|
-
isProcessing={feature.isProcessing}
|
|
101
|
-
onSelectImage={() => { void feature.selectImage(); }}
|
|
102
|
-
placeholderText={placeholderText}
|
|
103
|
-
/>
|
|
104
|
-
|
|
105
|
-
<PromptInput
|
|
106
|
-
value={prompt}
|
|
107
|
-
onChangeText={setPrompt}
|
|
108
|
-
isProcessing={feature.isProcessing}
|
|
109
|
-
label={promptLabel}
|
|
110
|
-
placeholder={promptPlaceholder}
|
|
111
|
-
samplePrompts={samplePrompts}
|
|
112
|
-
samplePromptsLabel={samplePromptsLabel}
|
|
113
|
-
/>
|
|
114
|
-
|
|
115
|
-
<ErrorDisplay error={feature.error} />
|
|
116
|
-
|
|
117
|
-
<ResultDisplay
|
|
118
|
-
imageUrl={feature.processedUrl}
|
|
119
|
-
isProcessing={feature.isProcessing}
|
|
120
|
-
onSave={() => { void handleSave(); }}
|
|
121
|
-
onReset={handleReset}
|
|
122
|
-
saveButtonText={saveButtonText}
|
|
123
|
-
resetButtonText={resetButtonText}
|
|
124
|
-
/>
|
|
125
|
-
|
|
126
|
-
<GenerateButton
|
|
127
|
-
isDisabled={isDisabled}
|
|
128
|
-
isProcessing={feature.isProcessing}
|
|
129
|
-
onPress={() => { void handleProcess(); }}
|
|
130
|
-
buttonText={generateButtonText}
|
|
131
|
-
/>
|
|
132
|
-
</ScrollView>
|
|
133
|
-
|
|
134
|
-
{renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress, title: processingText })}
|
|
135
|
-
</>
|
|
136
|
-
);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const styles = StyleSheet.create({
|
|
140
|
-
container: {
|
|
141
|
-
flex: 1,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Comparison Slider Component
|
|
3
|
-
* @description Before/After comparison slider for images
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { memo, useState, useRef, useMemo } from "react";
|
|
8
|
-
import { View, StyleSheet, Image, PanResponder } from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
AtomicText,
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
useResponsive,
|
|
13
|
-
} from "@umituz/react-native-design-system";
|
|
14
|
-
import type { ComparisonSliderProps } from "../../domain/entities";
|
|
15
|
-
|
|
16
|
-
export const ComparisonSlider: React.FC<ComparisonSliderProps> = memo(
|
|
17
|
-
function ComparisonSlider({
|
|
18
|
-
originalUri,
|
|
19
|
-
processedUri,
|
|
20
|
-
beforeLabel,
|
|
21
|
-
afterLabel,
|
|
22
|
-
}) {
|
|
23
|
-
const tokens = useAppDesignTokens();
|
|
24
|
-
const { width: screenWidth, horizontalPadding } = useResponsive();
|
|
25
|
-
const [sliderPosition, setSliderPosition] = useState(50);
|
|
26
|
-
const containerWidth = useRef(screenWidth - horizontalPadding * 2);
|
|
27
|
-
|
|
28
|
-
const panResponder = useRef(
|
|
29
|
-
PanResponder.create({
|
|
30
|
-
onStartShouldSetPanResponder: () => true,
|
|
31
|
-
onMoveShouldSetPanResponder: () => true,
|
|
32
|
-
onPanResponderMove: (_, gestureState) => {
|
|
33
|
-
const newPosition =
|
|
34
|
-
((gestureState.moveX - 16) / containerWidth.current) * 100;
|
|
35
|
-
setSliderPosition(Math.max(0, Math.min(100, newPosition)));
|
|
36
|
-
},
|
|
37
|
-
})
|
|
38
|
-
).current;
|
|
39
|
-
|
|
40
|
-
const themedStyles = useMemo(() => StyleSheet.create({
|
|
41
|
-
container: {
|
|
42
|
-
width: "100%",
|
|
43
|
-
aspectRatio: 1,
|
|
44
|
-
borderRadius: 20,
|
|
45
|
-
overflow: "hidden",
|
|
46
|
-
},
|
|
47
|
-
originalContainer: {
|
|
48
|
-
position: "absolute",
|
|
49
|
-
top: 0,
|
|
50
|
-
left: 0,
|
|
51
|
-
bottom: 0,
|
|
52
|
-
overflow: "hidden",
|
|
53
|
-
borderRightWidth: 2,
|
|
54
|
-
borderRightColor: tokens.colors.surface,
|
|
55
|
-
},
|
|
56
|
-
sliderHandle: {
|
|
57
|
-
position: "absolute",
|
|
58
|
-
top: "50%",
|
|
59
|
-
left: -20,
|
|
60
|
-
width: 40,
|
|
61
|
-
height: 40,
|
|
62
|
-
borderRadius: 20,
|
|
63
|
-
justifyContent: "center",
|
|
64
|
-
alignItems: "center",
|
|
65
|
-
marginTop: -20,
|
|
66
|
-
backgroundColor: tokens.colors.backgroundPrimary,
|
|
67
|
-
},
|
|
68
|
-
labelLeft: {
|
|
69
|
-
backgroundColor: tokens.colors.surface,
|
|
70
|
-
},
|
|
71
|
-
labelRight: {
|
|
72
|
-
backgroundColor: tokens.colors.primary,
|
|
73
|
-
}
|
|
74
|
-
}), [tokens]);
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<View
|
|
78
|
-
style={themedStyles.container}
|
|
79
|
-
onLayout={(e) => {
|
|
80
|
-
containerWidth.current = e.nativeEvent.layout.width;
|
|
81
|
-
}}
|
|
82
|
-
>
|
|
83
|
-
<View style={styles.imageContainer}>
|
|
84
|
-
<Image
|
|
85
|
-
source={{ uri: processedUri }}
|
|
86
|
-
style={styles.image}
|
|
87
|
-
resizeMode="cover"
|
|
88
|
-
/>
|
|
89
|
-
|
|
90
|
-
<View
|
|
91
|
-
style={[themedStyles.originalContainer, { width: `${sliderPosition}%` }]}
|
|
92
|
-
>
|
|
93
|
-
<Image
|
|
94
|
-
source={{ uri: originalUri }}
|
|
95
|
-
style={[styles.image, { width: containerWidth.current }]}
|
|
96
|
-
resizeMode="cover"
|
|
97
|
-
/>
|
|
98
|
-
</View>
|
|
99
|
-
|
|
100
|
-
<View
|
|
101
|
-
style={[styles.sliderLine, { left: `${sliderPosition}%` }]}
|
|
102
|
-
{...panResponder.panHandlers}
|
|
103
|
-
>
|
|
104
|
-
<View style={themedStyles.sliderHandle}>
|
|
105
|
-
<View style={styles.handleBars}>
|
|
106
|
-
<View
|
|
107
|
-
style={[
|
|
108
|
-
styles.handleBar,
|
|
109
|
-
{ backgroundColor: tokens.colors.primary },
|
|
110
|
-
]}
|
|
111
|
-
/>
|
|
112
|
-
<View
|
|
113
|
-
style={[
|
|
114
|
-
styles.handleBar,
|
|
115
|
-
{ backgroundColor: tokens.colors.primary },
|
|
116
|
-
]}
|
|
117
|
-
/>
|
|
118
|
-
</View>
|
|
119
|
-
</View>
|
|
120
|
-
</View>
|
|
121
|
-
|
|
122
|
-
{beforeLabel && (
|
|
123
|
-
<View style={[styles.label, styles.labelLeft, themedStyles.labelLeft]}>
|
|
124
|
-
<AtomicText
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
>
|
|
128
|
-
{beforeLabel}
|
|
129
|
-
</AtomicText>
|
|
130
|
-
</View>
|
|
131
|
-
)}
|
|
132
|
-
|
|
133
|
-
{afterLabel && (
|
|
134
|
-
<View style={[styles.label, styles.labelRight, themedStyles.labelRight]}>
|
|
135
|
-
<AtomicText
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
>
|
|
139
|
-
{afterLabel}
|
|
140
|
-
</AtomicText>
|
|
141
|
-
</View>
|
|
142
|
-
)}
|
|
143
|
-
</View>
|
|
144
|
-
</View>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
const styles = StyleSheet.create({
|
|
150
|
-
imageContainer: {
|
|
151
|
-
flex: 1,
|
|
152
|
-
position: "relative",
|
|
153
|
-
},
|
|
154
|
-
image: {
|
|
155
|
-
width: "100%",
|
|
156
|
-
height: "100%",
|
|
157
|
-
},
|
|
158
|
-
sliderLine: {
|
|
159
|
-
position: "absolute",
|
|
160
|
-
top: 0,
|
|
161
|
-
bottom: 0,
|
|
162
|
-
width: 2,
|
|
163
|
-
marginLeft: -1,
|
|
164
|
-
},
|
|
165
|
-
handleBars: {
|
|
166
|
-
flexDirection: "row",
|
|
167
|
-
gap: 4,
|
|
168
|
-
},
|
|
169
|
-
handleBar: {
|
|
170
|
-
width: 3,
|
|
171
|
-
height: 16,
|
|
172
|
-
borderRadius: 2,
|
|
173
|
-
},
|
|
174
|
-
label: {
|
|
175
|
-
position: "absolute",
|
|
176
|
-
top: 12,
|
|
177
|
-
paddingHorizontal: 8,
|
|
178
|
-
paddingVertical: 4,
|
|
179
|
-
borderRadius: 12,
|
|
180
|
-
},
|
|
181
|
-
labelLeft: {
|
|
182
|
-
left: 12,
|
|
183
|
-
},
|
|
184
|
-
labelRight: {
|
|
185
|
-
right: 12,
|
|
186
|
-
},
|
|
187
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error Display Component
|
|
3
|
-
* @description Displays error messages
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { memo } from "react";
|
|
8
|
-
import { View, StyleSheet } from "react-native";
|
|
9
|
-
import {
|
|
10
|
-
AtomicText,
|
|
11
|
-
AtomicIcon,
|
|
12
|
-
useAppDesignTokens,
|
|
13
|
-
} from "@umituz/react-native-design-system";
|
|
14
|
-
import type { ErrorDisplayProps } from "../../domain/entities";
|
|
15
|
-
|
|
16
|
-
export const ErrorDisplay: React.FC<ErrorDisplayProps> = memo(
|
|
17
|
-
function ErrorDisplay({ error }) {
|
|
18
|
-
const tokens = useAppDesignTokens();
|
|
19
|
-
|
|
20
|
-
if (!error) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<View
|
|
26
|
-
style={[
|
|
27
|
-
styles.container,
|
|
28
|
-
{ backgroundColor: tokens.colors.errorContainer },
|
|
29
|
-
]}
|
|
30
|
-
>
|
|
31
|
-
<AtomicIcon
|
|
32
|
-
name="alert-circle"
|
|
33
|
-
size="sm"
|
|
34
|
-
|
|
35
|
-
/>
|
|
36
|
-
<AtomicText
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
style={styles.errorText}
|
|
40
|
-
>
|
|
41
|
-
{error}
|
|
42
|
-
</AtomicText>
|
|
43
|
-
</View>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const styles = StyleSheet.create({
|
|
49
|
-
container: {
|
|
50
|
-
flexDirection: "row",
|
|
51
|
-
alignItems: "center",
|
|
52
|
-
gap: 12,
|
|
53
|
-
padding: 16,
|
|
54
|
-
borderRadius: 12,
|
|
55
|
-
marginVertical: 12,
|
|
56
|
-
},
|
|
57
|
-
errorText: {
|
|
58
|
-
flex: 1,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feature Header Component
|
|
3
|
-
* @description Header with hero image and description
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { memo } from "react";
|
|
7
|
-
import { View, StyleSheet, Image, Dimensions } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
useAppDesignTokens,
|
|
11
|
-
} from "@umituz/react-native-design-system";
|
|
12
|
-
import type { FeatureHeaderProps } from "../../domain/entities";
|
|
13
|
-
|
|
14
|
-
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
|
15
|
-
const IMAGE_WIDTH = SCREEN_WIDTH - 32;
|
|
16
|
-
const IMAGE_HEIGHT = IMAGE_WIDTH * 0.5;
|
|
17
|
-
|
|
18
|
-
export const FeatureHeader: React.FC<FeatureHeaderProps> = memo(
|
|
19
|
-
function FeatureHeader({ heroImage, description }) {
|
|
20
|
-
const tokens = useAppDesignTokens();
|
|
21
|
-
|
|
22
|
-
if (!heroImage && !description) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<View style={styles.container}>
|
|
28
|
-
{heroImage && (
|
|
29
|
-
<View
|
|
30
|
-
style={[
|
|
31
|
-
styles.imageContainer,
|
|
32
|
-
{ backgroundColor: tokens.colors.surface },
|
|
33
|
-
]}
|
|
34
|
-
>
|
|
35
|
-
<Image
|
|
36
|
-
source={heroImage}
|
|
37
|
-
style={[
|
|
38
|
-
styles.heroImage,
|
|
39
|
-
{ width: IMAGE_WIDTH, height: IMAGE_HEIGHT },
|
|
40
|
-
]}
|
|
41
|
-
resizeMode="cover"
|
|
42
|
-
/>
|
|
43
|
-
</View>
|
|
44
|
-
)}
|
|
45
|
-
{description && (
|
|
46
|
-
<AtomicText
|
|
47
|
-
|
|
48
|
-
style={[
|
|
49
|
-
styles.description,
|
|
50
|
-
{
|
|
51
|
-
color: tokens.colors.textSecondary,
|
|
52
|
-
marginTop: tokens.spacing.md,
|
|
53
|
-
},
|
|
54
|
-
]}
|
|
55
|
-
>
|
|
56
|
-
{description}
|
|
57
|
-
</AtomicText>
|
|
58
|
-
)}
|
|
59
|
-
</View>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const styles = StyleSheet.create({
|
|
65
|
-
container: {
|
|
66
|
-
marginBottom: 8,
|
|
67
|
-
},
|
|
68
|
-
imageContainer: {
|
|
69
|
-
borderRadius: 16,
|
|
70
|
-
overflow: "hidden",
|
|
71
|
-
},
|
|
72
|
-
heroImage: {
|
|
73
|
-
borderRadius: 16,
|
|
74
|
-
},
|
|
75
|
-
description: {
|
|
76
|
-
textAlign: "center",
|
|
77
|
-
lineHeight: 22,
|
|
78
|
-
paddingHorizontal: 8,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate Button Component
|
|
3
|
-
* @description Action button to trigger processing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { memo } from "react";
|
|
7
|
-
import { StyleSheet, TouchableOpacity } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
AtomicIcon,
|
|
11
|
-
AtomicSpinner,
|
|
12
|
-
useAppDesignTokens,
|
|
13
|
-
} from "@umituz/react-native-design-system";
|
|
14
|
-
import type { GenerateButtonProps } from "../../domain/entities";
|
|
15
|
-
|
|
16
|
-
export const GenerateButton: React.FC<GenerateButtonProps> = memo(
|
|
17
|
-
function GenerateButton({
|
|
18
|
-
isDisabled,
|
|
19
|
-
isProcessing,
|
|
20
|
-
onPress,
|
|
21
|
-
buttonText,
|
|
22
|
-
}) {
|
|
23
|
-
const tokens = useAppDesignTokens();
|
|
24
|
-
|
|
25
|
-
const disabled = isDisabled || isProcessing;
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<TouchableOpacity
|
|
29
|
-
onPress={onPress}
|
|
30
|
-
disabled={disabled}
|
|
31
|
-
activeOpacity={0.8}
|
|
32
|
-
style={[
|
|
33
|
-
styles.container,
|
|
34
|
-
{
|
|
35
|
-
backgroundColor: disabled
|
|
36
|
-
? tokens.colors.surfaceSecondary
|
|
37
|
-
: tokens.colors.primary,
|
|
38
|
-
},
|
|
39
|
-
]}
|
|
40
|
-
>
|
|
41
|
-
{isProcessing ? (
|
|
42
|
-
<AtomicSpinner size="sm" color={tokens.colors.backgroundPrimary} />
|
|
43
|
-
) : (
|
|
44
|
-
<>
|
|
45
|
-
<AtomicIcon
|
|
46
|
-
name="sparkles"
|
|
47
|
-
size="md"
|
|
48
|
-
color={disabled ? "surfaceVariant" : "onPrimary"}
|
|
49
|
-
/>
|
|
50
|
-
<AtomicText
|
|
51
|
-
style={[
|
|
52
|
-
styles.text,
|
|
53
|
-
{
|
|
54
|
-
color: disabled
|
|
55
|
-
? tokens.colors.textTertiary
|
|
56
|
-
: tokens.colors.backgroundPrimary,
|
|
57
|
-
},
|
|
58
|
-
]}
|
|
59
|
-
>
|
|
60
|
-
{buttonText}
|
|
61
|
-
</AtomicText>
|
|
62
|
-
</>
|
|
63
|
-
)}
|
|
64
|
-
</TouchableOpacity>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const styles = StyleSheet.create({
|
|
70
|
-
container: {
|
|
71
|
-
marginVertical: 24,
|
|
72
|
-
borderRadius: 28,
|
|
73
|
-
flexDirection: "row",
|
|
74
|
-
alignItems: "center",
|
|
75
|
-
justifyContent: "center",
|
|
76
|
-
paddingVertical: 16,
|
|
77
|
-
paddingHorizontal: 32,
|
|
78
|
-
},
|
|
79
|
-
icon: {
|
|
80
|
-
marginRight: 8,
|
|
81
|
-
},
|
|
82
|
-
text: {
|
|
83
|
-
fontWeight: "bold",
|
|
84
|
-
},
|
|
85
|
-
});
|