troberts-28 / react-native-timer-picker

A simple, flexible, performant duration picker for React Native apps (Expo & Bare Workflow). Great for timers, alarms and duration inputs. Includes iOS-style haptic and audio feedback.
https://www.npmjs.com/package/react-native-timer-picker
MIT License
142 stars 19 forks source link

[BUG] Infinite scroll FlatList flickering #33

Closed krisnapy closed 3 months ago

krisnapy commented 3 months ago

Scroll flickering when using 12 hours format.

import React, { useMemo } from 'react'
import { LinearGradient } from 'react-native-linear-gradient'
import { TimerPickerModal } from 'react-native-timer-picker'

import { theme } from 'theme'

import { TimerPickerModalProps } from './types'

const pickerTheme = {
  theme: 'light' as 'light',
  confirmButton: {
    borderColor: theme.colors.palette.brandPrimary,
  },
  cancelButton: {
    borderColor: theme.colors.palette.greyExtraLight,
  },
}

export const TimePickerModal = (props: TimerPickerModalProps) => {
  const { onCancel, onConfirm, visible, value, modalTitle} = props

  const currentValue = useMemo(() => {
    if (value) {
      return {
        hours: value.getHours(),
        minutes: value.getMinutes(),
        seconds: value.getSeconds(),
      }
    }
    return { hours: 0, minutes: 0, seconds: 0 }
  }, [value])

  const formatTimeToISO = ({
    hours = 0,
    minutes = 0,
    seconds = 0,
  }: {
    hours?: number;
    minutes?: number;
    seconds?: number;
  }) => {
    const date = new Date()
    date.setHours(hours, minutes, seconds)
    return date
  };

  return (
    <TimerPickerModal
      visible={visible}
      setIsVisible={onCancel}
      onConfirm={(pickedDuration) => {
        const dateValue = formatTimeToISO(pickedDuration)
        onConfirm(dateValue)
      }}
      onDurationChange={console.log}
      hideSeconds
      initialValue={currentValue}
      modalTitle={modalTitle}
      onCancel={onCancel}
      closeOnOverlayPress
      use12HourPicker
      LinearGradient={LinearGradient}
      styles={pickerTheme}
    />
  )
}

https://github.com/user-attachments/assets/8583fdf4-8866-4485-ac21-6e43493b4e79

troberts-28 commented 3 months ago

Hey @krisnapy, thanks for raising this! Will take a look and get this fixed.

troberts-28 commented 3 months ago

Hey @krisnapy,

I haven't been able to fix this completely but I've made some changes that should help.

The reason you get a flicker is that behind the scenes, each hour/minute/second picker auto-scrolls back up the list when you near the end of the list, to give the appearance of an infinite scroll. Normally the flicker is barely visible, but it's clearly visible if you scroll slowly, as in your video.

It's actually not just confined to the hour picker when using the 12 hour format, but affects all of the pickers. You are less likely to spot it in the minute/second pickers, simply because the list is longer.

Under the hood, the picker repeats the list of numbers a given number of times to avoid auto-scrolling every time you hit the end of the list. Previously, this was hard-coded as 3 repetitions. I have made it possible to configure the number of repititions (with these props: repeatHourNumbersNTimes, repeatMinuteNumbersNTimes and repeatSecondNumbersNTimes) , to reduce the amount of auto-scrolling (and therefore flickering). Bare in mind that there is a slight performance trade-off in increasing those numbers, as the list being rendered increases in length.

I have set the default number of repititions for repeatHourNumbersNTimes to 6, so that the list length mirrors that of the minutes/seconds pickers, and the auto-scroll flickering is less likely to be noticed.

Makes sense?

troberts-28 commented 3 months ago

I did also improve the auto-scroll function so that it doesn't flick down a number (from 11am to 12pm in your video), instead there is a slight flicker, but it remains on 11am.

troberts-28 commented 3 months ago

And one more thing!

If you want to avoid the flicker at all costs, you can use disableInfiniteScroll and set the number of repititions to some fairly high value. That will stop any auto-scrolling, but will mean a user can reach the end of the list by scrolling far in either direction.