Skip to content

Commit

Permalink
feat: tree & tree species information screens (#8)
Browse files Browse the repository at this point in the history
* Added tree info screen.

* Added edit mode for individual trees.

* Added species info view.

* Implemented data persistence for tree information.

* Added tree species info screen.

---------

Co-authored-by: Chris Torres <[email protected]>
  • Loading branch information
adityapawar1 and christophertorres1 authored Dec 11, 2024
1 parent d52cbf9 commit 6d4bb48
Show file tree
Hide file tree
Showing 49 changed files with 2,197 additions and 380 deletions.
7 changes: 6 additions & 1 deletion App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { Text } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { useFonts } from 'expo-font';
import { DMSans_400Regular, DMSans_700Bold } from '@expo-google-fonts/dm-sans';
import { DefaultTheme } from '@react-navigation/native';
Expand All @@ -22,5 +23,9 @@ export default function App() {
(Text as any).defaultProps = (Text as any).defaultProps || {};
(Text as any).defaultProps.style = { fontFamily: defaultFontFamily };

return <AppNavigator />;
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<AppNavigator />
</GestureHandlerRootView>
);
}
1 change: 1 addition & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"expo": {
"name": "Our City Forest",
"slug": "our-city-forest",
"newArchEnabled": true,
"owner": "ocfdev",
"version": "1.0.0",
"orientation": "portrait",
Expand Down
Binary file added assets/tree-info-bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
547 changes: 224 additions & 323 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
"cookie": "^1.0.2",
"cross-spawn": "^7.0.6",
"dotenv": "^16.4.5",
"expo": "^52.0.17",
"expo": "~52.0.18",
"expo-auth-session": "~6.0.1",
"expo-camera": "^16.0.7",
"expo-camera": "~16.0.9",
"expo-constants": "~17.0.3",
"expo-crypto": "~14.0.1",
"expo-dev-client": "~5.0.5",
"expo-dev-client": "~5.0.6",
"expo-device": "~7.0.1",
"expo-font": "^13.0.1",
"expo-linking": "~7.0.3",
Expand All @@ -47,9 +47,11 @@
"qs": "^6.13.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-native": "^0.76.3",
"react-native": "0.76.5",
"react-native-config": "^1.5.3",
"react-native-dotenv": "^3.4.11",
"react-native-element-dropdown": "^2.12.2",
"react-native-elements": "^3.4.3",
"react-native-gesture-handler": "~2.20.2",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "^4.12.0",
Expand Down
92 changes: 92 additions & 0 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Text, View } from 'react-native';
import { Dropdown as DropdownElement } from 'react-native-element-dropdown';
import { Icon } from 'react-native-elements';
import colors from '@/styles/colors';
import styles from './styles';

type DropdownProps<T extends string[]> = {
options: T;
setValue: (value: T[number]) => any;
value: string;
displayValue?: (s: string) => string;
};

type Option = {
label: string;
value: string;
i: number;
};

function Dropdown<T extends string[]>({
options,
setValue,
value,
displayValue = s => s,
}: DropdownProps<T>) {
return (
<View>
<DropdownElement
mode="default"
style={styles.dropdown}
placeholderStyle={[styles.text, styles.textContainer]}
selectedTextStyle={[styles.text, styles.textContainer]}
inputSearchStyle={styles.text}
itemTextStyle={[styles.text, styles.gray4]}
containerStyle={styles.dropdownContainer}
dropdownPosition="bottom"
iconStyle={styles.iconStyle}
data={options.map((option: T[number], i: number) => {
return { i, label: displayValue(option), value: option };
})}
maxHeight={400}
labelField="label"
valueField="value"
placeholder="Select..."
value={value}
renderItem={(item: Option, selected: boolean | undefined) => {
return (
<Text
style={[
styles.text,
styles.gray4,
styles.itemContainer,
selected && styles.selectedBar,
{ borderBottomLeftRadius: 0, borderTopLeftRadius: 0 },
item.i === 0 && {
borderTopLeftRadius: 5,
},
item.i === options.length - 1 && {
borderBottomLeftRadius: 5,
},
]}
>
{item.label}
</Text>
);
}}
renderRightIcon={visible =>
visible ? (
<Icon
name="arrow-drop-up"
type="material"
color={colors.gray4}
size={24}
/>
) : (
<Icon
name="arrow-drop-down"
type="material"
color={colors.gray4}
size={24}
/>
)
}
onChange={(item: Option) => {
setValue(item.value);
}}
/>
</View>
);
}

export default Dropdown;
73 changes: 73 additions & 0 deletions src/components/Dropdown/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { StyleSheet } from 'react-native';
import colors from '@/styles/colors';
import typography from '@/styles/typography';

