stream-chat-react-native-core 5.4.1 → 5.4.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/AttachmentPicker/AttachmentPicker.js +122 -433
- package/lib/commonjs/components/AttachmentPicker/AttachmentPicker.js.map +1 -1
- package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerItem.js +358 -0
- package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerItem.js.map +1 -0
- package/lib/commonjs/components/MessageList/MessageList.js +59 -40
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/NetworkDownIndicator.js +14 -6
- package/lib/commonjs/components/MessageList/NetworkDownIndicator.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js +4 -3
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +134 -85
- package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
- package/lib/commonjs/contexts/overlayContext/OverlayProvider.js +39 -17
- package/lib/commonjs/contexts/overlayContext/OverlayProvider.js.map +1 -1
- package/lib/commonjs/hooks/useAppStateListener.js +9 -15
- package/lib/commonjs/hooks/useAppStateListener.js.map +1 -1
- package/lib/commonjs/mock-builders/generator/message.js +1 -1
- package/lib/commonjs/mock-builders/generator/message.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/AttachmentPicker/AttachmentPicker.js +122 -433
- package/lib/module/components/AttachmentPicker/AttachmentPicker.js.map +1 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentPickerItem.js +358 -0
- package/lib/module/components/AttachmentPicker/components/AttachmentPickerItem.js.map +1 -0
- package/lib/module/components/MessageList/MessageList.js +59 -40
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/NetworkDownIndicator.js +14 -6
- package/lib/module/components/MessageList/NetworkDownIndicator.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useMessageList.js +4 -3
- package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/module/contexts/messageInputContext/MessageInputContext.js +134 -85
- package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
- package/lib/module/contexts/overlayContext/OverlayProvider.js +39 -17
- package/lib/module/contexts/overlayContext/OverlayProvider.js.map +1 -1
- package/lib/module/hooks/useAppStateListener.js +9 -15
- package/lib/module/hooks/useAppStateListener.js.map +1 -1
- package/lib/module/mock-builders/generator/message.js +1 -1
- package/lib/module/mock-builders/generator/message.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/AttachmentPicker/AttachmentPicker.d.ts +0 -11
- package/lib/typescript/components/AttachmentPicker/components/AttachmentPickerItem.d.ts +16 -0
- package/lib/typescript/components/MessageList/__tests__/useMessageList.test.d.ts +1 -0
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +0 -1
- package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -1
- package/lib/typescript/hooks/__tests__/useAppStateListener.test.d.ts +1 -0
- package/package.json +2 -2
- package/src/components/AttachmentPicker/AttachmentPicker.tsx +79 -322
- package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx +245 -0
- package/src/components/MessageList/MessageList.tsx +21 -20
- package/src/components/MessageList/NetworkDownIndicator.tsx +13 -10
- package/src/components/MessageList/__tests__/useMessageList.test.tsx +67 -0
- package/src/components/MessageList/hooks/useMessageList.ts +4 -7
- package/src/contexts/messageInputContext/MessageInputContext.tsx +27 -25
- package/src/contexts/overlayContext/OverlayProvider.tsx +28 -4
- package/src/hooks/__tests__/useAppStateListener.test.tsx +41 -0
- package/src/hooks/useAppStateListener.ts +9 -12
- package/src/mock-builders/generator/message.js +1 -1
- package/src/version.json +1 -1
|
@@ -1,31 +1,25 @@
|
|
|
1
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
2
|
-
|
|
3
1
|
Object.defineProperty(exports, "__esModule", {
|
|
4
2
|
value: true
|
|
5
3
|
});
|
|
6
4
|
exports.useAppStateListener = void 0;
|
|
7
5
|
|
|
8
|
-
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
9
|
-
|
|
10
6
|
var _react = require("react");
|
|
11
7
|
|
|
12
8
|
var _reactNative = require("react-native");
|
|
13
9
|
|
|
14
10
|
var useAppStateListener = function useAppStateListener(onForeground, onBackground) {
|
|
15
|
-
var
|
|
16
|
-
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
17
|
-
appState = _useState2[0],
|
|
18
|
-
setAppState = _useState2[1];
|
|
19
|
-
|
|
11
|
+
var appStateRef = (0, _react.useRef)(_reactNative.AppState.currentState);
|
|
20
12
|
var handleAppStateChange = (0, _react.useCallback)(function (nextAppState) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
var prevAppState = appStateRef.current;
|
|
14
|
+
|
|
15
|
+
if (prevAppState.match(/inactive|background/) && nextAppState === 'active') {
|
|
16
|
+
onForeground == null ? void 0 : onForeground();
|
|
17
|
+
} else if (prevAppState === 'active' && nextAppState.match(/inactive|background/)) {
|
|
18
|
+
onBackground == null ? void 0 : onBackground();
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
}, [onBackground, onForeground
|
|
21
|
+
appStateRef.current = nextAppState;
|
|
22
|
+
}, [onBackground, onForeground]);
|
|
29
23
|
(0, _react.useEffect)(function () {
|
|
30
24
|
var subscription = _reactNative.AppState.addEventListener('change', handleAppStateChange);
|
|
31
25
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["useAppStateListener.ts"],"names":["useAppStateListener","onForeground","onBackground","AppState","currentState","
|
|
1
|
+
{"version":3,"sources":["useAppStateListener.ts"],"names":["useAppStateListener","onForeground","onBackground","appStateRef","AppState","currentState","handleAppStateChange","nextAppState","prevAppState","current","match","subscription","addEventListener","remove","removeEventListener"],"mappings":";;;;;AAAA;;AACA;;AAEO,IAAMA,mBAAmB,GAAG,SAAtBA,mBAAsB,CAACC,YAAD,EAA4BC,YAA5B,EAA0D;AAC3F,MAAMC,WAAW,GAAG,mBAAOC,sBAASC,YAAhB,CAApB;AACA,MAAMC,oBAAoB,GAAG,wBAC3B,UAACC,YAAD,EAAkC;AAChC,QAAMC,YAAY,GAAGL,WAAW,CAACM,OAAjC;;AACA,QAAID,YAAY,CAACE,KAAb,CAAmB,qBAAnB,KAA6CH,YAAY,KAAK,QAAlE,EAA4E;AAC1EN,MAAAA,YAAY,QAAZ,YAAAA,YAAY;AACb,KAFD,MAEO,IAAIO,YAAY,KAAK,QAAjB,IAA6BD,YAAY,CAACG,KAAb,CAAmB,qBAAnB,CAAjC,EAA4E;AACjFR,MAAAA,YAAY,QAAZ,YAAAA,YAAY;AACb;;AACDC,IAAAA,WAAW,CAACM,OAAZ,GAAsBF,YAAtB;AACD,GAT0B,EAU3B,CAACL,YAAD,EAAeD,YAAf,CAV2B,CAA7B;AAaA,wBAAU,YAAM;AACd,QAAMU,YAAY,GAAGP,sBAASQ,gBAAT,CAA0B,QAA1B,EAAoCN,oBAApC,CAArB;;AAEA,WAAO,YAAM;AAGX,UAAIK,YAAJ,YAAIA,YAAY,CAAEE,MAAlB,EAA0B;AACxBF,QAAAA,YAAY,CAACE,MAAb;AACD,OAFD,MAEO;AACLT,8BAASU,mBAAT,CAA6B,QAA7B,EAAuCR,oBAAvC;AACD;AACF,KARD;AASD,GAZD,EAYG,CAACA,oBAAD,CAZH;AAaD,CA5BM","sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { AppState, AppStateStatus } from 'react-native';\n\nexport const useAppStateListener = (onForeground?: () => void, onBackground?: () => void) => {\n const appStateRef = useRef(AppState.currentState);\n const handleAppStateChange = useCallback(\n (nextAppState: AppStateStatus) => {\n const prevAppState = appStateRef.current;\n if (prevAppState.match(/inactive|background/) && nextAppState === 'active') {\n onForeground?.();\n } else if (prevAppState === 'active' && nextAppState.match(/inactive|background/)) {\n onBackground?.();\n }\n appStateRef.current = nextAppState;\n },\n [onBackground, onForeground],\n );\n\n useEffect(() => {\n const subscription = AppState.addEventListener('change', handleAppStateChange);\n\n return () => {\n // Following if-else logic is to support RN >= 0.65 and RN < 0.65 versions.\n // https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#:~:text=EventEmitter%23removeSubscription%20is%20now%20deprecated.%20(cb6cbd12f8%20by%20%40yungsters)\n if (subscription?.remove) {\n subscription.remove();\n } else {\n AppState.removeEventListener('change', handleAppStateChange);\n }\n };\n }, [handleAppStateChange]);\n};\n"]}
|
|
@@ -16,7 +16,7 @@ var generateMessage = function generateMessage() {
|
|
|
16
16
|
var timestamp = options.timestamp || new Date(new Date().getTime() - Math.floor(Math.random() * 100000));
|
|
17
17
|
return (0, _extends2["default"])({
|
|
18
18
|
attachments: [],
|
|
19
|
-
created_at: timestamp
|
|
19
|
+
created_at: timestamp,
|
|
20
20
|
html: '<p>regular</p>',
|
|
21
21
|
id: (0, _uuid.v4)(),
|
|
22
22
|
text: (0, _uuid.v4)(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["message.js"],"names":["generateMessage","options","timestamp","Date","getTime","Math","floor","random","attachments","created_at","
|
|
1
|
+
{"version":3,"sources":["message.js"],"names":["generateMessage","options","timestamp","Date","getTime","Math","floor","random","attachments","created_at","html","id","text","type","updated_at","toString","user","StreamReactNativeNamespace","generateStaticMessage","seed","date"],"mappings":";;;;;;;;;AAAA;;AAEA;;AAEO,IAAMA,eAAe,GAAG,SAAlBA,eAAkB,GAAkB;AAAA,MAAjBC,OAAiB,uEAAP,EAAO;AAC/C,MAAMC,SAAS,GACbD,OAAO,CAACC,SAAR,IAAqB,IAAIC,IAAJ,CAAS,IAAIA,IAAJ,GAAWC,OAAX,KAAuBC,IAAI,CAACC,KAAL,CAAWD,IAAI,CAACE,MAAL,KAAgB,MAA3B,CAAhC,CADvB;AAGA;AACEC,IAAAA,WAAW,EAAE,EADf;AAEEC,IAAAA,UAAU,EAAEP,SAFd;AAGEQ,IAAAA,IAAI,EAAE,gBAHR;AAIEC,IAAAA,EAAE,EAAE,eAJN;AAKEC,IAAAA,IAAI,EAAE,eALR;AAMEC,IAAAA,IAAI,EAAE,SANR;AAOEC,IAAAA,UAAU,EAAEZ,SAAS,CAACa,QAAV,EAPd;AAQEC,IAAAA,IAAI,EAAE;AARR,KASKf,OATL;AAWD,CAfM;;;AAiBP,IAAMgB,0BAA0B,GAAG,sCAAnC;;AACO,IAAMC,qBAAqB,GAAG,SAAxBA,qBAAwB,CAACC,IAAD,EAAOlB,OAAP,EAAgBmB,IAAhB;AAAA,SACnCpB,eAAe;AACbS,IAAAA,UAAU,EAAEW,IAAI,IAAI,6BADP;AAEbT,IAAAA,EAAE,EAAE,cAAOQ,IAAP,EAAaF,0BAAb,CAFS;AAGbL,IAAAA,IAAI,EAAEO,IAHO;AAIbL,IAAAA,UAAU,EAAEM,IAAI,IAAI;AAJP,KAKVnB,OALU,EADoB;AAAA,CAA9B","sourcesContent":["import { v4 as uuidv4, v5 as uuidv5 } from 'uuid';\n\nimport { generateUser } from './user';\n\nexport const generateMessage = (options = {}) => {\n const timestamp =\n options.timestamp || new Date(new Date().getTime() - Math.floor(Math.random() * 100000));\n\n return {\n attachments: [],\n created_at: timestamp,\n html: '<p>regular</p>',\n id: uuidv4(),\n text: uuidv4(),\n type: 'regular',\n updated_at: timestamp.toString(),\n user: generateUser(),\n ...options,\n };\n};\n\nconst StreamReactNativeNamespace = '9b244ee4-7d69-4d7b-ae23-cf89e9f7b035';\nexport const generateStaticMessage = (seed, options, date) =>\n generateMessage({\n created_at: date || '2020-04-27T13:39:49.331742Z',\n id: uuidv5(seed, StreamReactNativeNamespace),\n text: seed,\n updated_at: date || '2020-04-27T13:39:49.331742Z',\n ...options,\n });\n"]}
|
package/lib/module/version.json
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { BottomSheetHandleProps } from '@gorhom/bottom-sheet';
|
|
3
3
|
import type { AttachmentPickerErrorProps } from './components/AttachmentPickerError';
|
|
4
|
-
import type { Asset, File } from '../../types/types';
|
|
5
|
-
export declare type AttachmentPickerItemType = {
|
|
6
|
-
asset: Asset;
|
|
7
|
-
ImageOverlaySelectedComponent: React.ComponentType;
|
|
8
|
-
maxNumberOfFiles: number;
|
|
9
|
-
numberOfUploads: number;
|
|
10
|
-
selected: boolean;
|
|
11
|
-
setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
|
|
12
|
-
setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
|
|
13
|
-
numberOfAttachmentPickerImageColumns?: number;
|
|
14
|
-
};
|
|
15
4
|
export declare type AttachmentPickerProps = {
|
|
16
5
|
/**
|
|
17
6
|
* Custom UI component to render [draggable handle](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) of attachment picker.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Asset, File } from '../../../types/types';
|
|
3
|
+
declare type AttachmentPickerItemType = {
|
|
4
|
+
asset: Asset;
|
|
5
|
+
ImageOverlaySelectedComponent: React.ComponentType;
|
|
6
|
+
maxNumberOfFiles: number;
|
|
7
|
+
numberOfUploads: number;
|
|
8
|
+
selected: boolean;
|
|
9
|
+
setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
|
|
10
|
+
setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
|
|
11
|
+
numberOfAttachmentPickerImageColumns?: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const renderAttachmentPickerItem: ({ item }: {
|
|
14
|
+
item: AttachmentPickerItemType;
|
|
15
|
+
}) => JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,7 +3,6 @@ import { DeletedMessagesVisibilityType } from '../../../contexts/messagesContext
|
|
|
3
3
|
import type { DefaultStreamChatGenerics } from '../../../types/types';
|
|
4
4
|
export declare type UseMessageListParams = {
|
|
5
5
|
deletedMessagesVisibilityType?: DeletedMessagesVisibilityType;
|
|
6
|
-
inverted?: boolean;
|
|
7
6
|
noGroupByUser?: boolean;
|
|
8
7
|
threadList?: boolean;
|
|
9
8
|
};
|
|
@@ -166,7 +166,7 @@ export declare type LocalMessageInputContext<StreamChatGenerics extends DefaultS
|
|
|
166
166
|
newImage: ImageUpload;
|
|
167
167
|
}) => Promise<void>;
|
|
168
168
|
uploadNewFile: (file: File) => Promise<void>;
|
|
169
|
-
uploadNewImage: (image: Asset) => Promise<void>;
|
|
169
|
+
uploadNewImage: (image: Partial<Asset>) => Promise<void>;
|
|
170
170
|
};
|
|
171
171
|
export declare type InputMessageInputContextValue<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
172
172
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-chat-react-native-core",
|
|
3
3
|
"description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
|
|
4
|
-
"version": "5.4.
|
|
4
|
+
"version": "5.4.2-beta.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"company": "Stream.io Inc",
|
|
7
7
|
"name": "Stream.io Inc"
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@babel/runtime": "^7.12.5",
|
|
72
|
-
"@gorhom/bottom-sheet": "
|
|
72
|
+
"@gorhom/bottom-sheet": "4.4.5",
|
|
73
73
|
"dayjs": "1.10.5",
|
|
74
74
|
"file-loader": "6.2.0",
|
|
75
75
|
"i18next": "20.2.4",
|
|
@@ -1,34 +1,19 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Alert,
|
|
4
|
-
BackHandler,
|
|
5
|
-
Dimensions,
|
|
6
|
-
ImageBackground,
|
|
7
|
-
Keyboard,
|
|
8
|
-
Platform,
|
|
9
|
-
StatusBar,
|
|
10
|
-
StyleSheet,
|
|
11
|
-
Text,
|
|
12
|
-
View,
|
|
13
|
-
} from 'react-native';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { BackHandler, Dimensions, Keyboard, Platform, StatusBar, StyleSheet } from 'react-native';
|
|
14
3
|
|
|
15
|
-
import BottomSheet, {
|
|
16
|
-
BottomSheetFlatList,
|
|
17
|
-
BottomSheetHandleProps,
|
|
18
|
-
TouchableOpacity,
|
|
19
|
-
} from '@gorhom/bottom-sheet';
|
|
4
|
+
import BottomSheet, { BottomSheetFlatList, BottomSheetHandleProps } from '@gorhom/bottom-sheet';
|
|
20
5
|
import dayjs from 'dayjs';
|
|
21
6
|
import duration from 'dayjs/plugin/duration';
|
|
22
|
-
import { lookup } from 'mime-types';
|
|
23
7
|
|
|
24
8
|
import type { AttachmentPickerErrorProps } from './components/AttachmentPickerError';
|
|
25
9
|
|
|
10
|
+
import { renderAttachmentPickerItem } from './components/AttachmentPickerItem';
|
|
11
|
+
|
|
26
12
|
import { useAttachmentPickerContext } from '../../contexts/attachmentPickerContext/AttachmentPickerContext';
|
|
27
13
|
import { useTheme } from '../../contexts/themeContext/ThemeContext';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import
|
|
31
|
-
import { vh, vw } from '../../utils/utils';
|
|
14
|
+
import { getPhotos } from '../../native';
|
|
15
|
+
import type { Asset } from '../../types/types';
|
|
16
|
+
import { vh } from '../../utils/utils';
|
|
32
17
|
|
|
33
18
|
dayjs.extend(duration);
|
|
34
19
|
|
|
@@ -36,238 +21,11 @@ const styles = StyleSheet.create({
|
|
|
36
21
|
container: {
|
|
37
22
|
flexGrow: 1,
|
|
38
23
|
},
|
|
39
|
-
durationText: {
|
|
40
|
-
fontWeight: 'bold',
|
|
41
|
-
},
|
|
42
|
-
overlay: {
|
|
43
|
-
alignItems: 'flex-end',
|
|
44
|
-
flex: 1,
|
|
45
|
-
},
|
|
46
|
-
videoView: {
|
|
47
|
-
bottom: 5,
|
|
48
|
-
display: 'flex',
|
|
49
|
-
flexDirection: 'row',
|
|
50
|
-
justifyContent: 'space-between',
|
|
51
|
-
paddingHorizontal: 5,
|
|
52
|
-
position: 'absolute',
|
|
53
|
-
width: '100%',
|
|
54
|
-
},
|
|
55
24
|
});
|
|
56
25
|
|
|
57
26
|
const screenHeight = vh(100);
|
|
58
27
|
const fullScreenHeight = Dimensions.get('window').height;
|
|
59
28
|
|
|
60
|
-
export type AttachmentPickerItemType = {
|
|
61
|
-
asset: Asset;
|
|
62
|
-
ImageOverlaySelectedComponent: React.ComponentType;
|
|
63
|
-
maxNumberOfFiles: number;
|
|
64
|
-
numberOfUploads: number;
|
|
65
|
-
selected: boolean;
|
|
66
|
-
setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
|
|
67
|
-
setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
|
|
68
|
-
numberOfAttachmentPickerImageColumns?: number;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
type AttachmentImageProps = Omit<AttachmentPickerItemType, 'setSelectedFiles'>;
|
|
72
|
-
|
|
73
|
-
type AttachmentVideoProps = Omit<AttachmentPickerItemType, 'setSelectedImages'>;
|
|
74
|
-
|
|
75
|
-
const AttachmentVideo: React.FC<AttachmentVideoProps> = (props) => {
|
|
76
|
-
const {
|
|
77
|
-
asset,
|
|
78
|
-
ImageOverlaySelectedComponent,
|
|
79
|
-
maxNumberOfFiles,
|
|
80
|
-
numberOfAttachmentPickerImageColumns,
|
|
81
|
-
numberOfUploads,
|
|
82
|
-
selected,
|
|
83
|
-
setSelectedFiles,
|
|
84
|
-
} = props;
|
|
85
|
-
|
|
86
|
-
const {
|
|
87
|
-
theme: {
|
|
88
|
-
attachmentPicker: { durationText, image, imageOverlay },
|
|
89
|
-
colors: { overlay, white },
|
|
90
|
-
},
|
|
91
|
-
} = useTheme();
|
|
92
|
-
|
|
93
|
-
const { duration, playableDuration, uri } = asset;
|
|
94
|
-
|
|
95
|
-
const videoDuration = duration ? duration : playableDuration;
|
|
96
|
-
|
|
97
|
-
const ONE_HOUR_IN_SECONDS = 3600;
|
|
98
|
-
|
|
99
|
-
let durationLabel = '00:00';
|
|
100
|
-
|
|
101
|
-
if (videoDuration) {
|
|
102
|
-
const isDurationLongerThanHour = videoDuration / ONE_HOUR_IN_SECONDS >= 1;
|
|
103
|
-
const formattedDurationParam = isDurationLongerThanHour ? 'HH:mm:ss' : 'mm:ss';
|
|
104
|
-
const formattedVideoDuration = dayjs
|
|
105
|
-
.duration(videoDuration, 'second')
|
|
106
|
-
.format(formattedDurationParam);
|
|
107
|
-
durationLabel = formattedVideoDuration;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
|
|
111
|
-
|
|
112
|
-
const onPressVideo = async () => {
|
|
113
|
-
// For the case of expo messaging app where you need to fetch the asset uri from asset id
|
|
114
|
-
const localAssetURI = asset.id && (await getLocalAssetUri(asset.id));
|
|
115
|
-
if (selected) {
|
|
116
|
-
setSelectedFiles((files) => files.filter((file) => file.uri !== asset.uri));
|
|
117
|
-
} else {
|
|
118
|
-
setSelectedFiles((files) => {
|
|
119
|
-
if (numberOfUploads >= maxNumberOfFiles) {
|
|
120
|
-
Alert.alert('Maximum number of files reached');
|
|
121
|
-
return files;
|
|
122
|
-
}
|
|
123
|
-
return [
|
|
124
|
-
...files,
|
|
125
|
-
{
|
|
126
|
-
duration: durationLabel,
|
|
127
|
-
name: asset.filename,
|
|
128
|
-
size: asset.fileSize,
|
|
129
|
-
type: 'video',
|
|
130
|
-
uri: localAssetURI || asset.uri,
|
|
131
|
-
},
|
|
132
|
-
];
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<TouchableOpacity onPress={onPressVideo}>
|
|
139
|
-
<ImageBackground
|
|
140
|
-
source={{ uri }}
|
|
141
|
-
style={[
|
|
142
|
-
{
|
|
143
|
-
height: size,
|
|
144
|
-
margin: 1,
|
|
145
|
-
width: size,
|
|
146
|
-
},
|
|
147
|
-
image,
|
|
148
|
-
]}
|
|
149
|
-
>
|
|
150
|
-
{selected && (
|
|
151
|
-
<View style={[styles.overlay, { backgroundColor: overlay }, imageOverlay]}>
|
|
152
|
-
<ImageOverlaySelectedComponent />
|
|
153
|
-
</View>
|
|
154
|
-
)}
|
|
155
|
-
<View style={styles.videoView}>
|
|
156
|
-
<Recorder height={20} pathFill={white} width={25} />
|
|
157
|
-
{videoDuration ? (
|
|
158
|
-
<Text style={[styles.durationText, durationText, { color: white }]}>
|
|
159
|
-
{durationLabel}
|
|
160
|
-
</Text>
|
|
161
|
-
) : null}
|
|
162
|
-
</View>
|
|
163
|
-
</ImageBackground>
|
|
164
|
-
</TouchableOpacity>
|
|
165
|
-
);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const AttachmentImage: React.FC<AttachmentImageProps> = (props) => {
|
|
169
|
-
const {
|
|
170
|
-
asset,
|
|
171
|
-
ImageOverlaySelectedComponent,
|
|
172
|
-
maxNumberOfFiles,
|
|
173
|
-
numberOfAttachmentPickerImageColumns,
|
|
174
|
-
numberOfUploads,
|
|
175
|
-
selected,
|
|
176
|
-
setSelectedImages,
|
|
177
|
-
} = props;
|
|
178
|
-
const {
|
|
179
|
-
theme: {
|
|
180
|
-
attachmentPicker: { image, imageOverlay },
|
|
181
|
-
colors: { overlay },
|
|
182
|
-
},
|
|
183
|
-
} = useTheme();
|
|
184
|
-
|
|
185
|
-
const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
|
|
186
|
-
|
|
187
|
-
const { uri } = asset;
|
|
188
|
-
|
|
189
|
-
const onPressImage = () => {
|
|
190
|
-
if (selected) {
|
|
191
|
-
setSelectedImages((images) => images.filter((image) => image.uri !== asset.uri));
|
|
192
|
-
} else {
|
|
193
|
-
setSelectedImages((images) => {
|
|
194
|
-
if (numberOfUploads >= maxNumberOfFiles) {
|
|
195
|
-
Alert.alert('Maximum number of files reached');
|
|
196
|
-
return images;
|
|
197
|
-
}
|
|
198
|
-
return [...images, asset];
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<TouchableOpacity onPress={onPressImage}>
|
|
205
|
-
<ImageBackground
|
|
206
|
-
source={{ uri }}
|
|
207
|
-
style={[
|
|
208
|
-
{
|
|
209
|
-
height: size,
|
|
210
|
-
margin: 1,
|
|
211
|
-
width: size,
|
|
212
|
-
},
|
|
213
|
-
image,
|
|
214
|
-
]}
|
|
215
|
-
>
|
|
216
|
-
{selected && (
|
|
217
|
-
<View style={[styles.overlay, { backgroundColor: overlay }, imageOverlay]}>
|
|
218
|
-
<ImageOverlaySelectedComponent />
|
|
219
|
-
</View>
|
|
220
|
-
)}
|
|
221
|
-
</ImageBackground>
|
|
222
|
-
</TouchableOpacity>
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const renderItem = ({ item }: { item: AttachmentPickerItemType }) => {
|
|
227
|
-
const {
|
|
228
|
-
asset,
|
|
229
|
-
ImageOverlaySelectedComponent,
|
|
230
|
-
maxNumberOfFiles,
|
|
231
|
-
numberOfAttachmentPickerImageColumns,
|
|
232
|
-
numberOfUploads,
|
|
233
|
-
selected,
|
|
234
|
-
setSelectedFiles,
|
|
235
|
-
setSelectedImages,
|
|
236
|
-
} = item;
|
|
237
|
-
|
|
238
|
-
const contentType = lookup(asset.filename) || 'multipart/form-data';
|
|
239
|
-
|
|
240
|
-
const fileType = asset.filename
|
|
241
|
-
? contentType.startsWith('image/')
|
|
242
|
-
? 'image'
|
|
243
|
-
: 'video'
|
|
244
|
-
: asset.type === 'video'
|
|
245
|
-
? 'video'
|
|
246
|
-
: 'image';
|
|
247
|
-
|
|
248
|
-
return fileType === 'image' ? (
|
|
249
|
-
<AttachmentImage
|
|
250
|
-
asset={asset}
|
|
251
|
-
ImageOverlaySelectedComponent={ImageOverlaySelectedComponent}
|
|
252
|
-
maxNumberOfFiles={maxNumberOfFiles}
|
|
253
|
-
numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
|
|
254
|
-
numberOfUploads={numberOfUploads}
|
|
255
|
-
selected={selected}
|
|
256
|
-
setSelectedImages={setSelectedImages}
|
|
257
|
-
/>
|
|
258
|
-
) : (
|
|
259
|
-
<AttachmentVideo
|
|
260
|
-
asset={asset}
|
|
261
|
-
ImageOverlaySelectedComponent={ImageOverlaySelectedComponent}
|
|
262
|
-
maxNumberOfFiles={maxNumberOfFiles}
|
|
263
|
-
numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
|
|
264
|
-
numberOfUploads={numberOfUploads}
|
|
265
|
-
selected={selected}
|
|
266
|
-
setSelectedFiles={setSelectedFiles}
|
|
267
|
-
/>
|
|
268
|
-
);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
29
|
export type AttachmentPickerProps = {
|
|
272
30
|
/**
|
|
273
31
|
* Custom UI component to render [draggable handle](https://github.com/GetStream/stream-chat-react-native/blob/main/screenshots/docs/1.png) of attachment picker.
|
|
@@ -337,35 +95,39 @@ export const AttachmentPicker = React.forwardRef(
|
|
|
337
95
|
} = useAttachmentPickerContext();
|
|
338
96
|
|
|
339
97
|
const [currentIndex, setCurrentIndex] = useState(-1);
|
|
340
|
-
const
|
|
98
|
+
const endCursorRef = useRef<string>();
|
|
341
99
|
const [photoError, setPhotoError] = useState(false);
|
|
342
|
-
const
|
|
100
|
+
const hasNextPageRef = useRef(true);
|
|
343
101
|
const [loadingPhotos, setLoadingPhotos] = useState(false);
|
|
344
102
|
const [photos, setPhotos] = useState<Asset[]>([]);
|
|
345
|
-
const
|
|
103
|
+
const attemptedToLoadPhotosOnOpenRef = useRef(false);
|
|
346
104
|
|
|
347
|
-
const getMorePhotos = async () => {
|
|
348
|
-
if (
|
|
105
|
+
const getMorePhotos = useCallback(async () => {
|
|
106
|
+
if (
|
|
107
|
+
hasNextPageRef.current &&
|
|
108
|
+
!loadingPhotos &&
|
|
109
|
+
currentIndex > -1 &&
|
|
110
|
+
selectedPicker === 'images'
|
|
111
|
+
) {
|
|
112
|
+
setPhotoError(false);
|
|
349
113
|
setLoadingPhotos(true);
|
|
114
|
+
const endCursor = endCursorRef.current;
|
|
350
115
|
try {
|
|
351
116
|
const results = await getPhotos({
|
|
352
117
|
after: endCursor,
|
|
353
118
|
first: numberOfAttachmentImagesToLoadPerCall ?? 60,
|
|
354
119
|
});
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
setEndCursor(results.endCursor);
|
|
361
|
-
setHasNextPage(results.hasNextPage || false);
|
|
120
|
+
endCursorRef.current = results.endCursor;
|
|
121
|
+
setPhotos((prevPhotos) =>
|
|
122
|
+
endCursor ? [...prevPhotos, ...results.assets] : results.assets,
|
|
123
|
+
);
|
|
124
|
+
hasNextPageRef.current = !!results.hasNextPage;
|
|
362
125
|
} catch (error) {
|
|
363
|
-
console.log(error);
|
|
364
126
|
setPhotoError(true);
|
|
365
127
|
}
|
|
366
128
|
setLoadingPhotos(false);
|
|
367
129
|
}
|
|
368
|
-
};
|
|
130
|
+
}, [currentIndex, selectedPicker, loadingPhotos]);
|
|
369
131
|
|
|
370
132
|
useEffect(() => {
|
|
371
133
|
const backAction = () => {
|
|
@@ -381,27 +143,11 @@ export const AttachmentPicker = React.forwardRef(
|
|
|
381
143
|
const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
|
|
382
144
|
|
|
383
145
|
return () => backHandler.remove();
|
|
384
|
-
}, [selectedPicker]);
|
|
146
|
+
}, [selectedPicker, closePicker]);
|
|
385
147
|
|
|
386
148
|
useEffect(() => {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
clearTimeout(bottomSheetCloseOnKeyboardShowTimeout.current);
|
|
390
|
-
}
|
|
391
|
-
setSelectedPicker(undefined);
|
|
392
|
-
// This short timeout is to prevent a race condition
|
|
393
|
-
// where the close function is called during the point when a internal container layout happens within the bottomsheet due to keyboard affecting the layout
|
|
394
|
-
// 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
|
|
395
|
-
// this short delay ensures that close function is always called after a container layout due to keyboard change
|
|
396
|
-
bottomSheetCloseOnKeyboardShowTimeout.current = setTimeout(
|
|
397
|
-
() => (ref as React.MutableRefObject<BottomSheet | undefined>).current?.close(),
|
|
398
|
-
150,
|
|
399
|
-
);
|
|
400
|
-
};
|
|
401
|
-
const keyboardSubscription =
|
|
402
|
-
Platform.OS === 'ios'
|
|
403
|
-
? Keyboard.addListener('keyboardWillShow', hideAttachmentPicker)
|
|
404
|
-
: Keyboard.addListener('keyboardDidShow', hideAttachmentPicker);
|
|
149
|
+
const keyboardShowEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
150
|
+
const keyboardSubscription = Keyboard.addListener(keyboardShowEvent, closePicker);
|
|
405
151
|
|
|
406
152
|
return () => {
|
|
407
153
|
if (keyboardSubscription?.remove) {
|
|
@@ -410,38 +156,35 @@ export const AttachmentPicker = React.forwardRef(
|
|
|
410
156
|
}
|
|
411
157
|
|
|
412
158
|
// To keep compatibility with older versions of React Native, where `remove()` is not available
|
|
413
|
-
|
|
414
|
-
Keyboard.removeListener('keyboardWillShow', hideAttachmentPicker);
|
|
415
|
-
} else {
|
|
416
|
-
Keyboard.removeListener('keyboardDidShow', hideAttachmentPicker);
|
|
417
|
-
}
|
|
418
|
-
if (bottomSheetCloseOnKeyboardShowTimeout.current) {
|
|
419
|
-
clearTimeout(bottomSheetCloseOnKeyboardShowTimeout.current);
|
|
420
|
-
}
|
|
159
|
+
Keyboard.removeListener(keyboardShowEvent, closePicker);
|
|
421
160
|
};
|
|
422
|
-
}, []);
|
|
161
|
+
}, [closePicker]);
|
|
423
162
|
|
|
424
163
|
useEffect(() => {
|
|
425
164
|
if (currentIndex < 0) {
|
|
426
165
|
setSelectedPicker(undefined);
|
|
427
166
|
if (!loadingPhotos) {
|
|
428
|
-
|
|
429
|
-
|
|
167
|
+
endCursorRef.current = undefined;
|
|
168
|
+
hasNextPageRef.current = true;
|
|
169
|
+
attemptedToLoadPhotosOnOpenRef.current = false;
|
|
170
|
+
setPhotoError(false);
|
|
430
171
|
}
|
|
431
172
|
}
|
|
432
|
-
}, [currentIndex]);
|
|
173
|
+
}, [currentIndex, loadingPhotos]);
|
|
433
174
|
|
|
434
175
|
useEffect(() => {
|
|
435
176
|
if (
|
|
177
|
+
!attemptedToLoadPhotosOnOpenRef.current &&
|
|
436
178
|
selectedPicker === 'images' &&
|
|
437
|
-
|
|
179
|
+
endCursorRef.current === undefined &&
|
|
438
180
|
currentIndex > -1 &&
|
|
439
181
|
!loadingPhotos
|
|
440
182
|
) {
|
|
441
|
-
setPhotoError(false);
|
|
442
183
|
getMorePhotos();
|
|
184
|
+
// we do this only once on open for avoiding to request permissions in rationale dialog again and again on Android
|
|
185
|
+
attemptedToLoadPhotosOnOpenRef.current = true;
|
|
443
186
|
}
|
|
444
|
-
}, [currentIndex, selectedPicker]);
|
|
187
|
+
}, [currentIndex, selectedPicker, getMorePhotos, loadingPhotos]);
|
|
445
188
|
|
|
446
189
|
const selectedPhotos = photos.map((asset) => ({
|
|
447
190
|
asset,
|
|
@@ -464,29 +207,43 @@ export const AttachmentPicker = React.forwardRef(
|
|
|
464
207
|
* dimensions for screen and window, it is incorrect and we need to account for
|
|
465
208
|
* this. If you use a translucent header bar more adjustments are needed.
|
|
466
209
|
*/
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
?
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
210
|
+
const getAndroidBottomBarHeightAdjustment = (): number => {
|
|
211
|
+
if (Platform.OS === 'android') {
|
|
212
|
+
const statusBarHeight = StatusBar.currentHeight ?? 0;
|
|
213
|
+
const bottomBarHeight = fullScreenHeight - screenHeight - statusBarHeight;
|
|
214
|
+
if (bottomBarHeight === statusBarHeight) {
|
|
215
|
+
return translucentStatusBar ? 0 : statusBarHeight;
|
|
216
|
+
} else {
|
|
217
|
+
if (translucentStatusBar) {
|
|
218
|
+
if (bottomBarHeight > statusBarHeight) {
|
|
219
|
+
return -bottomBarHeight + statusBarHeight;
|
|
220
|
+
} else {
|
|
221
|
+
return bottomBarHeight > 0 ? -statusBarHeight : 0;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
return bottomBarHeight > 0 ? 0 : statusBarHeight;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return 0;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const getInitialSnapPoint = (): number => {
|
|
232
|
+
if (attachmentPickerBottomSheetHeight !== undefined) {
|
|
233
|
+
return attachmentPickerBottomSheetHeight;
|
|
234
|
+
}
|
|
235
|
+
if (Platform.OS === 'android') {
|
|
236
|
+
return (
|
|
237
|
+
308 +
|
|
238
|
+
(fullScreenHeight - screenHeight + getAndroidBottomBarHeightAdjustment()) -
|
|
239
|
+
handleHeight
|
|
240
|
+
);
|
|
241
|
+
} else {
|
|
242
|
+
return 308 + (fullScreenHeight - screenHeight);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
485
245
|
|
|
486
|
-
const initialSnapPoint =
|
|
487
|
-
attachmentPickerBottomSheetHeight ?? Platform.OS === 'android'
|
|
488
|
-
? 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment) - handleHeight
|
|
489
|
-
: 308 + (fullScreenHeight - screenHeight + androidBottomBarHeightAdjustment);
|
|
246
|
+
const initialSnapPoint = getInitialSnapPoint();
|
|
490
247
|
|
|
491
248
|
const finalSnapPoint =
|
|
492
249
|
Platform.OS === 'android'
|
|
@@ -531,12 +288,12 @@ export const AttachmentPicker = React.forwardRef(
|
|
|
531
288
|
keyExtractor={(item) => item.asset.uri}
|
|
532
289
|
numColumns={numberOfAttachmentPickerImageColumns ?? 3}
|
|
533
290
|
onEndReached={getMorePhotos}
|
|
534
|
-
renderItem={
|
|
291
|
+
renderItem={renderAttachmentPickerItem}
|
|
535
292
|
/>
|
|
536
293
|
</BottomSheet>
|
|
537
294
|
{selectedPicker === 'images' && photoError && (
|
|
538
295
|
<AttachmentPickerError
|
|
539
|
-
attachmentPickerBottomSheetHeight={
|
|
296
|
+
attachmentPickerBottomSheetHeight={initialSnapPoint}
|
|
540
297
|
attachmentPickerErrorButtonText={attachmentPickerErrorButtonText}
|
|
541
298
|
AttachmentPickerErrorImage={AttachmentPickerErrorImage}
|
|
542
299
|
attachmentPickerErrorText={attachmentPickerErrorText}
|