diff --git a/src/component/video/camera.tsx b/src/component/recording/camera.tsx similarity index 99% rename from src/component/video/camera.tsx rename to src/component/recording/camera.tsx index 8dbb6cb..eca3a42 100644 --- a/src/component/video/camera.tsx +++ b/src/component/recording/camera.tsx @@ -155,7 +155,7 @@ export default function CameraScreen({ const onMediaCaptured = useCallback( (media: PhotoFile | VideoFile) => { console.log(`Media captured! ${JSON.stringify(media)}`); - navigation.push("SaveVideo", { + navigation.push("SaveRecording", { videoId: uploadManager?.videoId, ...params, }); diff --git a/src/component/video/capture-button.tsx b/src/component/recording/capture-button.tsx similarity index 100% rename from src/component/video/capture-button.tsx rename to src/component/recording/capture-button.tsx diff --git a/src/component/video/constants.ts b/src/component/recording/constants.ts similarity index 100% rename from src/component/video/constants.ts rename to src/component/recording/constants.ts diff --git a/src/component/video/is-foreground.tsx b/src/component/recording/is-foreground.tsx similarity index 100% rename from src/component/video/is-foreground.tsx rename to src/component/recording/is-foreground.tsx diff --git a/src/component/video/use-video-details.tsx b/src/component/recording/use-recording-details.tsx similarity index 97% rename from src/component/video/use-video-details.tsx rename to src/component/recording/use-recording-details.tsx index 6b323dd..d7f9d07 100644 --- a/src/component/video/use-video-details.tsx +++ b/src/component/recording/use-recording-details.tsx @@ -54,7 +54,7 @@ function useDropdown( }; } -export const useVideoDetails = ({ +export const useRecordingDetails = ({ params: { mode, videoId, @@ -118,7 +118,7 @@ export const useVideoDetails = ({ } // Navigate if starting flow, terminateUploadStream if completing flow - if (mode === "start-video") { + if (mode === "start-recording") { const params: VideoFlowInputParams = { sessionName, gameType: gameType.value, diff --git a/src/component/video-card/video-card-footer.tsx b/src/component/video-card/video-card-footer.tsx new file mode 100644 index 0000000..f1863eb --- /dev/null +++ b/src/component/video-card/video-card-footer.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { StyleSheet, Text, View } from "react-native"; + +const VideoCardFooter = ({ videoName, lastPlayed }) => { + return ( + + {videoName} + {lastPlayed} + + ); +}; + +const styles = StyleSheet.create({ + videoName: { + fontSize: 18, + paddingTop: 5, + marginHorizontal: 16, + }, + videoDatetime: { + fontSize: 10, + color: "#A3A3A3", + marginHorizontal: 16, + }, +}); + +export default VideoCardFooter; diff --git a/src/component/video-card/video-card-header.tsx b/src/component/video-card/video-card-header.tsx new file mode 100644 index 0000000..179e9f0 --- /dev/null +++ b/src/component/video-card/video-card-header.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import { Image, StyleSheet, Text, View } from "react-native"; + +const VideoCardHeader = ({ + playerName, + location, + gameType, + locationIconURL, + profileImageURL, +}) => { + return ( + + + + {playerName} + + + {location} + + {gameType} + + + ); +}; + +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 VideoCardHeader; diff --git a/src/component/video-card/video-card-stat.tsx b/src/component/video-card/video-card-stat.tsx new file mode 100644 index 0000000..380853f --- /dev/null +++ b/src/component/video-card/video-card-stat.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { StyleSheet, Text, View } from "react-native"; + +const VideoCardStat = ({ videoStat, displayName }) => { + return ( + + {displayName} + {videoStat} + + ); +}; + +const styles = StyleSheet.create({ + statItem: { + fontSize: 10, + textAlign: "center", + color: "#666", + }, + statValue: { + fontSize: 22, + fontWeight: "400", + }, +}); + +export default VideoCardStat; diff --git a/src/component/video-card/video-card-stats-row-container.tsx b/src/component/video-card/video-card-stats-row-container.tsx new file mode 100644 index 0000000..fcd2ea5 --- /dev/null +++ b/src/component/video-card/video-card-stats-row-container.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { StyleSheet, View } from "react-native"; +import VideoCardStat from "./video-card-stat"; + +const VideoCardStatsRowContainer = ({ + makePercent, + medianRun, + duration, + shotPacing, +}) => { + const stats = [ + { displayName: "Make Percent", videoStat: makePercent }, + { displayName: "Median Run", videoStat: medianRun }, + { displayName: "Time Played", videoStat: duration }, + { displayName: "Shot Pacing", videoStat: shotPacing }, + ]; + + return ( + + {stats.map((stat, index) => ( + + + {index < stats.length - 1 && } + + ))} + + ); +}; + +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 VideoCardStatsRowContainer; diff --git a/src/component/video-card/video-card.tsx b/src/component/video-card/video-card.tsx new file mode 100644 index 0000000..331b182 --- /dev/null +++ b/src/component/video-card/video-card.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { Image, StyleSheet, View } from "react-native"; +import VideoCardFooter from "./video-card-footer"; +import VideoCardHeader from "./video-card-header"; +import VideoCardStatsRowContainer from "./video-card-stats-row-container"; + +const VideoCard = ({ + playerName, + location, + gameType, + makePercent, + medianRun, + duration, + shotPacing, + videoName, + lastPlayed, + imageURL, + profileImageURL, + locationIconURL, +}) => { + return ( + + + + + + + ); +}; + +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 VideoCard; diff --git a/src/navigation/tab-navigator.tsx b/src/navigation/tab-navigator.tsx index 8c6f0eb..83b4636 100644 --- a/src/navigation/tab-navigator.tsx +++ b/src/navigation/tab-navigator.tsx @@ -1,11 +1,11 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { Image } from "react-native"; -import CameraScreen from "../component/video/camera"; +import CameraScreen from "../component/recording/camera"; import Profile from "../screens/profile"; -import Session from "../screens/session-stack/session"; -import SessionFeed from "../screens/session-stack/session-feed"; -import VideoDetails from "../screens/video-stack/video-details"; +import RecordingDetails from "../screens/recording-stack/recording-details"; +import Video from "../screens/video-stack/video-details"; +import VideoFeed from "../screens/video-stack/video-feed"; import { tabIconColors } from "../styles"; import Icon from "../assets/icons/favicon.png"; @@ -15,34 +15,34 @@ const RecordStack = createNativeStackNavigator(); // tabBarIcon configuration should live on separate file and contain all logic/icons/rendering for the Tabs const tabIcons = { - SessionStack: , VideoStack: , + RecordingStack: , Profile: , }; -function VideoTabStack() { +function RecordingTabStack() { return ( ); } -function SessionTabStack() { +function VideoTabStack() { return ( - - + + ); } @@ -67,14 +67,14 @@ export default function Tabs(): React.JSX.Element { }, })} > - + Keyboard.dismiss()}> + + + {mode === "start-recording" ? "Record Session" : "Save Session"} + + + Session Name + + Game Type + + Table Size + + + + + {mode === "start-recording" && ( + navigation.goBack()} + > + Back + + )} + + {loading ? ( + + ) : ( + + {mode === "start-recording" ? "Next" : "Save"} + + )} + + + + + ); +} diff --git a/src/screens/video-stack/styles.ts b/src/screens/recording-stack/styles.ts similarity index 100% rename from src/screens/video-stack/styles.ts rename to src/screens/recording-stack/styles.ts diff --git a/src/screens/session-stack/session.tsx b/src/screens/session-stack/session.tsx deleted file mode 100644 index a1178bb..0000000 --- a/src/screens/session-stack/session.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from "react"; -import VideoDetails from "./video-details"; - -export default function SessionScreen({ navigation }) { - return ; -} diff --git a/src/screens/session-stack/video-details.tsx b/src/screens/session-stack/video-details.tsx deleted file mode 100644 index 70cc226..0000000 --- a/src/screens/session-stack/video-details.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from "react"; -import { ScrollView, StyleSheet, Text, View } from "react-native"; -import { - graph_data_two_measures, - mock_session_details, -} from "../../../test/mock/charts/mock-data"; -import BarGraph from "../../component/charts/bar-graph/bar-graph"; -import ChartContainer from "../../component/charts/container/chart-container"; -import ImageWithFallback from "../../component/image/image-with-fallback"; -import StatList from "../../component/video-details/video-stat-list"; - -// TODO: #134 remove Session when data piped through -// Splash should be an asset we use if an Image failed to load -import Session from "../../assets/sample_session.png"; -import Splash from "../../assets/splash.png"; -import BackHeader from "../../component/headers/back-header"; -import { borders, colors } from "../../styles"; - -export default function VideoDetails({ navigation }) { - // TODO: #134 Remove mock destructure block when data is piped through from BE - const { - sessionTitle, - date, - timePlayed, - medianRun, - makeRate, - shotPacing, - gameType, - notes, - } = mock_session_details; - - const leftColumnStats = [ - { title: "TIME PLAYED", value: timePlayed }, - { title: "MAKE RATE", value: makeRate }, - ]; - - const rightColumnStats = [ - { title: "MEDIAN RUN", value: medianRun }, - { title: "SHOT PACING", value: shotPacing }, - ]; - // End mock destructure - - return ( - <> - - - - {sessionTitle} - {date} - - - - - - - - - Game Type - {gameType} - Notes - {notes} - - - - - - ); -} - -// TODO: #130 scaled styles + maintain consistency with video-feed styles -const styles = StyleSheet.create({ - scrollContainer: { - backgroundColor: "white", // TODO #125 -- this color should not be set but implicitly inherit from theme - paddingBottom: 20, // guarantees some space at bottom of scrollable views - }, - headerSection: { - paddingHorizontal: 38, - paddingTop: 17, - paddingBottom: 14, - }, - header: { - fontSize: 24, - fontWeight: "bold", - }, - image: { - width: "100%", - height: 248, - }, - statsContainer: { - flexDirection: "row", - justifyContent: "space-between", - paddingLeft: 45, - paddingVertical: 42, - }, - statColumn: { - flex: 1, - padding: 5, - }, - horizontalDivider: { - width: "95%", - alignSelf: "center", - ...borders.dottedBottomBorder, - }, - textContainer: { - paddingHorizontal: 38, - }, - title: { - fontSize: 16, - color: colors.text.greyText, - paddingTop: 16, - }, - text: { - fontSize: 18, - textAlign: "left", - paddingTop: 6, - fontWeight: "400", - }, -}); diff --git a/src/screens/video-stack/video-details.tsx b/src/screens/video-stack/video-details.tsx index 324bb2c..70cc226 100644 --- a/src/screens/video-stack/video-details.tsx +++ b/src/screens/video-stack/video-details.tsx @@ -1,94 +1,129 @@ import React from "react"; +import { ScrollView, StyleSheet, Text, View } from "react-native"; import { - ActivityIndicator, - Keyboard, - Text, - TextInput, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from "react-native"; -import DropDownPicker from "react-native-dropdown-picker"; -import { useVideoDetails } from "../../component/video/use-video-details"; -import { globalInputStyles } from "../../styles"; -import { recordStyles as styles } from "./styles"; + graph_data_two_measures, + mock_session_details, +} from "../../../test/mock/charts/mock-data"; +import BarGraph from "../../component/charts/bar-graph/bar-graph"; +import ChartContainer from "../../component/charts/container/chart-container"; +import ImageWithFallback from "../../component/image/image-with-fallback"; +import StatList from "../../component/video-details/video-stat-list"; -export default function VideoDetails({ navigation, route }): React.JSX.Element { - const { mode } = route.params; +// TODO: #134 remove Session when data piped through +// Splash should be an asset we use if an Image failed to load +import Session from "../../assets/sample_session.png"; +import Splash from "../../assets/splash.png"; +import BackHeader from "../../component/headers/back-header"; +import { borders, colors } from "../../styles"; + +export default function VideoDetails({ navigation }) { + // TODO: #134 Remove mock destructure block when data is piped through from BE const { - sessionName, - setSessionName, + sessionTitle, + date, + timePlayed, + medianRun, + makeRate, + shotPacing, gameType, - tableSize, - handleSubmit, - loading, - } = useVideoDetails({ params: route.params, navigation }); + notes, + } = mock_session_details; - const dropDownStyles = { - style: globalInputStyles.dropdownStyle, - }; + const leftColumnStats = [ + { title: "TIME PLAYED", value: timePlayed }, + { title: "MAKE RATE", value: makeRate }, + ]; + + const rightColumnStats = [ + { title: "MEDIAN RUN", value: medianRun }, + { title: "SHOT PACING", value: shotPacing }, + ]; + // End mock destructure return ( - Keyboard.dismiss()}> - - - {mode === "start-video" ? "Record Session" : "Save Session"} - - - Session Name - - Game Type - - Table Size - + <> + + + + {sessionTitle} + {date} - - - {mode === "start-video" && ( - navigation.goBack()} - > - Back - - )} - - {loading ? ( - - ) : ( - - {mode === "start-video" ? "Next" : "Save"} - - )} - + + + + - - + + + Game Type + {gameType} + Notes + {notes} + + + + + ); } + +// TODO: #130 scaled styles + maintain consistency with video-feed styles +const styles = StyleSheet.create({ + scrollContainer: { + backgroundColor: "white", // TODO #125 -- this color should not be set but implicitly inherit from theme + paddingBottom: 20, // guarantees some space at bottom of scrollable views + }, + headerSection: { + paddingHorizontal: 38, + paddingTop: 17, + paddingBottom: 14, + }, + header: { + fontSize: 24, + fontWeight: "bold", + }, + image: { + width: "100%", + height: 248, + }, + statsContainer: { + flexDirection: "row", + justifyContent: "space-between", + paddingLeft: 45, + paddingVertical: 42, + }, + statColumn: { + flex: 1, + padding: 5, + }, + horizontalDivider: { + width: "95%", + alignSelf: "center", + ...borders.dottedBottomBorder, + }, + textContainer: { + paddingHorizontal: 38, + }, + title: { + fontSize: 16, + color: colors.text.greyText, + paddingTop: 16, + }, + text: { + fontSize: 18, + textAlign: "left", + paddingTop: 6, + fontWeight: "400", + }, +}); diff --git a/src/screens/session-stack/session-feed.tsx b/src/screens/video-stack/video-feed.tsx similarity index 57% rename from src/screens/session-stack/session-feed.tsx rename to src/screens/video-stack/video-feed.tsx index 78cae9c..17380de 100644 --- a/src/screens/session-stack/session-feed.tsx +++ b/src/screens/video-stack/video-feed.tsx @@ -1,28 +1,28 @@ 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"; +import sampleVideoImage from "../../assets/sample_session.png"; +import VideoCard from "../../component/video-card/video-card"; // Define the types for your navigation stack -type SessionStackParamList = { - Session: undefined; // Add other screens as needed +type VideoStackParamList = { + Video: undefined; // Add other screens as needed }; -type SessionFeedNavigationProp = StackNavigationProp< - SessionStackParamList, - "Session" +type VideoFeedNavigationProp = StackNavigationProp< + VideoStackParamList, + "Video" >; -// Define the props for SessionFeed component -interface SessionFeedProps { - navigation: SessionFeedNavigationProp; +// Define the props for VideoFeed component +interface VideoFeedProps { + navigation: VideoFeedNavigationProp; } -const SessionFeed: React.FC = ({ navigation }) => { +const VideoFeed: React.FC = ({ navigation }) => { return ( - navigation.push("Session")}> - navigation.push("Video")}> + = ({ navigation }) => { medianRun="7.3" duration="5:03:10" shotPacing="0:00:26" - imageURL={sampleSessionImage} - sessionName="Dusting off the chalk" + imageURL={sampleVideoImage} + videoName="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" @@ -49,4 +49,4 @@ const styles = StyleSheet.create({ }, }); -export default SessionFeed; +export default VideoFeed;