Skip to content

Commit

Permalink
feature: Add slide limit to iOS and Android (callstack#432)
Browse files Browse the repository at this point in the history
* Add slide limit on android and ios

* Adding limit to readme

* Revert babel change

* upper and lower limits with negative numbers

* Update snapshots

* PR feedback

* Update mLowerLimit and mUpperLimit defaults

* Update README.md

Co-authored-by: Erika Smith <[email protected]>
  • Loading branch information
alfonsocj and erikaannesmith authored Dec 17, 2022
1 parent 10c40da commit a649f8f
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ To use this library you need to ensure you are using the correct version of Reac
| `maximumValue` | Initial maximum value of the slider.<br/>Default value is 1. | number | No | |
| `minimumTrackTintColor` | The color used for the track to the left of the button.<br/>Overrides the default blue gradient image on iOS. | [color](https://reactnative.dev/docs/colors) | No | |
| `minimumValue` | Initial minimum value of the slider.<br/>Default value is 0. | number | No | |
| `lowerLimit` | Slide lower limit. The user won't be able to slide below this limit. | number | No | Android, iOS |
| `upperLimit` | Slide upper limit. The user won't be able to slide above this limit. | number | No | Android, iOS |
| `onSlidingStart` | Callback that is called when the user picks up the slider.<br/>The initial value is passed as an argument to the callback handler. | function | No | |
| `onSlidingComplete` | Callback that is called when the user releases the slider, regardless if the value has changed.<br/>The current value is passed as an argument to the callback handler. | function | No | |
| `onValueChange` | Callback continuously called while the user is dragging the slider. | function | No | |
Expand Down
33 changes: 32 additions & 1 deletion example/src/Examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ export interface Props {
}

const SliderExample = (props: SliderProps) => {
const [value, setValue] = useState(0);
const [value, setValue] = useState(props.value ?? 0);
return (
<View>
<Text style={styles.text}>{value && +value.toFixed(3)}</Text>
<Slider
step={0.5}
style={styles.slider}
{...props}
value={value}
onValueChange={setValue}
/>
</View>
Expand Down Expand Up @@ -103,6 +104,36 @@ export const examples: Props[] = [
return <SliderExample step={0.25} tapToSeek={true} />;
},
},
{
title: 'Limit on positive values [30, 80]',
render() {
return (
<SliderExample
step={1}
value={40}
minimumValue={0}
maximumValue={120}
lowerLimit={30}
upperLimit={80}
/>
);
},
},
{
title: 'Limit on negative values [-70, -20]',
render() {
return (
<SliderExample
step={1}
value={-30}
minimumValue={-80}
maximumValue={0}
lowerLimit={-70}
upperLimit={-20}
/>
);
},
},
{
title: 'onSlidingStart',
render(): React.ReactElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ public class ReactSlider extends AppCompatSeekBar {

private List<String> mAccessibilityIncrements;

/** Real limit value based on min and max values. This comes from props */
private double mRealLowerLimit = Long.MIN_VALUE;

/** Lower limit based on the SeekBar progress 0..total steps */
private int mLowerLimit;

/** Real limit value based on min and max values. This comes from props */
private double mRealUpperLimit = Long.MAX_VALUE;

/** Upper limit based on the SeekBar progress 0..total steps */
private int mUpperLimit;

public ReactSlider(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
Expand Down Expand Up @@ -102,6 +114,24 @@ private void disableStateListAnimatorIfNeeded() {
updateAll();
}

/* package */ void setLowerLimit(double value) {
mRealLowerLimit = value;
updateLowerLimit();
}

/* package */ void setUpperLimit(double value) {
mRealUpperLimit = value;
updateUpperLimit();
}

int getLowerLimit() {
return this.mLowerLimit;
}

int getUpperLimit() {
return this.mUpperLimit;
}

boolean isSliding() {
return isSliding;
}
Expand Down Expand Up @@ -186,9 +216,23 @@ private void updateAll() {
mStepCalculated = (mMaxValue - mMinValue) / (double) DEFAULT_TOTAL_STEPS;
}
setMax(getTotalSteps());
updateLowerLimit();
updateUpperLimit();
updateValue();
}

/** Update limit based on props limit, max and min */
private void updateLowerLimit() {
double limit = Math.max(mRealLowerLimit, mMinValue);
mLowerLimit = (int) Math.round((limit - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps());
}

/** Update limit based on props limit, max and min */
private void updateUpperLimit() {
double limit = Math.min(mRealUpperLimit, mMaxValue);
mUpperLimit = (int) Math.round((limit - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps());
}

/** Update value only (optimization in case only value is set). */
private void updateValue() {
setProgress((int) Math.round((mValue - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ public static void setMaximumValue(ReactSlider view, float value) {
view.setMaxValue(value);
}

public static void setLowerLimit(ReactSlider view, double value) {
view.setLowerLimit(value);
}

public static void setUpperLimit(ReactSlider view, double value) {
view.setUpperLimit(value);
}

public static void setStep(ReactSlider view, float value) {
view.setStep(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,20 @@ protected ViewManagerDelegate<ReactSlider> getDelegate() {
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
ReactSlider slider = (ReactSlider)seekbar;

if(progress < slider.getLowerLimit()) {
progress = slider.getLowerLimit();
seekbar.setProgress(progress);
} else if (progress > slider.getUpperLimit()) {
progress = slider.getUpperLimit();
seekbar.setProgress(progress);
}

ReactContext reactContext = (ReactContext) seekbar.getContext();
int reactTag = seekbar.getId();
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag)
.dispatchEvent(new ReactSliderEvent(reactTag, ((ReactSlider)seekbar).toRealProgress(progress), fromUser));
.dispatchEvent(new ReactSliderEvent(reactTag, slider.toRealProgress(progress), fromUser));
}

@Override
Expand Down Expand Up @@ -154,6 +164,16 @@ public void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibi
ReactSliderManagerImpl.setAccessibilityIncrements(view, accessibilityIncrements);
}

@ReactProp(name = "lowerLimit")
public void setLowerLimit(ReactSlider view, float value) {
ReactSliderManagerImpl.setLowerLimit(view, value);
}

@ReactProp(name = "upperLimit")
public void setUpperLimit(ReactSlider view, float value) {
ReactSliderManagerImpl.setUpperLimit(view, value);
}

@Override
@ReactProp(name = "thumbImage")
public void setThumbImage(ReactSlider view, @androidx.annotation.Nullable ReadableMap source) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,21 @@ public class ReactSliderManager extends SimpleViewManager<ReactSlider> {
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
ReactSlider slider = (ReactSlider)seekbar;

if(progress < slider.getLowerLimit()) {
progress = slider.getLowerLimit();
seekbar.setProgress(progress);
} else if(progress > slider.getUpperLimit()) {
progress = slider.getUpperLimit();
seekbar.setProgress(progress);
}

ReactContext reactContext = (ReactContext) seekbar.getContext();
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
new ReactSliderEvent(
seekbar.getId(),
((ReactSlider)seekbar).toRealProgress(progress), fromUser));
slider.toRealProgress(progress), fromUser));
}

@Override
Expand Down Expand Up @@ -136,6 +146,16 @@ public void setMaximumValue(ReactSlider view, float value) {
ReactSliderManagerImpl.setMaximumValue(view, value);
}

@ReactProp(name = "lowerLimit")
public void setLowerLimit(ReactSlider view, float value) {
ReactSliderManagerImpl.setLowerLimit(view, value);
}

@ReactProp(name = "upperLimit")
public void setUpperLimit(ReactSlider view, float value) {
ReactSliderManagerImpl.setUpperLimit(view, value);
}

@ReactProp(name = "step", defaultFloat = 0f)
public void setStep(ReactSlider view, float value) {
ReactSliderManagerImpl.setStep(view, value);
Expand Down
3 changes: 3 additions & 0 deletions package/ios/RNCSlider.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
@property (nonatomic, assign) float lastValue;
@property (nonatomic, assign) bool isSliding;

@property (nonatomic, assign) float lowerLimit;
@property (nonatomic, assign) float upperLimit;

@property (nonatomic, strong) UIImage *trackImage;
@property (nonatomic, strong) UIImage *minimumTrackImage;
@property (nonatomic, strong) UIImage *maximumTrackImage;
Expand Down
3 changes: 3 additions & 0 deletions package/ios/RNCSliderComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ typedef void (^RNCLoadImageFailureBlock)();
@property (nonatomic, assign) float lastValue;
@property (nonatomic, assign) bool isSliding;

@property (nonatomic, assign) float lowerLimit;
@property (nonatomic, assign) float upperLimit;

@property (nonatomic, strong) UIImage *trackImage;
@property (nonatomic, strong) UIImage *minimumTrackImage;
@property (nonatomic, strong) UIImage *maximumTrackImage;
Expand Down
15 changes: 14 additions & 1 deletion package/ios/RNCSliderComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#import "RCTFabricComponentsPlugins.h"
#import "RNCSlider.h"


using namespace facebook::react;

@interface RNCSliderComponentView () <RCTRNCSliderViewProtocol>
Expand Down Expand Up @@ -118,6 +117,14 @@ - (void)RNCSendSliderEvent:(RNCSlider *)sender withContinuous:(BOOL)continuous i
{
float value = [sender discreteValue:sender.value];

if (value < sender.lowerLimit) {
value = sender.lowerLimit;
[sender setValue:value animated:NO];
} else if (value > sender.upperLimit) {
value = sender.upperLimit;
[sender setValue:value animated:NO];
}

if(!sender.isSliding) {
[sender setValue:value animated:NO];
}
Expand Down Expand Up @@ -163,6 +170,12 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
if (oldScreenProps.maximumValue != newScreenProps.maximumValue) {
[slider setMaximumValue:newScreenProps.maximumValue];
}
if (oldScreenProps.lowerLimit != newScreenProps.lowerLimit) {
slider.lowerLimit = newScreenProps.lowerLimit;
}
if (oldScreenProps.upperLimit != newScreenProps.upperLimit) {
slider.upperLimit = newScreenProps.upperLimit;
}
if (oldScreenProps.tapToSeek != newScreenProps.tapToSeek) {
slider.tapToSeek = newScreenProps.tapToSeek;
}
Expand Down
10 changes: 10 additions & 0 deletions package/ios/RNCSliderManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ static void RNCSendSliderEvent(RNCSlider *sender, BOOL continuous, BOOL isSlidin
{
float value = [sender discreteValue:sender.value];

if (value < sender.lowerLimit) {
value = sender.lowerLimit;
[sender setValue:value animated:NO];
} else if (value > sender.upperLimit) {
value = sender.upperLimit;
[sender setValue:value animated:NO];
}

if(!sender.isSliding) {
[sender setValue:value animated:NO];
}
Expand Down Expand Up @@ -144,6 +152,8 @@ - (void)sliderTouchEnd:(RNCSlider *)sender
RCT_EXPORT_VIEW_PROPERTY(maximumTrackImage, UIImage);
RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
RCT_EXPORT_VIEW_PROPERTY(lowerLimit, float);
RCT_EXPORT_VIEW_PROPERTY(upperLimit, float);
RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(onRNCSliderValueChange, RCTBubblingEventBlock);
Expand Down
2 changes: 2 additions & 0 deletions package/src/RNCSliderNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface NativeProps extends ViewProps {
thumbTintColor?: ColorValue;
trackImage?: ImageSource;
value?: Float;
lowerLimit?: Float;
upperLimit?: Float;
}

export default codegenNativeComponent<NativeProps>(
Expand Down
27 changes: 27 additions & 0 deletions package/src/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import type {ImageSource} from 'react-native/Libraries/Image/ImageSource';

import type {Ref} from 'react';

const LIMIT_MIN_VALUE = Number.MIN_SAFE_INTEGER;
const LIMIT_MAX_VALUE = Number.MAX_SAFE_INTEGER;

type Event = NativeSyntheticEvent<
Readonly<{
value: number;
Expand Down Expand Up @@ -100,6 +103,16 @@ type Props = ViewProps &
*/
maximumValue?: number;

/**
* The lower limit value of the slider. The user won't be able to slide below this limit.
*/
lowerLimit?: number;

/**
* The upper limit value of the slider. The user won't be able to slide above this limit.
*/
upperLimit?: number;

/**
* The color used for the track to the left of the button.
* Overrides the default blue gradient image on iOS.
Expand Down Expand Up @@ -221,9 +234,21 @@ const SliderComponent = (
}
: null;

const lowerLimit =
!!localProps.lowerLimit || localProps.lowerLimit === 0
? localProps.lowerLimit
: LIMIT_MIN_VALUE;

const upperLimit =
!!localProps.upperLimit || localProps.upperLimit === 0
? localProps.upperLimit
: LIMIT_MAX_VALUE;

return (
<RCTSliderNativeComponent
{...localProps}
lowerLimit={lowerLimit}
upperLimit={upperLimit}
accessibilityState={_accessibilityState}
thumbImage={
Platform.OS === 'web'
Expand Down Expand Up @@ -255,6 +280,8 @@ SliderWithRef.defaultProps = {
step: 0,
inverted: false,
tapToSeek: false,
lowerLimit: LIMIT_MIN_VALUE,
upperLimit: LIMIT_MAX_VALUE,
};

let styles = StyleSheet.create(
Expand Down
2 changes: 2 additions & 0 deletions package/src/__tests__/Slider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ describe('<Slider />', () => {
thumbTintColor={'green'}
onSlidingComplete={() => {}}
onValueChange={() => {}}
lowerLimit={0}
upperLimit={1}
/>,
)
.toJSON();
Expand Down
Loading

0 comments on commit a649f8f

Please sign in to comment.