Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support timer component #934

Merged
merged 6 commits into from
Aug 26, 2024
Merged
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
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ import PinInputExample from "./PinInputExample";
import KeyboardAvoidingViewExample from "./KeyboardAvoidingViewExample";
import ThemeExample from "./ThemeExample";
import LoadingIndicatorExample from "./LoadingIndicatorExample";
import TimerExample from "./TimerExample";

const ROUTES = {
Timer: TimerExample,
LoadingIndicator: LoadingIndicatorExample,
Theme: ThemeExample,
AudioPlayer: AudioPlayerExample,
Expand Down
85 changes: 85 additions & 0 deletions example/src/TimerExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as React from "react";
import { View, StyleSheet, Text } from "react-native";
import { withTheme } from "@draftbit/ui";
import Section, { Container } from "./Section";
import { Button, Timer } from "@draftbit/core";

const TimerExample: React.FC = () => {
const timerRef = React.useRef<any>(null);
const [countDirection, setCountDirection] = React.useState<"up" | "down">(
"up"
);

const handleStart = () => timerRef.current?.start();
const handleStop = () => timerRef.current?.stop();
const handleReset = () => timerRef.current?.reset();
const handleResetToCustomTime = () => timerRef.current?.reset(5000);

const handleDirectionToggle = () =>
setCountDirection((prev) => (prev === "up" ? "down" : "up"));
return (
<Container style={{}}>
<Section style={{}} title="Default">
<View
style={{
flexDirection: "column",
justifyContent: "center",
}}
>
<Timer
ref={timerRef}
initialTime={60000}
timerEndTime={70000}
updateInterval={1000}
format="mm:ss"
countDirection={countDirection}
onTimerChange={(value: number) => {
console.log("onTimerChange : ", value);
}}
onTimerEnd={() => {
console.log("onTimerEnd");
// eslint-disable-next-line no-alert
alert("onTimerEnd");
}}
style={{
fontSize: 50,
fontWeight: "bold",
}}
/>
<Text style={styles.directionText}>
Count direction : {countDirection}
</Text>
<View style={styles.buttonsContainer}>
<Button title="Start Timer" onPress={handleStart} />
<Button title="Stop Timer" onPress={handleStop} />
<Button title="Reset Timer" onPress={handleReset} />
<Button title="Reset to 5s" onPress={handleResetToCustomTime} />
<Button
title={`Toggle Direction`}
onPress={handleDirectionToggle}
/>
</View>
</View>
</Section>
</Container>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 20,
},
buttonsContainer: {
marginTop: 20,
gap: 10,
},
directionText: {
textAlign: "center",
fontSize: 20,
marginVertical: 15,
},
});

export default withTheme(TimerExample);
138 changes: 138 additions & 0 deletions packages/core/src/components/Timer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, {
useState,
useEffect,
useRef,
useImperativeHandle,
forwardRef,
} from "react";
import { Text, StyleSheet, TextStyle, StyleProp } from "react-native";

interface TimerProps {
style?: StyleProp<TextStyle>;
initialTime?: number;
updateInterval?: number;
format?: "ss" | "mm:ss" | "hh:mm:ss" | "ss:ms" | "mm:ss:ms" | "hh:mm:ss:ms";
onTimerChange?: (time: number) => void;
onTimerEnd?: () => void;
countDirection?: "up" | "down";
timerEndTime?: number;
}

export interface TimerHandle {
start: () => void;
stop: () => void;
reset: (newTime?: number) => void;
}

const Timer = forwardRef<TimerHandle, TimerProps>(
(
{
style,
initialTime,
updateInterval = 1000,
format = "mm:ss",
onTimerChange,
onTimerEnd,
countDirection = "up",
timerEndTime,
},
ref
) => {
const defaultInitialTime = countDirection === "up" ? 0 : 100000;
const [time, setTime] = useState(initialTime ?? defaultInitialTime);
const timerRef = useRef<NodeJS.Timeout | null>(null);
sieu-db marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
onTimerChange?.(time);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [time]);

useImperativeHandle(ref, () => ({
start: startTimer,
stop: stopTimer,
reset: resetTimer,
}));

const startTimer = () => {
if (timerRef.current) return;
timerRef.current = setInterval(() => {
setTime((prevTime) => {
const newTime =
countDirection === "up"
? prevTime + updateInterval
: prevTime - updateInterval;
// Count down
if (newTime <= 0 && countDirection === "down") {
sieu-db marked this conversation as resolved.
Show resolved Hide resolved
clearTimer();
onTimerEnd?.();
return 0;
}
// Count up
if (
countDirection === "up" &&
timerEndTime !== undefined &&
newTime >= timerEndTime
) {
clearTimer();
onTimerEnd?.();
return timerEndTime;
}

return newTime;
});
}, updateInterval);
};

const stopTimer = () => clearTimer();

const resetTimer = (
newTime: number = initialTime ?? defaultInitialTime
) => {
clearTimer();
setTime(newTime);
};

const clearTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};

useEffect(() => {
return () => clearTimer();
}, []);

const formatTime = (milliseconds: number): string => {
const totalSeconds = Math.floor(milliseconds / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds - hours * 3600) / 60);
const seconds = totalSeconds - hours * 3600 - minutes * 60;
const ms = milliseconds % 1000;

const formattedHours = String(hours).padStart(2, "0");
const formattedMinutes = String(minutes).padStart(2, "0");
const formattedSeconds = String(seconds).padStart(2, "0");
const formattedMs = String(ms).padStart(3, "0");

return format
.replace("hh", formattedHours)
.replace("mm", formattedMinutes)
.replace("ss", formattedSeconds)
.replace("ms", formattedMs);
};

return (
<Text style={[styles.defaultTimerStyle, style]}>{formatTime(time)}</Text>
);
}
);

const styles = StyleSheet.create({
defaultTimerStyle: {
fontSize: 24,
textAlign: "center",
},
});

export default Timer;
1 change: 1 addition & 0 deletions packages/core/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export { default as SimpleStyleScrollView } from "./components/SimpleStyleScroll
export { default as SimpleStyleSectionList } from "./components/SimpleStyleScrollables/SimpleStyleSectionList";
export { default as SimpleStyleSwipeableList } from "./components/SimpleStyleScrollables/SimpleStyleSwipeableList";
export { default as LoadingIndicator } from "./components/LoadingIndicator";
export { default as Timer } from "./components/Timer";

/* Deprecated: Fix or Delete! */
export { default as AccordionItem } from "./deprecated-components/AccordionItem";
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export {
SimpleStyleSectionList,
SimpleStyleSwipeableList,
LoadingIndicator,
Timer,
} from "@draftbit/core";

export {
Expand Down
Loading