export default StyleSheet.create({
gray4: {
color: colors.gray4,
},

black3: {
color: colors.gray3,
},

textContainer: {
paddingHorizontal: 20,
},

selectedBar: {
borderLeftWidth: 8,
borderColor: colors.primary,
overflow: 'hidden',
paddingHorizontal: 20 - 8,
margin: 0,
},

text: {
...typography.normalRegular,
color: colors.gray3,
textTransform: 'capitalize',
textAlign: 'left',
},

outer: {
position: 'relative',
zIndex: 1,
},

label: {
marginBottom: 8,
},

dropdown: {
height: 47,
borderWidth: 1,
borderRadius: 10,
paddingRight: 10,
textAlign: 'left',
flex: 1,
alignItems: 'center',
borderColor: colors.gray5,
},

dropdownContainer: {
borderRadius: 5,
borderWidth: 1,
borderColor: colors.gray5,
position: 'relative',
},

itemContainer: {
paddingHorizontal: 20,
paddingVertical: 14,
},

icon: {
marginRight: 5,
},

iconStyle: {
width: 20,
height: 20,
},
});
6 changes: 4 additions & 2 deletions src/components/QRCodeScanner/QRCodeScanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
useCameraPermissions,
} from 'expo-camera';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import SvgFlashCircle from '@/icons/FlashCircle';
import SvgXButton from '@/icons/XButton';
import { HomeStackParamList } from '@/types/navigation';
import styles from './styles';

Expand Down Expand Up @@ -58,10 +60,10 @@ export default function QRCodeScanner({ navigation }: QRCodeScannerProps) {
<SafeAreaView style={styles.container}>
<View style={styles.iconFlex}>
<TouchableOpacity onPress={() => setFlashEnabled(!flashEnabled)}>
<Text style={styles.icon}>Flash</Text>
<SvgFlashCircle />
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Text style={styles.icon}>X</Text>
<SvgXButton />
</TouchableOpacity>
</View>

Expand Down
3 changes: 2 additions & 1 deletion src/components/QRCodeScanner/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default StyleSheet.create({
iconFlex: {
flex: 0,
width: '100%',
paddingHorizontal: 44,
paddingHorizontal: 27,
flexDirection: 'row',
justifyContent: 'space-between',
},
Expand Down Expand Up @@ -90,6 +90,7 @@ export default StyleSheet.create({
scanButtonDisabled: {
backgroundColor: colors.gray4,
},

scanButtonEnabled: {
backgroundColor: colors.tertiary,
},
Expand Down
129 changes: 129 additions & 0 deletions src/components/SpeciesDisplay/SpeciesDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Fragment } from 'react';
import { Text, TextInput, View } from 'react-native';
import SvgBear from '@/icons/Bear';
import SvgFlash from '@/icons/Flash';
import SvgFruit from '@/icons/Fruit';
import SvgLeaf from '@/icons/Leaf';
import Lightbulb from '@/icons/Lightbulb';
import SvgLocationPin from '@/icons/Location';
import SvgRuler from '@/icons/Ruler';
import SvgShapes from '@/icons/Shapes';
import SvgWarning2 from '@/icons/Warning2';
import SvgWateringCan from '@/icons/WateringCan';
import { Species } from '@/types/species';
import { displayValue, Tree } from '@/types/tree';
import styles from './styles';

type SpeciesDisplayProps = {
speciesData: Partial<Species>;
treeData: Tree[];
};
export default function SpeciesDisplay({
speciesData,
treeData,
}: SpeciesDisplayProps) {
return (
<View style={styles.main}>
<Text style={styles.text}>{speciesData.description}</Text>

<View style={styles.funFactHeader}>
<Lightbulb />
<Text style={styles.funFact}>Fun Fact</Text>
</View>

<TextInput
style={styles.textInput}
value={speciesData.fun_fact ?? ''}
editable={false}
multiline
numberOfLines={4}
/>

<View style={styles.separator}></View>

<Text style={styles.header}>Properties</Text>
<View style={styles.properties}>
{speciesData.height_ft && (
<View style={styles.property}>
<SvgRuler />
<Text style={styles.propertyText}>{speciesData.height_ft} ft</Text>
</View>
)}

{speciesData.tree_shape && (
<View style={styles.property}>
<SvgShapes />
<Text style={styles.propertyText}>
{displayValue(speciesData.tree_shape)}
</Text>
</View>
)}

{speciesData.water_amount && (
<View style={styles.property}>
<SvgWateringCan />
<Text style={styles.propertyText}>
{displayValue(speciesData.water_amount)}
</Text>
</View>
)}

{speciesData.root_damage_potential && (
<View style={styles.property}>
<SvgWarning2 />
<Text style={styles.propertyText}>
{displayValue(speciesData.root_damage_potential)}
</Text>
</View>
)}

{speciesData.fruit_type && (
<View style={styles.property}>
<SvgFruit />
<Text style={styles.propertyText}>
{displayValue(speciesData.fruit_type)} Fruit
</Text>
</View>
)}

{speciesData.ca_native && (
<View style={styles.property}>
<SvgBear />
<Text style={styles.propertyText}>CA Native</Text>
</View>
)}

{speciesData.evegreen && (
<View style={styles.property}>
<SvgLeaf />
<Text style={styles.propertyText}>Evegreen</Text>
</View>
)}

{speciesData.powerline_friendly && (
<View style={styles.property}>
<SvgFlash />
<Text style={styles.propertyText}>Powerline Friendly</Text>
</View>
)}
</View>

{treeData?.length > 0 && (
<>
<Text style={styles.header}>Location</Text>
<View style={styles.locations}>
{treeData?.map(tree => (
<View style={styles.locationEntry} key={tree.tree_id}>
<SvgLocationPin />
<Text style={styles.propertyText}>
Bank #{tree.bank ?? 0} {' '}|{' '} Row #{tree.row ?? 0}
{/* TODO: Needs to support range of rows */}
</Text>
</View>
))}
</View>
</>
)}
</View>
);
}
Loading

0 comments on commit 6d4bb48

Please sign in to comment.