Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions package/src/components/AttachmentPicker/AttachmentPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
LayoutChangeEvent,
} from 'react-native';

import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated';

import { useBottomSheetSpringConfigs } from '@gorhom/bottom-sheet';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
Expand Down Expand Up @@ -119,15 +121,44 @@ export const AttachmentPicker = () => {
[semantics.backgroundCoreElevation1],
);

const animatedIndex = useSharedValue(currentIndex);

// This is required to prevent the attachment picker from getting out of sync
// with the rest of the state. While there are more prudent fixes, this is about
// as much as we can do now without refactoring the entire state layer for the
// picker. When we do that, this can be removed completely.
const reactToIndex = useStableCallback((index: number) => {
if (index === -1) {
attachmentPickerStore.setSelectedPicker(undefined);
}

if (index === 0) {
// TODO: Extend the store to at least accept a default value.
// This in particular is not nice.
attachmentPickerStore.setSelectedPicker('images');
}
});

useAnimatedReaction(
() => animatedIndex.value,
(index, previousIndex) => {
if ((index === 0 || index === -1) && index !== previousIndex) {
runOnJS(reactToIndex)(index);
}
},
);

return (
<BottomSheet
android_keyboardInputMode='adjustResize'
backgroundStyle={backgroundStyle}
enablePanDownToClose={false}
enableContentPanningGesture={false}
enableDynamicSizing={false}
handleComponent={RenderNull}
index={currentIndex}
onAnimate={setCurrentIndex}
animatedIndex={animatedIndex}
// @ts-ignore
ref={ref}
snapPoints={snapPoints}
Expand Down
18 changes: 4 additions & 14 deletions package/src/contexts/messageInputContext/MessageInputContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, {
useRef,
useState,
} from 'react';
import { Alert, Linking, Platform, TextInput, TextInputProps } from 'react-native';
import { Alert, Linking, TextInput, TextInputProps } from 'react-native';

import { lookup as lookupMimeType } from 'mime-types';
import {
Expand Down Expand Up @@ -540,19 +540,9 @@ export const MessageInputProvider = ({
*/
const openAttachmentPicker = useCallback(() => {
dismissKeyboard();
const run = () => {
attachmentPickerStore.setSelectedPicker('images');
openPicker();
};

if (Platform.OS === 'android') {
setTimeout(() => {
run();
}, 200);
} else {
run();
}
}, [openPicker, attachmentPickerStore]);
attachmentPickerStore.setSelectedPicker('images');
openPicker();
}, [attachmentPickerStore, openPicker]);

/**
* Function to close the attachment picker if the MediaLibrary is installed.
Expand Down
35 changes: 1 addition & 34 deletions package/src/hooks/useAttachmentPickerBottomSheet.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useRef } from 'react';

import BottomSheet from '@gorhom/bottom-sheet';
import { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';

/**
* This hook is used to manage the state of the attachment picker bottom sheet.
* It provides functions to open and close the bottom sheet, as well as a reference to the bottom sheet itself.
* It also handles the cleanup of the timeout used to close the bottom sheet.
* The bottom sheet is used to display the attachment picker UI.
* The `openPicker` function opens the bottom sheet, and the `closePicker` function closes it.
* The `bottomSheetRef` is a reference to the bottom sheet component, which allows for programmatic control of the bottom sheet.
* The `bottomSheetCloseTimeoutRef` is used to store the timeout ID for the close operation, allowing for cleanup if necessary.
*/
export const useAttachmentPickerBottomSheet = () => {
const bottomSheetCloseTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);
const bottomSheetRef = useRef<BottomSheet>(null);

useEffect(
() =>
// cleanup the timeout if the component unmounts
() => {
if (bottomSheetCloseTimeoutRef.current) {
clearTimeout(bottomSheetCloseTimeoutRef.current);
}
},
[],
);

const openPicker = useCallback((ref: React.RefObject<BottomSheetMethods | null>) => {
if (bottomSheetCloseTimeoutRef.current) {
clearTimeout(bottomSheetCloseTimeoutRef.current);
}
if (ref.current?.snapToIndex) {
ref.current.snapToIndex(0);
} else {
Expand All @@ -40,27 +23,11 @@ export const useAttachmentPickerBottomSheet = () => {

const closePicker = useCallback((ref: React.RefObject<BottomSheetMethods | null>) => {
if (ref.current?.close) {
if (bottomSheetCloseTimeoutRef.current) {
clearTimeout(bottomSheetCloseTimeoutRef.current);
}
ref.current.close();
// Attempt to close the bottomsheet again to circumvent accidental opening on Android.
// Details: This to prevent a race condition where the close function is called during the point when a internal container layout happens within the bottomsheet due to keyboard affecting the layout
// If the container layout measures a shorter height than previous but if the close snapped to the previous height's position, the bottom sheet will show up
// this short delay ensures that close function is always called after a container layout due to keyboard change
// NOTE: this timeout has to be above 500 as the keyboardAnimationDuration is 500 in the bottomsheet library - see src/hooks/useKeyboard.ts there for more details
bottomSheetCloseTimeoutRef.current = setTimeout(() => {
ref.current?.close();
}, 600);
}
}, []);

useEffect(() => {
closePicker(bottomSheetRef);
}, [closePicker]);

return {
bottomSheetCloseTimeoutRef,
bottomSheetRef,
closePicker,
openPicker,
Expand Down
14 changes: 11 additions & 3 deletions package/src/hooks/useKeyboardVisibility.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { EventSubscription, Keyboard } from 'react-native';
import { EventSubscription, Keyboard, Platform } from 'react-native';

import { KeyboardControllerPackage } from '../components/KeyboardCompatibleView/KeyboardControllerAvoidingView';

Expand All @@ -24,8 +24,16 @@ export const useKeyboardVisibility = () => {
),
);
} else {
listeners.push(Keyboard.addListener('keyboardWillShow', () => setIsKeyboardVisible(true)));
listeners.push(Keyboard.addListener('keyboardWillHide', () => setIsKeyboardVisible(false)));
listeners.push(
Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', () =>
setIsKeyboardVisible(true),
),
);
listeners.push(
Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', () =>
setIsKeyboardVisible(false),
),
);
}

return () => listeners.forEach((listener) => listener.remove());
Expand Down
Loading