Skip to content

Commit

Permalink
Merge branch 'patch/v1.10.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
troberts-28 committed Aug 4, 2024
2 parents dd5168d + a3ada93 commit 49dd757
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 111 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ return (
| use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false |
| amLabel | Set the AM label if using the 12-hour picker | String | am | false |
| pmLabel | Set the PM label if using the 12-hour picker | String | pm | false |
| repeatHourNumbersNTimes | Set the number of times the list of hours is repeated in the picker | Number | 6 | false |
| repeatHourNumbersNTimes | Set the number of times the list of hours is repeated in the picker | Number | 7 | false |
| repeatMinuteNumbersNTimes | Set the number of times the list of minutes is repeated in the picker | Number | 3 | false |
| repeatSecondNumbersNTimes | Set the number of times the list of seconds is repeated in the picker | Number | 3 | false |
| disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
Expand Down Expand Up @@ -491,7 +491,7 @@ Note the minor limitations to the allowed styles for `pickerContainer` and `pick

When the `disableInfiniteScroll` prop is not set, the picker gives the appearance of an infinitely scrolling picker by auto-scrolling forward/back when you near the start/end of the list. When the picker auto-scrolls, a momentary flicker is visible if you are scrolling very slowly.

To mitigate for this, you can modify the `repeatHourNumbersNTimes`, `repeatMinuteNumbersNTimes` and `repeatSecondNumbersNTimes` props. These set the number of times the list of numbers in each picker is repeated. These have a performance trade-off: higher values mean the picker has to auto-scroll less to maintain the infinite scroll, but has to render a longer list of numbers. By default, the props are set to 6, 3 and 3, respectively, which balances that trade-off effectively.
To mitigate for this, you can modify the `repeatHourNumbersNTimes`, `repeatMinuteNumbersNTimes` and `repeatSecondNumbersNTimes` props. These set the number of times the list of numbers in each picker is repeated. These have a performance trade-off: higher values mean the picker has to auto-scroll less to maintain the infinite scroll, but has to render a longer list of numbers. By default, the props are set to 7, 3 and 3, respectively, which balances that trade-off effectively.

Note that you can avoid the auto-scroll flickering entirely by disabling infinite scroll. You could then set the above props to high values, so that a user has to scroll far down/up the list to reach the end of the list.

Expand Down
4 changes: 0 additions & 4 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export default function App() {
<TimerPickerModal
Audio={Audio}
closeOnOverlayPress
disableInfiniteScroll
Haptics={Haptics}
LinearGradient={LinearGradient}
modalProps={{
Expand All @@ -119,9 +118,6 @@ export default function App() {
setAlarmStringExample1(formatTime(pickedDuration));
setShowPickerExample1(false);
}}
repeatHourNumbersNTimes={1}
repeatMinuteNumbersNTimes={1}
repeatSecondNumbersNTimes={1}
setIsVisible={setShowPickerExample1}
styles={{
theme: "dark",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"url": "https://github.com/troberts-28"
},
"license": "MIT",
"version": "1.10.2",
"version": "1.10.3",
"main": "dist/commonjs/index.js",
"module": "dist/module/index.js",
"types": "dist/typescript/index.d.ts",
Expand Down
147 changes: 80 additions & 67 deletions src/components/DurationScroll/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
generateNumbers,
} from "../../utils/generateNumbers";
import { getAdjustedLimit } from "../../utils/getAdjustedLimit";
import { getScrollIndex } from "../../utils/getScrollIndex";
import { getDurationAndIndexFromScrollOffset } from "../../utils/getDurationAndIndexFromScrollOffset";
import { getInitialScrollIndex } from "../../utils/getInitialScrollIndex";

import type { DurationScrollProps, DurationScrollRef } from "./types";

Expand Down Expand Up @@ -56,19 +57,29 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
topPickerGradientOverlayProps,
} = props;

const data = useMemo(() => {
const safeRepeatNumbersNTimes = useMemo(() => {
if (!disableInfiniteScroll && repeatNumbersNTimes < 2) {
return 2;
} else if (repeatNumbersNTimes < 1) {
return 1;
}

return Math.round(repeatNumbersNTimes);
}, [disableInfiniteScroll, repeatNumbersNTimes]);

const numbersForFlatList = useMemo(() => {
if (is12HourPicker) {
return generate12HourNumbers({
padNumbersWithZero,
repeatNTimes: repeatNumbersNTimes,
repeatNTimes: safeRepeatNumbersNTimes,
disableInfiniteScroll,
padWithNItems,
});
}

return generateNumbers(numberOfItems, {
padNumbersWithZero,
repeatNTimes: repeatNumbersNTimes,
repeatNTimes: safeRepeatNumbersNTimes,
disableInfiniteScroll,
padWithNItems,
});
Expand All @@ -78,22 +89,24 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
numberOfItems,
padNumbersWithZero,
padWithNItems,
repeatNumbersNTimes,
safeRepeatNumbersNTimes,
]);

const initialScrollIndex = useMemo(
() =>
getScrollIndex({
getInitialScrollIndex({
disableInfiniteScroll,
numberOfItems,
padWithNItems,
repeatNumbersNTimes,
repeatNumbersNTimes: safeRepeatNumbersNTimes,
value: initialValue,
}),
[
disableInfiniteScroll,
initialValue,
numberOfItems,
padWithNItems,
repeatNumbersNTimes,
safeRepeatNumbersNTimes,
]
);

Expand Down Expand Up @@ -134,6 +147,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
setClickSound(sound);
}
};

loadSound();

// Unload sound when component unmounts
Expand All @@ -143,27 +157,6 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [Audio]);

useImperativeHandle(ref, () => ({
reset: (options) => {
flatListRef.current?.scrollToIndex({
animated: options?.animated ?? false,
index: initialScrollIndex,
});
},
setValue: (value, options) => {
flatListRef.current?.scrollToIndex({
animated: options?.animated ?? false,
index: getScrollIndex({
numberOfItems,
padWithNItems,
repeatNumbersNTimes,
value: value,
}),
});
},
latestDuration: latestDuration,
}));

const renderItem = useCallback(
({ item }: { item: string }) => {
let stringItem = item;
Expand Down Expand Up @@ -233,25 +226,23 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
}

if (aggressivelyGetLatestDuration) {
const newIndex = Math.round(
e.nativeEvent.contentOffset.y /
styles.pickerItemContainer.height
);
let newDuration =
(disableInfiniteScroll
? newIndex
: newIndex + padWithNItems) %
(numberOfItems + 1);
const newValues = getDurationAndIndexFromScrollOffset({
disableInfiniteScroll,
itemHeight: styles.pickerItemContainer.height,
numberOfItems,
padWithNItems,
yContentOffset: e.nativeEvent.contentOffset.y,
});

if (newDuration !== latestDuration.current) {
if (newValues.duration !== latestDuration.current) {
// check limits
if (newDuration > adjustedLimited.max) {
newDuration = adjustedLimited.max;
} else if (newDuration < adjustedLimited.min) {
newDuration = adjustedLimited.min;
if (newValues.duration > adjustedLimited.max) {
newValues.duration = adjustedLimited.max;
} else if (newValues.duration < adjustedLimited.min) {
newValues.duration = adjustedLimited.min;
}

latestDuration.current = newDuration;
latestDuration.current = newValues.duration;
}
}

Expand Down Expand Up @@ -299,20 +290,19 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(

const onMomentumScrollEnd = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
const newIndex = Math.round(
e.nativeEvent.contentOffset.y /
styles.pickerItemContainer.height
);
let newDuration =
(disableInfiniteScroll
? newIndex
: newIndex + padWithNItems) %
(numberOfItems + 1);
const newValues = getDurationAndIndexFromScrollOffset({
disableInfiniteScroll,
itemHeight: styles.pickerItemContainer.height,
numberOfItems,
padWithNItems,
yContentOffset: e.nativeEvent.contentOffset.y,
});

// check limits
if (newDuration > adjustedLimited.max) {
if (newValues.duration > adjustedLimited.max) {
const targetScrollIndex =
newIndex - (newDuration - adjustedLimited.max);
newValues.index -
(newValues.duration - adjustedLimited.max);
flatListRef.current?.scrollToIndex({
animated: true,
index:
Expand All @@ -321,27 +311,28 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
? targetScrollIndex
: adjustedLimited.max - 1,
}); // scroll down to max
newDuration = adjustedLimited.max;
} else if (newDuration < adjustedLimited.min) {
newValues.duration = adjustedLimited.max;
} else if (newValues.duration < adjustedLimited.min) {
const targetScrollIndex =
newIndex + (adjustedLimited.min - newDuration);
newValues.index +
(adjustedLimited.min - newValues.duration);
flatListRef.current?.scrollToIndex({
animated: true,
index:
// guard against scrolling beyond end of list
targetScrollIndex <= data.length - 1
targetScrollIndex <= numbersForFlatList.length - 1
? targetScrollIndex
: adjustedLimited.min,
}); // scroll up to min
newDuration = adjustedLimited.min;
newValues.duration = adjustedLimited.min;
}

onDurationChange(newDuration);
onDurationChange(newValues.duration);
},
[
adjustedLimited.max,
adjustedLimited.min,
data.length,
numbersForFlatList.length,
disableInfiniteScroll,
numberOfItems,
onDurationChange,
Expand All @@ -363,15 +354,15 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
} else if (
viewableItems[0]?.index &&
viewableItems[0].index >=
numberOfItems * (repeatNumbersNTimes - 0.5)
numberOfItems * (safeRepeatNumbersNTimes - 0.5)
) {
flatListRef.current?.scrollToIndex({
animated: false,
index: viewableItems[0].index - numberOfItems - 1,
index: viewableItems[0].index - numberOfItems,
});
}
},
[numberOfItems, repeatNumbersNTimes]
[numberOfItems, safeRepeatNumbersNTimes]
);

const getItemLayout = useCallback(
Expand All @@ -391,6 +382,28 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
},
]);

useImperativeHandle(ref, () => ({
reset: (options) => {
flatListRef.current?.scrollToIndex({
animated: options?.animated ?? false,
index: initialScrollIndex,
});
},
setValue: (value, options) => {
flatListRef.current?.scrollToIndex({
animated: options?.animated ?? false,
index: getInitialScrollIndex({
disableInfiniteScroll,
numberOfItems,
padWithNItems,
repeatNumbersNTimes: safeRepeatNumbersNTimes,
value: value,
}),
});
},
latestDuration: latestDuration,
}));

return (
<View
pointerEvents={isDisabled ? "none" : undefined}
Expand All @@ -406,7 +419,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
testID={testID}>
<FlatList
ref={flatListRef}
data={data}
data={numbersForFlatList}
decelerationRate={0.88}
getItemLayout={getItemLayout}
initialScrollIndex={initialScrollIndex}
Expand All @@ -420,7 +433,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
showsVerticalScrollIndicator={false}
snapToAlignment="start"
// used in place of snapToOffset due to bug on Android
snapToOffsets={[...Array(data.length)].map(
snapToOffsets={[...Array(numbersForFlatList.length)].map(
(_, i) => i * styles.pickerItemContainer.height
)}
testID="duration-scroll-flatlist"
Expand Down
Loading

0 comments on commit 49dd757

Please sign in to comment.