diff --git a/frontend/assets/colorConstants.tsx b/frontend/assets/colorConstants.tsx
index 3fddb95..3a64572 100644
--- a/frontend/assets/colorConstants.tsx
+++ b/frontend/assets/colorConstants.tsx
@@ -1,18 +1,20 @@
const Colors = {
- LIGHTFGREEN: '#E0EEC6',
- DARKGREEN: '#243E36',
+ LIGHTFGREEN: '#d8edc2',
+ DARKGREEN: '#012b26',
+ DARKTRANS: '#07332f',
DARKGREEN2: '#224A3E',
DARKGREEN3: '#2E5C4E',
- WHITE: '#fff',
- BLACK: '000',
+ WHITE: '#ffffff',
+ BLACK: '#000000',
DARKLIMEGREEN: '#4B8552',
- GREY: '#ccc',
+ GREY: '#cccccc',
FILLGREEN: '#7CA982',
ERROR: 'red',
TRANSGREEN: '#366959',
BLACKTRANS: '#000000aa',
BLUE: 'blue',
GREYGREEN: '#B4C792',
+ TEAL: '#6bcfca'
};
export default Colors;
diff --git a/frontend/package.json b/frontend/package.json
index 37c2cd2..d9282a8 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -38,8 +38,8 @@
"react-native-circular-progress-indicator": "^4.4.2",
"react-native-elements": "^3.4.3",
"react-native-gesture-handler": "^2.13.3",
- "react-native-paper": "^5.11.1",
- "react-native-reanimated": "^3.5.4",
+ "react-native-paper": "^5.11.3",
+ "react-native-reanimated": "~3.3.0",
"react-native-safe-area-context": "^4.6.3",
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
diff --git a/frontend/src/components/appNavigation.tsx b/frontend/src/components/appNavigation.tsx
index f5724f4..9f2cef0 100644
--- a/frontend/src/components/appNavigation.tsx
+++ b/frontend/src/components/appNavigation.tsx
@@ -23,7 +23,7 @@ import FoodEntryEdit from '../screens/Food/foodEntryEdit';
import SettingsScreen from '../screens/settings';
import YourForms from '../screens/yourForms';
import CommunityHub from '../screens/communityHub';
-
+import FootprintDecomp from '../screens/footpringDecomp';
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
@@ -34,11 +34,19 @@ const AppNavigation = (): JSX.Element => {
-
-
-
-
+
+
+
= ({ thisUser }) => {
+ const lvl = getUserLevel(thisUser);
+ // testing--const lvl = 7;
+ const lvlThresh = [250, 500, 1000, 2000, 4000, 8000, 16000, 32000, 64000];
+ const maxExp = lvlThresh[lvl - 1];
+
+ const curr = thisUser.score;
+ // testing--const curr = 14039;
+
+ const progress = (curr / maxExp) * 100;
+
+ return (
+
+
+
+
+
+
+ Level: {lvl}
+
+
+ {`${curr}/${maxExp}`}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ width: 250,
+ marginVertical: 4,
+ },
+ progressBar: {
+ flexDirection: 'row',
+ height: 5,
+ backgroundColor: Colors.WHITE,
+ borderRadius: 10,
+ overflow: 'hidden',
+ },
+ currProgression:{
+ backgroundColor: Colors.TEAL,
+ height: '100%',
+
+ },
+ levelContainer:{
+ flex: 1,
+ justifyContent: 'flex-start',
+ paddingLeft: 5,
+ },
+ texts:{
+ marginTop: 5,
+ color: Colors.WHITE,
+ fontSize: 12,
+ },
+ progContainer:{
+ justifyContent: 'flex-end',
+ },
+ textBox:{
+ flexDirection: 'row'
+ }
+});
+
+export default ExpProgressBar;
diff --git a/frontend/src/components/sampleData/sampleChllgInput.tsx b/frontend/src/components/sampleData/sampleChllgInput.tsx
new file mode 100644
index 0000000..056267f
--- /dev/null
+++ b/frontend/src/components/sampleData/sampleChllgInput.tsx
@@ -0,0 +1,21 @@
+import { type challenge } from "../types";
+
+const sampleChallenges: challenge[] = [
+ {
+ id: 1,
+ title: 'Meatless Mondays',
+ description: 'Try going vegitarian (or vegan for a bigger challenge) for an entire Monday',
+ },
+ {
+ id: 2,
+ title: "One man's treasure another man's innovation",
+ description: 'Visit a thrift shop and turn 3 items into one sculpture',
+ },
+ {
+ id: 3,
+ title: 'Good re-Soup',
+ description: 'Reuse and repurpose your leftovers by turning it into stock!',
+ },
+ ];
+
+ export default sampleChallenges;
\ No newline at end of file
diff --git a/frontend/src/components/types.tsx b/frontend/src/components/types.tsx
index 2a8294d..0a7221f 100644
--- a/frontend/src/components/types.tsx
+++ b/frontend/src/components/types.tsx
@@ -31,3 +31,20 @@ export interface profileWidgetBoxProps {
photoURL: string;
user: User;
}
+
+export interface carbonWidgetProps {
+ carbonUser: User;
+}
+
+export interface challenge {
+ id: number;
+ title: string;
+ description: string;
+}
+export interface challengesProps {
+ challenges: challenge[];
+}
+
+export interface ExpProgressBarProps {
+ thisUser: User,
+}
\ No newline at end of file
diff --git a/frontend/src/screens/dashboard.tsx b/frontend/src/screens/dashboard.tsx
index 8bbe62f..93ab8b9 100644
--- a/frontend/src/screens/dashboard.tsx
+++ b/frontend/src/screens/dashboard.tsx
@@ -1,12 +1,16 @@
import React, { useEffect, useState } from 'react';
-import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import { ScrollView, View, StyleSheet, TouchableOpacity } from 'react-native';
import { useFonts } from 'expo-font';
import Colors from '../../assets/colorConstants';
+
import ProfileWidgetBox from '../widgets/profileWidget';
-import WidgetBox from '../widgets/widgetBox';
+import CarbonWidgetBox from '../widgets/carbonWidgetBox';
+import ChallengesWidget from '../widgets/challengesWidgetBox';
+
import type { RootStackParamList } from '../components/types';
import type { StackNavigationProp } from '@react-navigation/stack';
import { useNavigation } from '@react-navigation/native';
+
import { type User } from '../models/User';
import { UsersAPI } from '../APIs/UsersAPI';
import { type TransportationEntry } from '../models/Transportation';
@@ -15,6 +19,7 @@ import { type FoodEntry } from '../models/Food';
import { type EnergyEntry } from '../models/Energy';
import { FoodAPI } from '../APIs/FoodAPI';
import { EnergyAPI } from '../APIs/EnergyAPI';
+import sampleChallenges from '../components/sampleData/sampleChllgInput';
export type StackNavigation = StackNavigationProp;
export default function DashBoardScreen(): JSX.Element {
@@ -22,7 +27,8 @@ export default function DashBoardScreen(): JSX.Element {
const [transportationEntry, setTransportationEntry] = useState();
const [foodEntry, setFoodEntry] = useState();
const [energyEntry, setEnergyEntry] = useState();
- const [photoURL] = useState("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Default_pfp.svg/2048px-Default_pfp.svg.png");
+
+ const [photoURL] = useState("https://cdn.vox-cdn.com/thumbor/osQ-EchVP5I1xQlgtouC48YqzNc=/0x0:1750x941/1200x800/filters:focal(735x331:1015x611)/cdn.vox-cdn.com/uploads/chorus_image/image/53111667/Mewtwo_M01.0.0.png");
const navigation = useNavigation();
@@ -59,13 +65,7 @@ export default function DashBoardScreen(): JSX.Element {
}
return (
-
-
-
- Dashboard
-
-
-
+
+
+ {
+ navigation.navigate('FootprintDecomp');
+ }}
+ >
+
+
+
+
+
+
+
+
-
-
-
- {
- navigation.navigate('TransportationHistory');
- }}
- >
-
-
-
-
-
- {
- navigation.navigate('FoodHistory');
- }}
- >
-
-
-
-
-
-
-
- {
- navigation.navigate('EnergyHistory');
- }}
- >
-
-
-
-
-
-
+
);
}
const styles = StyleSheet.create({
container: {
- alignItems: 'center',
- backgroundColor: Colors.WHITE,
- flex: 1,
- justifyContent: 'center',
- },
- header: {
- fontSize: 24,
- },
- headerBox: {
- backgroundColor: Colors.WHITE,
- },
- headerContainer: {
- alignItems: 'center',
+ backgroundColor: Colors.LIGHTFGREEN,
+ flexGrow: 1,
},
profileWidgetContainer: {
- padding: 10,
- flexDirection: 'column',
- },
- widgetContainer: {
- padding: 10,
- flexDirection: 'row',
+ alignItems: 'center',
+ marginHorizontal: 10
},
widgetBoarder: {
padding: 10,
diff --git a/frontend/src/screens/footpringDecomp.tsx b/frontend/src/screens/footpringDecomp.tsx
new file mode 100644
index 0000000..d1c889b
--- /dev/null
+++ b/frontend/src/screens/footpringDecomp.tsx
@@ -0,0 +1,113 @@
+import React, { useEffect, useState } from 'react';
+import { ScrollView, View, StyleSheet, TouchableOpacity } from 'react-native';
+import { useFonts } from 'expo-font';
+import Colors from '../../assets/colorConstants';
+import WidgetBox from '../widgets/widgetBox';
+import type { RootStackParamList } from '../components/types';
+import type { StackNavigationProp } from '@react-navigation/stack';
+import { useNavigation } from '@react-navigation/native';
+import { type User } from '../models/User';
+import { UsersAPI } from '../APIs/UsersAPI';
+import { type TransportationEntry } from '../models/Transportation';
+import { TransportationAPI } from '../APIs/TransportationAPI';
+import { type FoodEntry } from '../models/Food';
+import { type EnergyEntry } from '../models/Energy';
+import { FoodAPI } from '../APIs/FoodAPI';
+import { EnergyAPI } from '../APIs/EnergyAPI';
+export type StackNavigation = StackNavigationProp;
+
+export default function FootprintDecomp(): JSX.Element {
+ const [user, setUser] = useState(undefined);
+ const [transportationEntry, setTransportationEntry] = useState();
+ const [foodEntry, setFoodEntry] = useState();
+ const [energyEntry, setEnergyEntry] = useState();
+
+
+ const navigation = useNavigation();
+
+ const [loaded] = useFonts({
+ Montserrat: require('../../assets/fonts/MontserratThinRegular.ttf'),
+ Josefin: require('../../assets/fonts/JosefinSansThinRegular.ttf'),
+ });
+
+ useEffect(() => {
+ void UsersAPI.GetLoggedInUser().then((res) => {
+ if (res != null) {
+ setUser(res);
+ }
+ });
+ void TransportationAPI.getTransportationMetricForToday().then((res) => {
+ if (res != null) {
+ setTransportationEntry(res)
+ }
+ });
+ void FoodAPI.getFoodMetricForToday().then((res) => {
+ if (res != null) {
+ setFoodEntry(res)
+ }
+ });
+ void EnergyAPI.getEnergyMetricForToday().then((res) => {
+ if (res != null) {
+ setEnergyEntry(res)
+ }
+ });
+ }, [loaded]);
+
+ if (!loaded || user === undefined || transportationEntry === undefined || foodEntry === undefined || energyEntry === undefined) {
+ return <>>;
+ }
+
+ return (
+
+
+
+ {
+ navigation.navigate('TransportationHistory');
+ }}
+ >
+
+
+
+
+
+ {
+ navigation.navigate('FoodHistory');
+ }}
+ >
+
+
+
+
+
+
+
+ {
+ navigation.navigate('EnergyHistory');
+ }}
+ >
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: Colors.LIGHTFGREEN,
+ flexGrow: 1,
+ },
+ widgetContainer: {
+ flexDirection: 'row',
+ alignContent: 'center',
+ justifyContent: 'center'
+ },
+ widgetBoarder: {
+ padding: 10,
+ },
+});
diff --git a/frontend/src/widgets/carbonWidgetBox.tsx b/frontend/src/widgets/carbonWidgetBox.tsx
new file mode 100644
index 0000000..1fa42c0
--- /dev/null
+++ b/frontend/src/widgets/carbonWidgetBox.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { View, Text,StyleSheet } from 'react-native';
+import Colors from '../../assets/colorConstants';
+import { type carbonWidgetProps } from '../components/types';
+import { useFonts } from 'expo-font';
+
+const CarbonWidgetBox: React.FC = ({ carbonUser }) => {
+ const [loaded] = useFonts({
+ Montserrat: require('../../assets/fonts/MontserratThinRegular.ttf'),
+ Josefin: require('../../assets/fonts/JosefinSansThinRegular.ttf'),
+ });
+
+ if (!loaded) {
+ return <>>;
+ }
+
+ return (
+
+
+ Carbon Footprint
+ getUserFootprint
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ boxContainer: {
+ backgroundColor: Colors.DARKGREEN,
+ borderRadius: 15,
+ height: 150,
+ width: 325,
+ flexDirection: 'column',
+ // ios shadow
+ shadowColor: Colors.BLACK,
+ shadowOffset: { width: 2, height: 4 },
+ shadowOpacity: 0.6,
+ shadowRadius: 15,
+ // andriod shadow
+ elevation: 5,
+ alignItems: 'flex-start',
+ },
+ footprint:{
+ textAlign: 'center',
+ color: Colors.WHITE,
+ fontSize: 14,
+ paddingLeft: '30%',
+ paddingTop: '10%',
+ },
+ header: {
+ color: Colors.WHITE,
+ textAlign: 'left',
+ fontSize: 18,
+ paddingTop: 25,
+ paddingLeft: 20,
+ fontWeight: '700',
+ },
+
+
+});
+
+export default CarbonWidgetBox;
diff --git a/frontend/src/widgets/challengesWidgetBox.tsx b/frontend/src/widgets/challengesWidgetBox.tsx
new file mode 100644
index 0000000..96bfc24
--- /dev/null
+++ b/frontend/src/widgets/challengesWidgetBox.tsx
@@ -0,0 +1,128 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { View, Text, TouchableOpacity, StyleSheet, Animated } from 'react-native';
+import Colors from '../../assets/colorConstants';
+import { type challengesProps, type RootStackParamList } from '../components/types';
+import { useFonts } from 'expo-font';
+import type { StackNavigationProp } from '@react-navigation/stack';
+import { useNavigation } from '@react-navigation/native';
+
+export type StackNavigation = StackNavigationProp;
+
+const ChallengesWidget: React.FC = ({ challenges }) => {
+ const [loaded] = useFonts({
+ Montserrat: require('../../assets/fonts/MontserratThinRegular.ttf'),
+ Josefin: require('../../assets/fonts/JosefinSansThinRegular.ttf'),
+ });
+
+ const [expandedChallenge, setExpandedChallenge] = useState(null);
+ const containerHeight = useRef(new Animated.Value(0)).current;
+ const navigation = useNavigation();
+ const handleChallengePress = (challengeId: number): void => {
+ setExpandedChallenge((prev) => (prev === challengeId ? null : challengeId));
+ };
+
+
+ useEffect(() => {
+ const expandedContentHeight = challenges.reduce((totalHeight, chal) => {
+ return totalHeight + (expandedChallenge === chal.id ? 50 : 0);
+ }, 0);
+
+ const newHeight = Math.max(50, 150 + expandedContentHeight);
+
+ Animated.timing(containerHeight, {
+ toValue: 130 + newHeight,
+ duration: 400,
+ useNativeDriver: false
+ }).start();
+ }, [challenges, containerHeight, expandedChallenge]);
+
+ if (!loaded) {
+ return <>>;
+ }
+
+ return (
+
+ Your Challenges
+
+ {challenges.map((chal) => (
+
+ handleChallengePress(chal.id)}>
+ {chal.title}
+ {expandedChallenge === chal.id && (
+ {chal.description}
+ )}
+
+
+ ))}
+
+ navigation.navigate('CommunityHub')}>
+ View More
+
+
+ );
+ };
+
+ const styles = StyleSheet.create({
+ challengeBox: {
+ backgroundColor: Colors.DARKGREEN,
+ borderRadius: 15,
+ justifyContent: 'center',
+ width: 325,
+ flexDirection: 'column',
+ // ios shadow
+ shadowColor: Colors.BLACK,
+ shadowOffset: { width: 2, height: 4 },
+ shadowOpacity: 0.6,
+ shadowRadius: 15,
+ // andriod shadow
+ elevation: 5,
+ margin: 10,
+ },
+ challengeContainer: {
+ justifyContent: 'center',
+ marginTop: 30,
+ paddingVertical: 10,
+ marginHorizontal: 5,
+ borderRadius: 10
+ },
+ challengeListContainer: {
+ padding: 15,
+ backgroundColor: Colors.DARKTRANS,
+ borderRadius: 10,
+ marginHorizontal: 10,
+ marginVertical: 5,
+ },
+ challengeTitle: {
+ fontSize: 16,
+ color: Colors.WHITE,
+ fontWeight: '600',
+
+ },
+ challengeDescription: {
+ marginTop: 5,
+ color: Colors.WHITE,
+ },
+ header: {
+ color: Colors.WHITE,
+ textAlign: 'left',
+ fontSize: 18,
+ paddingLeft: 20,
+ fontWeight: '700',
+ paddingBottom: 10,
+ position: 'absolute',
+ top: 20,
+ left: 0,
+ right: 0,
+ zIndex: 1,
+ marginBottom: 10
+ },
+ viewMore:{
+ color: Colors.WHITE,
+ textAlign: 'right',
+ marginHorizontal: 20,
+ marginBottom: 5,
+
+ }
+ });
+
+export default ChallengesWidget;
diff --git a/frontend/src/widgets/profileWidget.tsx b/frontend/src/widgets/profileWidget.tsx
index e23036f..2058b74 100644
--- a/frontend/src/widgets/profileWidget.tsx
+++ b/frontend/src/widgets/profileWidget.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import { View, Text, Image, StyleSheet } from 'react-native';
+import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
import Colors from '../../assets/colorConstants';
import { type profileWidgetBoxProps } from '../components/types';
import { useFonts } from 'expo-font';
-import { getUserLevel } from '../models/User';
+import ExpProgressBar from '../components/expProgressBar';
const ProfileWidgetBox: React.FC = ({ photoURL, user }) => {
const [loaded] = useFonts({
@@ -18,11 +18,25 @@ const ProfileWidgetBox: React.FC = ({ photoURL, user }) =
return (
-
+
{user.full_name}
- Email: {user.email}
- Level: {getUserLevel(user)}
+
+
+
+
+
+ Badges
+
+
+ Rank getUserRank
+
+
+
+
+
+
+
);
};
@@ -31,32 +45,53 @@ const styles = StyleSheet.create({
boxContainer: {
alignItems: 'center',
backgroundColor: Colors.DARKGREEN,
- borderRadius: 10,
+ borderRadius: 15,
justifyContent: 'center',
- height: 300,
- width: 300,
+ height: 350,
+ width: 325,
flexDirection: 'column',
+ // ios shadow
+ shadowColor: Colors.BLACK,
+ shadowOffset: { width: 2, height: 4 },
+ shadowOpacity: 0.6,
+ shadowRadius: 15,
+ // andriod shadow
+ elevation: 5,
+ },
+ buttonsContainer:{
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
+ button:{
+ backgroundColor: Colors.DARKTRANS,
+ marginHorizontal: 10,
+ padding: 15,
+ borderRadius: 10,
+ top: '5%'
},
- level:{
+ buttonText:{
color: Colors.WHITE,
- fontSize: 16,
- flex: 1,
- },
+ },
name: {
- flex: 1,
- fontSize: 16,
+ fontSize: 20,
color: Colors.WHITE,
fontWeight: '700',
+ },
+ nameBox: {
top: '20%',
+ flex: 2,
+ alignItems: 'center'
},
profilePicture: {
- width: 50,
- height: 50,
- borderRadius: 25,
+ width: 90,
+ height: 90,
+ borderRadius: 45,
alignItems: 'flex-start',
top: '15%',
-
},
+ progressBar:{
+ }
+
});
export default ProfileWidgetBox;