@planningcenter/chat-react-native 3.2.0-rc.25 → 3.2.0-rc.27
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/build/components/conversation/message_form/message_form_attachment_image.d.ts +13 -0
- package/build/components/conversation/message_form/message_form_attachment_image.d.ts.map +1 -0
- package/build/components/conversation/message_form/message_form_attachment_image.js +78 -0
- package/build/components/conversation/message_form/message_form_attachment_image.js.map +1 -0
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +128 -16
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversations/conversation_actions.d.ts +2 -2
- package/build/components/conversations/conversation_actions.d.ts.map +1 -1
- package/build/components/conversations/conversation_actions.js.map +1 -1
- package/build/components/conversations/conversation_preview.d.ts +3 -1
- package/build/components/conversations/conversation_preview.d.ts.map +1 -1
- package/build/components/conversations/conversation_preview.js +2 -2
- package/build/components/conversations/conversation_preview.js.map +1 -1
- package/build/components/group_conversation_list.d.ts +19 -0
- package/build/components/group_conversation_list.d.ts.map +1 -0
- package/build/components/group_conversation_list.js +48 -0
- package/build/components/group_conversation_list.js.map +1 -0
- package/build/components/index.d.ts +1 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +1 -0
- package/build/components/index.js.map +1 -1
- package/build/contexts/conversations_context.js +1 -1
- package/build/contexts/conversations_context.js.map +1 -1
- package/build/hooks/attachments/supported_extensions.d.ts +2 -0
- package/build/hooks/attachments/supported_extensions.d.ts.map +1 -0
- package/build/hooks/attachments/supported_extensions.js +48 -0
- package/build/hooks/attachments/supported_extensions.js.map +1 -0
- package/build/hooks/index.d.ts +4 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +4 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_api.d.ts +2 -2
- package/build/hooks/use_api.d.ts.map +1 -1
- package/build/hooks/use_api.js.map +1 -1
- package/build/hooks/use_attachment_uploader.d.ts +26 -0
- package/build/hooks/use_attachment_uploader.d.ts.map +1 -0
- package/build/hooks/use_attachment_uploader.js +111 -0
- package/build/hooks/use_attachment_uploader.js.map +1 -0
- package/build/hooks/use_upload_client.d.ts +28 -0
- package/build/hooks/use_upload_client.d.ts.map +1 -0
- package/build/hooks/use_upload_client.js +32 -0
- package/build/hooks/use_upload_client.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/navigation/index.d.ts +2 -2
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +2 -2
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_new/components/groups_form.d.ts +3 -1
- package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/groups_form.js +7 -9
- package/build/screens/conversation_new/components/groups_form.js.map +1 -1
- package/build/screens/conversation_new/conversation_new_screen.d.ts.map +1 -1
- package/build/screens/conversation_new/conversation_new_screen.js +2 -2
- package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +27 -2
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/conversations/conversations_screen.js +1 -1
- package/build/screens/conversations/conversations_screen.js.map +1 -1
- package/build/utils/native_adapters/configuration.d.ts +4 -1
- package/build/utils/native_adapters/configuration.d.ts.map +1 -1
- package/build/utils/native_adapters/configuration.js +13 -1
- package/build/utils/native_adapters/configuration.js.map +1 -1
- package/build/utils/native_adapters/image_picker.d.ts +25 -0
- package/build/utils/native_adapters/image_picker.d.ts.map +1 -0
- package/build/utils/native_adapters/image_picker.js +9 -0
- package/build/utils/native_adapters/image_picker.js.map +1 -0
- package/build/utils/native_adapters/index.d.ts +1 -0
- package/build/utils/native_adapters/index.d.ts.map +1 -1
- package/build/utils/native_adapters/index.js +1 -0
- package/build/utils/native_adapters/index.js.map +1 -1
- package/build/utils/upload_uri.d.ts +23 -0
- package/build/utils/upload_uri.d.ts.map +1 -0
- package/build/utils/upload_uri.js +60 -0
- package/build/utils/upload_uri.js.map +1 -0
- package/package.json +2 -2
- package/src/components/conversation/message_form/message_form_attachment_image.tsx +121 -0
- package/src/components/conversation/message_form.tsx +197 -31
- package/src/components/conversations/conversation_actions.tsx +2 -2
- package/src/components/conversations/conversation_preview.tsx +8 -2
- package/src/components/group_conversation_list.tsx +82 -0
- package/src/components/index.tsx +1 -0
- package/src/contexts/conversations_context.tsx +1 -1
- package/src/hooks/attachments/supported_extensions.ts +47 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use_api.ts +2 -2
- package/src/hooks/use_attachment_uploader.ts +179 -0
- package/src/hooks/use_upload_client.ts +67 -0
- package/src/index.tsx +1 -0
- package/src/navigation/index.tsx +2 -5
- package/src/screens/conversation_new/components/groups_form.tsx +11 -11
- package/src/screens/conversation_new/conversation_new_screen.tsx +6 -2
- package/src/screens/conversation_screen.tsx +31 -1
- package/src/screens/conversations/conversations_screen.tsx +1 -1
- package/src/utils/native_adapters/configuration.ts +15 -1
- package/src/utils/native_adapters/image_picker.ts +31 -0
- package/src/utils/native_adapters/index.ts +1 -0
- package/src/utils/upload_uri.ts +69 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import DeviceInfo from 'react-native-device-info';
|
|
2
|
+
const brand = DeviceInfo.getBrand();
|
|
3
|
+
const model = DeviceInfo.getModel();
|
|
4
|
+
const systemName = DeviceInfo.getSystemName();
|
|
5
|
+
const systemVersion = DeviceInfo.getSystemVersion();
|
|
6
|
+
const readableVersion = DeviceInfo.getReadableVersion();
|
|
7
|
+
const appName = DeviceInfo.getApplicationName();
|
|
8
|
+
/**
|
|
9
|
+
* This is for accessing https://github.com/planningcenter/upload
|
|
10
|
+
*/
|
|
11
|
+
export class UploadUri {
|
|
12
|
+
session;
|
|
13
|
+
app;
|
|
14
|
+
constructor({ session }) {
|
|
15
|
+
this.session = session;
|
|
16
|
+
}
|
|
17
|
+
get schema() {
|
|
18
|
+
if (this.env === 'development') {
|
|
19
|
+
return 'http';
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return 'https';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
get host() {
|
|
26
|
+
return `${this.subdomain}.${this.domain}.${this.tld}`;
|
|
27
|
+
}
|
|
28
|
+
get subdomain() {
|
|
29
|
+
switch (this.env) {
|
|
30
|
+
case 'staging':
|
|
31
|
+
return 'upload-staging';
|
|
32
|
+
default:
|
|
33
|
+
return 'upload';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
get domain() {
|
|
37
|
+
return this.env === 'development' ? 'pco' : 'planningcenteronline';
|
|
38
|
+
}
|
|
39
|
+
get tld() {
|
|
40
|
+
switch (this.env) {
|
|
41
|
+
case 'development':
|
|
42
|
+
return 'test';
|
|
43
|
+
default:
|
|
44
|
+
return 'com';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
get env() {
|
|
48
|
+
return this.session?.env || 'production';
|
|
49
|
+
}
|
|
50
|
+
get baseUrl() {
|
|
51
|
+
return `${this.schema}://${this.host}`;
|
|
52
|
+
}
|
|
53
|
+
get headers() {
|
|
54
|
+
return {
|
|
55
|
+
'User-Agent': `${appName}/${readableVersion} (${brand}, ${model}, ${systemName}, ${systemVersion})`,
|
|
56
|
+
Authorization: `Bearer ${this.session.token?.access_token}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=upload_uri.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload_uri.js","sourceRoot":"","sources":["../../src/utils/upload_uri.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,0BAA0B,CAAA;AAEjD,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;AACnC,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;AACnC,MAAM,UAAU,GAAG,UAAU,CAAC,aAAa,EAAE,CAAA;AAC7C,MAAM,aAAa,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;AACnD,MAAM,eAAe,GAAG,UAAU,CAAC,kBAAkB,EAAE,CAAA;AACvD,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,EAAE,CAAA;AAE/C;;GAEG;AACH,MAAM,OAAO,SAAS;IACpB,OAAO,CAAS;IAChB,GAAG,CAAS;IAEZ,YAAY,EAAE,OAAO,EAAwB;QAC3C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,IAAI,MAAM;QACR,IAAI,IAAI,CAAC,GAAG,KAAK,aAAa,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAA;QACf,CAAC;aAAM,CAAC;YACN,OAAO,OAAO,CAAA;QAChB,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;IACvD,CAAC;IAED,IAAI,SAAS;QACX,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,KAAK,SAAS;gBACZ,OAAO,gBAAgB,CAAA;YACzB;gBACE,OAAO,QAAQ,CAAA;QACnB,CAAC;IACH,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,sBAAsB,CAAA;IACpE,CAAC;IAED,IAAI,GAAG;QACL,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,KAAK,aAAa;gBAChB,OAAO,MAAM,CAAA;YACf;gBACE,OAAO,KAAK,CAAA;QAChB,CAAC;IACH,CAAC;IAED,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,YAAY,CAAA;IAC1C,CAAC;IAED,IAAI,OAAO;QACT,OAAO,GAAG,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;IACxC,CAAC;IAED,IAAI,OAAO;QACT,OAAO;YACL,YAAY,EAAE,GAAG,OAAO,IAAI,eAAe,KAAK,KAAK,KAAK,KAAK,KAAK,UAAU,KAAK,aAAa,GAAG;YACnG,aAAa,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE;SAC5D,CAAA;IACH,CAAC;CACF","sourcesContent":["import DeviceInfo from 'react-native-device-info'\nimport { Session } from './session'\nconst brand = DeviceInfo.getBrand()\nconst model = DeviceInfo.getModel()\nconst systemName = DeviceInfo.getSystemName()\nconst systemVersion = DeviceInfo.getSystemVersion()\nconst readableVersion = DeviceInfo.getReadableVersion()\nconst appName = DeviceInfo.getApplicationName()\n\n/**\n * This is for accessing https://github.com/planningcenter/upload\n */\nexport class UploadUri {\n session: Session\n app?: string\n\n constructor({ session }: { session: Session }) {\n this.session = session\n }\n\n get schema() {\n if (this.env === 'development') {\n return 'http'\n } else {\n return 'https'\n }\n }\n\n get host() {\n return `${this.subdomain}.${this.domain}.${this.tld}`\n }\n\n get subdomain() {\n switch (this.env) {\n case 'staging':\n return 'upload-staging'\n default:\n return 'upload'\n }\n }\n\n get domain(): 'pco' | 'planningcenteronline' {\n return this.env === 'development' ? 'pco' : 'planningcenteronline'\n }\n\n get tld() {\n switch (this.env) {\n case 'development':\n return 'test'\n default:\n return 'com'\n }\n }\n\n get env() {\n return this.session?.env || 'production'\n }\n\n get baseUrl() {\n return `${this.schema}://${this.host}`\n }\n\n get headers() {\n return {\n 'User-Agent': `${appName}/${readableVersion} (${brand}, ${model}, ${systemName}, ${systemVersion})`,\n Authorization: `Bearer ${this.session.token?.access_token}`,\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.2.0-rc.
|
|
3
|
+
"version": "3.2.0-rc.27",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"prettier": "^3.4.2",
|
|
56
56
|
"typescript": "<5.6.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "38c6eb6290424c017191c143c533a192de92d02d"
|
|
59
59
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
AnimatableNumericValue,
|
|
4
|
+
DimensionValue,
|
|
5
|
+
Image as ReactNativeImage,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
View,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { FileAttachment } from '../../../hooks/use_attachment_uploader'
|
|
10
|
+
import { useTheme } from '../../../hooks'
|
|
11
|
+
import { Icon, IconButton, Spinner } from '../../display'
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
uri: string
|
|
15
|
+
alt: string
|
|
16
|
+
status: FileAttachment['status']
|
|
17
|
+
width?: number
|
|
18
|
+
height?: number
|
|
19
|
+
removeAttachment: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MessageFormAttachmentImage({ uri, alt, status, removeAttachment }: Props) {
|
|
23
|
+
function opacity() {
|
|
24
|
+
if (status === 'uploading') {
|
|
25
|
+
return 0.5
|
|
26
|
+
}
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
29
|
+
const styles = useStyles({
|
|
30
|
+
width: 50,
|
|
31
|
+
height: 50,
|
|
32
|
+
borderRadius: 8,
|
|
33
|
+
})
|
|
34
|
+
const loading = status === 'uploading'
|
|
35
|
+
const error = status === 'error'
|
|
36
|
+
const ready = status === 'success'
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View
|
|
40
|
+
accessible={Boolean(alt)}
|
|
41
|
+
accessibilityRole="image"
|
|
42
|
+
accessibilityState={{ busy: loading }}
|
|
43
|
+
>
|
|
44
|
+
<ReactNativeImage source={{ uri }} style={[styles.image, { opacity: opacity() }]} alt={alt} />
|
|
45
|
+
{ready && (
|
|
46
|
+
<IconButton
|
|
47
|
+
name="general.x"
|
|
48
|
+
accessibilityLabel="Remove Attachment"
|
|
49
|
+
size="md"
|
|
50
|
+
style={styles.removeAttachmentIcon}
|
|
51
|
+
onPress={removeAttachment}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
{loading && (
|
|
55
|
+
<View style={[styles.loadingBackground]}>
|
|
56
|
+
<Spinner size={24} />
|
|
57
|
+
</View>
|
|
58
|
+
)}
|
|
59
|
+
{error && (
|
|
60
|
+
<View style={styles.errorBackground}>
|
|
61
|
+
<View style={styles.errorIconBackground}>
|
|
62
|
+
<Icon name="churchCenter.exclamationCircle" size={18} color="red" />
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
)}
|
|
66
|
+
</View>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Styles {
|
|
71
|
+
width: DimensionValue
|
|
72
|
+
height: DimensionValue
|
|
73
|
+
borderRadius: AnimatableNumericValue | string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const useStyles = ({ width, height, borderRadius }: Styles) => {
|
|
77
|
+
const { colors } = useTheme()
|
|
78
|
+
|
|
79
|
+
return StyleSheet.create({
|
|
80
|
+
image: {
|
|
81
|
+
backgroundColor: colors.fillColorNeutral070,
|
|
82
|
+
width,
|
|
83
|
+
height,
|
|
84
|
+
borderRadius,
|
|
85
|
+
},
|
|
86
|
+
removeAttachmentIcon: {
|
|
87
|
+
position: 'absolute',
|
|
88
|
+
top: 4,
|
|
89
|
+
right: 4,
|
|
90
|
+
backgroundColor: 'rgba(255, 255, 255, 0.5)',
|
|
91
|
+
borderRadius: 24,
|
|
92
|
+
width: 24,
|
|
93
|
+
height: 24,
|
|
94
|
+
justifyContent: 'center',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
},
|
|
97
|
+
loadingBackground: {
|
|
98
|
+
position: 'absolute',
|
|
99
|
+
top: 0,
|
|
100
|
+
left: 0,
|
|
101
|
+
borderRadius,
|
|
102
|
+
width,
|
|
103
|
+
height,
|
|
104
|
+
},
|
|
105
|
+
errorBackground: {
|
|
106
|
+
position: 'absolute',
|
|
107
|
+
top: 0,
|
|
108
|
+
left: 0,
|
|
109
|
+
borderRadius,
|
|
110
|
+
width,
|
|
111
|
+
height,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
},
|
|
115
|
+
errorIconBackground: {
|
|
116
|
+
backgroundColor: 'white',
|
|
117
|
+
padding: 2,
|
|
118
|
+
borderRadius: 50,
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useNavigation, useTheme as useNavigationTheme, useRoute } from '@react-navigation/native'
|
|
2
|
-
import React, { useContext, useEffect, useState } from 'react'
|
|
2
|
+
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
|
3
3
|
import { StyleSheet, TextInput, View, ViewProps } from 'react-native'
|
|
4
4
|
import { IconButton, Text } from '../../components'
|
|
5
5
|
import { useTheme } from '../../hooks'
|
|
@@ -7,6 +7,13 @@ import { ConversationResource } from '../../types'
|
|
|
7
7
|
import { useMessageCreate } from '../../hooks/use_message_create'
|
|
8
8
|
import { ConversationScreenProps } from '../../screens/conversation_screen'
|
|
9
9
|
import { ChatContext } from '../../contexts/chat_context'
|
|
10
|
+
import { ImagePicker, ImagePickerResult } from '../../utils/native_adapters'
|
|
11
|
+
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
12
|
+
import {
|
|
13
|
+
DenormalizedAttachmentResourceForCreate,
|
|
14
|
+
DenormalizedMessageAttachmentResourceForCreate,
|
|
15
|
+
} from '../../types/resources/denormalized_attachment_resource'
|
|
16
|
+
import { MessageFormAttachmentImage } from './message_form/message_form_attachment_image'
|
|
10
17
|
|
|
11
18
|
export const MessageForm = {
|
|
12
19
|
Root: MessageFormRoot,
|
|
@@ -20,7 +27,16 @@ interface MessagesFormRootProps extends ViewProps {
|
|
|
20
27
|
conversation: ConversationResource
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
const MessageFormContext = React.createContext
|
|
30
|
+
const MessageFormContext = React.createContext<{
|
|
31
|
+
text: string
|
|
32
|
+
setText: (text: string) => void
|
|
33
|
+
onSubmit: () => void
|
|
34
|
+
disabled: boolean
|
|
35
|
+
canGiphy: boolean
|
|
36
|
+
usingGiphy: boolean
|
|
37
|
+
setUsingGiphy: (usingGiphy: boolean) => void
|
|
38
|
+
attachmentUploader?: ReturnType<typeof useAttachmentUploader>
|
|
39
|
+
}>({
|
|
24
40
|
text: '',
|
|
25
41
|
setText: (_text: string) => {},
|
|
26
42
|
onSubmit: () => {},
|
|
@@ -38,7 +54,25 @@ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
|
|
|
38
54
|
const [usingGiphy, setUsingGiphy] = useState(false)
|
|
39
55
|
const navigation = useNavigation()
|
|
40
56
|
const route = useRoute() as ConversationScreenProps['route']
|
|
41
|
-
const {
|
|
57
|
+
const {
|
|
58
|
+
status,
|
|
59
|
+
isPending,
|
|
60
|
+
reset: resetMutation,
|
|
61
|
+
mutate,
|
|
62
|
+
} = useMessageCreate({
|
|
63
|
+
conversationId: conversation.id,
|
|
64
|
+
})
|
|
65
|
+
const attachmentUploader = useAttachmentUploader({
|
|
66
|
+
conversationId: conversation.id,
|
|
67
|
+
})
|
|
68
|
+
const resetAttachmentUploader = attachmentUploader.reset
|
|
69
|
+
|
|
70
|
+
const reset = useCallback(() => {
|
|
71
|
+
resetAttachmentUploader()
|
|
72
|
+
resetMutation()
|
|
73
|
+
setText('')
|
|
74
|
+
setUsingGiphy(false)
|
|
75
|
+
}, [resetAttachmentUploader, resetMutation])
|
|
42
76
|
|
|
43
77
|
useEffect(() => {
|
|
44
78
|
if (canGiphy && !usingGiphy && text.startsWith('/giphy ')) {
|
|
@@ -50,7 +84,6 @@ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
|
|
|
50
84
|
useEffect(() => {
|
|
51
85
|
switch (status) {
|
|
52
86
|
case 'success':
|
|
53
|
-
setText('')
|
|
54
87
|
reset()
|
|
55
88
|
break
|
|
56
89
|
}
|
|
@@ -58,15 +91,16 @@ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
|
|
|
58
91
|
|
|
59
92
|
useEffect(() => {
|
|
60
93
|
if (route.params.clear_input) {
|
|
61
|
-
|
|
62
|
-
setUsingGiphy(false)
|
|
94
|
+
reset()
|
|
63
95
|
navigation.setParams({ ...route.params, clear_input: false })
|
|
64
96
|
}
|
|
65
|
-
}, [navigation, route.params])
|
|
97
|
+
}, [reset, navigation, route.params])
|
|
66
98
|
|
|
67
99
|
const canSubmit = (() => {
|
|
68
100
|
if (isPending) return false
|
|
101
|
+
if (attachmentUploader?.pendingUploads) return false
|
|
69
102
|
if (text.length > 0) return true
|
|
103
|
+
if (attachmentUploader?.attachments?.length) return true
|
|
70
104
|
return false
|
|
71
105
|
})()
|
|
72
106
|
const disabled = !canSubmit
|
|
@@ -81,7 +115,16 @@ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
|
|
|
81
115
|
search_term: text,
|
|
82
116
|
})
|
|
83
117
|
} else {
|
|
84
|
-
|
|
118
|
+
let attachmentsForSubmit: DenormalizedAttachmentResourceForCreate[] = []
|
|
119
|
+
if (attachmentUploader?.attachmentIds) {
|
|
120
|
+
attachmentsForSubmit = attachmentUploader.attachmentIds.map(
|
|
121
|
+
(id: string): DenormalizedMessageAttachmentResourceForCreate => ({
|
|
122
|
+
type: 'MessageAttachment',
|
|
123
|
+
id,
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
mutate({ text, attachments: attachmentsForSubmit })
|
|
85
128
|
}
|
|
86
129
|
}
|
|
87
130
|
|
|
@@ -95,6 +138,7 @@ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
|
|
|
95
138
|
canGiphy,
|
|
96
139
|
usingGiphy,
|
|
97
140
|
setUsingGiphy,
|
|
141
|
+
attachmentUploader,
|
|
98
142
|
}}
|
|
99
143
|
>
|
|
100
144
|
<View style={styles.textInputContainer}>{children}</View>
|
|
@@ -102,25 +146,63 @@ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
|
|
|
102
146
|
)
|
|
103
147
|
}
|
|
104
148
|
|
|
149
|
+
function MessageFormAttachments() {
|
|
150
|
+
const styles = useMessageFormStyles()
|
|
151
|
+
const { attachmentUploader } = React.useContext(MessageFormContext)
|
|
152
|
+
const numberOfAttachments = attachmentUploader?.attachments?.length || 0
|
|
153
|
+
const attachments = attachmentUploader?.attachments || []
|
|
154
|
+
|
|
155
|
+
if (numberOfAttachments === 0) {
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<View style={styles.messageFormAttachments}>
|
|
161
|
+
{attachments.map(attachment => {
|
|
162
|
+
return (
|
|
163
|
+
<MessageFormAttachmentImage
|
|
164
|
+
key={attachment.file.uri}
|
|
165
|
+
uri={attachment.file.uri}
|
|
166
|
+
alt={attachment.file.name}
|
|
167
|
+
status={attachment.status}
|
|
168
|
+
width={attachment.file.width}
|
|
169
|
+
height={attachment.file.height}
|
|
170
|
+
removeAttachment={() => {
|
|
171
|
+
attachmentUploader?.removeAttachment(attachment)
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
)
|
|
175
|
+
})}
|
|
176
|
+
</View>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
105
180
|
function MessageFormInput() {
|
|
106
181
|
const styles = useMessageFormStyles()
|
|
107
|
-
const { text, setText, onSubmit, usingGiphy } =
|
|
182
|
+
const { text, setText, onSubmit, usingGiphy, attachmentUploader } =
|
|
183
|
+
React.useContext(MessageFormContext)
|
|
184
|
+
const attachmentError = attachmentUploader?.errorMessage
|
|
108
185
|
|
|
109
186
|
return (
|
|
110
|
-
<View style={styles.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
187
|
+
<View style={styles.textInputBoundary}>
|
|
188
|
+
<MessageFormAttachments />
|
|
189
|
+
<View style={styles.textInput}>
|
|
190
|
+
{usingGiphy ? (
|
|
191
|
+
<View style={styles.giphyBadge}>
|
|
192
|
+
<Text>/Giphy</Text>
|
|
193
|
+
</View>
|
|
194
|
+
) : null}
|
|
195
|
+
|
|
196
|
+
<TextInput
|
|
197
|
+
aria-disabled={true}
|
|
198
|
+
placeholder="Send a message"
|
|
199
|
+
onChangeText={setText}
|
|
200
|
+
value={text}
|
|
201
|
+
onSubmitEditing={onSubmit}
|
|
202
|
+
/>
|
|
203
|
+
</View>
|
|
204
|
+
|
|
205
|
+
{attachmentError ? <Text style={styles.inputErrorMessage}>{attachmentError}</Text> : null}
|
|
124
206
|
</View>
|
|
125
207
|
)
|
|
126
208
|
}
|
|
@@ -143,19 +225,82 @@ function MessageFormSubmitBtn() {
|
|
|
143
225
|
}
|
|
144
226
|
|
|
145
227
|
function MessageFormAttachmentPicker() {
|
|
146
|
-
const
|
|
228
|
+
const styles = useMessageFormStyles()
|
|
229
|
+
const { usingGiphy, attachmentUploader } = React.useContext(MessageFormContext)
|
|
230
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
231
|
+
|
|
232
|
+
function uploadImagePickerResult(result: ImagePickerResult) {
|
|
233
|
+
if (result.canceled) {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const filteredAssets = result.assets
|
|
238
|
+
.filter(asset => {
|
|
239
|
+
return asset.fileSize && asset.fileName && asset.mimeType
|
|
240
|
+
})
|
|
241
|
+
.map(asset => {
|
|
242
|
+
return {
|
|
243
|
+
uri: asset.uri,
|
|
244
|
+
name: asset.fileName as string,
|
|
245
|
+
type: asset.mimeType as string,
|
|
246
|
+
size: asset.fileSize as number,
|
|
247
|
+
height: asset.height,
|
|
248
|
+
width: asset.width,
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
attachmentUploader?.handleFilesAttached(filteredAssets)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const openCamera = async () => {
|
|
256
|
+
setIsOpen(false)
|
|
257
|
+
let result = await ImagePicker.openCameraAsync()
|
|
258
|
+
if (!result.canceled) {
|
|
259
|
+
uploadImagePickerResult(result)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const pickImage = async () => {
|
|
264
|
+
setIsOpen(false)
|
|
265
|
+
let result = await ImagePicker.openImageLibraryAsync()
|
|
266
|
+
if (!result.canceled) {
|
|
267
|
+
uploadImagePickerResult(result)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
147
270
|
|
|
148
271
|
if (usingGiphy) {
|
|
149
272
|
return null
|
|
150
273
|
}
|
|
151
274
|
|
|
152
275
|
return (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
276
|
+
// TODO: Design Pass
|
|
277
|
+
<View style={styles.attachmentPicker}>
|
|
278
|
+
{isOpen && (
|
|
279
|
+
<View style={styles.attachmentPickerButtons}>
|
|
280
|
+
<IconButton
|
|
281
|
+
accessibilityLabel="Take a photo"
|
|
282
|
+
size="md"
|
|
283
|
+
appearance="neutral"
|
|
284
|
+
name={'general.videoCamera'}
|
|
285
|
+
onPress={openCamera}
|
|
286
|
+
/>
|
|
287
|
+
<IconButton
|
|
288
|
+
accessibilityLabel="Choose a photo"
|
|
289
|
+
size="md"
|
|
290
|
+
appearance="neutral"
|
|
291
|
+
name={'churchCenter.photosIos'}
|
|
292
|
+
onPress={pickImage}
|
|
293
|
+
/>
|
|
294
|
+
</View>
|
|
295
|
+
)}
|
|
296
|
+
<IconButton
|
|
297
|
+
accessibilityLabel="File Menu"
|
|
298
|
+
size="md"
|
|
299
|
+
appearance="neutral"
|
|
300
|
+
name={'general.outlinedPlusCircle'}
|
|
301
|
+
onPress={() => setIsOpen(!isOpen)}
|
|
302
|
+
/>
|
|
303
|
+
</View>
|
|
159
304
|
)
|
|
160
305
|
}
|
|
161
306
|
|
|
@@ -203,13 +348,16 @@ const useMessageFormStyles = () => {
|
|
|
203
348
|
alignItems: 'center',
|
|
204
349
|
gap: 12,
|
|
205
350
|
},
|
|
206
|
-
|
|
351
|
+
textInputBoundary: {
|
|
207
352
|
borderRadius: 24,
|
|
208
353
|
borderWidth: 1,
|
|
209
354
|
padding: 12,
|
|
210
355
|
paddingHorizontal: 20,
|
|
211
356
|
borderColor: theme.colors.fillColorNeutral050Base,
|
|
212
357
|
flex: 1,
|
|
358
|
+
gap: 12,
|
|
359
|
+
},
|
|
360
|
+
textInput: {
|
|
213
361
|
flexDirection: 'row',
|
|
214
362
|
gap: 12,
|
|
215
363
|
},
|
|
@@ -224,5 +372,23 @@ const useMessageFormStyles = () => {
|
|
|
224
372
|
height: 36,
|
|
225
373
|
width: 36,
|
|
226
374
|
},
|
|
375
|
+
attachmentPicker: {
|
|
376
|
+
position: 'relative',
|
|
377
|
+
},
|
|
378
|
+
attachmentPickerButtons: {
|
|
379
|
+
position: 'absolute',
|
|
380
|
+
left: 0,
|
|
381
|
+
bottom: 40,
|
|
382
|
+
zIndex: 10,
|
|
383
|
+
gap: 16,
|
|
384
|
+
},
|
|
385
|
+
messageFormAttachments: {
|
|
386
|
+
flexDirection: 'row',
|
|
387
|
+
gap: 8,
|
|
388
|
+
},
|
|
389
|
+
inputErrorMessage: {
|
|
390
|
+
color: theme.colors.statusErrorText,
|
|
391
|
+
fontSize: 14,
|
|
392
|
+
},
|
|
227
393
|
})
|
|
228
394
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Platform, Pressable, StyleSheet, View, ViewStyle } from 'react-native'
|
|
2
|
+
import { Platform, Pressable, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
|
|
3
3
|
import ReanimatedSwipeable, {
|
|
4
4
|
SwipeableMethods,
|
|
5
5
|
} from 'react-native-gesture-handler/ReanimatedSwipeable'
|
|
@@ -22,7 +22,7 @@ export function ConversationActions({
|
|
|
22
22
|
children: ReactNode
|
|
23
23
|
conversation: ConversationResource
|
|
24
24
|
onPress: () => void
|
|
25
|
-
style?: ViewStyle
|
|
25
|
+
style?: StyleProp<ViewStyle>
|
|
26
26
|
}) {
|
|
27
27
|
const swipeableRef = useRef<SwipeableMethods>(null)
|
|
28
28
|
const styles = useStyles()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { StyleSheet, View } from 'react-native'
|
|
2
|
+
import { StyleSheet, View, ViewStyle } from 'react-native'
|
|
3
3
|
import { ConversationResource } from '../../types'
|
|
4
4
|
import { AvatarGroup, Heading, Text, Badge } from '../display'
|
|
5
5
|
import { formatDatePreview } from '../../utils/date'
|
|
@@ -12,12 +12,14 @@ interface ConversationPreviewProps {
|
|
|
12
12
|
conversation: ConversationResource
|
|
13
13
|
onPress: () => void
|
|
14
14
|
showBadges?: boolean
|
|
15
|
+
style?: ViewStyle
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export const ConversationPreview = ({
|
|
18
19
|
conversation,
|
|
19
20
|
onPress,
|
|
20
21
|
showBadges = true,
|
|
22
|
+
style,
|
|
21
23
|
}: ConversationPreviewProps) => {
|
|
22
24
|
const styles = useStyles()
|
|
23
25
|
const {
|
|
@@ -32,7 +34,11 @@ export const ConversationPreview = ({
|
|
|
32
34
|
} = conversation
|
|
33
35
|
|
|
34
36
|
return (
|
|
35
|
-
<ConversationActions
|
|
37
|
+
<ConversationActions
|
|
38
|
+
conversation={conversation}
|
|
39
|
+
style={[styles.previewRow, style]}
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
>
|
|
36
42
|
<AvatarGroup size="lg" sourceUris={previewAvatarUrls || []} />
|
|
37
43
|
<View style={styles.conversationBody}>
|
|
38
44
|
<Heading numberOfLines={1} variant="h3" style={styles.title}>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, View, ViewStyle } from 'react-native'
|
|
3
|
+
import { useConversations } from '../hooks/use_conversations'
|
|
4
|
+
import { ConversationPreview } from './conversations/conversation_preview'
|
|
5
|
+
import { ConversationRequestArgs } from '../utils/request/conversation'
|
|
6
|
+
import { useTheme } from '../hooks'
|
|
7
|
+
import { Icon, Text } from './display'
|
|
8
|
+
|
|
9
|
+
interface GroupConversationsProps extends Partial<ConversationRequestArgs> {
|
|
10
|
+
limit?: number
|
|
11
|
+
onConversationPress: (conversation: any) => void
|
|
12
|
+
style?: ViewStyle
|
|
13
|
+
ListHeaderComponent?:
|
|
14
|
+
| React.ComponentType<any>
|
|
15
|
+
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
16
|
+
ListOverflowFooterComponent?:
|
|
17
|
+
| React.ComponentType<any>
|
|
18
|
+
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GroupConversations is a component that displays a list of conversations
|
|
23
|
+
* for a specific group.
|
|
24
|
+
*
|
|
25
|
+
* Originally designed for use in CCA.
|
|
26
|
+
*/
|
|
27
|
+
export const GroupConversations = ({
|
|
28
|
+
limit,
|
|
29
|
+
onConversationPress,
|
|
30
|
+
style,
|
|
31
|
+
ListHeaderComponent,
|
|
32
|
+
ListOverflowFooterComponent,
|
|
33
|
+
chat_group_graph_id,
|
|
34
|
+
}: GroupConversationsProps) => {
|
|
35
|
+
const styles = useStyles()
|
|
36
|
+
const { conversations = [] } = useConversations({
|
|
37
|
+
chat_group_graph_id,
|
|
38
|
+
group_source_app_name: undefined,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={style}>
|
|
43
|
+
<>{ListHeaderComponent}</>
|
|
44
|
+
{conversations.length === 0 && (
|
|
45
|
+
<View style={styles.listEmpty}>
|
|
46
|
+
<Icon size={24} name="general.textMessage" style={styles.listEmptyIcon} />
|
|
47
|
+
<Text variant="secondary">No conversations found</Text>
|
|
48
|
+
</View>
|
|
49
|
+
)}
|
|
50
|
+
{conversations.slice(0, limit).map(conversation => (
|
|
51
|
+
<ConversationPreview
|
|
52
|
+
style={styles.conversation}
|
|
53
|
+
key={conversation.id}
|
|
54
|
+
showBadges={false}
|
|
55
|
+
conversation={conversation}
|
|
56
|
+
onPress={() => onConversationPress(conversation)}
|
|
57
|
+
/>
|
|
58
|
+
))}
|
|
59
|
+
{conversations.length > (limit || 0) && <>{ListOverflowFooterComponent}</>}
|
|
60
|
+
</View>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const useStyles = () => {
|
|
65
|
+
const { colors } = useTheme()
|
|
66
|
+
return StyleSheet.create({
|
|
67
|
+
constainer: {},
|
|
68
|
+
conversation: {
|
|
69
|
+
borderBottomWidth: 0,
|
|
70
|
+
},
|
|
71
|
+
listItem: { color: colors.fillColorNeutral020 },
|
|
72
|
+
listEmpty: {
|
|
73
|
+
justifyContent: 'center',
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
paddingVertical: 16,
|
|
76
|
+
gap: 8,
|
|
77
|
+
},
|
|
78
|
+
listEmptyIcon: {
|
|
79
|
+
color: colors.fillColorNeutral020,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
}
|
package/src/components/index.tsx
CHANGED
|
@@ -36,7 +36,7 @@ const ConversationsContext = createContext<ConversationsContextValue>({
|
|
|
36
36
|
|
|
37
37
|
export const ConversationsContextProvider = ({
|
|
38
38
|
children,
|
|
39
|
-
args,
|
|
39
|
+
args = {},
|
|
40
40
|
}: PropsWithChildren<{ args: ConversationFiltersParams }>) => {
|
|
41
41
|
const [activeConversationId, setActiveConversationId] = useState<number | undefined>()
|
|
42
42
|
const { chat_group_graph_id, group_source_app_name } = args
|