Merge pull request 'Create Session Card' (#126) from dean/session-card into master

Reviewed-on: railbird/railbird-mobile#126
Reviewed-by: Ivan Malison <ivanmalison@gmail.com>
Reviewed-by: Kat Huang <kkathuang@gmail.com>
This commit is contained in:
Ivan Malison 2024-02-20 22:25:11 -07:00
commit cef4119afa
11 changed files with 317 additions and 8 deletions

View File

@ -30,6 +30,7 @@
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "^6.3.21",
"@types/react": "~18.2.14",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

View File

@ -0,0 +1,26 @@
import React from "react";
import { StyleSheet, Text, View } from "react-native";
const SessionCardFooter = ({ sessionName, lastPlayed }) => {
return (
<View>
<Text style={styles.sessionName}>{sessionName}</Text>
<Text style={styles.sessionDatetime}>{lastPlayed}</Text>
</View>
);
};
const styles = StyleSheet.create({
sessionName: {
fontSize: 18,
paddingTop: 5,
marginHorizontal: 16,
},
sessionDatetime: {
fontSize: 10,
color: "#A3A3A3",
marginHorizontal: 16,
},
});
export default SessionCardFooter;

View File

@ -0,0 +1,68 @@
import React from "react";
import { Image, StyleSheet, Text, View } from "react-native";
const SessionCardHeader = ({
playerName,
location,
gameType,
locationIconURL,
profileImageURL,
}) => {
return (
<View style={styles.headerContainer}>
<Image
style={styles.headerProfileImage}
source={{ uri: profileImageURL }}
accessibilityLabel="Profile image"
/>
<View style={styles.headerText}>
<Text style={styles.playerName}>{playerName}</Text>
<View style={styles.locationContainer}>
<Image source={{ uri: locationIconURL }} style={styles.icon} />
<Text style={styles.locationText}>{location}</Text>
</View>
<Text style={styles.gameType}>{gameType}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
headerContainer: {
flexDirection: "row",
alignItems: "center",
marginHorizontal: 16,
},
headerProfileImage: {
width: 60,
height: 60,
resizeMode: "contain",
},
headerText: {
flexDirection: "column",
padding: 10,
},
playerName: {
fontSize: 24,
fontWeight: "bold",
},
locationContainer: {
flexDirection: "row",
alignItems: "center",
},
locationText: {
fontSize: 13,
},
gameType: {
fontSize: 13,
},
icon: {
width: 18,
height: 18,
resizeMode: "contain",
},
});
export default SessionCardHeader;

View File

@ -0,0 +1,25 @@
import React from "react";
import { StyleSheet, Text, View } from "react-native";
const SessionCardStat = ({ sessionStat, displayName }) => {
return (
<View>
<Text style={styles.statItem}>{displayName}</Text>
<Text style={styles.statValue}>{sessionStat}</Text>
</View>
);
};
const styles = StyleSheet.create({
statItem: {
fontSize: 10,
textAlign: "center",
color: "#666",
},
statValue: {
fontSize: 22,
fontWeight: "400",
},
});
export default SessionCardStat;

View File

@ -0,0 +1,48 @@
import React from "react";
import { StyleSheet, View } from "react-native";
import SessionCardStat from "./session-card-stat";
const SessionCardStatsRowContainer = ({
makePercent,
medianRun,
duration,
shotPacing,
}) => {
const stats = [
{ displayName: "Make Percent", sessionStat: makePercent },
{ displayName: "Median Run", sessionStat: medianRun },
{ displayName: "Time Played", sessionStat: duration },
{ displayName: "Shot Pacing", sessionStat: shotPacing },
];
return (
<View style={styles.statsContainer}>
{stats.map((stat, index) => (
<React.Fragment key={index}>
<SessionCardStat
displayName={stat.displayName}
sessionStat={stat.sessionStat}
/>
{index < stats.length - 1 && <View style={styles.verticalSpacer} />}
</React.Fragment>
))}
</View>
);
};
const styles = StyleSheet.create({
statsContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
margin: 16,
},
verticalSpacer: {
width: 1,
backgroundColor: "#A3A3A3",
height: "100%",
marginHorizontal: 12,
},
});
export default SessionCardStatsRowContainer;

View File

@ -0,0 +1,65 @@
import React from "react";
import { Image, StyleSheet, View } from "react-native";
import SessionCardFooter from "./session-card-footer";
import SessionCardHeader from "./session-card-header";
import SessionCardStatsRowContainer from "./session-card-stats-row-container";
const SessionCard = ({
playerName,
location,
gameType,
makePercent,
medianRun,
duration,
shotPacing,
sessionName,
lastPlayed,
imageURL,
profileImageURL,
locationIconURL,
}) => {
return (
<View style={styles.card}>
<SessionCardHeader
playerName={playerName}
location={location}
gameType={gameType}
locationIconURL={locationIconURL}
profileImageURL={profileImageURL}
/>
<SessionCardStatsRowContainer
makePercent={makePercent}
medianRun={medianRun}
duration={duration}
shotPacing={shotPacing}
/>
<Image source={imageURL} style={styles.image} />
<SessionCardFooter sessionName={sessionName} lastPlayed={lastPlayed} />
</View>
);
};
const styles = StyleSheet.create({
card: {
backgroundColor: "white",
borderRadius: 8,
borderWidth: 1,
borderColor: "#ddd",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
margin: 10,
overflow: "hidden",
},
image: {
width: "100%",
height: "50%",
},
});
export default SessionCard;

