eletrotupi / tcc/ commit / 51a7c2b

frontend: add screen layout

Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago 51a7c2bc2232d2d4a32f2314cb9bd484478cb323 Browse files
Parents 7a3f55e
Changed files
2
Additions
+159
Deletions
0
/dev/null → frontend/components/ui/ScreenLayout.tsx
1 +
import React from 'react';
2 +

3 +
import {
4 +
  View,
5 +
  ScrollView,
6 +
  StyleSheet,
7 +
  Image,
8 +
  Pressable,
9 +
  StatusBar,
10 +
} from 'react-native';
11 +

12 +
import { SafeAreaView } from 'react-native-safe-area-context';
13 +
import { IconSymbol } from '@/components/ui/icon-symbol';
14 +
import { useThemeColor } from '@/hooks/use-theme-color';
15 +
import { Typography, Spacing, BorderRadius } from '@/constants/theme'
16 +
import { ThemedText } from '@/components/misc/themed-text'
17 +

18 +
interface ScreenLayoutProps {
19 +
  children: React.ReactNode;
20 +
  userName?: string;
21 +
  userAvatar?: string;
22 +
  onNotificationPress?: () => void;
23 +
  showNotificationBadge?: boolean;
24 +
  scrollEnabled?: boolean;
25 +
}
26 +

27 +
export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
28 +
  children,
29 +
  userName = 'Usuário',
30 +
  userAvatar,
31 +
  onNotificationPress,
32 +
  showNotificationBadge = false,
33 +
  scrollEnabled = true,
34 +
}) => {
35 +
  const textColor = useThemeColor({}, 'text');
36 +
  const backgroundColor = useThemeColor({}, 'background');
37 +
  const surfaceColor = useThemeColor({}, 'surface');
38 +
  const dividerColor = useThemeColor({}, 'divider');
39 +
  const accentBlue = useThemeColor({}, 'accentBlue');
40 +
  const tintColor = useThemeColor({}, 'tint');
41 +

42 +
  const styles = StyleSheet.create({
43 +
    container: {
44 +
      flex: 1,
45 +
      backgroundColor: backgroundColor,
46 +
    },
47 +
    safeAreaView: {
48 +
      flex: 1,
49 +
      backgroundColor: backgroundColor,
50 +
    },
51 +
    header: {
52 +
      paddingHorizontal: Spacing.containerPadding,
53 +
      paddingVertical: 12,
54 +
      backgroundColor: surfaceColor,
55 +
      borderBottomWidth: 1,
56 +
      borderBottomColor: dividerColor,
57 +
      flexDirection: 'row',
58 +
      justifyContent: 'space-between',
59 +
      alignItems: 'center',
60 +
    },
61 +
    headerLeft: {
62 +
      flexDirection: 'row',
63 +
      alignItems: 'center',
64 +
      gap: Spacing.inlineGapSm,
65 +
    },
66 +
    avatar: {
67 +
      width: 40,
68 +
      height: 40,
69 +
      borderRadius: 20,
70 +
      backgroundColor: accentBlue,
71 +
    },
72 +
    userName: {
73 +
      fontSize: Typography.bodyLg.fontSize,
74 +
      fontWeight: '600',
75 +
      color: textColor,
76 +
    },
77 +
    notificationButton: {
78 +
      width: 40,
79 +
      height: 40,
80 +
      borderRadius: BorderRadius.md,
81 +
      justifyContent: 'center',
82 +
      alignItems: 'center',
83 +
      activeOpacity: 0.7,
84 +
    },
85 +
    notificationBadge: {
86 +
      position: 'absolute',
87 +
      top: 4,
88 +
      right: 4,
89 +
      width: 8,
90 +
      height: 8,
91 +
      borderRadius: 4,
92 +
      backgroundColor: tintColor,
93 +
    },
94 +
    scrollContent: {
95 +
      paddingHorizontal: Spacing.containerPadding,
96 +
      paddingVertical: Spacing.sectionGap,
97 +
    },
98 +
  });
99 +
 
100 +
  return (
101 +
    <View style={styles.container}>
102 +
      <StatusBar
103 +
        barStyle="dark-content"
104 +
        backgroundColor={surfaceColor}
105 +
      />
106 +
      <SafeAreaView style={styles.safeAreaView}>
107 +
        <View style={styles.header}>
108 +
          <View style={styles.headerLeft}>
109 +
            {userAvatar ? (
110 +
              <Image
111 +
                source={{ uri: userAvatar }}
112 +
                style={styles.avatar}
113 +
              />
114 +
            ) : (
115 +
              <View style={styles.avatar} />
116 +
            )}
117 +
            <View>
118 +
              <ThemedText style={styles.userName}>
119 +
                Olá, {userName}
120 +
              </ThemedText>
121 +
            </View>
122 +
          </View>
123 +

124 +
          <Pressable
125 +
            style={styles.notificationButton}
126 +
            onPress={onNotificationPress}
127 +
            hitSlop={8}
128 +
          >
129 +
            <View
130 +
              style={{
131 +
                fontSize: 24,
132 +
                color: textColor,
133 +
              }}
134 +
            >
135 +
            <IconSymbol size={24} name="notifications" color={textColor} />
136 +
            </View>
137 +
            {showNotificationBadge && (
138 +
              <View style={styles.notificationBadge} />
139 +
            )}
140 +
          </Pressable>
141 +
        </View>
142 +

143 +
        <ScrollView
144 +
          scrollEnabled={scrollEnabled}
145 +
          showsVerticalScrollIndicator={false}
146 +
          contentContainerStyle={scrollEnabled ? undefined : { flex: 1 }}
147 +
          scrollEventThrottle={16}
148 +
        >
149 +
          <View style={styles.scrollContent}>
150 +
            {children}
151 +
          </View>
152 +
        </ScrollView>
153 +
      </SafeAreaView>
154 +
    </View>
155 +
  );
156 +
};
157 +

158 +
export default ScreenLayout;
frontend/components/ui/icon-symbol.tsx
1 1
// Fallback for using MaterialIcons on Android and web.
2 2

3 3
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
4 4
import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
5 5
import { ComponentProps } from 'react';
16 16
 * TODO: We might get rid of this completely and just wrap a <MeterialIcons>
17 17
 * since we won't have iOS stuff
18 18
 */
19 19
const MAPPING = {
20 20
  'house.fill': 'home',
21 +
  'notifications': 'notifications',
21 22
  'add-circle': 'add-circle',
22 23
  'history': 'history',
23 24
  'lightbulb-outline': 'lightbulb-outline',
24 25
  'paperplane.fill': 'send',
25 26
  'chevron.left.forwardslash.chevron.right': 'code',
45 46
  style?: StyleProp<TextStyle>;
46 47
  weight?: SymbolWeight;
47 48
}) {
48 49
  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
49 50
}