Skip to content
This repository was archived by the owner on Nov 10, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';

import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
import StopWatch from "./src/StopWatch";
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StopWatch />
<StatusBar style="auto" />
</View>
);
Expand All @@ -13,8 +13,6 @@ export default function App() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: "#fff",
},
});
18 changes: 15 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@
"dependencies": {
"expo": "~49.0.15",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.6",
"jest": "^29.2.1",
"jest-expo": "~49.0.0",
"jest": "^29.2.1"
"react": "18.2.0",
"react-native": "0.72.6"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@testing-library/react-native": "^12.4.3",
"@tsconfig/react-native": "^3.0.2",
"@types/jest": "^29.5.11",
"@types/node": "^20.11.6",
"@types/react": "~18.2.14",
"@types/react-native": "^0.73.0",
"@types/react-test-renderer": "^18.0.7",
"typescript": "^5.3.3"
},
Expand Down
80 changes: 80 additions & 0 deletions src/Components/LapsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ScrollView, StyleSheet, Text, View } from "react-native";
// Import utility function for time display
import { displayTime } from "../util/displayTime";

// Define TypeScript interface for LapsProps
interface LapsProps {
laps: number[] | null; // Array of lap times or null
}

// Define TypeScript interface for LapsEntryProps
interface LapsEntryProps {
time: number; // Time for each lap
idx: number; // Index of the lap
}

// Functional component to display each lap entry
const LapsEntry = ({ time, idx }: LapsEntryProps) => {
return (
<View>
{/* Display the lap number and time */}
<Text style={styles.lapsEntry} key={idx}>{`Lap #${idx + 1}: ${displayTime(
time
)}`}</Text>
{/* Separator line */}
<View style={{ flex: 1, height: 1, backgroundColor: "black" }} />
</View>
);
};

// Functional component to display the list of laps
export const LapsList = ({ laps }: LapsProps) => {
// Using ScrollView for the list of laps
return (
<View style={styles.lapsListContainer}>
{/* Title for the laps section */}
<Text style={styles.title}>Laps</Text>
{/* Check if laps exist and render them */}
{laps && (
<ScrollView
style={styles.lapsList}
contentContainerStyle={styles.lapsListContent}
>
<View testID="lap-list">
{/* Map through each lap and render a LapsEntry component */}
{laps.map((time, idx) => (
<LapsEntry time={time} idx={idx} key={idx} />
))}
</View>
</ScrollView>
)}
</View>
);
};

const styles = StyleSheet.create({
lapsListContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
lapsEntry: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: "#ccc",
fontSize: 16,
color: "#333",
width: "100%",
},
lapsListContent: { paddingBottom: 50 },
lapsList: {
paddingHorizontal: 30,
width: "100%",
},
title: {
fontSize: 30,
fontWeight: "bold",
color: "#333",
marginBottom: 10,
},
});
144 changes: 141 additions & 3 deletions src/StopWatch.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,146 @@
import { View } from 'react-native';
// Import necessary components from react-native and react
import { View, StyleSheet, Text } from "react-native";
import { useState, useEffect } from "react";
// Import custom components and utility functions
import StopWatchButton from "./StopWatchButton";
import { displayTime } from "./util/displayTime";
import { LapsList } from "./Components/LapsList";

