Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/oscillator example #131

Merged
merged 15 commits into from
Sep 25, 2024
Merged

Feat/oscillator example #131

merged 15 commits into from
Sep 25, 2024

Conversation

maciejmakowski2003
Copy link
Collaborator

@maciejmakowski2003 maciejmakowski2003 commented Sep 23, 2024

  • Implemented custom silder
  • Reorganize oscillator example
  • Added switching oscillator type

Closes #127

Copy link
Member

@michalsek michalsek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at reanimated slider example: https://docs.swmansion.com/react-native-reanimated/examples/slider/

We can go with similar design to this :)

Besides the comments i've left (which are exaplanatory only), I think that having a constant width slider isn't a problem in our case so we can simplify that part

{Object.entries(Examples).map(
([key, example]: [ExampleKey, Example]) => (
<TouchableOpacity
onPress={() => navigation.navigate(key as never)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onPress={() => navigation.navigate(key as never)}
onPress={() => navigation.navigate(key)}

Comment on lines 163 to 164
<Text>Oscillator type: {oscillatorType}</Text>
<Button title={`Click to change type`} onPress={toggleOscillatorType} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This carousel toggle seems a bit wierd, can we have row of small buttons for each type + highlight for the currently active one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ofc!

const { value, onValueChange, minimumValue, maximumValue, step, style } =
props;
const x = useSharedValue(0);
const [sliderWidth, setSliderWidth] = useState<number>(300);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want the width to be defined by the user of the component, it should be also used in initialization. Preferably as a separate prop instead of extracting from style prop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it would be better to have sliderWidth as share value, state changes generate component re-renders, usually when handling animations we would like to avoid that as much as possible.

(xValue: number) => {
const fraction = xValue / sliderWidth;
const newValue = minimumValue + fraction * (maximumValue - minimumValue);
runOnJS(onValueChange)(Math.round(newValue / step) * step);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runOnJS call isn't necessary here: convertXToValue is always called inside runOnJS in the gesture callbacks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I would separate the calculation from setting a value / calling the onValueChange callback

setSliderWidth(style.width as number);
}

runOnJS(convertValueToX)(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect isn't a reanimated related hook and runs only on the JS thread. Thus we don't need the runONJS function

Suggested change
runOnJS(convertValueToX)(value);
convertValueToX(value);

<View style={[styles.default, style]}>
<GestureDetector gesture={panGesture}>
<View
style={[styles.slider, styles.default, { width: sliderWidth }]}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to have variable-size slider, best approach I think would be to:

  • use sliderWidth as an reanimated shared value

  • add an onLayout property that sets the sliderWidth shared value

  • or add ref to the top view component, add useLayoutEffect hook, that would call measure function on the view ref and sets up the sliderWidth shared value: https://reactnative.dev/docs/direct-manipulation

style={[styles.slider, styles.default, { width: sliderWidth }]}
hitSlop={hitSlop}
>
<Animated.View style={[styles.progress, { width: x }]} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Animation of width is slower than translation! (width changes forces react-native to re-calculate layout of the component)

Instead you can make this view as same width as its container, add a overflow: 'hidden' to the parent and instead of animating the width, animate the translation of this View:

for a value equal to minimum value it would be translated on x by 100% of container width,
for a value equal to max value it would be translated on x by 0.


useEffect(() => {
if (style && 'width' in style) {
setSliderWidth(style.width as number);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work, in react native style can be one of 4 types:

  • plain js object with styling properties passed directly to the component style={{ width: 300 }}
  • style created with StyleSheet.create({ }), in this case, the the style property will be an integer - id of the registered style.
  • a boolean :), people often use something like style={amIInsane && styles.insanityBox}
  • an array of combination of the 3 above

In order to be able to access property in such mess, you have to call StyleSheet.flatten on the style property first https://reactnative.dev/docs/stylesheet#flatten

But I think in this case I would be misuse of the flatten function

Comment on lines 18 to 31
<ScrollView contentContainerStyle={styles.scrollView}>
{Object.entries(Examples).map(([key, example]) => (
<TouchableOpacity
onPress={() => navigation.navigate(key as never)}
key={key}
style={styles.button}
>
<Text style={styles.title}>{example.title}</Text>
<Text style={styles.subtitle}>{example.subtitle}</Text>
</TouchableOpacity>
))}
{Object.entries(Examples).map(
([key, example]: [ExampleKey, Example]) => (
<TouchableOpacity
onPress={() => navigation.navigate(key)}
key={key}
style={styles.button}
>
<Text style={styles.title}>{example.title}</Text>
<Text style={styles.subtitle}>{example.subtitle}</Text>
</TouchableOpacity>
)
)}
</ScrollView>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of array of items of same shape etc. it is better to use FlatList component

Comment on lines 181 to 184
oscillatorTypeContainer: {
flexDirection: 'row',
marginTop: 20,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  oscillatorTypeContainer: {
    flexDirection: 'row',
    marginTop: 20,
  },

Not something to fix or change, but small hint, towards such components. Usually I prefer to separate margin around/between components for the components style itself. It helps to reuse same component when there are different margin between components in different places. So for example:

return (
  <Screen>
    <FirstComponent />
    <Spacer.Vertical size={20} />
    <Row>
      {buttons.map(/* ... *.)}
    </Row>
  </Screen>
);

where spacer is something like:

const Vertical: FC<Props> = ({ size }) => {
  return <View style={{ height: size }} />
};

/* ... */

export default {
  Vertical,
  Horizontal,
}

@maciejmakowski2003 maciejmakowski2003 merged commit 6d62cd2 into main Sep 25, 2024
2 checks passed
@maciejmakowski2003 maciejmakowski2003 deleted the feat/oscillator-example branch September 25, 2024 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Android] Fix slider
2 participants