frontend: flesh mood components screen
Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago
6b55ce5b08c2cf596a6f02f8e37e459809efd41d Browse filesParents
2b711efChanged files
1
Additions
+245
Deletions
0
/dev/null → frontend/app/entry/mood-components.tsx
1 +
import React from 'react';
2 +
import {
3 +
View,
4 +
Text,
5 +
ScrollView,
6 +
Pressable,
7 +
StyleSheet,
8 +
} from 'react-native';
9 +
import { router } from 'expo-router';
10 +
import { Ionicons } from '@expo/vector-icons';
11 +
12 +
import { ScaleSlider } from '@/components/ui/ScaleSlider';
13 +
import { Row, Grid } from '@/components/ui/LayoutHelpers';
14 +
import { Section, SectionHeader } from '@/components/ui/Sections';
15 +
import { Button } from '@/components/ui/Button';
16 +
import { Card } from '@/components/ui/Cards';
17 +
import { Theme } from '@/constants/theme';
18 +
import {
19 +
MOOD_COMPONENTS,
20 +
getMoodComponent,
21 +
intensityLabel,
22 +
} from '@/constants/mood-components';
23 +
24 +
import {
25 +
useMoodEntryStore,
26 +
} from '@/stores/moodEntry';
27 +
28 +
export default function MoodComponentsScreen() {
29 +
const {
30 +
selectedMood,
31 +
components,
32 +
addComponent,
33 +
removeComponent,
34 +
setComponentIntensity,
35 +
} = useMoodEntryStore();
36 +
37 +
const activeIds = components.map((c) => c.id);
38 +
const available = MOOD_COMPONENTS.filter((d) => !activeIds.includes(d.id));
39 +
40 +
const handleDone = () => {
41 +
router.back();
42 +
};
43 +
44 +
return (
45 +
<View style={styles.container}>
46 +
47 +
{/* Header */}
48 +
<View style={styles.header}>
49 +
<Pressable onPress={() => router.back()} hitSlop={8}>
50 +
<Ionicons name="close" size={24} color={Theme.colors.text} />
51 +
</Pressable>
52 +
<Text style={styles.headerTitle}>Editar Componentes</Text>
53 +
<Pressable onPress={handleDone} hitSlop={8}>
54 +
<Text style={styles.headerAction}>Pronto</Text>
55 +
</Pressable>
56 +
</View>
57 +
58 +
<ScrollView
59 +
contentContainerStyle={styles.content}
60 +
showsVerticalScrollIndicator={false}
61 +
>
62 +
63 +
{/* Subtitle */}
64 +
{selectedMood && (
65 +
<View style={styles.subtitleBlock}>
66 +
<Text style={styles.subtitleSmall}>Baseado no seu humor selecionado</Text>
67 +
<Text style={styles.subtitleLarge}>{selectedMood.label}</Text>
68 +
</View>
69 +
)}
70 +
71 +
{/* Active components */}
72 +
{components.length > 0 && (
73 +
<Section>
74 +
<SectionHeader
75 +
title="Intensidade dos Componentes"
76 +
info={`Ativos: ${components.length}`} />
77 +
78 +
{components.map((comp) => {
79 +
const def = getMoodComponent(comp.id);
80 +
if (!def) return null;
81 +
82 +
return (
83 +
<Card key={comp.id} style={styles.activeCard}>
84 +
{/* Card header */}
85 +
<Row style={styles.activeCardHeader}>
86 +
<View style={[styles.dot, { backgroundColor: def.color }]} />
87 +
<Text style={styles.activeCardLabel}>{def.label}</Text>
88 +
<Pressable
89 +
onPress={() => removeComponent(comp.id)}
90 +
hitSlop={8}
91 +
>
92 +
<Ionicons
93 +
name="remove-circle-outline"
94 +
size={22}
95 +
color="#F87171"
96 +
/>
97 +
</Pressable>
98 +
</Row>
99 +
100 +
{/* Slider */}
101 +
<ScaleSlider
102 +
variant="compact"
103 +
label="Intensidade"
104 +
value={comp.intensity}
105 +
onValueChange={(v) => setComponentIntensity(comp.id, v)}
106 +
min={1}
107 +
max={10}
108 +
step={1}
109 +
minLabel="Suave"
110 +
maxLabel="Intensa"
111 +
style={styles.slider}
112 +
/>
113 +
</Card>
114 +
);
115 +
})}
116 +
117 +
</Section>
118 +
)}
119 +
120 +
{/* Available to add */}
121 +
{available.length > 0 && (
122 +
<Section>
123 +
<SectionHeader title="Adicionar outros" />
124 +
<Grid gap={2}>
125 +
{available.map((def) => (
126 +
<Card key={def.id} style={styles.availableItem}>
127 +
<Pressable
128 +
onPress={() => addComponent(def.id)}
129 +
style={styles.availableItemInternalWrapper}
130 +
>
131 +
<Row gap={8} style={styles.availableInner}>
132 +
<View style={[styles.dot, { backgroundColor: def.color }]} />
133 +
<Text style={styles.availableLabel}>{def.label}</Text>
134 +
</Row>
135 +
136 +
<Ionicons
137 +
name="add-circle-outline"
138 +
size={20}
139 +
color={Theme.colors.light.smallAddButtonIcon}
140 +
/>
141 +
</Pressable>
142 +
</Card>
143 +
))}
144 +
</Grid>
145 +
</Section>
146 +
)}
147 +
</ScrollView>
148 +
</View>
149 +
);
150 +
}
151 +
152 +
const styles = StyleSheet.create({
153 +
container: {
154 +
flex: 1,
155 +
},
156 +
header: {
157 +
flexDirection: 'row',
158 +
justifyContent: 'space-between',
159 +
alignItems: 'center',
160 +
paddingHorizontal: Theme.spacing.containerPadding,
161 +
paddingVertical: 16,
162 +
borderBottomWidth: 1,
163 +
borderBottomColor: Theme.colors.light.divider,
164 +
},
165 +
headerTitle: {
166 +
fontSize: Theme.typography.bodyLg.fontSize,
167 +
fontWeight: '700',
168 +
color: Theme.colors.light.text,
169 +
},
170 +
headerAction: {
171 +
fontSize: Theme.typography.bodyMd.fontSize,
172 +
fontWeight: '600',
173 +
color: Theme.colors.light.tint,
174 +
},
175 +
content: {
176 +
padding: Theme.spacing.containerPadding,
177 +
gap: Theme.spacing.sectionGap,
178 +
paddingBottom: 120,
179 +
},
180 +
subtitleBlock: {
181 +
alignItems: 'center',
182 +
gap: 4,
183 +
},
184 +
subtitleSmall: {
185 +
fontSize: Theme.typography.bodyMd.fontSize,
186 +
color: Theme.colors.light.textSecondary,
187 +
},
188 +
subtitleLarge: {
189 +
fontSize: 28,
190 +
fontWeight: '700',
191 +
color: Theme.colors.light.text,
192 +
},
193 +
section: {
194 +
gap: Theme.spacing.cardGap,
195 +
},
196 +
sectionHeader: {
197 +
justifyContent: 'space-between',
198 +
alignItems: 'center',
199 +
},
200 +
activeCard: {
201 +
padding: Theme.spacing.cardGap,
202 +
},
203 +
activeCardHeader: {
204 +
alignItems: 'center',
205 +
justifyContent: 'space-between',
206 +
marginBottom: 16
207 +
},
208 +
activeCardLabel: {
209 +
flex: 1,
210 +
... { ...Theme.typography.bodyLg, lineHeight: 24 },
211 +
color: Theme.colors.light.text,
212 +
marginLeft: 4,
213 +
},
214 +
dot: {
215 +
width: 12,
216 +
height: 12,
217 +
borderRadius: 6,
218 +
flexShrink: 0,
219 +
},
220 +
slider: {
221 +
marginTop: 4,
222 +
},
223 +
availableItem: {
224 +
flexDirection: 'row',
225 +
justifyContent: 'space-between',
226 +
alignItems: 'center',
227 +
paddingHorizontal: 12,
228 +
paddingVertical: 10,
229 +
},
230 +
availableItemInternalWrapper: {
231 +
flexDirection: 'row',
232 +
width: '100%',
233 +
justifyContent: 'space-between',
234 +
alignItems: 'center',
235 +
},
236 +
availableInner: {
237 +
alignItems: 'center',
238 +
flexShrink: 1,
239 +
},
240 +
availableLabel: {
241 +
fontSize: Theme.typography.bodyMd.fontSize,
242 +
fontWeight: '500',
243 +
color: Theme.colors.light.text,
244 +
}
245 +
});