@planningcenter/chat-react-native 1.7.0-rc.0 → 2.0.0-rc.0
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.d.ts +9 -0
- package/build/components/conversation/message.d.ts.map +1 -0
- package/build/components/conversation/message.js +73 -0
- package/build/components/conversation/message.js.map +1 -0
- package/build/components/conversation/message_form.d.ts +20 -0
- package/build/components/conversation/message_form.d.ts.map +1 -0
- package/build/components/conversation/message_form.js +103 -0
- package/build/components/conversation/message_form.js.map +1 -0
- package/build/components/conversation/message_reaction.d.ts +23 -0
- package/build/components/conversation/message_reaction.d.ts.map +1 -1
- package/build/components/conversation/message_reaction.js +1 -4
- package/build/components/conversation/message_reaction.js.map +1 -1
- package/build/components/display/keyboard_view.d.ts +4 -0
- package/build/components/display/keyboard_view.d.ts.map +1 -0
- package/build/components/display/keyboard_view.js +46 -0
- package/build/components/display/keyboard_view.js.map +1 -0
- package/build/index.d.ts +4 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -6
- package/build/index.js.map +1 -1
- package/build/navigation/index.d.ts.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +21 -84
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +69 -50
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/native_adapters/clipboard.d.ts +6 -0
- package/build/utils/native_adapters/clipboard.d.ts.map +1 -0
- package/build/utils/native_adapters/clipboard.js +9 -0
- package/build/utils/native_adapters/clipboard.js.map +1 -0
- package/build/utils/native_adapters/configuration.d.ts +10 -0
- package/build/utils/native_adapters/configuration.d.ts.map +1 -0
- package/build/utils/native_adapters/configuration.js +18 -0
- package/build/utils/native_adapters/configuration.js.map +1 -0
- package/build/utils/native_adapters/index.d.ts +3 -0
- package/build/utils/native_adapters/index.d.ts.map +1 -0
- package/build/utils/native_adapters/index.js +3 -0
- package/build/utils/native_adapters/index.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/utils/native_adapters/configuration.ts +21 -0
- package/src/components/conversation/message.tsx +87 -0
- package/src/components/conversation/message_form.tsx +159 -0
- package/src/components/conversation/message_reaction.tsx +1 -4
- package/src/components/display/keyboard_view.tsx +69 -0
- package/src/index.tsx +10 -6
- package/src/screens/conversation_screen.tsx +30 -115
- package/src/screens/message_actions_screen.tsx +114 -77
- package/src/utils/index.ts +1 -0
- package/src/utils/native_adapters/clipboard.ts +9 -0
- package/src/utils/native_adapters/configuration.ts +25 -0
- package/src/utils/native_adapters/index.ts +2 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useTheme as useNavigationTheme } from '@react-navigation/native'
|
|
2
|
+
import React, { useMemo, useState } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
KeyboardAvoidingView,
|
|
5
|
+
Platform,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
useWindowDimensions,
|
|
8
|
+
View,
|
|
9
|
+
ViewProps,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
12
|
+
|
|
13
|
+
export function KeyboardView({ children }: ViewProps) {
|
|
14
|
+
const styles = useStyles()
|
|
15
|
+
|
|
16
|
+
const { height } = useWindowDimensions()
|
|
17
|
+
const insets = useSafeAreaInsets()
|
|
18
|
+
const [pageSheetGap, setPageSheetGap] = useState<number>(0)
|
|
19
|
+
const keyboardVerticalOffset = useMemo(
|
|
20
|
+
() => pageSheetGap + insets.top - insets.bottom,
|
|
21
|
+
[pageSheetGap, insets.top, insets.bottom]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View
|
|
26
|
+
style={styles.pageGapView}
|
|
27
|
+
onLayout={event => {
|
|
28
|
+
const { height: viewHeight } = event.nativeEvent.layout
|
|
29
|
+
setPageSheetGap(height - viewHeight - insets.top)
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<KeyboardAvoidingView
|
|
33
|
+
keyboardVerticalOffset={keyboardVerticalOffset}
|
|
34
|
+
behavior={Platform.select({ ios: 'padding' })}
|
|
35
|
+
style={styles.container}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</KeyboardAvoidingView>
|
|
39
|
+
</View>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const useStyles = () => {
|
|
44
|
+
const navigationTheme = useNavigationTheme()
|
|
45
|
+
|
|
46
|
+
return StyleSheet.create({
|
|
47
|
+
container: {
|
|
48
|
+
flex: 1,
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
},
|
|
51
|
+
pageGapView: {
|
|
52
|
+
flex: 1,
|
|
53
|
+
},
|
|
54
|
+
keyboardView: {
|
|
55
|
+
flex: 1,
|
|
56
|
+
},
|
|
57
|
+
listContainer: {
|
|
58
|
+
gap: 12,
|
|
59
|
+
paddingHorizontal: 16,
|
|
60
|
+
paddingVertical: 12,
|
|
61
|
+
},
|
|
62
|
+
textInputContainer: {
|
|
63
|
+
borderTopWidth: 1,
|
|
64
|
+
padding: 12,
|
|
65
|
+
backgroundColor: navigationTheme.colors.card,
|
|
66
|
+
},
|
|
67
|
+
textInput: { borderRadius: 16, borderWidth: 1, padding: 12 },
|
|
68
|
+
})
|
|
69
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
export { ChatProvider, ChatContext, CreateChatThemeProps } from './contexts'
|
|
2
|
+
export { DesignSystemScreen } from './screens'
|
|
3
|
+
export { ChatStack } from './navigation'
|
|
4
|
+
export {
|
|
5
|
+
TemporaryDefaultColorsType,
|
|
6
|
+
ChatAdapters,
|
|
7
|
+
ClipboardAdapter,
|
|
8
|
+
Session,
|
|
9
|
+
Client,
|
|
10
|
+
} from './utils'
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
1
|
import {
|
|
3
2
|
StaticScreenProps,
|
|
4
3
|
useNavigation,
|
|
5
4
|
useTheme as useNavigationTheme,
|
|
6
5
|
} from '@react-navigation/native'
|
|
7
|
-
import colorFunction from 'color'
|
|
8
6
|
import React, { useEffect } from 'react'
|
|
9
|
-
import { FlatList, StyleSheet,
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
7
|
+
import { FlatList, StyleSheet, View } from 'react-native'
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
9
|
+
import { Message } from '../components/conversation/message'
|
|
10
|
+
import { MessageForm } from '../components/conversation/message_form'
|
|
11
|
+
import { KeyboardView } from '../components/display/keyboard_view'
|
|
14
12
|
import { useConversationMessages } from '../hooks/use_conversation_messages'
|
|
15
13
|
import { useSuspenseGet } from '../hooks/use_suspense_api'
|
|
16
|
-
import { ConversationResource
|
|
14
|
+
import { ConversationResource } from '../types'
|
|
17
15
|
|
|
18
16
|
export type ConversationScreenProps = StaticScreenProps<{
|
|
19
17
|
conversation_id: string
|
|
@@ -43,130 +41,47 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
43
41
|
}, [conversation, conversation_id, navigation])
|
|
44
42
|
|
|
45
43
|
return (
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<View style={styles.textInputContainer}>
|
|
58
|
-
<TextInput
|
|
59
|
-
aria-disabled={true}
|
|
60
|
-
placeholder="Send a message"
|
|
61
|
-
onChangeText={() => console.log('TODO: Implement sending a message')}
|
|
62
|
-
value=""
|
|
63
|
-
style={styles.textInput}
|
|
44
|
+
<View style={styles.container}>
|
|
45
|
+
<KeyboardView style={styles.keyboardView}>
|
|
46
|
+
<FlatList
|
|
47
|
+
inverted
|
|
48
|
+
contentContainerStyle={styles.listContainer}
|
|
49
|
+
refreshing={isRefetching}
|
|
50
|
+
onRefresh={refetch}
|
|
51
|
+
data={messages}
|
|
52
|
+
keyExtractor={item => item.id}
|
|
53
|
+
renderItem={({ item }) => <Message {...item} conversation_id={conversation_id} />}
|
|
54
|
+
onEndReached={() => fetchNextPage()}
|
|
64
55
|
/>
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
<MessageForm.Root conversation={conversation}>
|
|
57
|
+
{/* <MessageForm.AttachmentPicker /> */}
|
|
58
|
+
{/* <MessageForm.Commands /> */}
|
|
59
|
+
<MessageForm.TextInput />
|
|
60
|
+
<MessageForm.SubmitButton />
|
|
61
|
+
</MessageForm.Root>
|
|
62
|
+
</KeyboardView>
|
|
63
|
+
</View>
|
|
67
64
|
)
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
const useStyles = () => {
|
|
71
68
|
const navigationTheme = useNavigationTheme()
|
|
69
|
+
const { bottom } = useSafeAreaInsets()
|
|
72
70
|
|
|
73
71
|
return StyleSheet.create({
|
|
74
72
|
container: {
|
|
75
73
|
flex: 1,
|
|
76
74
|
justifyContent: 'center',
|
|
77
75
|
backgroundColor: navigationTheme.colors.card,
|
|
76
|
+
paddingBottom: bottom,
|
|
77
|
+
},
|
|
78
|
+
keyboardView: {
|
|
79
|
+
flex: 1,
|
|
78
80
|
},
|
|
79
81
|
listContainer: {
|
|
80
82
|
gap: 12,
|
|
81
83
|
paddingHorizontal: 16,
|
|
82
84
|
paddingVertical: 12,
|
|
83
85
|
},
|
|
84
|
-
textInputContainer: {
|
|
85
|
-
borderTopWidth: 1,
|
|
86
|
-
padding: 12,
|
|
87
|
-
backgroundColor: navigationTheme.colors.card,
|
|
88
|
-
},
|
|
89
|
-
textInput: { borderRadius: 16, borderWidth: 1, padding: 12 },
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function Message(props: MessageResource & { conversation_id: string }) {
|
|
94
|
-
const { text, conversation_id, reactionCounts } = props
|
|
95
|
-
const styles = useMessageStyles(props)
|
|
96
|
-
const navigation = useNavigation()
|
|
97
|
-
const handleMessagePress = () => {
|
|
98
|
-
navigation.navigate('MessageActions', {
|
|
99
|
-
message_id: props.id,
|
|
100
|
-
conversation_id,
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
// TODO: open the reaction screen to show who reacted
|
|
104
|
-
const handleReactionPress = handleMessagePress
|
|
105
|
-
|
|
106
|
-
if (!text) return null
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<View style={styles.message}>
|
|
110
|
-
{!props.mine && (
|
|
111
|
-
<View style={styles.messageAuthor}>
|
|
112
|
-
<Avatar size={'md'} sourceUri={props.author.avatar} />
|
|
113
|
-
</View>
|
|
114
|
-
)}
|
|
115
|
-
<View style={styles.messageContent}>
|
|
116
|
-
{!props.mine && (
|
|
117
|
-
<Text variant="tertiary" style={styles.authorName}>
|
|
118
|
-
{props.author.name}
|
|
119
|
-
</Text>
|
|
120
|
-
)}
|
|
121
|
-
<PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
|
|
122
|
-
<Text style={styles.messageText}>{text}</Text>
|
|
123
|
-
</PlatformPressable>
|
|
124
|
-
<View style={styles.messageReactions}>
|
|
125
|
-
{reactionCounts.map(reaction => (
|
|
126
|
-
<MessageReaction
|
|
127
|
-
key={reaction.value}
|
|
128
|
-
reaction={reaction}
|
|
129
|
-
onPress={handleReactionPress}
|
|
130
|
-
/>
|
|
131
|
-
))}
|
|
132
|
-
</View>
|
|
133
|
-
</View>
|
|
134
|
-
</View>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const useMessageStyles = ({ mine }: MessageResource) => {
|
|
139
|
-
const { colors } = useTheme()
|
|
140
|
-
const activeColor = colorFunction(colors.interaction).alpha(0.2).string()
|
|
141
|
-
|
|
142
|
-
return StyleSheet.create({
|
|
143
|
-
message: {
|
|
144
|
-
gap: 8,
|
|
145
|
-
flexDirection: mine ? 'row-reverse' : 'row',
|
|
146
|
-
},
|
|
147
|
-
messageContent: {
|
|
148
|
-
gap: 8,
|
|
149
|
-
flexShrink: 1,
|
|
150
|
-
},
|
|
151
|
-
messageAuthor: {
|
|
152
|
-
flexDirection: 'row',
|
|
153
|
-
gap: 8,
|
|
154
|
-
},
|
|
155
|
-
authorName: {},
|
|
156
|
-
authorAvatar: {},
|
|
157
|
-
messageBubble: {
|
|
158
|
-
backgroundColor: mine ? activeColor : colors.fillColorNeutral070,
|
|
159
|
-
borderRadius: 12,
|
|
160
|
-
paddingVertical: 6,
|
|
161
|
-
paddingHorizontal: 8,
|
|
162
|
-
},
|
|
163
|
-
messageText: {
|
|
164
|
-
color: colors.textColorDefaultPrimary,
|
|
165
|
-
},
|
|
166
|
-
messageReactions: {
|
|
167
|
-
flexDirection: 'row',
|
|
168
|
-
gap: 4,
|
|
169
|
-
justifyContent: mine ? 'flex-end' : 'flex-start',
|
|
170
|
-
},
|
|
171
86
|
})
|
|
172
87
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
1
2
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
3
|
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
|
3
4
|
import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import React, { useCallback, useContext } from 'react'
|
|
6
|
+
import { Alert, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
7
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
8
|
+
import { Text, TextButton } from '../components'
|
|
9
|
+
import { REACTION_EMOJIS, useReactionStyles } from '../components/conversation/message_reaction'
|
|
9
10
|
import { ChatContext } from '../contexts'
|
|
10
11
|
import { useTheme } from '../hooks'
|
|
11
12
|
import { getMessagesRequestArgs, useConversationMessages } from '../hooks/use_conversation_messages'
|
|
12
13
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
13
14
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
14
15
|
import { updateRecordInPagesData } from '../utils'
|
|
15
|
-
import {
|
|
16
|
+
import { Clipboard } from '../utils/native_adapters'
|
|
16
17
|
|
|
17
18
|
export const ReactScreenOptions: NativeStackNavigationOptions = {
|
|
18
19
|
presentation: 'formSheet',
|
|
@@ -34,39 +35,61 @@ export function MessageActionsScreen({ route }: ReactionScreenProps) {
|
|
|
34
35
|
const queryClient = useQueryClient()
|
|
35
36
|
const styles = useStyles()
|
|
36
37
|
|
|
37
|
-
const { messages, queryKey } = useConversationMessages(
|
|
38
|
+
const { messages, queryKey, refetch } = useConversationMessages(
|
|
38
39
|
{ conversation_id },
|
|
39
40
|
{ refetchOnMount: false }
|
|
40
41
|
)
|
|
41
42
|
const message = messages.find(m => m.id === message_id)
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
const myReactions = message?.reactionCounts
|
|
44
|
+
.filter(reaction => reaction.mine)
|
|
45
|
+
.map(reaction => reaction.value)
|
|
46
|
+
|
|
47
|
+
const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {
|
|
48
|
+
return {
|
|
49
|
+
value: value as ReactionCountResource['value'],
|
|
50
|
+
emoji,
|
|
51
|
+
mine: myReactions?.includes(value as ReactionCountResource['value']),
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const handleCopyPress = (text?: string) => {
|
|
56
|
+
Clipboard.setStringAsync(text || '')
|
|
57
|
+
navigation.goBack()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handleReactionPress = useCallback(
|
|
61
|
+
({ value, mine }: { value: keyof typeof REACTION_EMOJIS; mine?: boolean }) => {
|
|
62
|
+
const requestParams = getMessagesRequestArgs({ conversation_id })
|
|
63
|
+
|
|
64
|
+
// Value has already been updated
|
|
65
|
+
const endpoint = !mine ? 'react' : 'unreact'
|
|
66
|
+
const url = `/me/conversations/${conversation_id}/messages/${message_id}/${endpoint}`
|
|
67
|
+
const fieldsWithValueJoined = Object.fromEntries(
|
|
68
|
+
Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return client.post({
|
|
72
|
+
url,
|
|
60
73
|
data: {
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
...requestParams.data,
|
|
75
|
+
data: {
|
|
76
|
+
type: 'Message',
|
|
77
|
+
attributes: { value: value },
|
|
78
|
+
},
|
|
79
|
+
fields: fieldsWithValueJoined,
|
|
63
80
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
})
|
|
82
|
+
},
|
|
83
|
+
[client, conversation_id, message_id]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const deleteMessage = useCallback(() => {
|
|
87
|
+
const url = `/me/conversations/${conversation_id}/messages/${message_id}/`
|
|
68
88
|
|
|
69
|
-
|
|
89
|
+
return client.delete({ url })
|
|
90
|
+
}, [client, conversation_id, message_id])
|
|
91
|
+
|
|
92
|
+
const { mutate: handleReaction, isPending } = useMutation({
|
|
70
93
|
mutationFn: handleReactionPress,
|
|
71
94
|
onSuccess: (result: ApiResource<MessageResource>) => {
|
|
72
95
|
const updatedMessage = result.data
|
|
@@ -80,77 +103,92 @@ export function MessageActionsScreen({ route }: ReactionScreenProps) {
|
|
|
80
103
|
)
|
|
81
104
|
navigation.goBack()
|
|
82
105
|
},
|
|
106
|
+
onError: () => {
|
|
107
|
+
Alert.alert('Oops', 'We were unable to react to this message. Please try again.')
|
|
108
|
+
},
|
|
83
109
|
})
|
|
84
110
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
111
|
+
const { mutate: handleDeleteMessage } = useMutation({
|
|
112
|
+
mutationFn: deleteMessage,
|
|
113
|
+
onSuccess: () => {
|
|
114
|
+
refetch()
|
|
115
|
+
navigation.goBack()
|
|
116
|
+
},
|
|
117
|
+
onError: () => {
|
|
118
|
+
Alert.alert('Oops', 'We were unable to delete this message. Please try again.')
|
|
119
|
+
},
|
|
95
120
|
})
|
|
96
121
|
|
|
97
122
|
return (
|
|
98
|
-
<
|
|
123
|
+
<View style={styles.container}>
|
|
99
124
|
<View style={styles.reactionList}>
|
|
100
|
-
{availableReactions.map((
|
|
101
|
-
<
|
|
102
|
-
key={index}
|
|
103
|
-
onPress={() => mutate(value)}
|
|
104
|
-
style={({ pressed }) => [
|
|
105
|
-
styles.reaction,
|
|
106
|
-
mine && styles.reactionActive,
|
|
107
|
-
pressed && styles.pressed,
|
|
108
|
-
]}
|
|
109
|
-
variant="plain"
|
|
110
|
-
disabled={isPending}
|
|
111
|
-
>
|
|
112
|
-
{emoji}
|
|
113
|
-
</TextButton>
|
|
125
|
+
{availableReactions.map((reaction, index) => (
|
|
126
|
+
<Reaction key={index} reaction={reaction} onPress={() => handleReaction(reaction)} />
|
|
114
127
|
))}
|
|
115
128
|
</View>
|
|
116
129
|
<View style={styles.actions}>
|
|
117
130
|
<View style={styles.actionButton}>
|
|
118
|
-
<TextButton onPress={() =>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
<TextButton onPress={() => handleCopyPress(message?.text)}>Copy</TextButton>
|
|
132
|
+
<TextButton
|
|
133
|
+
appearance="danger"
|
|
134
|
+
onPress={() => handleDeleteMessage()}
|
|
135
|
+
disabled={isPending}
|
|
136
|
+
>
|
|
122
137
|
Delete
|
|
123
138
|
</TextButton>
|
|
124
139
|
</View>
|
|
125
140
|
</View>
|
|
126
|
-
</
|
|
141
|
+
</View>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const Reaction = ({
|
|
146
|
+
reaction,
|
|
147
|
+
onPress,
|
|
148
|
+
}: {
|
|
149
|
+
reaction: { value: ReactionCountResource['value']; emoji: string; mine: boolean | undefined }
|
|
150
|
+
onPress: () => void
|
|
151
|
+
}) => {
|
|
152
|
+
const styles = useStyles()
|
|
153
|
+
const reactionStyles = useReactionStyles({ mine: reaction.mine ? 1 : 0 })
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<PlatformPressable
|
|
157
|
+
key={reaction.value}
|
|
158
|
+
style={[reactionStyles.reaction, styles.reaction]}
|
|
159
|
+
onPress={onPress}
|
|
160
|
+
>
|
|
161
|
+
<Text style={reactionStyles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
|
|
162
|
+
</PlatformPressable>
|
|
127
163
|
)
|
|
128
164
|
}
|
|
129
165
|
|
|
130
166
|
const useStyles = () => {
|
|
131
167
|
const theme = useTheme()
|
|
168
|
+
const { height } = useWindowDimensions()
|
|
169
|
+
const { bottom } = useSafeAreaInsets()
|
|
170
|
+
const btnBorderWidth = 1
|
|
171
|
+
const baseSize = 44
|
|
172
|
+
const reactionBtnSize = Platform.select({
|
|
173
|
+
ios: baseSize,
|
|
174
|
+
android: baseSize + btnBorderWidth * 2,
|
|
175
|
+
})
|
|
176
|
+
|
|
132
177
|
return StyleSheet.create({
|
|
133
178
|
container: {
|
|
134
179
|
justifyContent: 'flex-start',
|
|
135
180
|
paddingTop: 12,
|
|
181
|
+
paddingBottom: bottom,
|
|
136
182
|
width: '100%',
|
|
137
183
|
backgroundColor: theme.colors.fillColorNeutral100Inverted,
|
|
184
|
+
height,
|
|
138
185
|
},
|
|
139
186
|
reaction: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
color: '#ffffff',
|
|
146
|
-
backgroundColor: theme.colors.fillColorNeutral050Base,
|
|
147
|
-
},
|
|
148
|
-
reactionActive: {
|
|
149
|
-
borderColor: theme.colors.interaction,
|
|
150
|
-
backgroundColor: colorFunction(theme.colors.interaction).alpha(0.2).string(),
|
|
151
|
-
},
|
|
152
|
-
pressed: {
|
|
153
|
-
opacity: 0.5,
|
|
187
|
+
height: reactionBtnSize,
|
|
188
|
+
width: reactionBtnSize,
|
|
189
|
+
borderWidth: btnBorderWidth,
|
|
190
|
+
borderRadius: 32,
|
|
191
|
+
justifyContent: 'center',
|
|
154
192
|
},
|
|
155
193
|
reactionList: {
|
|
156
194
|
justifyContent: 'center',
|
|
@@ -159,7 +197,6 @@ const useStyles = () => {
|
|
|
159
197
|
flexDirection: 'row',
|
|
160
198
|
borderBottomColor: theme.colors.fillColorNeutral040,
|
|
161
199
|
borderBottomWidth: 1,
|
|
162
|
-
flex: 1,
|
|
163
200
|
},
|
|
164
201
|
actions: { flex: 1 },
|
|
165
202
|
actionButton: { padding: 12, paddingBottom: 100, gap: 12 },
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class ClipboardAdapter {
|
|
2
|
+
getStringAsync: () => Promise<string>
|
|
3
|
+
setStringAsync: (_: string) => Promise<void>
|
|
4
|
+
|
|
5
|
+
constructor(methods: ClipboardAdapter) {
|
|
6
|
+
this.getStringAsync = methods.getStringAsync
|
|
7
|
+
this.setStringAsync = methods.setStringAsync
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ClipboardAdapter } from './clipboard'
|
|
2
|
+
|
|
3
|
+
type ChatConfigurations = {
|
|
4
|
+
clipboard: ClipboardAdapter
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class ChatAdapters {
|
|
8
|
+
static configure(configurations: ChatConfigurations) {
|
|
9
|
+
Clipboard = configurations.clipboard
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const methodMissing = () => {
|
|
14
|
+
console.warn('ChatAdapters.configure() must be called before using any adapters')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let Clipboard: ClipboardAdapter = new ClipboardAdapter({
|
|
18
|
+
getStringAsync: async () => {
|
|
19
|
+
methodMissing()
|
|
20
|
+
return ''
|
|
21
|
+
},
|
|
22
|
+
setStringAsync: async (_: string) => methodMissing(),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export { Clipboard }
|