Skip to content

Commit 74a45ba

Browse files
committed
Refactor code structure for improved readability and maintainability
1 parent 2775c83 commit 74a45ba

File tree

7 files changed

+187
-200
lines changed

7 files changed

+187
-200
lines changed

App.tsx

Lines changed: 54 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,82 @@
1-
import { StyleSheet, View } from 'react-native';
2-
1+
import { useFonts } from "expo-font";
32
import { useRef } from 'react';
4-
3+
import { StyleSheet, View } from 'react-native';
54
import { GestureHandlerRootView } from 'react-native-gesture-handler';
6-
import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
5+
import { useSharedValue } from 'react-native-reanimated';
76
import { SafeAreaProvider } from 'react-native-safe-area-context';
87
import { BottomTab } from './src/components/bottom-tab';
98
import { CircularDraggableSlider, CircularDraggableSliderRefType } from './src/components/circle-time';
109
import { TimeRange } from './src/components/time-range';
1110

12-
// Handle timezone offset to ensure correct time display
13-
// Note: This is a simple implementation. For production, consider using a proper timezone library
14-
const TimezoneOffsetMs = -new Date().getTimezoneOffset() * 60000;
15-
16-
/**
17-
* Generate an array of time slots for the time range selector
18-
* Creates 20 time slots starting from 13:00 with 30-minute intervals
19-
*/
20-
const dates = new Array(20).fill(0).map((_, index) => {
21-
const hour = Math.floor(index / 2) + 13;
22-
const minutes = index % 2 === 0 ? 0 : 30;
23-
return new Date(2025, 0, 1, hour, minutes);
24-
});
25-
26-
27-
2811
const LinesAmount = 200;
2912

3013
export default function App() {
3114
const animatedNumber = useSharedValue(0);
3215
const previousTick = useSharedValue(0);
3316

3417
const circularSliderRef = useRef<CircularDraggableSliderRefType>(null);
35-
const date = useSharedValue(dates[0].getTime());
36-
37-
const clockDate = useDerivedValue(() => {
38-
'worklet';
39-
return date.value + TimezoneOffsetMs;
40-
});
18+
const selectedDuration = useSharedValue(1);
4119

4220
return (
4321
<SafeAreaProvider>
44-
<GestureHandlerRootView style={{ flex: 1 }}>
45-
<View style={{ flex: 1 }}>
46-
<View style={styles.container}>
47-
<CircularDraggableSlider
48-
ref={circularSliderRef}
49-
bigLineIndexOffset={10}
50-
linesAmount={LinesAmount}
51-
maxLineHeight={20}
52-
lineColor="rgba(255,255,255,0.5)"
53-
bigLineColor="white"
54-
minLineHeight={8}
55-
onCompletion={async () => {
56-
57-
}}
58-
onProgressChange={sliderProgress => {
59-
'worklet';
60-
if (sliderProgress < 0) {
61-
return;
62-
}
22+
<FontsProvider>
23+
<GestureHandlerRootView style={styles.fill}>
24+
<View style={{ flex: 1 }}>
25+
<View style={styles.container}>
26+
<CircularDraggableSlider
27+
ref={circularSliderRef}
28+
bigLineIndexOffset={10}
29+
linesAmount={LinesAmount}
30+
maxLineHeight={20}
31+
minLineHeight={8}
32+
onProgressChange={sliderProgress => {
33+
'worklet';
34+
if (sliderProgress < 0) {
35+
return;
36+
}
6337

64-
// Only trigger haptics when crossing a line (when tick value changes)
65-
if (sliderProgress !== previousTick.value) {
66-
previousTick.value = sliderProgress;
67-
}
38+
// Only trigger haptics when crossing a line (when tick value changes)
39+
if (sliderProgress !== previousTick.value) {
40+
previousTick.value = sliderProgress;
41+
}
6842

69-
// Bind the progress value to the animated number
70-
animatedNumber.value = sliderProgress;
71-
}}
72-
/>
73-
<TimeRange
74-
dates={dates}
75-
onDateChange={updatedDate => {
76-
'worklet';
77-
date.value = updatedDate;
78-
}}
79-
/>
43+
// Bind the progress value to the animated number
44+
animatedNumber.value = sliderProgress;
45+
}}
46+
/>
47+
<TimeRange
48+
onDurationChange={durationHours => {
49+
'worklet';
50+
selectedDuration.value = durationHours;
51+
}}
52+
/>
53+
</View>
54+
<BottomTab />
8055
</View>
81-
<BottomTab />
82-
</View>
83-
</GestureHandlerRootView>
56+
</GestureHandlerRootView>
57+
</FontsProvider>
8458
</SafeAreaProvider>
8559
);
8660
};
8761

