11import { Dimensions , StyleSheet , Text , View } from 'react-native' ;
22
3- import { forwardRef } from 'react' ;
3+ import { useMemo } from 'react' ;
44
55import { LinearGradient } from 'expo-linear-gradient' ;
66import type { SharedValue } from 'react-native-reanimated' ;
@@ -17,188 +17,172 @@ import { LineTime } from './line-time';
1717
1818
1919export type DraggableSliderProps = {
20- linesAmount : number ;
21- maxLineHeight : number ;
22- minLineHeight : number ;
2320 bigLineIndexOffset ?: number ;
24- lineWidth ?: number ;
25- indicatorColor ?: string ;
26- lineColor ?: string ;
27- bigLineColor ?: string ;
28- mediumLineColor ?: string ;
29- selectedDuration ?: SharedValue < number > ;
21+ selectedDuration : SharedValue < number > ;
3022} ;
3123
3224const { height : WindowHeight } = Dimensions . get ( 'window' ) ;
3325const radius = 200 ;
3426const HOUR_LABELS_AMOUNT = 10 ;
27+ const maxLineHeight = 20 ;
28+ const minLineHeight = 8 ;
29+ const linesAmount = 200 ;
30+ const lineWidth = 1.5 ;
3531
36- export type CircularDraggableSliderRefType = {
37- resetTimer : ( ) => void ;
38- runTimer : ( to : number ) => void ;
39- stopTimer : ( ) => void ;
40- } ;
41-
42- export const CircularDraggableSlider = forwardRef <
43- CircularDraggableSliderRefType ,
44- DraggableSliderProps
45- > (
46- (
47- {
48- linesAmount,
49- maxLineHeight,
50- minLineHeight,
51- bigLineIndexOffset = 10 ,
52- lineWidth = 1.5 ,
53- lineColor = '#c6c6c6' ,
54- bigLineColor = '#c6c6c6' ,
55- mediumLineColor = '#c6c6c6' ,
56- selectedDuration,
57- } ,
58- ref ,
59- ) => {
60- const progress = useSharedValue ( 0 ) ;
61- const isTimerEnabled = useSharedValue ( false ) ;
62- const diameter = 2 * Math . PI * radius ;
63- const distanceBetweenTwoTicks = diameter / linesAmount ;
64- const listWidth = diameter ;
32+ export const CircleTime : React . FC < DraggableSliderProps > = ( {
33+ bigLineIndexOffset = 10 ,
34+ selectedDuration,
35+ } ) => {
36+ const progress = useSharedValue ( 0 ) ;
37+ const diameter = 2 * Math . PI * radius ;
38+ const distanceBetweenTwoTicks = diameter / linesAmount ;
39+ const listWidth = diameter ;
6540
66- const offset = 0 ;
67- const progressRadiants = useDerivedValue ( ( ) => {
68- return interpolate (
69- - progress . value ,
70- [ 0 , listWidth ] ,
71- [ offset , 2 * Math . PI + offset ] ,
72- ) ;
73- } , [ listWidth ] ) ;
41+ const progressRadiants = useDerivedValue ( ( ) => {
42+ return interpolate (
43+ - progress . value ,
44+ [ 0 , listWidth ] ,
45+ [ 0 , 2 * Math . PI ] ,
46+ ) ;
47+ } , [ listWidth ] ) ;
7448
75-
76- useAnimatedReaction (
77- ( ) => selectedDuration ?. value ?? null ,
78- duration => {
79- if ( duration === null ) {
80- return ;
81- }
49+ const mediumLineHeight = useMemo (
50+ ( ) => ( maxLineHeight + minLineHeight ) / 2 ,
51+ [ maxLineHeight , minLineHeight ] ,
52+ ) ;
53+ const lineIndices = useMemo (
54+ ( ) => Array . from ( { length : linesAmount } , ( _ , index ) => index ) ,
55+ [ linesAmount ] ,
56+ ) ;
57+ const hourLabels = useMemo ( ( ) => {
58+ return Array . from ( { length : HOUR_LABELS_AMOUNT } , ( _ , hourIndex ) => {
59+ const label = `${ hourIndex + 1 } hr` ;
60+ const tickIndex = ( hourIndex + 1 ) * bigLineIndexOffset ;
61+ return { label, tickIndex } ;
62+ } ) ;
63+ } , [ bigLineIndexOffset ] ) ;
8264
83- const clampedDuration = Math . max ( 0 , duration ) ;
84- const targetIndex = Math . min (
85- linesAmount - 1 ,
86- clampedDuration * bigLineIndexOffset ,
87- ) ;
88- const targetProgress = - distanceBetweenTwoTicks * targetIndex ;
65+ useAnimatedReaction (
66+ ( ) => selectedDuration . value ,
67+ duration => {
68+ 'worklet' ;
69+ const clampedDuration = Math . max ( 0 , duration ) ;
70+ const targetIndex = Math . min (
71+ linesAmount - 1 ,
72+ clampedDuration * bigLineIndexOffset ,
73+ ) ;
74+ const targetProgress = - distanceBetweenTwoTicks * targetIndex ;
75+ if ( Math . abs ( progress . value - targetProgress ) < 0.01 ) {
76+ return ;
77+ }
8978
90- cancelAnimation ( progress ) ;
91- // Use immediate update for real-time synchronization during scroll
92- progress . value = targetProgress ;
93- } ,
94- [ bigLineIndexOffset , distanceBetweenTwoTicks , selectedDuration ] ,
95- ) ;
79+ cancelAnimation ( progress ) ;
80+ progress . value = targetProgress ;
81+ } ,
82+ [ bigLineIndexOffset , distanceBetweenTwoTicks , linesAmount ] ,
83+ ) ;
9684
97- return (
98- < View style = { styles . container } >
99- < View
100- pointerEvents = "none"
101- style = { [
102- {
103- height : radius * 2 ,
104- width : radius * 2 ,
105- right : 60 ,
106- transform : [
107- {
108- translateY : WindowHeight / 2 - radius - 36 ,
109- } ,
110- ] ,
111- } ,
112- ] } >
85+ return (
86+ < View style = { styles . container } >
87+ < View
88+ pointerEvents = "none"
89+ style = { [
90+ {
91+ height : radius * 2 ,
92+ width : radius * 2 ,
93+ right : 60 ,
94+ transform : [
95+ {
96+ translateY : WindowHeight / 2 - radius - 36 ,
97+ } ,
98+ ] ,
99+ } ,
100+ ] } >
113101
114- < Animated . View pointerEvents = "none" >
102+ < Animated . View pointerEvents = "none" >
115103
116- { new Array ( linesAmount ) . fill ( 0 ) . map ( ( _ , index ) => {
117- const isBigLine = index % bigLineIndexOffset === 0 ;
118- const midpointOffset = bigLineIndexOffset / 2 ;
119- const isMediumLine = ! isBigLine && index % bigLineIndexOffset === midpointOffset ;
120- const mediumLineHeight = ( maxLineHeight + minLineHeight ) / 2 ;
121- let height : number ;
122- let color : string ;
104+ { lineIndices . map ( index => {
105+ const isBigLine = index % bigLineIndexOffset === 0 ;
106+ const midpointOffset = bigLineIndexOffset / 2 ;
107+ const isMediumLine =
108+ ! isBigLine && index % bigLineIndexOffset === midpointOffset ;
109+ let height : number ;
110+ let color : string ;
123111
124- if ( isBigLine ) {
125- height = maxLineHeight ;
126- color = bigLineColor ;
127- } else if ( isMediumLine ) {
128- height = mediumLineHeight ;
129- color = mediumLineColor ;
130- } else {
131- height = minLineHeight ;
132- color = lineColor ;
133- }
112+ if ( isBigLine ) {
113+ height = maxLineHeight ;
114+ color = '#c6c6c6' ;
115+ } else if ( isMediumLine ) {
116+ height = mediumLineHeight ;
117+ color = '#c6c6c6' ;
118+ } else {
119+ height = minLineHeight ;
120+ color = '#c6c6c6' ;
121+ }
134122
135- return (
136- < LineTime
137- disabled = { isTimerEnabled }
138- key = { index }
139- height = { height }
140- radius = { radius }
141- progressRadiants = { progressRadiants }
142- index = { index }
143- lineWidth = { lineWidth }
144- color = { color }
145- linesAmount = { linesAmount }
146- />
147- ) ;
148- } ) }
149- { new Array ( HOUR_LABELS_AMOUNT ) . fill ( 0 ) . map ( ( _ , hourIndex ) => {
150- const label = `${ hourIndex + 1 } hr` ;
151- const tickIndex = ( hourIndex + 1 ) * bigLineIndexOffset ;
152- if ( tickIndex > linesAmount ) {
153- return null ;
154- }
123+ return (
124+ < LineTime
125+ key = { index }
126+ height = { height }
127+ radius = { radius }
128+ progressRadiants = { progressRadiants }
129+ index = { index }
130+ lineWidth = { lineWidth }
131+ color = { color }
132+ linesAmount = { linesAmount }
133+ />
134+ ) ;
135+ } ) }
136+ { hourLabels . map ( ( { label, tickIndex } ) => {
137+ if ( tickIndex > linesAmount ) {
138+ return null ;
139+ }
155140
156- return (
157- < HourLabel
158- key = { label }
159- label = { label }
160- radius = { radius }
161- linesAmount = { linesAmount }
162- progressRadiants = { progressRadiants }
163- tickIndex = { tickIndex }
164- />
165- ) ;
166- } ) }
167- </ Animated . View >
168- < LinearGradient
169- colors = { [ '#000000' , '#000000' , '#000000' , '#00000070' , 'transparent' ] }
170- start = { {
171- x : 0 ,
172- y : 0
173- } }
174- end = { {
175- x : 1.1 ,
176- y : 0
177- } }
178- style = { {
179- position : 'absolute' ,
180- height : radius * 2 + 94 ,
181- width : radius * 2 + 94 ,
182- left : - 220 ,
183- top : - ( WindowHeight / 2 - radius + 8 ) ,
184- // backgroundColor: 'red',
185- borderRadius : 1000
186- } }
187- />
188- </ View >
189- < Animated . View
190- pointerEvents = "none"
191- style = { [
192- {
193- height : WindowHeight / 2 ,
194- } ,
195- styles . timer ,
196- ] }
141+ return (
142+ < HourLabel
143+ key = { label }
144+ label = { label }
145+ radius = { radius }
146+ linesAmount = { linesAmount }
147+ progressRadiants = { progressRadiants }
148+ tickIndex = { tickIndex }
149+ />
150+ ) ;
151+ } ) }
152+ </ Animated . View >
153+ < LinearGradient
154+ colors = { [ '#000000' , '#000000' , '#000000' , '#00000070' , 'transparent' ] }
155+ start = { {
156+ x : 0 ,
157+ y : 0
158+ } }
159+ end = { {
160+ x : 1.1 ,
161+ y : 0
162+ } }
163+ style = { {
164+ position : 'absolute' ,
165+ height : radius * 2 + 94 ,
166+ width : radius * 2 + 94 ,
167+ left : - 220 ,
168+ top : - ( WindowHeight / 2 - radius + 8 ) ,
169+ // backgroundColor: 'red',
170+ borderRadius : 1000
171+ } }
197172 />
198173 </ View >
199- ) ;
200- } ,
201- ) ;
174+ < Animated . View
175+ pointerEvents = "none"
176+ style = { [
177+ {
178+ height : WindowHeight / 2 ,
179+ } ,
180+ styles . timer ,
181+ ] }
182+ />
183+ </ View >
184+ ) ;
185+ } ;
202186
203187type HourLabelProps = {
204188 label : string ;
0 commit comments