eletrotupi / tcc/ commit / 3dd5c9c

frontend: finally add sliders for mood entry

Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago 3dd5c9c336e53556c29b79567dbbc8913667907e Browse files
Parents 0329ddc
Changed files
2
Additions
+213
Deletions
-1
frontend/app/(tabs)/new/entry.tsx
1 1
import { useState, useEffect } from 'react';
2 2
import {
3 3
  Link, router, useLocalSearchParams
4 4
} from 'expo-router';
5 5

19 19
import { Card } from '@/components/ui/Cards';
20 20
import { Button } from '@/components/ui/Button';
21 21
import { Input, TextArea } from '@/components/ui/Input';
22 22
import { Section, SectionHeader } from '@/components/ui/Sections';
23 23
import { Spacing, Typography, Colors } from '@/constants/theme';
24 +
import { ScaleSlider } from '@/components/ui/ScaleSlider';
25 +
import { Ionicons } from '@expo/vector-icons';
26 +
import { useThemeColor } from '@/hooks/use-theme-color';
24 27

25 28
export default function NewMoodEntry() {
26 29
  const { user } = useAuth();
30 +
  const tintColor = useThemeColor({}, 'tint');
31 +

27 32
  // TODO: transform this into a specific mood component
28 33
  const { initialMood } = useLocalSearchParams();
34 +
  const [stress, setStress] = useState(0);
35 +
  const [anxiety, setAnxiety] = useState(0);
36 +
  const [energy, setEnergy] = useState(0);
29 37

30 38
  const editComponents = () => {
31 39
    console.log("Wants to open the components");
32 40
  }
33 41

45 53
              Muito Bem
46 54
            </ThemedText>
47 55
          </Center>
48 56

49 57
          <Card style={styles.resumeCard}>
58 +
            <Col gap={24}>
59 +
              <ScaleSlider
60 +
                variant="rich"
61 +
                label="Energia"
62 +
                value={energy}
63 +
                onValueChange={setEnergy}
64 +
                icon={<Ionicons name="flower-outline" size={20} color={tintColor} />}
65 +
                minLabel="Baixa"
66 +
                maxLabel="Alta"
67 +
              />
68 +

69 +
              <ScaleSlider
70 +
                variant="rich"
71 +
                label="Ansiedade"
72 +
                value={anxiety}
73 +
                onValueChange={setAnxiety}
74 +
                icon={<Ionicons name="sad-outline" size={20} color="#FB923C" />}
75 +
                minLabel="Calmo"
76 +
                maxLabel="Ansioso"
77 +
              />
78 +

79 +
              <ScaleSlider
80 +
                variant="rich"
81 +
                label="Estresse"
82 +
                value={stress}
83 +
                onValueChange={setStress}
84 +
                icon={<Ionicons name="flame" size={20} color="#f87171" />}
85 +
                minLabel="Relaxado"
86 +
                maxLabel="Estressado"
87 +
              />
88 +
            </Col>
50 89
          </Card>
51 90

52 91
          <Card style={styles.annotationsCard}>
53 92
            <TextArea
54 93
              label="Notas sobre o dia"
94 133
  },
95 134
  annotationsCard: {
96 135
    padding: Spacing.cardGap
97 136
  },
98 137
  resumeCard: {
99 -
    height: 361
138 +
    padding: Spacing.cardGap,
100 139
  }
101 140
});
/dev/null → frontend/components/ui/ScaleSlider.tsx
1 +
import React, { useState } from 'react';
2 +
import {
3 +
  View,
4 +
  Text,
5 +
  StyleSheet,
6 +
  ViewStyle,
7 +
} from 'react-native';
8 +
import Slider from '@react-native-community/slider';
9 +
import { useThemeColor } from '@/hooks/use-theme-color';
10 +
import { Theme } from '@/constants/theme';
11 +

12 +
type SliderVariant = 'rich' | 'compact';
13 +

14 +
interface ScaleSliderProps {
15 +
  label: string;
16 +
  value: number;
17 +
  onValueChange: (value: number) => void;
18 +
  min?: number;
19 +
  max?: number;
20 +
  step?: number;
21 +
  minLabel?: string;
22 +
  maxLabel?: string;
23 +
  icon?: React.ReactNode;      // rich variant only
24 +
  showMax?: boolean;           // shows "x/10" format instead of just "x"
25 +
  variant?: SliderVariant;
26 +
  style?: ViewStyle;
27 +
}
28 +

