@umituz/react-native-ai-generation-content 1.17.247 → 1.17.248
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/couple-future/index.ts +2 -0
- package/src/features/couple-future/infrastructure/couplePromptEnhancer.ts +16 -0
- package/src/features/couple-future/presentation/components/WardrobeSelector.tsx +102 -48
- package/src/features/couple-future/presentation/screens/CoupleFeatureScreen.tsx +125 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.248",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -28,6 +28,8 @@ export type {
|
|
|
28
28
|
ArtistStyleSelectorProps,
|
|
29
29
|
WardrobeSelectorProps,
|
|
30
30
|
} from "./presentation/components";
|
|
31
|
+
export { CoupleFeatureScreen } from "./presentation/screens/CoupleFeatureScreen";
|
|
32
|
+
export type { CoupleFeatureScreenProps } from "./presentation/screens/CoupleFeatureScreen";
|
|
31
33
|
export {
|
|
32
34
|
COUPLE_FEATURE_CONFIGS,
|
|
33
35
|
ROMANTIC_MOOD_OPTIONS,
|
|
@@ -31,6 +31,13 @@ const ARTIST_STYLE_DESCRIPTIONS: Record<string, string> = {
|
|
|
31
31
|
daVinci: "Leonardo da Vinci's renaissance mastery with subtle sfumato and perfect proportion",
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
const WARDROBE_STYLE_DESCRIPTIONS: Record<string, string> = {
|
|
35
|
+
casual: "casual comfortable outfit with relaxed fit",
|
|
36
|
+
formal: "elegant formal attire with sophisticated style",
|
|
37
|
+
sporty: "athletic sporty wear with dynamic energy",
|
|
38
|
+
elegant: "refined elegant clothing with graceful details",
|
|
39
|
+
};
|
|
40
|
+
|
|
34
41
|
const getIntensityLabel = (intensity: number): string => {
|
|
35
42
|
if (intensity >= 75) return "strong";
|
|
36
43
|
if (intensity >= 50) return "moderate";
|
|
@@ -80,5 +87,14 @@ export const enhanceCouplePrompt = (
|
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
if (selections.wardrobeStyle) {
|
|
91
|
+
const wardrobeDescription = WARDROBE_STYLE_DESCRIPTIONS[selections.wardrobeStyle];
|
|
92
|
+
if (wardrobeDescription) {
|
|
93
|
+
const intensity = selections.wardrobeIntensity || 70;
|
|
94
|
+
const label = getIntensityLabel(intensity);
|
|
95
|
+
enhanced += `. Dress them in ${label} ${wardrobeDescription}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
return enhanced;
|
|
84
100
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wardrobe Selector Component
|
|
3
|
-
*
|
|
3
|
+
* Wardrobe style selector with intensity control
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View, StyleSheet } from "react-native";
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet, ScrollView } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
AtomicText,
|
|
10
10
|
AtomicIcon,
|
|
@@ -12,6 +12,13 @@ import {
|
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
13
|
import type { CoupleFeatureSelection } from "../../domain/types";
|
|
14
14
|
|
|
15
|
+
const WARDROBE_STYLES = [
|
|
16
|
+
{ id: "casual", iconKey: "shirt-outline" },
|
|
17
|
+
{ id: "formal", iconKey: "briefcase-outline" },
|
|
18
|
+
{ id: "sporty", iconKey: "fitness-outline" },
|
|
19
|
+
{ id: "elegant", iconKey: "star-outline" },
|
|
20
|
+
];
|
|
21
|
+
|
|
15
22
|
export interface WardrobeSelectorProps {
|
|
16
23
|
selection: CoupleFeatureSelection;
|
|
17
24
|
onSelectionChange: (selection: CoupleFeatureSelection) => void;
|
|
@@ -20,68 +27,115 @@ export interface WardrobeSelectorProps {
|
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
export const WardrobeSelector: React.FC<WardrobeSelectorProps> = ({
|
|
30
|
+
selection,
|
|
31
|
+
onSelectionChange,
|
|
23
32
|
translationPrefix,
|
|
24
33
|
t,
|
|
25
34
|
}) => {
|
|
26
35
|
const tokens = useAppDesignTokens();
|
|
36
|
+
const selectedStyle = selection.wardrobeStyle;
|
|
37
|
+
const intensity = selection.wardrobeIntensity || 70;
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
style={[
|
|
32
|
-
s.card,
|
|
33
|
-
{
|
|
34
|
-
backgroundColor: tokens.colors.surface,
|
|
35
|
-
borderColor: tokens.colors.borderLight,
|
|
36
|
-
},
|
|
37
|
-
]}
|
|
38
|
-
>
|
|
39
|
-
<View
|
|
40
|
-
style={[
|
|
41
|
-
s.iconContainer,
|
|
42
|
-
{ backgroundColor: tokens.colors.surfaceVariant },
|
|
43
|
-
]}
|
|
44
|
-
>
|
|
45
|
-
<AtomicIcon name="checkroom" size="xl" color="textSecondary" />
|
|
46
|
-
</View>
|
|
39
|
+
const handleStyleSelect = (styleId: string) => {
|
|
40
|
+
onSelectionChange({ ...selection, wardrobeStyle: styleId });
|
|
41
|
+
};
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
>
|
|
52
|
-
{t(`${translationPrefix}.title`)}
|
|
53
|
-
</AtomicText>
|
|
43
|
+
const handleIntensityChange = (value: number) => {
|
|
44
|
+
onSelectionChange({ ...selection, wardrobeIntensity: value });
|
|
45
|
+
};
|
|
54
46
|
|
|
47
|
+
return (
|
|
48
|
+
<ScrollView style={s.container}>
|
|
49
|
+
<View style={s.section}>
|
|
55
50
|
<AtomicText
|
|
56
51
|
type="bodyMedium"
|
|
57
|
-
style={[s.
|
|
52
|
+
style={[s.title, { color: tokens.colors.textPrimary }]}
|
|
58
53
|
>
|
|
59
|
-
{t(`${translationPrefix}.
|
|
54
|
+
{t(`${translationPrefix}.selectStyle`)}
|
|
60
55
|
</AtomicText>
|
|
61
56
|
|
|
62
|
-
<View
|
|
63
|
-
style
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
<View style={s.grid}>
|
|
58
|
+
{WARDROBE_STYLES.map((style) => {
|
|
59
|
+
const isSelected = selectedStyle === style.id;
|
|
60
|
+
return (
|
|
61
|
+
<TouchableOpacity
|
|
62
|
+
key={style.id}
|
|
63
|
+
style={[
|
|
64
|
+
s.card,
|
|
65
|
+
{
|
|
66
|
+
backgroundColor: isSelected
|
|
67
|
+
? `${tokens.colors.primary}15`
|
|
68
|
+
: tokens.colors.surface,
|
|
69
|
+
borderColor: isSelected
|
|
70
|
+
? tokens.colors.primary
|
|
71
|
+
: tokens.colors.borderLight,
|
|
72
|
+
},
|
|
73
|
+
]}
|
|
74
|
+
onPress={() => handleStyleSelect(style.id)}
|
|
75
|
+
>
|
|
76
|
+
<AtomicIcon
|
|
77
|
+
name={style.iconKey as never}
|
|
78
|
+
size="lg"
|
|
79
|
+
color={isSelected ? "primary" : "textSecondary"}
|
|
80
|
+
/>
|
|
81
|
+
<AtomicText
|
|
82
|
+
type="labelMedium"
|
|
83
|
+
style={[
|
|
84
|
+
s.label,
|
|
85
|
+
{
|
|
86
|
+
color: isSelected
|
|
87
|
+
? tokens.colors.primary
|
|
88
|
+
: tokens.colors.textSecondary,
|
|
89
|
+
fontWeight: isSelected ? "600" : "400",
|
|
90
|
+
},
|
|
91
|
+
]}
|
|
92
|
+
>
|
|
93
|
+
{t(`${translationPrefix}.styles.${style.id}`)}
|
|
94
|
+
</AtomicText>
|
|
95
|
+
</TouchableOpacity>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
74
98
|
</View>
|
|
75
99
|
</View>
|
|
76
|
-
|
|
100
|
+
|
|
101
|
+
{selectedStyle && (
|
|
102
|
+
<View style={s.section}>
|
|
103
|
+
<View style={s.header}>
|
|
104
|
+
<AtomicText type="bodyMedium" style={[s.title, { color: tokens.colors.textPrimary }]}>
|
|
105
|
+
{t(`${translationPrefix}.intensity`)}
|
|
106
|
+
</AtomicText>
|
|
107
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.primary, fontWeight: "600" }}>
|
|
108
|
+
{intensity}%
|
|
109
|
+
</AtomicText>
|
|
110
|
+
</View>
|
|
111
|
+
|
|
112
|
+
<View style={s.slider}>
|
|
113
|
+
{[25, 50, 75, 100].map((value) => (
|
|
114
|
+
<TouchableOpacity
|
|
115
|
+
key={value}
|
|
116
|
+
style={[s.button, { backgroundColor: intensity === value ? tokens.colors.primary : tokens.colors.surface, borderColor: intensity === value ? tokens.colors.primary : tokens.colors.borderLight }]}
|
|
117
|
+
onPress={() => handleIntensityChange(value)}
|
|
118
|
+
>
|
|
119
|
+
<AtomicText type="labelSmall" style={{ color: intensity === value ? tokens.colors.onPrimary : tokens.colors.textSecondary }}>
|
|
120
|
+
{value}%
|
|
121
|
+
</AtomicText>
|
|
122
|
+
</TouchableOpacity>
|
|
123
|
+
))}
|
|
124
|
+
</View>
|
|
125
|
+
</View>
|
|
126
|
+
)}
|
|
127
|
+
</ScrollView>
|
|
77
128
|
);
|
|
78
129
|
};
|
|
79
130
|
|
|
80
131
|
const s = StyleSheet.create({
|
|
81
|
-
container: { flex: 1
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
132
|
+
container: { flex: 1 },
|
|
133
|
+
section: { padding: 16, marginBottom: 8 },
|
|
134
|
+
title: { fontWeight: "600", marginBottom: 12 },
|
|
135
|
+
grid: { flexDirection: "row", flexWrap: "wrap", gap: 12 },
|
|
136
|
+
card: { width: "47%", padding: 16, borderRadius: 12, borderWidth: 2, alignItems: "center", justifyContent: "center" },
|
|
137
|
+
label: { marginTop: 8, textAlign: "center" },
|
|
138
|
+
header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
|
|
139
|
+
slider: { flexDirection: "row", gap: 8, marginTop: 8 },
|
|
140
|
+
button: { flex: 1, padding: 12, borderRadius: 8, borderWidth: 2, alignItems: "center" },
|
|
87
141
|
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Couple Feature Screen
|
|
3
|
+
* Generic screen for couple feature selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet, ScrollView } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicButton,
|
|
10
|
+
AtomicText,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import type { CoupleFeatureId, CoupleFeatureSelection } from "../../domain/types";
|
|
14
|
+
import { COUPLE_FEATURE_CONFIGS } from "../../infrastructure/coupleFeatureRegistry";
|
|
15
|
+
import { RomanticMoodSelector } from "../components/RomanticMoodSelector";
|
|
16
|
+
import { ArtStyleSelector } from "../components/ArtStyleSelector";
|
|
17
|
+
import { ArtistStyleSelector } from "../components/ArtistStyleSelector";
|
|
18
|
+
import { WardrobeSelector } from "../components/WardrobeSelector";
|
|
19
|
+
|
|
20
|
+
interface CoupleFeatureScreenProps {
|
|
21
|
+
featureId: CoupleFeatureId;
|
|
22
|
+
selection: CoupleFeatureSelection;
|
|
23
|
+
onSelectionChange: (selection: CoupleFeatureSelection) => void;
|
|
24
|
+
onContinue: () => void;
|
|
25
|
+
onBack: () => void;
|
|
26
|
+
t: (key: string) => string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const FeatureComponentMap = {
|
|
30
|
+
"romantic-mood": RomanticMoodSelector,
|
|
31
|
+
"art-style": ArtStyleSelector,
|
|
32
|
+
"artist-style": ArtistStyleSelector,
|
|
33
|
+
"wardrobe": WardrobeSelector,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const CoupleFeatureScreen: React.FC<CoupleFeatureScreenProps> = ({
|
|
37
|
+
featureId,
|
|
38
|
+
selection,
|
|
39
|
+
onSelectionChange,
|
|
40
|
+
onContinue,
|
|
41
|
+
onBack,
|
|
42
|
+
t,
|
|
43
|
+
}) => {
|
|
44
|
+
const tokens = useAppDesignTokens();
|
|
45
|
+
const config = COUPLE_FEATURE_CONFIGS[featureId];
|
|
46
|
+
|
|
47
|
+
if (!config) return null;
|
|
48
|
+
|
|
49
|
+
const FeatureComponent = FeatureComponentMap[featureId];
|
|
50
|
+
if (!FeatureComponent) return null;
|
|
51
|
+
|
|
52
|
+
const selectorProps = {
|
|
53
|
+
selection,
|
|
54
|
+
onSelectionChange,
|
|
55
|
+
translationPrefix: config.translationPrefix,
|
|
56
|
+
t,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View
|
|
61
|
+
style={[
|
|
62
|
+
styles.container,
|
|
63
|
+
{ backgroundColor: tokens.colors.backgroundPrimary },
|
|
64
|
+
]}
|
|
65
|
+
>
|
|
66
|
+
<View style={styles.header}>
|
|
67
|
+
<AtomicText
|
|
68
|
+
type="headlineMedium"
|
|
69
|
+
style={{ color: tokens.colors.textPrimary }}
|
|
70
|
+
>
|
|
71
|
+
{t(`${config.translationPrefix}.title`)}
|
|
72
|
+
</AtomicText>
|
|
73
|
+
</View>
|
|
74
|
+
|
|
75
|
+
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
|
76
|
+
<FeatureComponent {...selectorProps} />
|
|
77
|
+
</ScrollView>
|
|
78
|
+
|
|
79
|
+
<View
|
|
80
|
+
style={[
|
|
81
|
+
styles.footer,
|
|
82
|
+
{ borderTopColor: tokens.colors.borderLight },
|
|
83
|
+
]}
|
|
84
|
+
>
|
|
85
|
+
<AtomicButton
|
|
86
|
+
title={t("common.back")}
|
|
87
|
+
onPress={onBack}
|
|
88
|
+
variant="secondary"
|
|
89
|
+
style={styles.backButton}
|
|
90
|
+
/>
|
|
91
|
+
<AtomicButton
|
|
92
|
+
title={t("common.continue")}
|
|
93
|
+
onPress={onContinue}
|
|
94
|
+
variant="primary"
|
|
95
|
+
style={styles.continueButton}
|
|
96
|
+
/>
|
|
97
|
+
</View>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const styles = StyleSheet.create({
|
|
103
|
+
container: {
|
|
104
|
+
flex: 1,
|
|
105
|
+
},
|
|
106
|
+
header: {
|
|
107
|
+
padding: 16,
|
|
108
|
+
paddingTop: 20,
|
|
109
|
+
},
|
|
110
|
+
content: {
|
|
111
|
+
flex: 1,
|
|
112
|
+
},
|
|
113
|
+
footer: {
|
|
114
|
+
flexDirection: "row",
|
|
115
|
+
padding: 16,
|
|
116
|
+
borderTopWidth: 1,
|
|
117
|
+
gap: 12,
|
|
118
|
+
},
|
|
119
|
+
backButton: {
|
|
120
|
+
flex: 1,
|
|
121
|
+
},
|
|
122
|
+
continueButton: {
|
|
123
|
+
flex: 2,
|
|
124
|
+
},
|
|
125
|
+
});
|