1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "RNCSliderManager.h"
9
10#import <React/RCTBridge.h>
11#import <React/RCTEventDispatcher.h>
12#import "RNCSlider.h"
13#import <React/UIView+React.h>
14
15@implementation RNCSliderManager
16{
17  BOOL _isSliding;
18}
19
20RCT_EXPORT_MODULE()
21
22- (UIView *)view
23{
24  RNCSlider *slider = [RNCSlider new];
25  [slider addTarget:self action:@selector(sliderValueChanged:)
26   forControlEvents:UIControlEventValueChanged];
27  [slider addTarget:self action:@selector(sliderTouchStart:)
28   forControlEvents:UIControlEventTouchDown];
29  [slider addTarget:self action:@selector(sliderTouchEnd:)
30   forControlEvents:(UIControlEventTouchUpInside |
31                     UIControlEventTouchUpOutside |
32                     UIControlEventTouchCancel)];
33
34  UITapGestureRecognizer *tapGesturer;
35  tapGesturer = [[UITapGestureRecognizer alloc] initWithTarget: self action:@selector(tapHandler:)];
36  [tapGesturer setNumberOfTapsRequired: 1];
37  [slider addGestureRecognizer:tapGesturer];
38
39  return slider;
40}
41
42- (void)tapHandler:(UITapGestureRecognizer *)gesture {
43  if ([gesture.view class] != [RNCSlider class]) {
44    return;
45  }
46  RNCSlider *slider = (RNCSlider *)gesture.view;
47  slider.isSliding = _isSliding;
48
49  // Ignore this tap if in the middle of a slide.
50  if (_isSliding) {
51    return;
52  }
53
54  if (!slider.tapToSeek) {
55    return;
56  }
57
58  CGPoint touchPoint = [gesture locationInView:slider];
59  float rangeWidth = slider.maximumValue - slider.minimumValue;
60  float sliderPercent = touchPoint.x / slider.bounds.size.width;
61  slider.lastValue = slider.value;
62  float value = slider.minimumValue + (rangeWidth * sliderPercent);
63
64  [slider setValue:[slider discreteValue:value] animated: YES];
65
66  if (slider.onRNCSliderSlidingStart) {
67    slider.onRNCSliderSlidingStart(@{
68      @"value": @(slider.lastValue),
69    });
70  }
71
72  // Trigger onValueChange to address https://github.com/react-native-community/react-native-slider/issues/212
73  if (slider.onRNCSliderValueChange) {
74    slider.onRNCSliderValueChange(@{
75      @"value": @(slider.value),
76    });
77  }
78
79  if (slider.onRNCSliderSlidingComplete) {
80    slider.onRNCSliderSlidingComplete(@{
81      @"value": @(slider.value),
82    });
83  }
84}
85
86static void RNCSendSliderEvent(RNCSlider *sender, BOOL continuous, BOOL isSlidingStart)
87{
88  float value = [sender discreteValue:sender.value];
89
90  if (value < sender.lowerLimit) {
91      value = sender.lowerLimit;
92      [sender setValue:value animated:NO];
93  } else if (value > sender.upperLimit) {
94      value = sender.upperLimit;
95      [sender setValue:value animated:NO];
96  }
97
98  if(!sender.isSliding) {
99    [sender setValue:value animated:NO];
100  }
101
102  if (continuous) {
103    if (sender.onRNCSliderValueChange && sender.lastValue != value) {
104      sender.onRNCSliderValueChange(@{
105        @"value": @(value),
106      });
107    }
108  } else {
109    if (sender.onRNCSliderSlidingComplete && !isSlidingStart) {
110      sender.onRNCSliderSlidingComplete(@{
111        @"value": @(value),
112      });
113    }
114      if (sender.onRNCSliderSlidingStart && isSlidingStart) {
115        sender.onRNCSliderSlidingStart(@{
116          @"value": @(value),
117        });
118      }
119  }
120
121  sender.lastValue = value;
122}
123
124- (void)sliderValueChanged:(RNCSlider *)sender
125{
126  RNCSendSliderEvent(sender, YES, NO);
127}
128
129- (void)sliderTouchStart:(RNCSlider *)sender
130{
131  RNCSendSliderEvent(sender, NO, YES);
132  _isSliding = YES;
133  sender.isSliding = YES;
134}
135
136- (void)sliderTouchEnd:(RNCSlider *)sender
137{
138  RNCSendSliderEvent(sender, NO, NO);
139  _isSliding = NO;
140  sender.isSliding = NO;
141}
142
143RCT_CUSTOM_VIEW_PROPERTY(value, float, RNCSlider)
144{
145  if (!view.isSliding) {
146    view.value = [RCTConvert float:json];
147  }
148}
149RCT_EXPORT_VIEW_PROPERTY(step, float);
150RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage);
151RCT_EXPORT_VIEW_PROPERTY(minimumTrackImage, UIImage);
152RCT_EXPORT_VIEW_PROPERTY(maximumTrackImage, UIImage);
153RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
154RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
155RCT_EXPORT_VIEW_PROPERTY(lowerLimit, float);
156RCT_EXPORT_VIEW_PROPERTY(upperLimit, float);
157RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor);
158RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor);
159RCT_EXPORT_VIEW_PROPERTY(onRNCSliderValueChange, RCTBubblingEventBlock);
160RCT_EXPORT_VIEW_PROPERTY(onRNCSliderSlidingStart, RCTBubblingEventBlock);
161RCT_EXPORT_VIEW_PROPERTY(onRNCSliderSlidingComplete, RCTBubblingEventBlock);
162RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor);
163RCT_EXPORT_VIEW_PROPERTY(thumbImage, UIImage);
164RCT_EXPORT_VIEW_PROPERTY(inverted, BOOL);
165RCT_EXPORT_VIEW_PROPERTY(tapToSeek, BOOL);
166RCT_EXPORT_VIEW_PROPERTY(accessibilityUnits, NSString);
167RCT_EXPORT_VIEW_PROPERTY(accessibilityIncrements, NSArray);
168
169RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RNCSlider)
170{
171  if (json) {
172    [view setDisabled: [RCTConvert BOOL:json]];
173  } else {
174    [view setDisabled: defaultView.enabled];
175  }
176}
177
178@end
179