29 +
export const ScaleSlider: React.FC<ScaleSliderProps> = ({
30 +
  label,
31 +
  value,
32 +
  onValueChange,
33 +
  min = 0,
34 +
  max = 10,
35 +
  step = 1,
36 +
  minLabel,
37 +
  maxLabel,
38 +
  icon,
39 +
  showMax,
40 +
  variant = 'compact',
41 +
  style,
42 +
}) => {
43 +
  const textColor = useThemeColor({}, 'text');
44 +
  const secondaryColor = useThemeColor({}, 'textSecondary');
45 +
  const tintColor = useThemeColor({}, 'tint');
46 +
  const trackingColor = useThemeColor({}, 'sliderTracking');
47 +
  const sliderLabelsColor = useThemeColor({}, 'sliderLabels');
48 +

49 +
  const isRich = variant === 'rich';
50 +

51 +
  const valueDisplay = showMax || !isRich
52 +
    ? `${value}/${max}`
53 +
    : `${value}`;
54 +

55 +
  return (
56 +
    <View style={[styles.container, style]}>
57 +

58 +
      {/* Header row */}
59 +
      <View style={styles.header}>
60 +
        <View style={styles.labelRow}>
61 +
          {isRich && icon && (
62 +
            <View style={styles.iconWrapper}>{icon}</View>
63 +
          )}
64 +
          <Text style={[
65 +
            isRich ? styles.labelRich : styles.labelCompact,
66 +
            { color: textColor },
67 +
          ]}>
68 +
            {label}
69 +
          </Text>
70 +
        </View>
71 +

72 +
        <Text style={[
73 +
          isRich ? styles.valueRich : styles.valueCompact,
74 +
          { color: textColor },
75 +
        ]}>
76 +
          {valueDisplay}
77 +
        </Text>
78 +
      </View>
79 +

80 +
      <View style={{ marginRight: -10, marginLeft: -10 }}>
81 +
        {/* Slider */}
82 +
        <Slider
83 +
          style={styles.slider}
84 +
          minimumValue={min}
85 +
          maximumValue={max}
86 +
          step={step}
87 +
          value={value}
88 +
          onValueChange={onValueChange}
89 +
          minimumTrackTintColor={trackingColor}
90 +
          maximumTrackTintColor={trackingColor}
91 +
          thumbTintColor={tintColor}
92 +
        />
93 +
      </View>
94 +

95 +
      {/* Min / Max labels */}
96 +
      {(minLabel || maxLabel) && (
97 +
        <View style={styles.rangeLabels}>
98 +
          {minLabel && (
99 +
            <Text style={[styles.rangeLabel, { color: sliderLabelsColor }]}>
100 +
              {minLabel.toUpperCase()}
101 +
            </Text>
102 +
          )}
103 +
          {maxLabel && (
104 +
            <Text style={[styles.rangeLabel, { color: sliderLabelsColor }]}>
105 +
              {maxLabel.toUpperCase()}
106 +
            </Text>
107 +
          )}
108 +
        </View>
109 +
      )}
110 +

111 +
    </View>
112 +
  );
113 +
};
114 +

115 +
// ============================================================================
116 +
// STYLES
117 +
// ============================================================================
118 +

119 +
const styles = StyleSheet.create({
120 +
  container: {
121 +
    width: '100%',
122 +
  },
123 +
  header: {
124 +
    flexDirection: 'row',
125 +
    justifyContent: 'space-between',
126 +
    alignItems: 'center',
127 +
    marginBottom: 4,
128 +
  },
129 +
  labelRow: {
130 +
    flexDirection: 'row',
131 +
    alignItems: 'center',
132 +
    gap: 8,
133 +
  },
134 +
  iconWrapper: {
135 +
    justifyContent: 'center',
136 +
    alignItems: 'center',
137 +
  },
138 +

139 +
  // Rich variant
140 +
  labelRich: {
141 +
    fontSize: 14,
142 +
    fontWeight: 500
143 +
  },
144 +
  valueRich: {
145 +
    fontSize: 18,
146 +
    lineHeight: 28,
147 +
    fontWeight: 600
148 +
  },
149 +

150 +
  // Compact variant
151 +
  labelCompact: {
152 +
    fontSize: Theme.typography.bodyLg.fontSize,
153 +
  },
154 +
  valueCompact: {
155 +
    fontSize: Theme.typography.bodyMd.fontSize,
156 +
    fontWeight: Theme.typography.bodyMd.fontWeight,
157 +
    color: secondaryColor,
158 +
  },
159 +

160 +
  slider: {
161 +
    width: '100%',
162 +
    height: 40,
163 +
  },
164 +
  rangeLabels: {
165 +
    flexDirection: 'row',
166 +
    justifyContent: 'space-between',
167 +
    marginTop: -4,
168 +
  },
169 +
  rangeLabel: {
170 +
    ...Theme.typography.labelXs,
171 +
    fontWeight: 500,
172 +
  },
173 +
});