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
8 changes: 4 additions & 4 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import React from 'react';
import { StyleSheet, 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>
<StatusBar style="auto" />
<StopWatch />
</View>
);
}
Expand Down
86 changes: 13 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,24 @@
# Technical Instructions
1. Fork this repo to your local Github account.
2. Create a new branch to complete all your work in.
3. Test your work using the provided tests
4. Create a Pull Request against the Shopify Main branch when you're done and all tests are passing
# Tom Miller's Stopwatch

# Project Overview
The goal of this project is to implement a stopwatch application using React Native and TypeScript. The stopwatch should have the following functionality:
## Features

- Start the stopwatch to begin counting time.
- Stop the stopwatch to pause the timer.
- Displays Laps when a button is pressed.
- Reset the stopwatch to zero.
My stopwatch app has the following features:
- Start button to start/resume the timer and a stop button to pause it.
- Reset button to reset the times and laps to 0.
- Lap button to record the laps easily along with minimum and maximum laps colored in red and green respectively to identify your slowest and fastest times easily.

You will be provided with a basic project structure that includes the necessary files and dependencies. Your task is to write the code to implement the stopwatch functionality and ensure that it works correctly.

## Project Setup
To get started with the project, follow these steps:
<img src="https://github.com/HamsterStack/eng-intern-assessment-react-native/assets/108938294/f1c2f8de-d75b-4b12-8a85-e05274f57adf" width="200">

1. Clone the project repository to your local development environment.

2. Install the required dependencies by running npm install in the project directory.

3. Familiarize yourself with the project structure. The main files you will be working with are:
- /App.tsx: The main component that renders the stopwatch and handles its functionality.
- src/Stopwatch.tsx: A separate component that represents the stopwatch display.
- src/StopwatchButton.tsx: A separate component that represents the start, stop, and reset buttons.

4. Review the existing code in the above files to understand the initial structure and component hierarchy.
## Tests

## Project Goals
Your specific goals for this project are as follows:
I added a few extra tests along with the original ones, such as a test to see that the maximum and minimum laps are colored correctly
and a test to ensure you can't add laps while paused.

1. Implement the stopwatch functionality:
- The stopwatch should start counting when the user clicks the start button.
- The stopwatch should stop counting when the user clicks the stop button.
- The stopwatch should reset to zero when the user clicks the reset button.
- The stopwatch should record and display laps when user clicks the lap button.
## Design Choices

2. Ensure code quality:
- Write clean, well-structured, and maintainable code.
- Follow best practices and adhere to the React and TypeScript coding conventions.
- Pay attention to code readability, modularity, and performance.

3. Test your code:
- Run the application and test the stopwatch functionality to ensure it works correctly.
- Verify that the stopwatch starts, stops, resets, and records laps as expected.

4. Code documentation:
- Document your code by adding comments and explanatory notes where necessary.
- Provide clear explanations of the implemented functionality and any important details.

5. Version control:
- Use Git for version control. Commit your changes regularly and push them to a branch in your forked repository.

6. Create a Pull Request:
- Once you have completed the project goals, create a pull request to merge your changes into the main repository.
- Provide a clear description of the changes made and any relevant information for the code review.

## Getting Started
To start working on the project, follow these steps:

1. Clone the repository to your local development environment.

2. Install the required dependencies by running npm install in the project directory.

3. Open the project in your preferred code editor.

4. Review the existing code in the src directory to understand the initial structure and component hierarchy.

5. Implement the stopwatch functionality by modifying the necessary components (App.tsx, Stopwatch.tsx, StopwatchButton.tsx).

6. Run the application using npm start and test the stopwatch functionality.

7. Commit your changes regularly and push them to a branch in your forked repository.

8. Once you have completed the project goals, create a pull request to merge your changes into the main repository.

## Resources
Here are some resources that may be helpful during your work on this project:

- [TypeScript Documentation](https://www.typescriptlang.org/docs/) - Official documentation for TypeScript, offering guidance on TypeScript features and usage.

- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) - Explore React Testing Library, a popular testing library for React applications.
- I created a useStopWatch hook to help separate all the logic of the stopwatch and keep all components clean and simple.
- I used Date objects instead of setInterval due to it being less accurate, especially for something where every second is important like a stopwatch.
118 changes: 114 additions & 4 deletions src/StopWatch.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,118 @@
import { View } from 'react-native';
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import StopwatchButton from './StopWatchButton';
import { useStopWatch } from './hooks/useStopWatch';

