diff --git a/App.tsx b/App.tsx index 0329d0c..a076846 100644 --- a/App.tsx +++ b/App.tsx @@ -1,11 +1,11 @@ import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Text, View } from 'react-native'; +import StopWatch from './src/components/StopWatch'; export default function App() { return ( - Open up App.tsx to start working on your app! - + ); } @@ -13,8 +13,8 @@ export default function App() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', + backgroundColor: "#0D0916", + alignItems: "center", + justifyContent: "center", }, }); diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx deleted file mode 100644 index 5c7eb74..0000000 --- a/src/StopWatch.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { View } from 'react-native'; - -export default function StopWatch() { - return ( - - - ); -} \ No newline at end of file diff --git a/src/StopWatchButton.tsx b/src/StopWatchButton.tsx deleted file mode 100644 index 8768555..0000000 --- a/src/StopWatchButton.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { View } from 'react-native'; - -export default function StopWatchButton() { - return ( - - - ); -} \ No newline at end of file diff --git a/src/components/StopWatch.tsx b/src/components/StopWatch.tsx new file mode 100644 index 0000000..faa28cf --- /dev/null +++ b/src/components/StopWatch.tsx @@ -0,0 +1,194 @@ +import { FlatList, StyleSheet, Text, View } from 'react-native'; +import StopWatchButton from './StopWatchButton'; +import { useEffect, useState } from 'react'; +import { formatTime } from '../utils/formatTime'; +import { LapItem } from '../types/LapItem'; + +//renderLap function is used to render each lapItem into a row with the formatted time +const renderLap = ({ item }: { item: LapItem }) => { + return ( + + + + {item.index.toString()} + + + + + {formatTime(item.lapTime)} + + + + + {formatTime(item.generalTime)} + + + + + ); +}; + +export default function StopWatch() { + //States: + //startTime, the startTime of the stopwatch, necessary for precise milliseconds display + //time, the time in milliseconds passed from the beginning + //isTimerOn, a boolean state needed to set the timer on/off + //lapItems, a state array needed to add lapItems and display the laps + const [startTime, setStartTime] = useState(null); + const [time, setTime] = useState(0); + const [isTimerOn, setIsTimerOn] = useState(false); + const [lapItems, setLapItems] = useState([]); + + //useEffect is needed here to update the time once the user presses Start or Stop + useEffect(() => { + let interval: number | null = null; + + //Checks if the timer is on: + //If it is on: keep incrementing the time + if(isTimerOn){ + if (startTime === null) { + setStartTime(Date.now() - time); + } + + interval = setInterval(() => { + setTime(Date.now() - startTime!); + }, 1); + } else { + //If the timer is off: stop incrementing the time + setStartTime(null); + } + + //Clear the interval on return + return () => clearInterval(interval!); + }) + + //reset the timer by resetting all time states and laps + function resetTimer(){ + setTime(0); + setIsTimerOn(false); + setLapItems([]); + } + + //function to add a lap item + function addLap(){ + //Gets the latest lap item to get the time and the index, if there's no previous lap item, start at 0 + const newestTime = lapItems.length > 0 ? lapItems[lapItems.length-1].generalTime : 0; + const newestIndex = lapItems.length > 0 ? lapItems[lapItems.length-1].index : 0; + + //Create a new lap item using the latest lap item + //Increment the index and calculate the lap time using the time now - the time at which the previous lap was at + const newLapItem = { + index: newestIndex+1, + lapTime: time-newestTime, + generalTime: time + } + + //Create a new laps array and add the new object + const newLaps = [ + ...lapItems.slice(0, lapItems.length), + newLapItem, + ...lapItems.slice(lapItems.length) + ]; + + //Set the lap items to the new array + setLapItems(newLaps); + } + + return ( + + + {formatTime(time)} + + + {lapItems.length > 0 ? + + + Lap + + + Lap Time + + + Overall Time + + : + null} + + + + + setIsTimerOn(true)} isDisabled={isTimerOn} buttonColor='#62a84f' buttonBorderColor='#326345'/> + setIsTimerOn(false)} isDisabled={!isTimerOn}> + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + justifyContent: "center", + alignItems: "center", + width: "100%", + height: "100%", + }, + stopwatchContainer: { + justifyContent: "flex-end", + alignItems: "center", + height: "30%", + width: "90%", + }, + lapsHeader: { + flexDirection: "row", + width: "100%", + marginBottom: 4, + }, + lapsHeaderLeftSection: { + width: "20%", + }, + lapsHeaderMiddleSection: { + width: "40%", + alignItems: "center", + }, + lapsHeaderRightSection: { + width: "40%", + alignItems: "flex-end", + }, + lapsContainer: { + height: "40%", + width: "80%", + }, + lapItemContainer:{ + flexDirection: "row", + alignItems: "center", + }, + buttonsContainer:{ + justifyContent: "flex-end", + alignItems: "center", + height: "30%", + width: "90%", + }, + buttonsRowContainer: { + flexDirection: "row", + justifyContent: "space-between", + width: "80%", + marginBottom: 32, + }, + stopwatchText: { + color: "#FAFAFA", + fontSize: 48, + }, + text: { + color: "#FAFAFA", + fontSize: 16, + }, +}) \ No newline at end of file diff --git a/src/components/StopWatchButton.tsx b/src/components/StopWatchButton.tsx new file mode 100644 index 0000000..aa82837 --- /dev/null +++ b/src/components/StopWatchButton.tsx @@ -0,0 +1,66 @@ +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { StopWatchButtonProps } from '../types/ButtonProps'; + +export default function StopWatchButton( + { + action, + onPress, + height = 40, + width = 80, + buttonColor = "#6BBDF3", + buttonBorderColor = "#4796D0", + borderRadius = 8, + isDisabled = false, + hitslop = { top: 20, bottom: 20, left: 20, right: 20 }, + ...props + } + : StopWatchButtonProps) { + + //Button title is just the action name capitalized + const buttonTitle = action.substring(0, 1).toUpperCase() + action.substring(1); + + return ( + + + null} + > + {buttonTitle} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + width: "100%", + height: "100%", + }, + button: { + width: "100%", + height: "100%", + justifyContent: "center", + alignItems: "center" + }, + text: { + color: "#FAFAFA", + }, +}); diff --git a/src/types/ButtonProps.ts b/src/types/ButtonProps.ts new file mode 100644 index 0000000..a8d8a57 --- /dev/null +++ b/src/types/ButtonProps.ts @@ -0,0 +1,28 @@ +import { DimensionValue, Insets } from "react-native"; +import { StopwatchButtonActionTypes } from "./StopwatchButtonActionTypes"; + +//The stopwatch button takes in as input the following: +//action: the type of the button, what it does +//onPress: the function which is executes on press +//height: adjustable height if needed +//width: adjustable width if needed +//buttonColor: adjustable button color if needed, default is light blue +//buttonBorderColor: adjustable border color if needed, default is shade of light blue +//isDisabled: boolean to disable the button if needed +//hitslop: adjustable hitslop, default is 20 +export interface StopWatchButtonProps { + action: StopwatchButtonActionTypes + onPress(): void; + + height?: DimensionValue; + width?: DimensionValue; + + buttonColor?: string; + buttonBorderColor?: string; + + borderRadius?: number; + bottomBorderWidth?: number; + + isDisabled?: boolean; + hitslop?: Insets; +} \ No newline at end of file diff --git a/src/types/LapItem.ts b/src/types/LapItem.ts new file mode 100644 index 0000000..cc71e86 --- /dev/null +++ b/src/types/LapItem.ts @@ -0,0 +1,6 @@ +//LapItem contains the field needed to display the laps +export type LapItem = { + index: number; + lapTime: number; + generalTime: number; +} \ No newline at end of file diff --git a/src/types/StopwatchButtonActionTypes.ts b/src/types/StopwatchButtonActionTypes.ts new file mode 100644 index 0000000..ef55fcc --- /dev/null +++ b/src/types/StopwatchButtonActionTypes.ts @@ -0,0 +1,6 @@ +//The different types of buttons +export type StopwatchButtonActionTypes = + | "start" + | "stop" + | "reset" + | "lap"; \ No newline at end of file diff --git a/src/utils/formatTime.tsx b/src/utils/formatTime.tsx new file mode 100644 index 0000000..4ff576f --- /dev/null +++ b/src/utils/formatTime.tsx @@ -0,0 +1,9 @@ +//Function which formats the time from milliseconds to HH:MM:SS.(mls) +export function formatTime(millis: number): string { + const hours = Math.floor((millis/1000)/3600); + const minutes = Math.floor(((millis/1000) - (hours*3600))/60); + const seconds = Math.floor((millis/1000)%60); + const millisFormatted = millis%1000; + + return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}.${millisFormatted.toString().padStart(3, "0")}`; +} \ No newline at end of file diff --git a/tests/Stopwatch.test.js b/tests/Stopwatch.test.js index d5e9f1f..584f38a 100644 --- a/tests/Stopwatch.test.js +++ b/tests/Stopwatch.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react-native'; -import Stopwatch from '../src/Stopwatch'; +import Stopwatch from '../src/components/StopWatch'; describe('Stopwatch', () => { test('renders initial state correctly', () => {