@umituz/react-native-ai-generation-content 1.17.247 → 1.17.249

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.247",
3
+ "version": "1.17.249",
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
- * Simplified placeholder for Phase 1
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
- return (
29
- <View style={s.container}>
30
- <View
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
- <AtomicText
49
- type="headlineSmall"
50
- style={[s.title, { color: tokens.colors.textPrimary }]}
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.subtitle, { color: tokens.colors.textSecondary }]}
52
+ style={[s.title, { color: tokens.colors.textPrimary }]}
58
53
  >
59
- {t(`${translationPrefix}.comingSoon`)}
54
+ {t(`${translationPrefix}.selectStyle`)}
60
55
  </AtomicText>
61
56
 
62
- <View
63
- style={[
64
- s.badge,
65
- { backgroundColor: `${tokens.colors.primary}15` },
66
- ]}
67
- >
68
- <AtomicText
69
- type="labelSmall"
70
- style={{ color: tokens.colors.primary, fontWeight: "600" }}
71
- >
72
- {t("common.comingSoon")}
73
- </AtomicText>
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
- </View>
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, padding: 16, justifyContent: "center" },
82
- card: { padding: 24, borderRadius: 16, borderWidth: 2, alignItems: "center" },
83
- iconContainer: { width: 80, height: 80, borderRadius: 40, alignItems: "center", justifyContent: "center", marginBottom: 16 },
84
- title: { fontWeight: "600", marginBottom: 8, textAlign: "center" },
85
- subtitle: { marginBottom: 16, textAlign: "center" },
86
- badge: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8 },
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
+ export 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
+ });