62+
63+
const FontsProvider = ({ children }: { children: React.ReactNode }) => {
64+
65+
const [fontsLoaded] = useFonts({
66+
'SF-Pro-Rounded-Bold': require('./assets/fonts/SF-Pro-Rounded-Bold.otf'),
67+
});
68+
69+
if (!fontsLoaded) {
70+
return null;
71+
}
72+
73+
return <>{children}</>;
74+
};
75+
8876
const styles = StyleSheet.create({
77+
fill: {
78+
flex: 1
79+
},
8980
button: {
9081
alignItems: 'center',
9182
aspectRatio: 1,

app.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"favicon": "./assets/images/favicon.png"
2727
},
2828
"plugins": [
29+
"expo-font",
2930
[
3031
"expo-splash-screen",
3132
{
4.12 MB
Binary file not shown.

bun.lock

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"": {
55
"name": "clones",
66
"dependencies": {
7+
"@expo-google-fonts/lexend-deca": "^0.4.1",
78
"@expo/vector-icons": "^15.0.3",
89
"@gorhom/bottom-sheet": "^5",
910
"@react-navigation/elements": "^2.6.3",
@@ -12,7 +13,7 @@
1213
"date-fns": "^4.1.0",
1314
"expo": "~54.0.20",
1415
"expo-constants": "~18.0.10",
15-
"expo-font": "~14.0.9",
16+
"expo-font": "^14.0.9",
1617
"expo-haptics": "~15.0.7",
1718
"expo-image": "~3.0.10",
1819
"expo-linear-gradient": "^15.0.7",
@@ -258,6 +259,8 @@
258259

259260
"@eslint/plugin-kit": ["@eslint/[email protected]", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="],
260261

262+
"@expo-google-fonts/lexend-deca": ["@expo-google-fonts/[email protected]", "", {}, "sha512-bVHjNL7hqQai4ca8l0mNim5LvBLiZaRECreRjkI96w3jZ+XBWytdCc5aGtlCkL9zaa6GSwgMpHhoKxBC84+OQA=="],
263+
261264
"@expo/cli": ["@expo/[email protected]", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@expo/code-signing-certificates": "^0.0.5", "@expo/config": "~12.0.10", "@expo/config-plugins": "~54.0.2", "@expo/devcert": "^1.1.2", "@expo/env": "~2.0.7", "@expo/image-utils": "^0.8.7", "@expo/json-file": "^10.0.7", "@expo/mcp-tunnel": "~0.0.7", "@expo/metro": "~54.1.0", "@expo/metro-config": "~54.0.7", "@expo/osascript": "^2.3.7", "@expo/package-manager": "^1.9.8", "@expo/plist": "^0.4.7", "@expo/prebuild-config": "^54.0.6", "@expo/schema-utils": "^0.1.7", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.3.0", "@react-native/dev-middleware": "0.81.5", "@urql/core": "^5.0.6", "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "env-editor": "^0.4.1", "expo-server": "^1.0.2", "freeport-async": "^2.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "lan-network": "^0.1.6", "minimatch": "^9.0.0", "node-forge": "^1.3.1", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^3.0.1", "pretty-bytes": "^5.6.0", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", "requireg": "^0.2.2", "resolve": "^1.22.2", "resolve-from": "^5.0.0", "resolve.exports": "^2.0.3", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^7.4.3", "terminal-link": "^2.1.1", "undici": "^6.18.2", "wrap-ansi": "^7.0.0", "ws": "^8.12.1" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "build/bin/cli" } }, "sha512-wUJVTByZzDN0q8UjXDlu6WD2BWoTJCKVVBGUBNmvViDX4FhnESwefmtXPoO54QUUKs6vY89WZryHllGArGfLLw=="],
262265

263266
"@expo/code-signing-certificates": ["@expo/[email protected]", "", { "dependencies": { "node-forge": "^1.2.1", "nullthrows": "^1.1.1" } }, "sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw=="],

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"lint": "expo lint"
1212
},
1313
"dependencies": {
14+
"@expo-google-fonts/lexend-deca": "^0.4.1",
1415
"@expo/vector-icons": "^15.0.3",
1516
"@gorhom/bottom-sheet": "^5",
1617
"@react-navigation/elements": "^2.6.3",
@@ -19,7 +20,7 @@
1920
"date-fns": "^4.1.0",
2021
"expo": "~54.0.20",
2122
"expo-constants": "~18.0.10",
22-
"expo-font": "~14.0.9",
23+
"expo-font": "^14.0.9",
2324
"expo-haptics": "~15.0.7",
2425
"expo-image": "~3.0.10",
2526
"expo-linear-gradient": "^15.0.7",

src/components/circle-time/index.tsx

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,6 @@ export const CircularDraggableSlider = forwardRef<
9494
const diameter = 2 * Math.PI * radius;
9595
const distanceBetweenTwoTicks = diameter / linesAmount;
9696
const listWidth = diameter;
97-
const mediumLineHeight =
98-
minLineHeight + (maxLineHeight - minLineHeight) / 2;
99-
const mediumLineIndexWithinGroup =
100-
bigLineIndexOffset > 1 ? Math.round(bigLineIndexOffset / 2) : null;
10197

10298
const { runTimer, stopTimer, resetTimer, isTimerEnabled } = useTimer({
10399
progress,
@@ -186,28 +182,30 @@ export const CircularDraggableSlider = forwardRef<
186182
]}>
187183
<Animated.View pointerEvents="none">
188184
{new Array(linesAmount).fill(0).map((_, index) => {
189-
const groupPosition =
190-
bigLineIndexOffset > 0
191-
? index % bigLineIndexOffset
192-
: index;
193-
const isBigLine =
194-
bigLineIndexOffset > 0 && groupPosition === 0;
195-
const isMediumLine =
196-
!isBigLine &&
197-
mediumLineIndexWithinGroup !== null &&
198-
groupPosition === mediumLineIndexWithinGroup;
185+
// Determine line type based on position
186+
const isBigLine = index % bigLineIndexOffset === 0;
199187

200-
const height = isBigLine
201-
? maxLineHeight
202-
: isMediumLine
203-
? mediumLineHeight
204-
: minLineHeight;
188+
// Calculate the midpoint between consecutive big lines
189+
const midpointOffset = bigLineIndexOffset / 2;
190+
const isMediumLine = !isBigLine && index % bigLineIndexOffset === midpointOffset;
205191

206-
const color = isBigLine
207-
? bigLineColor
208-
: isMediumLine
209-
? mediumLineColor
210-
: lineColor;
192+
// Calculate medium line height as the average of max and min
193+
const mediumLineHeight = (maxLineHeight + minLineHeight) / 2;
194+
195+
// Determine height based on line type
196+
let height: number;
197+
let color: string;
198+
199+
if (isBigLine) {
200+
height = maxLineHeight;
201+
color = bigLineColor;
202+
} else if (isMediumLine) {
203+
height = mediumLineHeight;
204+
color = mediumLineColor;
205+
} else {
206+
height = minLineHeight;
207+
color = lineColor;
208+
}
211209

212210
return (
213211
<LineTime
@@ -258,4 +256,4 @@ const styles = StyleSheet.create({
258256
position: 'absolute',
259257
width: '100%',
260258
},
261-
});
259+
});

0 commit comments

Comments
 (0)