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:
commit
cef4119afa
@ -30,6 +30,7 @@
|
|||||||
"@react-navigation/bottom-tabs": "^6.5.11",
|
"@react-navigation/bottom-tabs": "^6.5.11",
|
||||||
"@react-navigation/native": "^6.1.9",
|
"@react-navigation/native": "^6.1.9",
|
||||||
"@react-navigation/native-stack": "^6.9.17",
|
"@react-navigation/native-stack": "^6.9.17",
|
||||||
|
"@react-navigation/stack": "^6.3.21",
|
||||||
"@types/react": "~18.2.14",
|
"@types/react": "~18.2.14",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||||
"@typescript-eslint/parser": "^6.17.0",
|
"@typescript-eslint/parser": "^6.17.0",
|
||||||
|
BIN
src/assets/sample_session.png
Normal file
BIN
src/assets/sample_session.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 695 KiB |
26
src/component/session-card/session-card-footer.tsx
Normal file
26
src/component/session-card/session-card-footer.tsx
Normal 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;
|
68
src/component/session-card/session-card-header.tsx
Normal file
68
src/component/session-card/session-card-header.tsx
Normal 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;
|
25
src/component/session-card/session-card-stat.tsx
Normal file
25
src/component/session-card/session-card-stat.tsx
Normal 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;
|
@ -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;
|
65
src/component/session-card/session-card.tsx
Normal file
65
src/component/session-card/session-card.tsx
Normal 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;
|
@ -3,7 +3,8 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|||||||
import { Image } from "react-native";
|
import { Image } from "react-native";
|
||||||
import CameraScreen from "../component/video/camera";
|
import CameraScreen from "../component/video/camera";
|
||||||
import Profile from "../screens/profile";
|
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 VideoDetails from "../screens/video-stack/video-details";
|
||||||
import { tabIconColors } from "../styles";
|
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
|
// tabBarIcon configuration should live on separate file and contain all logic/icons/rendering for the Tabs
|
||||||
const tabIcons = {
|
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 }} />,
|
VideoStack: <Image source={Icon} style={{ width: 20, height: 20 }} />,
|
||||||
Profile: <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
|
* Functional component creating a tab navigator with called
|
||||||
* Uses React Navigation's Tab.Navigator. Customizes tab bar appearance and icons.
|
* 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
|
<Tab.Screen
|
||||||
name="Session"
|
name="SessionStack"
|
||||||
component={Session}
|
component={SessionTabStack}
|
||||||
options={{ tabBarLabel: "Session" }}
|
options={{ tabBarLabel: "Session" }}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
|
52
src/screens/session-stack/session-feed.tsx
Normal file
52
src/screens/session-stack/session-feed.tsx
Normal 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;
|
@ -3,10 +3,10 @@ import { StyleSheet, View } from "react-native";
|
|||||||
import {
|
import {
|
||||||
graph_data_two_measures,
|
graph_data_two_measures,
|
||||||
line_chart_two_y_data,
|
line_chart_two_y_data,
|
||||||
} from "../../test/mock/charts/mock-data";
|
} from "../../../test/mock/charts/mock-data";
|
||||||
import BarGraph from "../component/charts/bar-graph/bar-graph";
|
import BarGraph from "../../component/charts/bar-graph/bar-graph";
|
||||||
import ChartContainer from "../component/charts/container/chart-container";
|
import ChartContainer from "../../component/charts/container/chart-container";
|
||||||
import LineGraph from "../component/charts/line-graph/line-graph";
|
import LineGraph from "../../component/charts/line-graph/line-graph";
|
||||||
|
|
||||||
export default function SessionScreen() {
|
export default function SessionScreen() {
|
||||||
return (
|
return (
|
14
yarn.lock
14
yarn.lock
@ -3193,6 +3193,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.21.tgz#debac6becc6b6692da09ec30e705e476a780dfe1"
|
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.21.tgz#debac6becc6b6692da09ec30e705e476a780dfe1"
|
||||||
integrity sha512-eyS2C6McNR8ihUoYfc166O1D8VYVh9KIl0UQPI8/ZJVsStlfSTgeEEh+WXge6+7SFPnZ4ewzEJdSAHH+jzcEfg==
|
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":
|
"@react-navigation/native-stack@^6.9.17":
|
||||||
version "6.9.17"
|
version "6.9.17"
|
||||||
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.17.tgz#4fc370b14be07296423ae8c00940fb002c6001b5"
|
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.17.tgz#4fc370b14be07296423ae8c00940fb002c6001b5"
|
||||||
@ -3218,6 +3223,15 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
nanoid "^3.1.23"
|
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":
|
"@repeaterjs/repeater@^3.0.4":
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.5.tgz#b77571685410217a548a9c753aa3cdfc215bfc78"
|
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.5.tgz#b77571685410217a548a9c753aa3cdfc215bfc78"
|
||||||
|
Loading…
Reference in New Issue
Block a user