export default function StopWatch() {
function formatNumber(num: number) {
return String(num).padStart(2, '0');
}

const formatTime = (milliseconds: number) => {
const hours = formatNumber(Math.floor(milliseconds / 3600000));
const minutes = formatNumber(Math.floor((milliseconds % 3600000) / 60000));
const seconds = formatNumber(Math.floor((milliseconds % 60000) / 1000));
const centiseconds = formatNumber(Math.floor((milliseconds % 1000) / 10));

return `${hours}:${minutes}:${seconds}.${centiseconds}`;
};

export const lapColors = {
default: 'black',
minLap: 'red',
maxLap: '#5ab068'
};

export default function Stopwatch() {

const stopwatch = useStopWatch();
return (
<View >

<View style={styles.container}>
<View style={styles.contentContainer}>
<Text style={styles.time}>{formatTime(stopwatch.milliseconds)}</Text>
<StopwatchButton
onResume={stopwatch.resume}
onReset={stopwatch.reset}
onLap={stopwatch.lap}
onPause={stopwatch.pause}
isPaused={stopwatch.isPaused}
/>

<ScrollView
style={styles.lapScrollView}
contentContainerStyle={styles.lapScrollViewContent}
testID='lap-list'
>

{stopwatch.laps.slice().reverse().map((lap, index) => {
let lapColor = lapColors.default;

if (stopwatch.laps.length >= 3) {
if (lap === stopwatch.minLapTime) {
lapColor = lapColors.minLap;
}
else if (lap === stopwatch.maxLapTime) {
lapColor = lapColors.maxLap;
}
}

return (
<React.Fragment key={index}>
<View style={styles.lapContentContainer}>
<Text style={[styles.lap, { color: lapColor }]} testID={`lap-${index}`}>
Lap {stopwatch.laps.length - index}
</Text>
<Text style={[styles.lap, { color: lapColor }]} testID={`time-${index}`}>
{formatTime(lap)}
</Text>
</View>
<View style={styles.divider} />
</React.Fragment>
);
})}
</ScrollView>
</View>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
contentContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
lapContentContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
width:'75%',
},
time: {
fontSize: 56,
fontWeight: '500',
marginBottom: 20,
minWidth:320,
},
lapScrollView: {
maxHeight: 210,
width: '100%',
marginTop:'10%',
},
lapScrollViewContent: {
alignItems: 'center',
},
lap: {
fontSize: 18,
marginTop: 12,
},
divider: {
borderBottomColor: 'lightgray',
borderBottomWidth: 1,
width: '75%',
marginTop:6,
}
});
22 changes: 18 additions & 4 deletions src/StopWatchButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { View } from 'react-native';

export default function StopWatchButton() {
import React from 'react';
import { Button, View } from 'react-native';

export default function StopwatchButton({ onResume, onReset, onLap, onPause, isPaused }: {
onReset: () => void;
onLap: () => void;
onPause: () => void;
onResume: () => void;
isPaused: boolean;
}) {

return (
<View >
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
<Button testID='start-button' title="Start" onPress={onResume} disabled={!isPaused} />
<Button testID='stop-button' title="Stop" onPress={onPause} disabled={isPaused} />
<Button testID='reset-button' title="Reset" onPress={onReset} />
<Button testID='lap-button' title="Lap" onPress={onLap} />
</View>
);
}
}

118 changes: 118 additions & 0 deletions src/components/StopWatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import StopwatchButton from './StopWatchButton';
import { useStopWatch } from '../hooks/useStopWatch';

function formatNumber(num: number) {
return String(num).padStart(2, '0');
}

const formatTime = (milliseconds: number) => {
const hours = formatNumber(Math.floor(milliseconds / 3600000));
const minutes = formatNumber(Math.floor((milliseconds % 3600000) / 60000));
const seconds = formatNumber(Math.floor((milliseconds % 60000) / 1000));
const centiseconds = formatNumber(Math.floor((milliseconds % 1000) / 10));

return `${hours}:${minutes}:${seconds}.${centiseconds}`;
};

export const lapColors = {
default: 'black',
minLap: 'red',
maxLap: '#5ab068'
};

export default function Stopwatch() {

const stopwatch = useStopWatch();
return (

<View style={styles.container}>
<View style={styles.contentContainer}>
<Text style={styles.time}>{formatTime(stopwatch.milliseconds)}</Text>
<StopwatchButton
onResume={stopwatch.resume}
onReset={stopwatch.reset}
onLap={stopwatch.lap}
onPause={stopwatch.pause}
isPaused={stopwatch.isPaused}
/>

<ScrollView
style={styles.lapScrollView}
contentContainerStyle={styles.lapScrollViewContent}
testID='lap-list'
>

{stopwatch.laps.slice().reverse().map((lap, index) => {
let lapColor = lapColors.default;

if (stopwatch.laps.length >= 3) {
if (lap === stopwatch.minLapTime) {
lapColor = lapColors.minLap;
}
else if (lap === stopwatch.maxLapTime) {
lapColor = lapColors.maxLap;
}
}

return (
<React.Fragment key={index}>
<View style={styles.lapContentContainer}>
<Text style={[styles.lap, { color: lapColor }]} testID={`lap-${index}`}>
Lap {stopwatch.laps.length - index}
</Text>
<Text style={[styles.lap, { color: lapColor }]} testID={`time-${index}`}>
{formatTime(lap)}
</Text>
</View>
<View style={styles.divider} />
</React.Fragment>
);
})}
</ScrollView>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
contentContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
lapContentContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
width:'75%',
},
time: {
fontSize: 56,
fontWeight: '500',
marginBottom: 20,
minWidth:320,
},
lapScrollView: {
maxHeight: 210,
width: '100%',
marginTop:'10%',
},
lapScrollViewContent: {
alignItems: 'center',
},
lap: {
fontSize: 18,
marginTop: 12,
},
divider: {
borderBottomColor: 'lightgray',
borderBottomWidth: 1,
width: '75%',
marginTop:6,
}
});
Loading