View File

@ -3,7 +3,8 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { Image } from "react-native";
import CameraScreen from "../component/video/camera";
import Profile from "../screens/profile";
import Session from "../screens/session";
import Session from "../screens/session-stack/session";
import SessionFeed from "../screens/session-stack/session-feed";
import VideoDetails from "../screens/video-stack/video-details";
import { tabIconColors } from "../styles";
@ -14,7 +15,7 @@ const RecordStack = createNativeStackNavigator();
// tabBarIcon configuration should live on separate file and contain all logic/icons/rendering for the Tabs
const tabIcons = {
Session: <Image source={Icon} style={{ width: 20, height: 20 }} />,
SessionStack: <Image source={Icon} style={{ width: 20, height: 20 }} />,
VideoStack: <Image source={Icon} style={{ width: 20, height: 20 }} />,
Profile: <Image source={Icon} style={{ width: 20, height: 20 }} />,
};
@ -37,6 +38,15 @@ function VideoTabStack() {
);
}
function SessionTabStack() {
return (
<RecordStack.Navigator screenOptions={{ headerShown: false }}>
<RecordStack.Screen name="SessionFeed" component={SessionFeed} />
<RecordStack.Screen name="Session" component={Session} />
</RecordStack.Navigator>
);
}
/**
* Functional component creating a tab navigator with called
* Uses React Navigation's Tab.Navigator. Customizes tab bar appearance and icons.
@ -58,8 +68,8 @@ export default function Tabs(): React.JSX.Element {
})}
>
<Tab.Screen
name="Session"
component={Session}
name="SessionStack"
component={SessionTabStack}
options={{ tabBarLabel: "Session" }}
/>
<Tab.Screen

View File

@ -0,0 +1,52 @@
import { StackNavigationProp } from "@react-navigation/stack";
import React from "react";
import { StyleSheet, TouchableOpacity, View } from "react-native";
import sampleSessionImage from "../../assets/sample_session.png";
import SessionCard from "../../component/session-card/session-card";
// Define the types for your navigation stack
type SessionStackParamList = {
Session: undefined; // Add other screens as needed
};
type SessionFeedNavigationProp = StackNavigationProp<
SessionStackParamList,
"Session"
>;
// Define the props for SessionFeed component
interface SessionFeedProps {
navigation: SessionFeedNavigationProp;
}
const SessionFeed: React.FC<SessionFeedProps> = ({ navigation }) => {
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => navigation.push("Session")}>
<SessionCard
playerName="Dean Machine"
location="Family Billiards, San Francisco"
gameType="Straight Pool"
makePercent="34"
medianRun="7.3"
duration="5:03:10"
shotPacing="0:00:26"
imageURL={sampleSessionImage}
sessionName="Dusting off the chalk"
lastPlayed="Today at 2:37pm"
profileImageURL="https://www.pngall.com/wp-content/uploads/5/Profile-PNG-File.png"
locationIconURL="https://www.shutterstock.com/image-vector/blank-map-marker-vector-illustration-260nw-1150566347.jpg"
/>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f0f0f0", // Light gray background
alignItems: "center",
},
});
export default SessionFeed;

View File

@ -3,10 +3,10 @@ import { StyleSheet, View } from "react-native";
import {
graph_data_two_measures,
line_chart_two_y_data,
} from "../../test/mock/charts/mock-data";
import BarGraph from "../component/charts/bar-graph/bar-graph";
import ChartContainer from "../component/charts/container/chart-container";
import LineGraph from "../component/charts/line-graph/line-graph";
} from "../../../test/mock/charts/mock-data";
import BarGraph from "../../component/charts/bar-graph/bar-graph";
import ChartContainer from "../../component/charts/container/chart-container";
import LineGraph from "../../component/charts/line-graph/line-graph";
export default function SessionScreen() {
return (

View File

@ -3193,6 +3193,11 @@
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.21.tgz#debac6becc6b6692da09ec30e705e476a780dfe1"
integrity sha512-eyS2C6McNR8ihUoYfc166O1D8VYVh9KIl0UQPI8/ZJVsStlfSTgeEEh+WXge6+7SFPnZ4ewzEJdSAHH+jzcEfg==
"@react-navigation/elements@^1.3.22":
version "1.3.22"
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.22.tgz#37e25e46ca4715049795471056a9e7e58ac4a14e"
integrity sha512-HYKucs0TwQT8zMvgoZbJsY/3sZfzeP8Dk9IDv4agst3zlA7ReTx4+SROCG6VGC7JKqBCyQykHIwkSwxhapoc+Q==
"@react-navigation/native-stack@^6.9.17":
version "6.9.17"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.17.tgz#4fc370b14be07296423ae8c00940fb002c6001b5"
@ -3218,6 +3223,15 @@
dependencies:
nanoid "^3.1.23"
"@react-navigation/stack@^6.3.21":
version "6.3.21"
resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-6.3.21.tgz#beec1a384969d10c8ddab9978f382fa441182760"
integrity sha512-oh059bD9w6Q7YbcK3POXRHK+bfoafPU9gvunD0MHJGmPVe9bazn5OMNzRYextvB6BfwQy+v3dy76Opf0vIGcNg==
dependencies:
"@react-navigation/elements" "^1.3.22"
color "^4.2.3"
warn-once "^0.1.0"
"@repeaterjs/repeater@^3.0.4":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.5.tgz#b77571685410217a548a9c753aa3cdfc215bfc78"