@messenger-box/platform-mobile 0.0.1-alpha.338 → 0.0.1-alpha.340
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/CHANGELOG.md +4 -0
- package/lib/index.js +245 -47
- package/lib/index.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.d.ts +2 -0
- package/lib/screens/inbox/components/CachedImage/index.d.ts +8 -0
- package/package.json +2 -2
- package/src/screens/inbox/components/CachedImage/consts.ts +5 -0
- package/src/screens/inbox/components/CachedImage/index.tsx +103 -0
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +34 -7
- package/src/screens/inbox/containers/ConversationView.tsx +19 -5
- package/src/screens/inbox/containers/Dialogs.tsx +76 -51
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
2
|
+
import { Image } from 'native-base';
|
|
3
|
+
// import { Image } from "react-native"
|
|
4
|
+
import * as FileSystem from 'expo-file-system';
|
|
5
|
+
|
|
6
|
+
import * as CONST from './consts';
|
|
7
|
+
|
|
8
|
+
const CachedImage = (props: any) => {
|
|
9
|
+
const { source, cacheKey, placeholderContent } = props;
|
|
10
|
+
const { uri, headers, expiresIn } = source;
|
|
11
|
+
const fileURI = `${CONST.IMAGE_CACHE_FOLDER}${cacheKey}`;
|
|
12
|
+
|
|
13
|
+
const [imgUri, setImgUri] = useState<any>(fileURI);
|
|
14
|
+
|
|
15
|
+
const componentIsMounted = useRef(true);
|
|
16
|
+
const requestOption = headers ? { headers } : {};
|
|
17
|
+
|
|
18
|
+
const _callback = (downloadProgress: any) => {
|
|
19
|
+
if (componentIsMounted.current === false) {
|
|
20
|
+
downloadResumableRef.current.pauseAsync();
|
|
21
|
+
FileSystem.deleteAsync(fileURI, { idempotent: true }); // delete file locally if it was not downloaded properly
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const downloadResumableRef = useRef(FileSystem.createDownloadResumable(uri, fileURI, requestOption, _callback));
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
loadImage();
|
|
29
|
+
return () => {
|
|
30
|
+
componentIsMounted.current = false;
|
|
31
|
+
};
|
|
32
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
33
|
+
|
|
34
|
+
const loadImage = async () => {
|
|
35
|
+
try {
|
|
36
|
+
// Use the cached image if it exists
|
|
37
|
+
const metadata: any = await FileSystem.getInfoAsync(fileURI);
|
|
38
|
+
const expired = expiresIn && new Date().getTime() / 1000 - metadata?.modificationTime > expiresIn;
|
|
39
|
+
// console.log({expiresIn, expired})
|
|
40
|
+
|
|
41
|
+
// console.log({modificationTime: metadata.modificationTime, currentTime: new Date().getTime() / 1000})
|
|
42
|
+
// console.log({metadata})
|
|
43
|
+
if (!metadata.exists || metadata?.size === 0 || expired) {
|
|
44
|
+
if (componentIsMounted.current) {
|
|
45
|
+
setImgUri(null);
|
|
46
|
+
|
|
47
|
+
if (expired) {
|
|
48
|
+
await FileSystem.deleteAsync(fileURI, { idempotent: true });
|
|
49
|
+
}
|
|
50
|
+
// download to cache
|
|
51
|
+
setImgUri(null);
|
|
52
|
+
|
|
53
|
+
const response: any = await downloadResumableRef.current.downloadAsync();
|
|
54
|
+
if (componentIsMounted.current && response.status === 200) {
|
|
55
|
+
setImgUri(`${fileURI}?`); // deep clone to force re-render
|
|
56
|
+
}
|
|
57
|
+
if (response.status !== 200) {
|
|
58
|
+
FileSystem.deleteAsync(fileURI, { idempotent: true }); // delete file locally if it was not downloaded properly
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// console.log({ err })
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
// console.log({placeholderContent})
|
|
67
|
+
if (!imgUri) return placeholderContent || null;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Image
|
|
71
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
72
|
+
{...props}
|
|
73
|
+
source={{
|
|
74
|
+
...source,
|
|
75
|
+
uri: imgUri,
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const CacheManager = {
|
|
82
|
+
addToCache: async ({ file, key }: any) => {
|
|
83
|
+
await FileSystem.copyAsync({
|
|
84
|
+
from: file,
|
|
85
|
+
to: `${CONST.IMAGE_CACHE_FOLDER}${key}`,
|
|
86
|
+
});
|
|
87
|
+
// const uri = await FileSystem.getContentUriAsync(`${CONST.IMAGE_CACHE_FOLDER}${key}`)
|
|
88
|
+
// return uri
|
|
89
|
+
const uri = await CacheManager.getCachedUri({ key });
|
|
90
|
+
return uri;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
getCachedUri: async ({ key }: any) => {
|
|
94
|
+
const uri = await FileSystem.getContentUriAsync(`${CONST.IMAGE_CACHE_FOLDER}${key}`);
|
|
95
|
+
return uri;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
downloadAsync: async ({ uri, key, options }: any) => {
|
|
99
|
+
return await FileSystem.downloadAsync(uri, `${CONST.IMAGE_CACHE_FOLDER}${key}`, options);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default CachedImage;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle, no-use-before-define */
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Text, StyleSheet, TouchableOpacity, View, Platform } from 'react-native';
|
|
3
|
+
import { Text, StyleSheet, TouchableOpacity, View, Platform, Dimensions } from 'react-native';
|
|
4
4
|
|
|
5
5
|
import { MessageText, MessageImage, Time, utils } from 'react-native-gifted-chat';
|
|
6
|
-
|
|
6
|
+
import CachedImage from '../CachedImage';
|
|
7
7
|
const { isSameUser, isSameDay } = utils;
|
|
8
|
+
const windowWidth = Dimensions.get('window').width;
|
|
9
|
+
const windowHeight = Dimensions.get('window').height;
|
|
8
10
|
|
|
9
11
|
export default class Bubble extends React.Component<any> {
|
|
10
12
|
static defaultProps: {
|
|
@@ -22,8 +24,8 @@ export default class Bubble extends React.Component<any> {
|
|
|
22
24
|
tickStyle: {};
|
|
23
25
|
containerToNextStyle: {};
|
|
24
26
|
containerToPreviousStyle: {};
|
|
25
|
-
isShowImageViewer:false
|
|
26
|
-
setImageViewer:(obj:any,v:boolean) => void
|
|
27
|
+
isShowImageViewer: false;
|
|
28
|
+
setImageViewer: (obj: any, v: boolean) => void;
|
|
27
29
|
};
|
|
28
30
|
static propTypes: any;
|
|
29
31
|
constructor(props: any) {
|
|
@@ -59,8 +61,33 @@ export default class Bubble extends React.Component<any> {
|
|
|
59
61
|
if (this.props.renderMessageImage) {
|
|
60
62
|
return this.props.renderMessageImage(messageImageProps);
|
|
61
63
|
}
|
|
64
|
+
const { image, _id } = messageImageProps?.currentMessage;
|
|
65
|
+
|
|
62
66
|
return (
|
|
63
|
-
<TouchableOpacity onPress={()=>this.props.setImageViewer(messageImageProps?.currentMessage,true)}
|
|
67
|
+
<TouchableOpacity onPress={() => this.props.setImageViewer(messageImageProps?.currentMessage, true)}>
|
|
68
|
+
<View
|
|
69
|
+
style={{
|
|
70
|
+
width: windowWidth - (windowWidth - 150),
|
|
71
|
+
height: windowHeight - (windowHeight - 100),
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{/* <MessageImage
|
|
75
|
+
{...messageImageProps}
|
|
76
|
+
imageStyle={[styles.slackImage, messageImageProps.imageStyle]}
|
|
77
|
+
/> */}
|
|
78
|
+
<CachedImage
|
|
79
|
+
style={[styles.slackImage, { width: '100%', height: '100%' }]}
|
|
80
|
+
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
81
|
+
source={{
|
|
82
|
+
uri: image,
|
|
83
|
+
//headers: `Authorization: Bearer ${token}`,
|
|
84
|
+
expiresIn: 86400,
|
|
85
|
+
}}
|
|
86
|
+
resizeMode={'cover'}
|
|
87
|
+
alt={'image'}
|
|
88
|
+
/>
|
|
89
|
+
</View>
|
|
90
|
+
</TouchableOpacity>
|
|
64
91
|
);
|
|
65
92
|
}
|
|
66
93
|
return null;
|
|
@@ -243,6 +270,6 @@ Bubble.defaultProps = {
|
|
|
243
270
|
tickStyle: {},
|
|
244
271
|
containerToNextStyle: {},
|
|
245
272
|
containerToPreviousStyle: {},
|
|
246
|
-
isShowImageViewer:false,
|
|
247
|
-
setImageViewer:(obj:any,v:boolean) => null,
|
|
273
|
+
isShowImageViewer: false,
|
|
274
|
+
setImageViewer: (obj: any, v: boolean) => null,
|
|
248
275
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { Box, Button, HStack, Icon, Image, Spinner, Text, useColorModeValue, ScrollView } from 'native-base';
|
|
3
|
-
import { Platform } from 'react-native';
|
|
3
|
+
import { Platform, TouchableOpacity } from 'react-native';
|
|
4
4
|
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
|
5
5
|
import { useSelector } from 'react-redux';
|
|
6
6
|
import { orderBy, uniqBy } from 'lodash';
|
|
@@ -20,7 +20,7 @@ import { IFileInfo } from '@messenger-box/core';
|
|
|
20
20
|
import { config } from '../config';
|
|
21
21
|
import { userSelector } from '@adminide-stack/user-auth0-client';
|
|
22
22
|
import { SlackMessage, ImageViewerModal } from '../components/SlackMessageContainer';
|
|
23
|
-
|
|
23
|
+
import CachedImage from '../components/CachedImage';
|
|
24
24
|
const {
|
|
25
25
|
MESSAGES_PER_PAGE,
|
|
26
26
|
CALL_TO_ACTION_BOX_BGCOLOR,
|
|
@@ -138,7 +138,8 @@ const ConversationViewComponent = ({ channelId }: any) => {
|
|
|
138
138
|
?.map((et: any) => et?.token) ?? [],
|
|
139
139
|
)
|
|
140
140
|
?.flat(1)
|
|
141
|
-
?.filter((t: any) => t)
|
|
141
|
+
?.filter((t: any) => t)
|
|
142
|
+
?.filter((value: any, index: any, array: any) => array.indexOf(value) === index) ?? [];
|
|
142
143
|
|
|
143
144
|
if (tokens?.length > 0) setExpoTokens(tokens);
|
|
144
145
|
}
|
|
@@ -603,8 +604,21 @@ const ConversationViewComponent = ({ channelId }: any) => {
|
|
|
603
604
|
|
|
604
605
|
const modalContent = React.useMemo(() => {
|
|
605
606
|
if (!imageObject) return <></>;
|
|
606
|
-
const { image } = imageObject;
|
|
607
|
-
return
|
|
607
|
+
const { image, _id } = imageObject;
|
|
608
|
+
return (
|
|
609
|
+
<CachedImage
|
|
610
|
+
style={{ width: '100%', height: '100%' }}
|
|
611
|
+
resizeMode={'cover'}
|
|
612
|
+
// cacheKey={`${_id}-conversation-modal-image-key`}
|
|
613
|
+
cacheKey={`${_id}-slack-bubble-imageKey`}
|
|
614
|
+
source={{
|
|
615
|
+
uri: image,
|
|
616
|
+
//headers: `Authorization: Bearer ${token}`,
|
|
617
|
+
expiresIn: 86400,
|
|
618
|
+
}}
|
|
619
|
+
alt={'image'}
|
|
620
|
+
/>
|
|
621
|
+
);
|
|
608
622
|
}, [imageObject]);
|
|
609
623
|
|
|
610
624
|
const renderMessage = (props: any) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useEffect, useState } from 'react';
|
|
2
|
-
import { FlatList, Box, Heading, Input, Text, Icon, Center } from 'native-base';
|
|
2
|
+
import { FlatList, Box, Heading, Input, Text, Icon, Center, Spinner } from 'native-base';
|
|
3
3
|
import { Ionicons } from '@expo/vector-icons';
|
|
4
4
|
import { useSelector, useDispatch } from 'react-redux';
|
|
5
5
|
import { useNavigation, useRoute, useIsFocused, useFocusEffect } from '@react-navigation/native';
|
|
@@ -15,14 +15,15 @@ export interface InboxProps {
|
|
|
15
15
|
channelRole?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const DialogsComponent = (props: InboxProps) => {
|
|
19
19
|
const { channelFilters, channelRole } = props;
|
|
20
20
|
const { params } = useRoute<any>();
|
|
21
21
|
const auth = useSelector(userSelector);
|
|
22
22
|
const dispatch = useDispatch();
|
|
23
23
|
const navigation = useNavigation<any>();
|
|
24
24
|
const isFocused = useIsFocused();
|
|
25
|
-
const [
|
|
25
|
+
const [refreshing, setRefresh] = useState<boolean>(false);
|
|
26
|
+
// const [userDirectChannel, setUserDirectChannel] = useState<any>([]);
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
29
|
data: userChannels,
|
|
@@ -38,6 +39,7 @@ export interface InboxProps {
|
|
|
38
39
|
useFocusEffect(
|
|
39
40
|
React.useCallback(() => {
|
|
40
41
|
// Do something when the screen is focused
|
|
42
|
+
setRefresh(false);
|
|
41
43
|
getChannelsRefetch({ role: channelRole, criteria: channelFilters });
|
|
42
44
|
|
|
43
45
|
return () => {
|
|
@@ -47,38 +49,55 @@ export interface InboxProps {
|
|
|
47
49
|
}, [channelFilters]),
|
|
48
50
|
);
|
|
49
51
|
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
type: CHANGE_SETTINGS_ACTION,
|
|
54
|
-
payload: {
|
|
55
|
-
footerRender: false,
|
|
56
|
-
},
|
|
57
|
-
} as any);
|
|
58
|
-
}, 0);
|
|
59
|
-
return () => {
|
|
60
|
-
dispatch({
|
|
61
|
-
type: CHANGE_SETTINGS_ACTION,
|
|
62
|
-
payload: {
|
|
63
|
-
footerRender: true,
|
|
64
|
-
},
|
|
65
|
-
} as any);
|
|
66
|
-
};
|
|
67
|
-
}, []);
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (refreshing) getChannelsRefetch({ role: channelRole, criteria: channelFilters });
|
|
54
|
+
}, [refreshing]);
|
|
68
55
|
|
|
69
|
-
|
|
70
|
-
if (userChannels?.channelsByUser)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
const channels = React.useMemo(() => {
|
|
57
|
+
if (!userChannels?.channelsByUser?.length) return null;
|
|
58
|
+
setRefresh(false);
|
|
59
|
+
let uChannels: any =
|
|
60
|
+
userChannels?.channelsByUser?.filter((c: any) =>
|
|
61
|
+
c.members.some((u: any) => u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
|
|
62
|
+
) ?? [];
|
|
63
|
+
return uChannels || [];
|
|
64
|
+
}, [userChannels]);
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
// useEffect(() => {
|
|
67
|
+
// setTimeout(() => {
|
|
68
|
+
// dispatch({
|
|
69
|
+
// type: CHANGE_SETTINGS_ACTION,
|
|
70
|
+
// payload: {
|
|
71
|
+
// footerRender: false,
|
|
72
|
+
// },
|
|
73
|
+
// } as any);
|
|
74
|
+
// }, 0);
|
|
75
|
+
// return () => {
|
|
76
|
+
// dispatch({
|
|
77
|
+
// type: CHANGE_SETTINGS_ACTION,
|
|
78
|
+
// payload: {
|
|
79
|
+
// footerRender: true,
|
|
80
|
+
// },
|
|
81
|
+
// } as any);
|
|
82
|
+
// };
|
|
83
|
+
// }, []);
|
|
84
|
+
|
|
85
|
+
// useEffect(() => {
|
|
86
|
+
// if (userChannels?.channelsByUser) {
|
|
87
|
+
// if (userChannels?.channelsByUser?.length == 0) {
|
|
88
|
+
// setUserDirectChannel([]);
|
|
89
|
+
// }
|
|
90
|
+
// //Direct channel
|
|
91
|
+
// let userDirectChannels: any =
|
|
92
|
+
// userChannels?.channelsByUser
|
|
93
|
+
// ?.filter((i: any) => i.type == 'DIRECT')
|
|
94
|
+
// ?.filter((c: any) =>
|
|
95
|
+
// c.members.some((u: any) => u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
|
|
96
|
+
// ) ?? [];
|
|
97
|
+
|
|
98
|
+
// if (userDirectChannels?.length > 0) setUserDirectChannel(userDirectChannels);
|
|
99
|
+
// }
|
|
100
|
+
// }, [userChannels?.channelsByUser]);
|
|
82
101
|
|
|
83
102
|
const handleSelectChannel = useCallback((id: any, title: any) => {
|
|
84
103
|
if (params?.channelId) {
|
|
@@ -99,9 +118,9 @@ export interface InboxProps {
|
|
|
99
118
|
return (
|
|
100
119
|
<Box p={2}>
|
|
101
120
|
<FlatList
|
|
102
|
-
data={
|
|
103
|
-
onRefresh={
|
|
104
|
-
refreshing={
|
|
121
|
+
data={channels && channels?.length > 0 ? channels : []}
|
|
122
|
+
onRefresh={() => setRefresh(true)}
|
|
123
|
+
refreshing={refreshing}
|
|
105
124
|
contentContainerStyle={{ minHeight: '100%' }}
|
|
106
125
|
ItemSeparatorComponent={() => <Box height="0.5" backgroundColor="gray.200" />}
|
|
107
126
|
renderItem={({ item: channel }) => (
|
|
@@ -113,21 +132,27 @@ export interface InboxProps {
|
|
|
113
132
|
/>
|
|
114
133
|
)}
|
|
115
134
|
ListEmptyComponent={() => (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
<>
|
|
136
|
+
{userChannelsLoading ? (
|
|
137
|
+
<Spinner />
|
|
138
|
+
) : (
|
|
139
|
+
<Box p={5}>
|
|
140
|
+
<Heading>Chat</Heading>
|
|
141
|
+
<Input
|
|
142
|
+
height={50}
|
|
143
|
+
mt={3}
|
|
144
|
+
placeholder="Search"
|
|
145
|
+
borderRadius={50}
|
|
146
|
+
borderColor={'gray.200'}
|
|
147
|
+
borderWidth={2}
|
|
148
|
+
/>
|
|
149
|
+
<Center mt={6}>
|
|
150
|
+
<Icon as={Ionicons} name="chatbubbles" size={'xl'} />
|
|
151
|
+
<Text>You don't have any messages yet!</Text>
|
|
152
|
+
</Center>
|
|
153
|
+
</Box>
|
|
154
|
+
)}
|
|
155
|
+
</>
|
|
131
156
|
)}
|
|
132
157
|
keyExtractor={(item, index) => 'key' + index}
|
|
133
158
|
/>
|