stream-chat-react-native-core
Version:
The official React Native and Expo components for Stream Chat, a service for building chat applications
505 lines (442 loc) • 14.3 kB
text/typescript
import type React from 'react';
import {
ColorValue,
FlatList as DefaultFlatList,
StyleProp,
ViewProps,
ViewStyle,
} from 'react-native';
import type { NativeMultipartUpload } from './nativeMultipartUpload';
import type { File } from './types/types';
export type {
NativeMultipartAbortSignal,
NativeMultipartCanceledError,
NativeMultipartUpload,
NativeMultipartUploadEventEmitter,
NativeMultipartUploadHeader,
NativeMultipartUploadNativeResponse,
NativeMultipartUploadPart,
NativeMultipartUploadProgressConfig,
NativeMultipartUploadProgressEvent,
NativeMultipartUploadRequest,
NativeMultipartUploadResult,
NativeMultipartUploader,
NativeMultipartUploaderModule,
NativeMultipartUploaderProgressConfig,
NativeMultipartUploaderRequest,
} from './nativeMultipartUpload';
const fail = () => {
throw Error(
'Native handler was not registered, you should import stream-chat-expo or stream-chat-react-native',
);
};
type CompressImage = ({
compressImageQuality,
height,
uri,
width,
}: {
compressImageQuality: number;
height: number;
uri: string;
width: number;
}) => Promise<string> | never;
type DeleteFile = ({ uri }: { uri: string }) => Promise<boolean> | never;
type GetLocalAssetUri = (uriOrAssetId: string) => Promise<string | undefined> | never;
type OniOS14LibrarySelectionChange = (callback: () => void) => { unsubscribe: () => void };
type iOS14RefreshGallerySelection = () => Promise<void>;
type GetPhotos = ({ after, first }: { first: number; after?: string }) =>
| Promise<{
assets: File[];
endCursor: string;
hasNextPage: boolean;
iOSLimited: boolean;
}>
| never;
type PickDocument = ({ maxNumberOfFiles }: { maxNumberOfFiles?: number }) =>
| Promise<{
cancelled: boolean;
assets?: File[];
}>
| never;
type PickImageAssetType = {
askToOpenSettings?: boolean;
assets?: File[];
cancelled?: boolean;
};
export type PickImageOptions = {
maxNumberOfFiles?: number;
};
type PickImage = (options?: PickImageOptions) => Promise<PickImageAssetType> | never;
type SaveFileOptions = {
fileName: string;
fromUrl: string;
};
type SaveFile = (options: SaveFileOptions) => Promise<string> | never;
type SetClipboardString = (text: string) => Promise<void> | never;
type ShareOptions = {
type?: string;
url?: string;
};
type ShareImage = (options: ShareOptions) => Promise<boolean> | never;
type TakePhotoFileType = File & {
askToOpenSettings?: boolean;
cancelled?: boolean;
};
export type MediaTypes = 'image' | 'video' | 'mixed';
type TakePhoto = (options: {
compressImageQuality?: number;
mediaType?: MediaTypes;
}) => Promise<TakePhotoFileType> | never;
type HapticFeedbackMethod =
| 'impactHeavy'
| 'impactLight'
| 'impactMedium'
| 'notificationError'
| 'notificationSuccess'
| 'notificationWarning'
| 'selection';
type TriggerHaptic = (method: HapticFeedbackMethod) => void | never;
export type PlaybackStatus = {
currentPosition: number;
didJustFinish: boolean;
duration: number;
durationMillis: number;
error: string;
isBuffering: boolean;
isLoaded: boolean;
isLooping: boolean;
isMuted: boolean;
isPlaying: boolean;
isSeeking: boolean;
positionMillis: number;
shouldPlay: boolean;
};
export type PitchCorrectionQuality = 'low' | 'medium' | 'high';
export type AVPlaybackStatusToSet = {
isLooping: boolean;
isMuted: boolean;
pitchCorrectionQuality: PitchCorrectionQuality;
positionMillis: number;
progressUpdateIntervalMillis: number;
rate: number;
shouldCorrectPitch: boolean;
shouldPlay: boolean;
volume: number;
};
export type SoundOptions = {
basePathOrCallback?: string;
callback?: () => void;
filenameOrFile?: string;
initialStatus?: Partial<AVPlaybackStatusToSet>;
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void;
source?: { uri: string };
};
export type SoundReturnType = {
testID: string;
getDuration?: () => number;
isPlaying?: () => boolean;
onBuffer?: (props: { isBuffering: boolean }) => void;
onEnd?: () => void;
onLoad?: (payload: VideoPayloadData) => void;
onLoadStart?: () => void;
onPlaybackStateChanged?: (playbackState: PlaybackStatus) => void;
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void;
onProgress?: (data: VideoProgressData) => void;
onReadyForDisplay?: () => void;
onSeek?: (seekResponse: VideoSeekResponse) => void;
pause?: () => void;
pauseAsync?: () => void;
paused?: boolean;
play?: () => void;
playAsync?: () => void;
rate?: number;
replayAsync?: (status: Partial<AVPlaybackStatusToSet>) => void;
resizeMode?: string;
resume?: () => void;
seek?: (progress: number, tolerance?: number) => void;
setPositionAsync?: (millis: number) => void;
setProgressUpdateIntervalAsync?: (progressUpdateIntervalMillis: number) => void;
setRateAsync?: (
rate: number,
shouldCorrectPitch: boolean,
pitchCorrectionQuality?: PitchCorrectionQuality,
) => void;
soundRef?: React.RefObject<SoundReturnType>;
stopAsync?: () => void;
style?: StyleProp<ViewStyle>;
unloadAsync?: () => void;
uri?: string;
};
export type SoundType = {
initializeSound: (
source?: { uri: string },
initialStatus?: Partial<AVPlaybackStatusToSet>,
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void,
) => Promise<SoundReturnType | null>;
Player: React.ComponentType<SoundReturnType> | null;
};
export type RecordingStatus = {
canRecord: boolean;
currentMetering: number;
currentPosition: number;
durationMillis: number;
isDoneRecording: boolean;
isRecording: boolean;
metering: number;
mediaServicesDidReset?: boolean;
uri?: string | null;
};
export type AudioRecordingReturnType =
| string
| {
getStatusAsync: () => Promise<RecordingStatus>;
getURI: () => string | null;
pauseAsync: () => Promise<RecordingStatus>;
recording: string;
setProgressUpdateInterval: (progressUpdateIntervalMillis: number) => void;
stopAndUnloadAsync: () => Promise<RecordingStatus>;
}
| undefined;
export type AudioReturnType = {
accessGranted: boolean;
recording?: AudioRecordingReturnType;
};
export type RecordingOptions = {
/**
* A boolean that determines whether audio level information will be part of the status object under the "metering" key.
*/
isMeteringEnabled?: boolean;
};
export type AudioRecordingConfiguration = {
options?: RecordingOptions;
};
export type AudioType = {
audioRecordingConfiguration: AudioRecordingConfiguration;
startRecording: (
options?: RecordingOptions,
onRecordingStatusUpdate?: (recordingStatus: RecordingStatus) => void,
) => Promise<AudioReturnType>;
stopRecording: () => Promise<void>;
pausePlayer?: () => Promise<void>;
resumePlayer?: () => Promise<void>;
seekToPlayer?: (positionInMillis: number) => Promise<void>;
startPlayer?: (
uri?: AudioRecordingReturnType,
initialStatus?: Partial<AVPlaybackStatusToSet>,
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void,
) => Promise<void>;
stopPlayer?: () => Promise<void>;
};
export type VideoSeekResponse = {
currentTime: number;
seekTime: number;
};
export type VideoProgressData = {
currentTime: number;
seekableDuration: number;
playableDuration?: number;
};
export type VideoPayloadData = {
duration: number;
audioTracks?: { index: number; language: string; title: string; type: string }[];
currentPosition?: number;
naturalSize?: { height: number; orientation: 'portrait' | 'landscape'; width: number };
textTracks?: { index: number; language: string; title: string; type: string }[];
videoTracks?: {
bitrate: number;
codecs: string;
height: number;
trackId: number;
width: number;
}[];
};
export type VideoType = {
paused: boolean;
testID: string;
uri: string;
videoRef: React.RefObject<VideoType>;
onBuffer?: (props: { isBuffering: boolean }) => void;
onEnd?: () => void;
onLoad?: (payload: VideoPayloadData) => void;
onLoadStart?: () => void;
onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void;
onProgress?: (data: VideoProgressData) => void;
onReadyForDisplay?: () => void;
repeat?: boolean;
replayAsync?: () => void;
resizeMode?: string;
/**
* Absolute playback position in seconds. Assigning to it performs an absolute
* seek (expo-video's `VideoPlayer.currentTime`).
*/
currentTime?: number;
seek?: (seconds: number) => void;
seekBy?: (seconds: number) => void;
setPositionAsync?: (position: number) => void;
style?: StyleProp<ViewStyle>;
play?: () => void;
pause?: () => void;
replay?: () => void;
rate?: number;
};
export type NativeShimmerViewProps = ViewProps & {
baseColor?: ColorValue;
duration?: number;
enabled?: boolean;
gradientColor?: ColorValue;
};
type Handlers = {
Audio?: AudioType;
compressImage?: CompressImage;
deleteFile?: DeleteFile;
FlatList?: typeof DefaultFlatList;
getLocalAssetUri?: GetLocalAssetUri;
getPhotos?: GetPhotos;
iOS14RefreshGallerySelection?: iOS14RefreshGallerySelection;
multipartUpload?: NativeMultipartUpload;
oniOS14GalleryLibrarySelectionChange?: OniOS14LibrarySelectionChange;
overrideAudioRecordingConfiguration?: (
audioRecordingConfiguration: AudioRecordingConfiguration,
) => AudioRecordingConfiguration;
pickDocument?: PickDocument;
pickImage?: PickImage;
saveFile?: SaveFile;
SDK?: string;
setClipboardString?: SetClipboardString;
shareImage?: ShareImage;
Sound?: SoundType;
NativeShimmerView?: React.ComponentType<NativeShimmerViewProps>;
takePhoto?: TakePhoto;
triggerHaptic?: TriggerHaptic;
Video?: React.ComponentType<VideoType>;
};
export const NativeHandlers: Pick<
Handlers,
'Audio' | 'FlatList' | 'NativeShimmerView' | 'Video' | 'Sound'
> &
Required<
Pick<
Handlers,
| 'SDK'
| 'compressImage'
| 'deleteFile'
| 'getLocalAssetUri'
| 'getPhotos'
| 'iOS14RefreshGallerySelection'
| 'multipartUpload'
| 'oniOS14GalleryLibrarySelectionChange'
| 'pickDocument'
| 'pickImage'
| 'saveFile'
| 'setClipboardString'
| 'shareImage'
| 'takePhoto'
| 'triggerHaptic'
>
> = {
Audio: undefined,
compressImage: fail,
deleteFile: fail,
FlatList: DefaultFlatList,
getLocalAssetUri: fail,
getPhotos: fail,
iOS14RefreshGallerySelection: fail,
multipartUpload: fail,
oniOS14GalleryLibrarySelectionChange: fail,
pickDocument: fail,
pickImage: fail,
saveFile: fail,
SDK: '',
setClipboardString: fail,
shareImage: fail,
Sound: undefined,
NativeShimmerView: undefined,
takePhoto: fail,
triggerHaptic: fail,
Video: undefined,
};
export const registerNativeHandlers = (handlers: Handlers) => {
if (handlers.Audio !== undefined) {
NativeHandlers.Audio = handlers.Audio;
}
if (NativeHandlers.Audio && handlers.overrideAudioRecordingConfiguration) {
NativeHandlers.Audio.audioRecordingConfiguration = handlers.overrideAudioRecordingConfiguration(
NativeHandlers.Audio.audioRecordingConfiguration,
);
}
if (handlers.compressImage) {
NativeHandlers.compressImage = handlers.compressImage;
}
if (handlers.deleteFile !== undefined) {
NativeHandlers.deleteFile = handlers.deleteFile;
}
if (handlers.FlatList !== undefined) {
NativeHandlers.FlatList = handlers.FlatList;
}
if (handlers.getLocalAssetUri !== undefined) {
NativeHandlers.getLocalAssetUri = handlers.getLocalAssetUri;
}
if (handlers.getPhotos !== undefined) {
NativeHandlers.getPhotos = handlers.getPhotos;
}
if (handlers.iOS14RefreshGallerySelection !== undefined) {
NativeHandlers.iOS14RefreshGallerySelection = handlers.iOS14RefreshGallerySelection;
}
if (handlers.multipartUpload !== undefined) {
NativeHandlers.multipartUpload = handlers.multipartUpload;
}
if (handlers.oniOS14GalleryLibrarySelectionChange !== undefined) {
NativeHandlers.oniOS14GalleryLibrarySelectionChange =
handlers.oniOS14GalleryLibrarySelectionChange;
}
if (handlers.pickDocument !== undefined) {
NativeHandlers.pickDocument = handlers.pickDocument;
}
if (handlers.pickImage !== undefined) {
NativeHandlers.pickImage = handlers.pickImage;
}
if (handlers.saveFile !== undefined) {
NativeHandlers.saveFile = handlers.saveFile;
}
if (handlers.SDK) {
NativeHandlers.SDK = handlers.SDK;
}
if (handlers.shareImage !== undefined) {
NativeHandlers.shareImage = handlers.shareImage;
}
if (handlers.Sound) {
NativeHandlers.Sound = handlers.Sound;
}
if (handlers.NativeShimmerView !== undefined) {
NativeHandlers.NativeShimmerView = handlers.NativeShimmerView;
}
if (handlers.takePhoto !== undefined) {
NativeHandlers.takePhoto = handlers.takePhoto;
}
if (handlers.triggerHaptic !== undefined) {
NativeHandlers.triggerHaptic = handlers.triggerHaptic;
}
if (handlers.Video !== undefined) {
NativeHandlers.Video = handlers.Video;
}
if (handlers.setClipboardString !== undefined) {
NativeHandlers.setClipboardString = handlers.setClipboardString;
}
};
export const isImagePickerAvailable = () => !!NativeHandlers.takePhoto;
export const isDocumentPickerAvailable = () => !!NativeHandlers.pickDocument;
export const isClipboardAvailable = () => !!NativeHandlers.setClipboardString;
export const isVideoPlayerAvailable = () => !!NativeHandlers.Video;
export const isHapticFeedbackAvailable = () => !!NativeHandlers.triggerHaptic;
export const isShareImageAvailable = () => !!NativeHandlers.shareImage;
export const isFileSystemAvailable = () => !!NativeHandlers.saveFile || !!NativeHandlers.deleteFile;
export const isAudioRecorderAvailable = () => !!NativeHandlers.Audio?.startRecording;
export const isSoundPackageAvailable = () =>
!!NativeHandlers.Sound?.Player || !!NativeHandlers.Sound?.initializeSound;
export const isImageMediaLibraryAvailable = () =>
!!NativeHandlers.getPhotos &&
!!NativeHandlers.iOS14RefreshGallerySelection &&
!!NativeHandlers.oniOS14GalleryLibrarySelectionChange &&
!!NativeHandlers.getLocalAssetUri;
export const isNativeMultipartUploadAvailable = () => NativeHandlers.multipartUpload !== fail;