@planningcenter/chat-react-native 3.16.0-rc.1 → 3.16.0-rc.3
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/attachments/giphy_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/giphy_attachment.js +1 -11
- package/build/components/conversation/attachments/giphy_attachment.js.map +1 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +5 -21
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/shadow_message.d.ts +7 -0
- package/build/components/conversation/shadow_message.d.ts.map +1 -0
- package/build/components/conversation/shadow_message.js +156 -0
- package/build/components/conversation/shadow_message.js.map +1 -0
- package/build/components/primitive/avatar_primitive.d.ts +1 -0
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +4 -0
- package/build/components/primitive/avatar_primitive.js.map +1 -1
- package/build/hooks/index.d.ts +1 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +1 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_animated_message_background_color.d.ts +8 -0
- package/build/hooks/use_animated_message_background_color.d.ts.map +1 -0
- package/build/hooks/use_animated_message_background_color.js +29 -0
- package/build/hooks/use_animated_message_background_color.js.map +1 -0
- package/build/hooks/use_app_name.js +1 -1
- package/build/hooks/use_app_name.js.map +1 -1
- package/build/navigation/index.d.ts +2 -1
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +2 -1
- package/build/navigation/index.js.map +1 -1
- package/build/screens/bug_report_screen.d.ts.map +1 -1
- package/build/screens/bug_report_screen.js +12 -5
- package/build/screens/bug_report_screen.js.map +1 -1
- package/build/screens/get_help_screen.d.ts +2 -0
- package/build/screens/get_help_screen.d.ts.map +1 -1
- package/build/screens/get_help_screen.js +34 -4
- package/build/screens/get_help_screen.js.map +1 -1
- package/build/utils/assert_keys_are_numbers.d.ts +2 -0
- package/build/utils/assert_keys_are_numbers.d.ts.map +1 -0
- package/build/utils/assert_keys_are_numbers.js +12 -0
- package/build/utils/assert_keys_are_numbers.js.map +1 -0
- 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/package.json +2 -2
- package/src/components/conversation/attachments/giphy_attachment.tsx +1 -14
- package/src/components/conversation/message.tsx +11 -35
- package/src/components/conversation/shadow_message.tsx +266 -0
- package/src/components/primitive/avatar_primitive.tsx +4 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use_animated_message_background_color.ts +44 -0
- package/src/hooks/use_app_name.ts +1 -1
- package/src/navigation/index.tsx +2 -1
- package/src/screens/bug_report_screen.tsx +12 -5
- package/src/screens/get_help_screen.tsx +36 -4
- package/src/utils/assert_keys_are_numbers.ts +13 -0
- package/src/utils/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.16.0-rc.
|
|
3
|
+
"version": "3.16.0-rc.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"react-native-url-polyfill": "^2.0.0",
|
|
56
56
|
"typescript": "<5.6.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "41458eebf252d441367b1c30a6b35e5422486bf2"
|
|
59
59
|
}
|
|
@@ -5,6 +5,7 @@ import { useTheme } from '../../../hooks'
|
|
|
5
5
|
import { DenormalizedGiphyAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
6
6
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
7
7
|
import { Image } from '../../display'
|
|
8
|
+
import { assertKeysAreNumbers } from '../../../utils'
|
|
8
9
|
|
|
9
10
|
export function GiphyAttachment({
|
|
10
11
|
attachment,
|
|
@@ -46,20 +47,6 @@ export function GiphyAttachment({
|
|
|
46
47
|
)
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
const assertKeysAreNumbers = (obj: Object): Record<any, number> => {
|
|
50
|
-
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const assertNumber = (value: string): number => {
|
|
54
|
-
if (typeof value === 'number') return value
|
|
55
|
-
|
|
56
|
-
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
57
|
-
return Number(value)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return 0
|
|
61
|
-
}
|
|
62
|
-
|
|
63
50
|
const useStyles = ({ imageWidth, imageHeight }: { imageWidth: number; imageHeight: number }) => {
|
|
64
51
|
const { colors } = useTheme()
|
|
65
52
|
return StyleSheet.create({
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
2
|
import React, { useEffect } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
4
4
|
import { MessageReaction } from '../../components/conversation/message_reaction'
|
|
5
5
|
import { Avatar, Icon, Spinner, Text, TextInlineButton } from '../../components/display'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
useAnimatedMessageBackgroundColor,
|
|
8
|
+
useInteractionGhostBackgroundColor,
|
|
9
|
+
useTheme,
|
|
10
|
+
} from '../../hooks'
|
|
7
11
|
import { MessageResource } from '../../types'
|
|
8
12
|
import { ReactionCountResource } from '../../types/resources/reaction'
|
|
9
13
|
import { MessageAttachments } from './message_attachments'
|
|
@@ -15,13 +19,7 @@ import {
|
|
|
15
19
|
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
16
20
|
platformFontWeightMedium,
|
|
17
21
|
} from '../../utils/styles'
|
|
18
|
-
import Animated
|
|
19
|
-
useSharedValue,
|
|
20
|
-
useAnimatedStyle,
|
|
21
|
-
withTiming,
|
|
22
|
-
interpolateColor,
|
|
23
|
-
Easing,
|
|
24
|
-
} from 'react-native-reanimated'
|
|
22
|
+
import Animated from 'react-native-reanimated'
|
|
25
23
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
26
24
|
import { MessageReadReceipts } from './message_read_receipts'
|
|
27
25
|
import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
@@ -56,6 +54,8 @@ export function Message({
|
|
|
56
54
|
const isPersisted = !isNewMessage(message)
|
|
57
55
|
const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)
|
|
58
56
|
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
57
|
+
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
58
|
+
useAnimatedMessageBackgroundColor()
|
|
59
59
|
|
|
60
60
|
useEffect(() => {
|
|
61
61
|
if (pending) {
|
|
@@ -81,30 +81,6 @@ export function Message({
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const bgFadeProgress = useSharedValue(0)
|
|
85
|
-
const pressedColor = Platform.select({
|
|
86
|
-
ios: colors.fillColorNeutral050Base,
|
|
87
|
-
default: 'transparent',
|
|
88
|
-
})
|
|
89
|
-
const animatedBackgroundColor = useAnimatedStyle(() => {
|
|
90
|
-
const backgroundColor = interpolateColor(
|
|
91
|
-
bgFadeProgress.value,
|
|
92
|
-
[0, 1],
|
|
93
|
-
['transparent', pressedColor]
|
|
94
|
-
)
|
|
95
|
-
return {
|
|
96
|
-
backgroundColor,
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const handlePressIn = () => {
|
|
101
|
-
bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const handlePressOut = () => {
|
|
105
|
-
bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
106
|
-
}
|
|
107
|
-
|
|
108
84
|
const handleMessageLongPress = () => {
|
|
109
85
|
if (!isPersisted) return
|
|
110
86
|
|
|
@@ -149,8 +125,8 @@ export function Message({
|
|
|
149
125
|
<Pressable
|
|
150
126
|
onLongPress={handleMessageLongPress}
|
|
151
127
|
onPress={() => setShowMessageMetaToggle(!showMessageMetaToggle)}
|
|
152
|
-
onPressIn={
|
|
153
|
-
onPressOut={
|
|
128
|
+
onPressIn={handleMessagePressIn}
|
|
129
|
+
onPressOut={handleMessagePressOut}
|
|
154
130
|
android_ripple={{ color: colors.androidRippleNeutral }}
|
|
155
131
|
accessibilityHint="Long press to view message actions like reacting and copying."
|
|
156
132
|
>
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
3
|
+
import { Avatar, Icon, IconProps, Image, Text } from '../display'
|
|
4
|
+
import {
|
|
5
|
+
useAnimatedMessageBackgroundColor,
|
|
6
|
+
useFontScale,
|
|
7
|
+
useScalableNumberOfLines,
|
|
8
|
+
useTheme,
|
|
9
|
+
} from '../../hooks'
|
|
10
|
+
import { MessageResource } from '../../types'
|
|
11
|
+
import {
|
|
12
|
+
DenormalizedAttachmentResource,
|
|
13
|
+
DenormalizedGiphyAttachmentResource,
|
|
14
|
+
DenormalizedExpandedLinkAttachmentResource,
|
|
15
|
+
DenormalizedMessageAttachmentResource,
|
|
16
|
+
} from '../../types/resources/denormalized_attachment_resource'
|
|
17
|
+
import {
|
|
18
|
+
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
19
|
+
MAX_FONT_SIZE_MULTIPLIER,
|
|
20
|
+
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
21
|
+
platformFontWeightMedium,
|
|
22
|
+
} from '../../utils/styles'
|
|
23
|
+
import Animated from 'react-native-reanimated'
|
|
24
|
+
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
25
|
+
import { assertKeysAreNumbers } from '../../utils'
|
|
26
|
+
|
|
27
|
+
interface ShadowMessageProps extends MessageResource {}
|
|
28
|
+
|
|
29
|
+
export function ShadowMessage({ ...message }: ShadowMessageProps) {
|
|
30
|
+
const { text } = message
|
|
31
|
+
const styles = useStyles(message)
|
|
32
|
+
const { colors } = useTheme()
|
|
33
|
+
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
34
|
+
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
35
|
+
useAnimatedMessageBackgroundColor()
|
|
36
|
+
const scalableNumberOfLines = useScalableNumberOfLines(2)
|
|
37
|
+
|
|
38
|
+
const replyCount = 0 // TODO: Get reply count from message object
|
|
39
|
+
|
|
40
|
+
const handleNavigateToReplies = () => {
|
|
41
|
+
console.log('Navigate to replies') // TODO: Implement navigate to a reply screen
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Pressable
|
|
46
|
+
android_ripple={{ color: colors.androidRippleNeutral }}
|
|
47
|
+
onPress={handleNavigateToReplies}
|
|
48
|
+
onPressIn={handleMessagePressIn}
|
|
49
|
+
onPressOut={handleMessagePressOut}
|
|
50
|
+
accessibilityHint="Navigate to all replies for this message."
|
|
51
|
+
>
|
|
52
|
+
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
53
|
+
{!message.mine && (
|
|
54
|
+
<View>
|
|
55
|
+
<View style={styles.avatarWrapper}>
|
|
56
|
+
<Avatar
|
|
57
|
+
size="xs"
|
|
58
|
+
sourceUri={message.author.avatar}
|
|
59
|
+
style={styles.avatar}
|
|
60
|
+
maxFontSizeMultiplier={1}
|
|
61
|
+
/>
|
|
62
|
+
</View>
|
|
63
|
+
<TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
64
|
+
</View>
|
|
65
|
+
)}
|
|
66
|
+
<View style={styles.messageContent}>
|
|
67
|
+
<View
|
|
68
|
+
style={styles.messageBubble}
|
|
69
|
+
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
70
|
+
>
|
|
71
|
+
<MessageAttachmentImagery attachments={message.attachments} />
|
|
72
|
+
{text && (
|
|
73
|
+
<Text
|
|
74
|
+
variant="footnote"
|
|
75
|
+
style={styles.messageText}
|
|
76
|
+
numberOfLines={scalableNumberOfLines}
|
|
77
|
+
>
|
|
78
|
+
{text}
|
|
79
|
+
</Text>
|
|
80
|
+
)}
|
|
81
|
+
</View>
|
|
82
|
+
<View style={styles.messageMeta}>
|
|
83
|
+
<Text variant="footnote" style={styles.replyCountText}>
|
|
84
|
+
{replyCount} replies
|
|
85
|
+
</Text>
|
|
86
|
+
</View>
|
|
87
|
+
</View>
|
|
88
|
+
{message.mine && (
|
|
89
|
+
<MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
90
|
+
)}
|
|
91
|
+
</Animated.View>
|
|
92
|
+
</Pressable>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function MessageAttachmentImagery({
|
|
97
|
+
attachments,
|
|
98
|
+
}: {
|
|
99
|
+
attachments: DenormalizedAttachmentResource[]
|
|
100
|
+
}) {
|
|
101
|
+
if (!attachments || attachments.length === 0) return null
|
|
102
|
+
|
|
103
|
+
const attachment = attachments[0]
|
|
104
|
+
|
|
105
|
+
if (attachment.type === 'giphy') {
|
|
106
|
+
return <GiphyImage attachment={attachment} />
|
|
107
|
+
}
|
|
108
|
+
if (attachment.type === 'ExpandedLink') {
|
|
109
|
+
return <ExpandedLinkImage attachment={attachment} />
|
|
110
|
+
}
|
|
111
|
+
if (attachment.type === 'MessageAttachment') {
|
|
112
|
+
const contentType = attachment.attributes?.contentType
|
|
113
|
+
const basicType = contentType?.split('/')[0]
|
|
114
|
+
|
|
115
|
+
switch (basicType) {
|
|
116
|
+
case 'image':
|
|
117
|
+
return <MessageAttachmentImage attachment={attachment} />
|
|
118
|
+
case 'video':
|
|
119
|
+
return <MessageAttachmentIcon iconName="general.outlinedVideoFile" />
|
|
120
|
+
case 'audio':
|
|
121
|
+
return <MessageAttachmentIcon iconName="general.outlinedMusicFile" />
|
|
122
|
+
case 'application':
|
|
123
|
+
return <MessageAttachmentIcon iconName="general.outlinedGenericFile" />
|
|
124
|
+
default:
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function GiphyImage({ attachment }: { attachment: DenormalizedGiphyAttachmentResource }) {
|
|
133
|
+
const { title, giphy } = attachment
|
|
134
|
+
const { url } = giphy.fixedWidth
|
|
135
|
+
const { width, height } = assertKeysAreNumbers(giphy.fixedWidth)
|
|
136
|
+
const styles = useStyles({ imageWidth: width, imageHeight: height })
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<Image
|
|
140
|
+
source={{ uri: url }}
|
|
141
|
+
wrapperStyle={styles.imageWrapper}
|
|
142
|
+
style={styles.image}
|
|
143
|
+
alt={title}
|
|
144
|
+
loaderSize={16}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function ExpandedLinkImage({
|
|
150
|
+
attachment,
|
|
151
|
+
}: {
|
|
152
|
+
attachment: DenormalizedExpandedLinkAttachmentResource
|
|
153
|
+
}) {
|
|
154
|
+
const { title = '', imageUrl, imageHeight, imageWidth } = attachment.attributes
|
|
155
|
+
const styles = useStyles({ imageWidth, imageHeight })
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Image
|
|
159
|
+
source={{ uri: imageUrl }}
|
|
160
|
+
wrapperStyle={styles.imageWrapper}
|
|
161
|
+
style={styles.image}
|
|
162
|
+
alt={title}
|
|
163
|
+
loaderSize={16}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function MessageAttachmentImage({
|
|
169
|
+
attachment,
|
|
170
|
+
}: {
|
|
171
|
+
attachment: DenormalizedMessageAttachmentResource
|
|
172
|
+
}) {
|
|
173
|
+
const { url, urlMedium, filename, metadata = {} } = attachment.attributes
|
|
174
|
+
const styles = useStyles({ imageWidth: metadata.width, imageHeight: metadata.height })
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<Image
|
|
178
|
+
source={{ uri: urlMedium || url }}
|
|
179
|
+
style={styles.image}
|
|
180
|
+
wrapperStyle={styles.imageWrapper}
|
|
181
|
+
alt={filename}
|
|
182
|
+
loaderSize={16}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function MessageAttachmentIcon({ iconName }: { iconName: IconProps['name'] }) {
|
|
188
|
+
const styles = useStyles()
|
|
189
|
+
return (
|
|
190
|
+
<Icon
|
|
191
|
+
name={iconName}
|
|
192
|
+
style={styles.attachmentIcon}
|
|
193
|
+
maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface StylesProps {
|
|
199
|
+
imageWidth?: number
|
|
200
|
+
imageHeight?: number
|
|
201
|
+
mine?: boolean
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}) => {
|
|
205
|
+
const { colors } = useTheme()
|
|
206
|
+
const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })
|
|
207
|
+
const { width } = useWindowDimensions()
|
|
208
|
+
const tabletWidth = width >= 744 // Smallest iPad Mini's width
|
|
209
|
+
|
|
210
|
+
return StyleSheet.create({
|
|
211
|
+
message: {
|
|
212
|
+
gap: 8,
|
|
213
|
+
flexDirection: mine ? 'row-reverse' : 'row',
|
|
214
|
+
paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
215
|
+
},
|
|
216
|
+
messageContent: {
|
|
217
|
+
flex: 1,
|
|
218
|
+
gap: 4,
|
|
219
|
+
marginBottom: 12,
|
|
220
|
+
},
|
|
221
|
+
avatarWrapper: {
|
|
222
|
+
width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
},
|
|
225
|
+
avatar: {
|
|
226
|
+
marginBottom: 8,
|
|
227
|
+
opacity: 0.5,
|
|
228
|
+
},
|
|
229
|
+
messageBubble: {
|
|
230
|
+
flexDirection: 'row',
|
|
231
|
+
alignSelf: mine ? 'flex-end' : 'flex-start',
|
|
232
|
+
alignItems: 'center',
|
|
233
|
+
gap: 8,
|
|
234
|
+
borderColor: colors.borderColorDefaultBase,
|
|
235
|
+
borderWidth: 1,
|
|
236
|
+
borderRadius: 8,
|
|
237
|
+
maxWidth: tabletWidth ? 360 : '80%',
|
|
238
|
+
paddingVertical: 6,
|
|
239
|
+
paddingHorizontal: 8,
|
|
240
|
+
},
|
|
241
|
+
messageText: {
|
|
242
|
+
color: colors.textColorDefaultPlaceholder,
|
|
243
|
+
flexShrink: 1,
|
|
244
|
+
},
|
|
245
|
+
messageMeta: {
|
|
246
|
+
flexDirection: 'row',
|
|
247
|
+
justifyContent: mine ? 'flex-end' : 'flex-start',
|
|
248
|
+
},
|
|
249
|
+
replyCountText: {
|
|
250
|
+
color: colors.interaction,
|
|
251
|
+
fontWeight: platformFontWeightMedium,
|
|
252
|
+
},
|
|
253
|
+
imageWrapper: {
|
|
254
|
+
width: 32 * fontScale,
|
|
255
|
+
aspectRatio: imageWidth / imageHeight,
|
|
256
|
+
opacity: 0.5,
|
|
257
|
+
},
|
|
258
|
+
image: {
|
|
259
|
+
borderRadius: 4,
|
|
260
|
+
},
|
|
261
|
+
attachmentIcon: {
|
|
262
|
+
color: colors.iconColorDefaultDim,
|
|
263
|
+
fontSize: 16,
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
}
|
|
@@ -45,6 +45,7 @@ export type {
|
|
|
45
45
|
// =================================
|
|
46
46
|
|
|
47
47
|
const AVATAR_SIZES = {
|
|
48
|
+
xs: 'xs',
|
|
48
49
|
sm: 'sm',
|
|
49
50
|
md: 'md',
|
|
50
51
|
lg: 'lg',
|
|
@@ -60,18 +61,21 @@ type AvatarSize = (typeof AVATAR_SIZES)[keyof typeof AVATAR_SIZES]
|
|
|
60
61
|
type AvatarPresenceType = (typeof AVATAR_PRESENCE_TYPES)[keyof typeof AVATAR_PRESENCE_TYPES]
|
|
61
62
|
|
|
62
63
|
const AVATAR_PX: Record<AvatarSize, number> = {
|
|
64
|
+
[AVATAR_SIZES.xs]: 20,
|
|
63
65
|
[AVATAR_SIZES.sm]: 24,
|
|
64
66
|
[AVATAR_SIZES.md]: 32,
|
|
65
67
|
[AVATAR_SIZES.lg]: 40,
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
const AVATAR_PRESENCE_PX: Record<AvatarSize, number> = {
|
|
71
|
+
[AVATAR_SIZES.xs]: 8,
|
|
69
72
|
[AVATAR_SIZES.sm]: 10,
|
|
70
73
|
[AVATAR_SIZES.md]: 12,
|
|
71
74
|
[AVATAR_SIZES.lg]: 14,
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const AVATAR_FALLBACK_ICON_PX: Record<AvatarSize, number> = {
|
|
78
|
+
[AVATAR_SIZES.xs]: 10,
|
|
75
79
|
[AVATAR_SIZES.sm]: 12,
|
|
76
80
|
[AVATAR_SIZES.md]: 16,
|
|
77
81
|
[AVATAR_SIZES.lg]: 20,
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Platform } from 'react-native'
|
|
2
|
+
import {
|
|
3
|
+
useSharedValue,
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
withTiming,
|
|
6
|
+
interpolateColor,
|
|
7
|
+
Easing,
|
|
8
|
+
} from 'react-native-reanimated'
|
|
9
|
+
import { useTheme } from '../hooks'
|
|
10
|
+
|
|
11
|
+
export function useAnimatedMessageBackgroundColor() {
|
|
12
|
+
const { colors } = useTheme()
|
|
13
|
+
const bgFadeProgress = useSharedValue(0)
|
|
14
|
+
|
|
15
|
+
const pressedColor = Platform.select({
|
|
16
|
+
ios: colors.fillColorNeutral050Base,
|
|
17
|
+
default: 'transparent',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const animatedBackgroundColor = useAnimatedStyle(() => {
|
|
21
|
+
const backgroundColor = interpolateColor(
|
|
22
|
+
bgFadeProgress.value,
|
|
23
|
+
[0, 1],
|
|
24
|
+
['transparent', pressedColor]
|
|
25
|
+
)
|
|
26
|
+
return {
|
|
27
|
+
backgroundColor,
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const handleMessagePressIn = () => {
|
|
32
|
+
bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleMessagePressOut = () => {
|
|
36
|
+
bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
animatedBackgroundColor,
|
|
41
|
+
handleMessagePressIn,
|
|
42
|
+
handleMessagePressOut,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -5,7 +5,7 @@ export type AppName = 'chat' | 'churchcenter' | 'services'
|
|
|
5
5
|
export const useAppName = (): AppName => {
|
|
6
6
|
const applicationName = DeviceInfo.getApplicationName()
|
|
7
7
|
|
|
8
|
-
if (/
|
|
8
|
+
if (/church\s?center/i.test(applicationName)) {
|
|
9
9
|
return 'churchcenter'
|
|
10
10
|
}
|
|
11
11
|
|
package/src/navigation/index.tsx
CHANGED
|
@@ -238,8 +238,9 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
238
238
|
screen: GetHelpScreen,
|
|
239
239
|
options: ({ navigation }) => ({
|
|
240
240
|
headerTitle: 'Get help',
|
|
241
|
+
headerBackVisible: false,
|
|
241
242
|
presentation: 'modal',
|
|
242
|
-
|
|
243
|
+
headerRight: props => (
|
|
243
244
|
<HeaderTextButton {...props} onPress={navigation.goBack} title="Close" />
|
|
244
245
|
),
|
|
245
246
|
}),
|
|
@@ -73,6 +73,12 @@ enum QualifiedMobileAppName {
|
|
|
73
73
|
services = 'Services Mobile',
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
enum DisplayableAppName {
|
|
77
|
+
chat = 'Chat',
|
|
78
|
+
churchcenter = 'Church Center',
|
|
79
|
+
services = 'Services',
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
export function BugReportScreen() {
|
|
77
83
|
const styles = useStyles()
|
|
78
84
|
const navigation = useNavigation()
|
|
@@ -174,8 +180,8 @@ export function BugReportScreen() {
|
|
|
174
180
|
<BlankState.Content>
|
|
175
181
|
<BlankState.Heading style={styles.successTitle}>Thank you!</BlankState.Heading>
|
|
176
182
|
<BlankState.Text style={styles.successSubtitle}>
|
|
177
|
-
We appreciate you taking the time to help improve
|
|
178
|
-
you reported.
|
|
183
|
+
We appreciate you taking the time to help improve {DisplayableAppName[name]}! We'll take
|
|
184
|
+
a look at the issue you reported.
|
|
179
185
|
</BlankState.Text>
|
|
180
186
|
</BlankState.Content>
|
|
181
187
|
<BlankState.Button
|
|
@@ -213,8 +219,9 @@ export function BugReportScreen() {
|
|
|
213
219
|
<KeyboardView>
|
|
214
220
|
<ScrollView contentContainerStyle={styles.container}>
|
|
215
221
|
<Text style={styles.description}>
|
|
216
|
-
Thanks for helping us improve
|
|
217
|
-
|
|
222
|
+
Thanks for helping us improve {DisplayableAppName[name]}. We won't be able to respond to
|
|
223
|
+
your submission, but your feedback helps us improve the experience helps us improve the
|
|
224
|
+
experience.
|
|
218
225
|
</Text>
|
|
219
226
|
|
|
220
227
|
<View style={styles.textInputContainer}>
|
|
@@ -373,7 +380,7 @@ export function BugReportScreen() {
|
|
|
373
380
|
}
|
|
374
381
|
|
|
375
382
|
const VIDEO_RECORDING_HELP_URL = Platform.select({
|
|
376
|
-
android: 'https://support.google.com/android/answer/
|
|
383
|
+
android: 'https://support.google.com/android/answer/9075928?hl=en',
|
|
377
384
|
default: 'https://support.apple.com/en-us/HT208721',
|
|
378
385
|
})
|
|
379
386
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
2
|
import { useCallback, useMemo } from 'react'
|
|
3
|
-
import { Linking, StyleSheet, View } from 'react-native'
|
|
3
|
+
import { Alert, Linking, StyleSheet, View } from 'react-native'
|
|
4
4
|
import { Heading, PressableRow, Text, TextInlineButton } from '../components'
|
|
5
5
|
import { useApiGet, useTheme } from '../hooks'
|
|
6
6
|
import { useAppName } from '../hooks/use_app_name'
|
|
7
7
|
import { ResourceObject } from '../types'
|
|
8
|
+
import { Clipboard } from '../utils'
|
|
8
9
|
|
|
9
10
|
type GetHelpScreenRouteProps = {
|
|
10
11
|
type?: 'chat' | 'general'
|
|
@@ -121,13 +122,18 @@ export const GetHelpScreen = ({ route }: GetHelpScreenProps) => {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
const ContactRow = ({ email, phone }: { email?: string; phone?: string }) => {
|
|
124
|
-
if (!email && !phone) return null
|
|
125
|
-
|
|
126
125
|
const contact = email || phone
|
|
126
|
+
if (!contact) return null
|
|
127
127
|
|
|
128
128
|
return (
|
|
129
129
|
<PressableRow
|
|
130
|
-
onPress={() => {
|
|
130
|
+
onPress={() => {
|
|
131
|
+
if (email) {
|
|
132
|
+
sendEmail(contact)
|
|
133
|
+
} else {
|
|
134
|
+
placePhoneCall(contact)
|
|
135
|
+
}
|
|
136
|
+
}}
|
|
131
137
|
text={contact || ''}
|
|
132
138
|
isActive={true}
|
|
133
139
|
iconPath={email ? 'services.email' : 'general.phone'}
|
|
@@ -135,6 +141,32 @@ const ContactRow = ({ email, phone }: { email?: string; phone?: string }) => {
|
|
|
135
141
|
)
|
|
136
142
|
}
|
|
137
143
|
|
|
144
|
+
export function sendEmail(email: string) {
|
|
145
|
+
if (!email) return
|
|
146
|
+
|
|
147
|
+
const url = encodeURI(`mailto:${email}`)
|
|
148
|
+
|
|
149
|
+
Linking.openURL(url).catch(() => {
|
|
150
|
+
Alert.alert('Oops', `Unable to open: ${email}`, [
|
|
151
|
+
{ text: 'Copy to clipboard', onPress: () => Clipboard.setStringAsync(email) },
|
|
152
|
+
{ text: 'OK' },
|
|
153
|
+
])
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function placePhoneCall(phoneNumber: string) {
|
|
158
|
+
if (!phoneNumber) return
|
|
159
|
+
|
|
160
|
+
const url = encodeURI(`tel:${phoneNumber}`)
|
|
161
|
+
|
|
162
|
+
Linking.openURL(url).catch(() => {
|
|
163
|
+
Alert.alert('Oops', `Unable to open: ${phoneNumber}`, [
|
|
164
|
+
{ text: 'Copy to clipboard', onPress: () => Clipboard.setStringAsync(phoneNumber) },
|
|
165
|
+
{ text: 'OK' },
|
|
166
|
+
])
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
138
170
|
const useStyles = () => {
|
|
139
171
|
const { colors } = useTheme()
|
|
140
172
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const assertKeysAreNumbers = (obj: Object): Record<any, number> => {
|
|
2
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const assertNumber = (value: string): number => {
|
|
6
|
+
if (typeof value === 'number') return value
|
|
7
|
+
|
|
8
|
+
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
9
|
+
return Number(value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return 0
|
|
13
|
+
}
|
package/src/utils/index.ts
CHANGED