// Define the StopWatch component
export default function StopWatch() {
// State for managing the stopwatch status
const [stopWatchState, setStopWatchState] = useState("begin"); // Can be: begin, running, paused, stopped
const [elapsedTime, setElapsedTime] = useState(0); // Will store the elapsed time in seconds
const [laps, setLaps] = useState(null); // State to store lap times

// useEffect hook to handle the stopwatch functionality
useEffect(() => {
let intervalObj: ReturnType<typeof setInterval> | null;
if (stopWatchState == "running") {
// Set an interval to increment the elapsed time every second
intervalObj = setInterval(() => {
setElapsedTime((prevElapsedTime) => prevElapsedTime + 1);
}, 1000);
}
return () => {
// Clear the interval when the component unmounts or stopWatchState changes
if (intervalObj) {
clearInterval(intervalObj);
}
};
}, [stopWatchState, elapsedTime]);

// Function to handle starting the stopwatch
const handleStart = () => {
setStopWatchState("running");
};

// Function to handle stopping the stopwatch
const handleStop = () => {
setStopWatchState("stopped");
};

// Function to handle pausing the stopwatch
const handlePause = () => {
setStopWatchState("paused");
};

// Function to handle resetting the stopwatch
const handleReset = () => {
setStopWatchState("begin");
setElapsedTime(0);
setLaps(null);
};

// Function to handle recording a lap
const handleLap = () => {
if (stopWatchState == "running") {
laps
? setLaps((prevState) => [...prevState, elapsedTime])
: setLaps([elapsedTime]);
}
};

// Render the stopwatch UI
return (
<View >
<View style={styles.stopWatchContainer}>
<View style={styles.timeContainer}>
{stopWatchState !== "stopped" && ( // Only display the time if stopwatch is not stopped
<Text style={styles.timeText}>{displayTime(elapsedTime)}</Text>
)}
</View>
<View style={styles.buttonContainer}>
{/* Render buttons based on the current state of the stopwatch */}
{(stopWatchState === "begin" || stopWatchState === "stopped") && (
<StopWatchButton
label={"Start"}
handlePress={handleStart}
backgroundColor={"#64cc6e"}
/>
)}
{stopWatchState === "running" && (
<StopWatchButton
label={"Pause"}
handlePress={handlePause}
backgroundColor={"#f0b754"}
/>
)}

{stopWatchState === "paused" && (
<StopWatchButton
label={"Resume"}
handlePress={handleStart}
backgroundColor={"#64cc6e"}
/>
)}
{stopWatchState === "running" && (
<StopWatchButton
label={"Stop"}
handlePress={handleStop}
backgroundColor={"#fa4c25"}
/>
)}

<StopWatchButton
label={"Reset"}
handlePress={handleReset}
backgroundColor={"#64cc6e"}
/>
<StopWatchButton
label={"Lap"}
handlePress={handleLap}
backgroundColor={"#5476f0"}
/>
</View>

{/* Render the list of laps */}
<LapsList laps={laps} />
</View>
);
}
}

// StyleSheet for styling the component
const styles = StyleSheet.create({
stopWatchContainer: {
flex: 1,
paddingBottom: 40,
},
timeContainer: {
flex: 0.6,
marginTop: 30,
alignItems: "center",
justifyContent: "center",
},
timeText: {
fontSize: 50,
fontWeight: "bold",
justifyContent: "center",
textAlign: "center",
color: "#333",
},
buttonContainer: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 20,
},
});
40 changes: 36 additions & 4 deletions src/StopWatchButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
import { View } from 'react-native';
import { View, TouchableOpacity, StyleSheet, Text } from "react-native";
import { useContext, useState, useEffect } from "react";

export default function StopWatchButton() {
interface ButtonProps {
handlePress: () => void; // Function to be called when the button is pressed
label: string; // Text label for the button
backgroundColor: string; // Background color for the button (hex)
}

// Functional component for a customizable stopwatch button
export default function StopWatchButton({
handlePress,
label,
backgroundColor,
}: ButtonProps) {
return (
<View >
<View style={[styles.btn, { backgroundColor: backgroundColor }]}>
{/* TouchableOpacity is used to make the View respond to touches */}
<TouchableOpacity onPress={handlePress}>
{/* Text of the button */}
<Text style={styles.btnLabel}>{label}</Text>
</TouchableOpacity>
</View>
);
}
}

const styles = StyleSheet.create({
btn: {
padding: 10,
borderRadius: 5,
margin: 8,
backgroundColor: "#64cc6e", // Default background color, overridden by props if provided
},
btnLabel: {
fontSize: 20,
fontWeight: "bold",
color: "#fff",
textAlign: "center",
},
});
12 changes: 12 additions & 0 deletions src/util/displayTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function displayTime(elapsedTime: number): string {
// Calculations (elapsedTime is in seconds)
// Hours
const hours = Math.floor(elapsedTime / 3600);
// Minutes
const minutes = Math.floor((elapsedTime % 3600) / 60);
// Seconds
const seconds = Math.floor(elapsedTime % 60);
return `${hours.toString().padStart(2, "0")}:${minutes
.toString()
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
}
Loading