1- import { Dimensions , StyleSheet , View } from 'react-native' ;
1+ import { Dimensions , StyleSheet , Text , View } from 'react-native' ;
22
3- import { forwardRef , useImperativeHandle } from 'react' ;
3+ import { forwardRef } from 'react' ;
44
55import { LinearGradient } from 'expo-linear-gradient' ;
6- import { Gesture , GestureDetector } from 'react-native-gesture-handler ' ;
6+ import type { SharedValue } from 'react-native-reanimated ' ;
77import Animated , {
88 cancelAnimation ,
99 interpolate ,
1010 useAnimatedReaction ,
11+ useAnimatedStyle ,
1112 useDerivedValue ,
1213 useSharedValue ,
13- withDecay ,
1414 withTiming ,
1515} from 'react-native-reanimated' ;
1616
17- import { useTimer } from '../useTimer' ;
1817import { LineTime } from './line-time' ;
1918
2019
@@ -30,8 +29,6 @@ export type DraggableSliderProps = {
3029 bigLineIndexOffset ?: number ;
3130 // Optional: The width of each line, defaults to 1.5
3231 lineWidth ?: number ;
33- // Optional: Callback function called when the progress changes
34- onProgressChange ?: ( progress : number ) => void ;
3532 // Optional: Shared value representing the color of the indicator line
3633 indicatorColor ?: string ;
3734 // Optional: The color of the lines (default is #c6c6c6)
@@ -40,7 +37,7 @@ export type DraggableSliderProps = {
4037 bigLineColor ?: string ;
4138 // Optional: The color of the medium lines (default is #c6c6c6)
4239 mediumLineColor ?: string ;
43- onCompletion ?: ( ) => void ;
40+ selectedDuration ?: SharedValue < number > ;
4441} ;
4542
4643const { height : WindowHeight } = Dimensions . get ( 'window' ) ;
@@ -63,66 +60,20 @@ export const CircularDraggableSlider = forwardRef<
6360 minLineHeight,
6461 bigLineIndexOffset = 10 ,
6562 lineWidth = 1.5 ,
66- onProgressChange,
6763 lineColor = '#c6c6c6' ,
6864 bigLineColor = '#c6c6c6' ,
6965 mediumLineColor = '#c6c6c6' ,
70- onCompletion ,
66+ selectedDuration ,
7167 } ,
7268 ref ,
7369 ) => {
7470 const progress = useSharedValue ( 0 ) ;
75- const previousProgress = useSharedValue ( 0 ) ;
76-
71+ const isTimerEnabled = useSharedValue ( false ) ;
7772 const distanceBetweenTwoTicksRad = ( 2 * Math . PI ) / linesAmount ;
7873 const diameter = 2 * Math . PI * radius ;
7974 const distanceBetweenTwoTicks = diameter / linesAmount ;
8075 const listWidth = diameter ;
8176
82- const { runTimer, stopTimer, resetTimer, isTimerEnabled } = useTimer ( {
83- progress,
84- incrementOffset : distanceBetweenTwoTicks ,
85- onCompletion : ( ) => {
86- isTimerEnabled . value = false ;
87- onCompletion ?.( ) ;
88- } ,
89- } ) ;
90-
91- useImperativeHandle ( ref , ( ) => {
92- return {
93- resetTimer,
94- runTimer,
95- stopTimer,
96- } ;
97- } , [ resetTimer , runTimer , stopTimer ] ) ;
98-
99- const panGesture = Gesture . Pan ( )
100- . onBegin ( ( ) => {
101- if ( isTimerEnabled . value ) {
102- return ;
103- }
104- cancelAnimation ( progress ) ;
105- previousProgress . value = progress . value ;
106- } )
107- . onUpdate ( event => {
108- if ( isTimerEnabled . value ) {
109- return ;
110- }
111- progress . value = event . translationY + previousProgress . value ;
112- } )
113- . onFinalize ( event => {
114- if ( isTimerEnabled . value ) {
115- return ;
116- }
117- if ( progress . value > 0 ) {
118- cancelAnimation ( progress ) ;
119- progress . value = withTiming ( 0 , { duration : 500 } ) ;
120- return ;
121- }
122- progress . value = withDecay ( {
123- velocity : event . velocityY ,
124- } ) ;
125- } ) ;
12677
12778 // Offset set to 0 for 90-degree rotation (indicator at right side)
12879 const offset = 0 ;
@@ -140,10 +91,27 @@ export const CircularDraggableSlider = forwardRef<
14091 const amountOfSeconds = Math . round (
14192 ( radiants - offset ) / distanceBetweenTwoTicksRad ,
14293 ) ;
143- if ( onProgressChange ) {
144- onProgressChange ( amountOfSeconds ) ;
94+ } ,
95+ ) ;
96+
97+ useAnimatedReaction (
98+ ( ) => selectedDuration ?. value ?? null ,
99+ duration => {
100+ if ( duration === null ) {
101+ return ;
145102 }
103+
104+ const clampedDuration = Math . max ( 0 , duration ) ;
105+ const targetIndex = Math . min (
106+ linesAmount - 1 ,
107+ clampedDuration * bigLineIndexOffset ,
108+ ) ;
109+ const targetProgress = - distanceBetweenTwoTicks * targetIndex ;
110+
111+ cancelAnimation ( progress ) ;
112+ progress . value = withTiming ( targetProgress , { duration : 250 } ) ;
146113 } ,
114+ [ bigLineIndexOffset , distanceBetweenTwoTicks , selectedDuration ] ,
147115 ) ;
148116
149117 return (
@@ -205,6 +173,24 @@ export const CircularDraggableSlider = forwardRef<
205173 />
206174 ) ;
207175 } ) }
176+ { new Array ( 10 ) . fill ( 0 ) . map ( ( _ , hourIndex ) => {
177+ const label = `${ hourIndex + 1 } hr` ;
178+ const tickIndex = ( hourIndex + 1 ) * bigLineIndexOffset ;
179+ if ( tickIndex > linesAmount ) {
180+ return null ;
181+ }
182+
183+ return (
184+ < HourLabel
185+ key = { label }
186+ label = { label }
187+ radius = { radius }
188+ linesAmount = { linesAmount }
189+ progressRadiants = { progressRadiants }
190+ tickIndex = { tickIndex }
191+ />
192+ ) ;
193+ } ) }
208194 </ Animated . View >
209195 < LinearGradient
210196 // Background Linear Gradient
@@ -219,27 +205,70 @@ export const CircularDraggableSlider = forwardRef<
219205 } }
220206 style = { {
221207 position : 'absolute' ,
222- height : radius * 2 + 40 ,
223- width : radius ,
224- top : - ( WindowHeight / 2 - radius - 16 )
208+ height : radius * 2 + 94 ,
209+ width : radius + 20 ,
210+ left : - 30 ,
211+ top : - ( WindowHeight / 2 - radius + 8 ) ,
212+ // backgroundColor: 'red',
213+ borderRadius : 1000
225214 } }
226215 />
227216 </ View >
228- < GestureDetector gesture = { panGesture } >
229- < Animated . View
230- style = { [
231- {
232- height : WindowHeight / 2 ,
233- } ,
234- styles . timer ,
235- ] }
236- />
237- </ GestureDetector >
217+ < Animated . View
218+ pointerEvents = "none"
219+ style = { [
220+ {
221+ height : WindowHeight / 2 ,
222+ } ,
223+ styles . timer ,
224+ ] }
225+ />
238226 </ View >
239227 ) ;
240228 } ,
241229) ;
242230
231+ type HourLabelProps = {
232+ label : string ;
233+ progressRadiants : SharedValue < number > ;
234+ linesAmount : number ;
235+ tickIndex : number ;
236+ radius : number ;
237+ } ;
238+
239+ const LABEL_WIDTH = 60 ;
240+ const LABEL_HEIGHT = 28 ;
241+ const LABEL_RADIUS_OFFSET = 40 ;
242+
243+ const HourLabel : React . FC < HourLabelProps > = ( {
244+ label,
245+ progressRadiants,
246+ linesAmount,
247+ tickIndex,
248+ radius,
249+ } ) => {
250+ const rStyle = useAnimatedStyle ( ( ) => {
251+ const angle =
252+ ( ( 2 * Math . PI ) / linesAmount ) * tickIndex - progressRadiants . value ;
253+ const labelRadius = radius + LABEL_RADIUS_OFFSET ;
254+ const x = Math . cos ( angle ) * labelRadius ;
255+ const y = Math . sin ( angle ) * labelRadius ;
256+
257+ return {
258+ transform : [
259+ { translateX : x - LABEL_WIDTH / 2 } ,
260+ { translateY : y - LABEL_HEIGHT / 2 } ,
261+ ] ,
262+ } ;
263+ } , [ linesAmount , radius , tickIndex ] ) ;
264+
265+ return (
266+ < Animated . View pointerEvents = "none" style = { [ styles . hourLabel , rStyle ] } >
267+ < Text style = { styles . hourLabelText } > { label } </ Text >
268+ </ Animated . View >
269+ ) ;
270+ } ;
271+
243272const styles = StyleSheet . create ( {
244273 container : {
245274 width : '100%' ,
@@ -249,4 +278,17 @@ const styles = StyleSheet.create({
249278 position : 'absolute' ,
250279 width : '100%' ,
251280 } ,
281+ hourLabel : {
282+ position : 'absolute' ,
283+ width : LABEL_WIDTH ,
284+ height : LABEL_HEIGHT ,
285+ alignItems : 'center' ,
286+ justifyContent : 'center' ,
287+ } ,
288+ hourLabelText : {
289+ color : '#d8d8d8' ,
290+ fontFamily : 'SF-Pro-Rounded-Bold' ,
291+ fontSize : 20 ,
292+ left : 10
293+ } ,
252294} ) ;
0 commit comments