@planningcenter/chat-react-native 3.35.0-rc.4 → 3.35.0-rc.6
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.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +18 -15
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/messages_disabled_banners.d.ts +1 -0
- package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
- package/build/components/conversation/messages_disabled_banners.js +4 -0
- package/build/components/conversation/messages_disabled_banners.js.map +1 -1
- package/build/hooks/use_conversation.d.ts.map +1 -1
- package/build/hooks/use_conversation.js +1 -0
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +16 -12
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/group_notification_settings_screen.d.ts.map +1 -1
- package/build/screens/group_notification_settings_screen.js +6 -3
- package/build/screens/group_notification_settings_screen.js.map +1 -1
- package/build/screens/notification_settings_screen.js +2 -2
- package/build/screens/notification_settings_screen.js.map +1 -1
- package/build/screens/preferred_app_selection_screen.js +3 -3
- package/build/screens/preferred_app_selection_screen.js.map +1 -1
- package/build/types/resources/conversation.d.ts +1 -0
- package/build/types/resources/conversation.d.ts.map +1 -1
- package/build/types/resources/conversation.js.map +1 -1
- package/build/utils/can_submit_message.d.ts +10 -0
- package/build/utils/can_submit_message.d.ts.map +1 -0
- package/build/utils/can_submit_message.js +12 -0
- package/build/utils/can_submit_message.js.map +1 -0
- package/build/utils/is_input_editable.d.ts +6 -0
- package/build/utils/is_input_editable.d.ts.map +1 -0
- package/build/utils/is_input_editable.js +4 -0
- package/build/utils/is_input_editable.js.map +1 -0
- package/build/utils/request/conversation.d.ts.map +1 -1
- package/build/utils/request/conversation.js +1 -0
- package/build/utils/request/conversation.js.map +1 -1
- package/package.json +2 -2
- package/src/components/conversation/message_form.tsx +19 -9
- package/src/components/conversation/messages_disabled_banners.tsx +11 -0
- package/src/hooks/use_conversation.ts +1 -0
- package/src/screens/conversation_screen.tsx +47 -22
- package/src/screens/group_notification_settings_screen.tsx +6 -3
- package/src/screens/notification_settings_screen.tsx +2 -2
- package/src/screens/preferred_app_selection_screen.tsx +3 -3
- package/src/types/resources/conversation.ts +1 -0
- package/src/utils/__tests__/can_submit_message.test.ts +32 -0
- package/src/utils/__tests__/is_input_editable.test.ts +17 -0
- package/src/utils/can_submit_message.ts +23 -0
- package/src/utils/is_input_editable.ts +8 -0
- package/src/utils/request/conversation.ts +1 -0
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
platformPressedOpacityStyle,
|
|
39
39
|
} from '../../utils'
|
|
40
40
|
import { pickAttachmentPreviewKind } from '../../utils/attachment_kind'
|
|
41
|
+
import { canSubmitMessage } from '../../utils/can_submit_message'
|
|
42
|
+
import { isInputEditable } from '../../utils/is_input_editable'
|
|
41
43
|
import {
|
|
42
44
|
DocumentPicker,
|
|
43
45
|
DocumentPickerResult,
|
|
@@ -73,6 +75,7 @@ const MessageFormContext = React.createContext<{
|
|
|
73
75
|
setText: (text: string) => void
|
|
74
76
|
onSubmit: () => void
|
|
75
77
|
disabled: boolean
|
|
78
|
+
inputEditable: boolean
|
|
76
79
|
canGiphy: boolean
|
|
77
80
|
usingGiphy: boolean
|
|
78
81
|
setUsingGiphy: (usingGiphy: boolean) => void
|
|
@@ -86,6 +89,7 @@ const MessageFormContext = React.createContext<{
|
|
|
86
89
|
setText: (_text: string) => {},
|
|
87
90
|
onSubmit: () => {},
|
|
88
91
|
disabled: false,
|
|
92
|
+
inputEditable: true,
|
|
89
93
|
canGiphy: false,
|
|
90
94
|
usingGiphy: false,
|
|
91
95
|
setUsingGiphy: (_usingGiphy: boolean) => {},
|
|
@@ -180,15 +184,19 @@ function MessageFormRoot({
|
|
|
180
184
|
return () => clearTimeout(timeoutId)
|
|
181
185
|
}, [text, attachmentUploader.attachments, saveDraft, currentlyEditingMessage])
|
|
182
186
|
|
|
183
|
-
const canSubmit = (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
})
|
|
187
|
+
const canSubmit = canSubmitMessage({
|
|
188
|
+
isPending,
|
|
189
|
+
conversationDisabled: conversation.disabled ?? false,
|
|
190
|
+
pendingUploads: !!attachmentUploader?.pendingUploads,
|
|
191
|
+
hasFlaggedAttachments: !!attachmentUploader?.flaggedAttachmentCount,
|
|
192
|
+
hasText: text.length > 0,
|
|
193
|
+
hasSendableAttachments: !!attachmentUploader?.attachmentIds?.length,
|
|
194
|
+
})
|
|
191
195
|
const disabled = !canSubmit
|
|
196
|
+
const inputEditable = isInputEditable({
|
|
197
|
+
isPending,
|
|
198
|
+
conversationDisabled: conversation.disabled ?? false,
|
|
199
|
+
})
|
|
192
200
|
|
|
193
201
|
/*
|
|
194
202
|
Opening a FormSheet on Android while the keyboard is visible breaks the FormSheet's height & position.
|
|
@@ -252,6 +260,7 @@ function MessageFormRoot({
|
|
|
252
260
|
setText,
|
|
253
261
|
onSubmit: handleSubmit,
|
|
254
262
|
disabled,
|
|
263
|
+
inputEditable,
|
|
255
264
|
canGiphy,
|
|
256
265
|
usingGiphy,
|
|
257
266
|
setUsingGiphy,
|
|
@@ -398,7 +407,7 @@ function MessageFormInput() {
|
|
|
398
407
|
const styles = useMessageFormStyles()
|
|
399
408
|
const theme = useTheme()
|
|
400
409
|
const fontScale = useFontScale()
|
|
401
|
-
const { text, setText, onSubmit, usingGiphy, attachmentUploader, replyRootId } =
|
|
410
|
+
const { text, setText, onSubmit, inputEditable, usingGiphy, attachmentUploader, replyRootId } =
|
|
402
411
|
React.useContext(MessageFormContext)
|
|
403
412
|
const attachmentError = attachmentUploader?.errorMessage
|
|
404
413
|
|
|
@@ -445,6 +454,7 @@ function MessageFormInput() {
|
|
|
445
454
|
maxLength={usingGiphy ? GIPHY_MAX_LENGTH : MAX_MESSAGE_LENGTH}
|
|
446
455
|
onSubmitEditing={onSubmit}
|
|
447
456
|
submitBehavior={usingGiphy ? 'submit' : 'newline'}
|
|
457
|
+
editable={inputEditable}
|
|
448
458
|
/>
|
|
449
459
|
</View>
|
|
450
460
|
</View>
|
|
@@ -20,6 +20,17 @@ export const MemberMessagesDisabledBanner = () => {
|
|
|
20
20
|
)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export const ConversationDisabledBanner = () => {
|
|
24
|
+
const styles = useStyles()
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<MessagesDisabledBanner
|
|
28
|
+
description="Conversations with teens require at least 2 adults. This chat is paused until another adult is added."
|
|
29
|
+
style={styles.memberBanner}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
interface MessagesDisabledBannerProps {
|
|
24
35
|
description: string
|
|
25
36
|
style?: ViewStyle
|
|
@@ -18,6 +18,7 @@ import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_bu
|
|
|
18
18
|
import { Message } from '../components/conversation/message'
|
|
19
19
|
import { MessageForm } from '../components/conversation/message_form'
|
|
20
20
|
import {
|
|
21
|
+
ConversationDisabledBanner,
|
|
21
22
|
LeaderMessagesDisabledBanner,
|
|
22
23
|
MemberMessagesDisabledBanner,
|
|
23
24
|
} from '../components/conversation/messages_disabled_banners'
|
|
@@ -48,7 +49,9 @@ import {
|
|
|
48
49
|
} from '../hooks/use_product_analytics'
|
|
49
50
|
import { useScrollTracking } from '../hooks/use_scroll_tracking'
|
|
50
51
|
import { useTrackHighestSeenMessage } from '../hooks/use_track_highest_seen_message'
|
|
52
|
+
import { ConversationResource } from '../types/resources/conversation'
|
|
51
53
|
import { ConversationBadgeResource } from '../types/resources/conversation_badge'
|
|
54
|
+
import { MessageResource } from '../types/resources/message'
|
|
52
55
|
import { getRelativeDateStatus } from '../utils/date'
|
|
53
56
|
import {
|
|
54
57
|
groupMessages,
|
|
@@ -319,33 +322,55 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
319
322
|
/>
|
|
320
323
|
{!noMessages && <TypingIndicator />}
|
|
321
324
|
{showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// and edit message. This simplifies internal state handling.
|
|
330
|
-
key={
|
|
331
|
-
currentlyEditingMessage
|
|
332
|
-
? `edit-message-form-${currentlyEditingMessage.id}`
|
|
333
|
-
: 'new-message-form'
|
|
334
|
-
}
|
|
335
|
-
>
|
|
336
|
-
<MessageForm.AttachmentPicker />
|
|
337
|
-
<MessageForm.Commands />
|
|
338
|
-
<MessageForm.TextInput />
|
|
339
|
-
<MessageForm.SubmitButton />
|
|
340
|
-
</MessageForm.Root>
|
|
341
|
-
) : (
|
|
342
|
-
<MemberMessagesDisabledBanner />
|
|
343
|
-
)}
|
|
325
|
+
<ConversationBottomBar
|
|
326
|
+
conversation={conversation}
|
|
327
|
+
canReply={canReply}
|
|
328
|
+
replyRootAuthorFirstName={replyRootAuthorFirstName}
|
|
329
|
+
replyRootId={replyRootId}
|
|
330
|
+
currentlyEditingMessage={currentlyEditingMessage}
|
|
331
|
+
/>
|
|
344
332
|
</KeyboardView>
|
|
345
333
|
</View>
|
|
346
334
|
)
|
|
347
335
|
}
|
|
348
336
|
|
|
337
|
+
interface ConversationBottomBarProps {
|
|
338
|
+
conversation: ConversationResource
|
|
339
|
+
canReply: boolean | undefined
|
|
340
|
+
replyRootAuthorFirstName: string | undefined
|
|
341
|
+
replyRootId: string | null | undefined
|
|
342
|
+
currentlyEditingMessage: MessageResource | undefined
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function ConversationBottomBar({
|
|
346
|
+
conversation,
|
|
347
|
+
canReply,
|
|
348
|
+
replyRootAuthorFirstName,
|
|
349
|
+
replyRootId,
|
|
350
|
+
currentlyEditingMessage,
|
|
351
|
+
}: ConversationBottomBarProps) {
|
|
352
|
+
if (conversation.disabled) return <ConversationDisabledBanner />
|
|
353
|
+
if (!canReply) return <MemberMessagesDisabledBanner />
|
|
354
|
+
return (
|
|
355
|
+
<MessageForm.Root
|
|
356
|
+
replyRootAuthorFirstName={replyRootAuthorFirstName}
|
|
357
|
+
conversation={conversation}
|
|
358
|
+
replyRootId={replyRootId}
|
|
359
|
+
currentlyEditingMessage={currentlyEditingMessage}
|
|
360
|
+
key={
|
|
361
|
+
currentlyEditingMessage
|
|
362
|
+
? `edit-message-form-${currentlyEditingMessage.id}`
|
|
363
|
+
: 'new-message-form'
|
|
364
|
+
}
|
|
365
|
+
>
|
|
366
|
+
<MessageForm.AttachmentPicker />
|
|
367
|
+
<MessageForm.Commands />
|
|
368
|
+
<MessageForm.TextInput />
|
|
369
|
+
<MessageForm.SubmitButton />
|
|
370
|
+
</MessageForm.Root>
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
|
|
349
374
|
function InlineDateSeparator({ date }: DateSeparator) {
|
|
350
375
|
const styles = useDateSeparatorStyles()
|
|
351
376
|
const { isThisYear } = getRelativeDateStatus(date)
|
|
@@ -41,12 +41,14 @@ export function GroupNotificationSettingsScreen({ route }: GroupNotificationSett
|
|
|
41
41
|
<View style={styles.container}>
|
|
42
42
|
<View style={styles.sectionOuter}>
|
|
43
43
|
<View style={styles.sectionInner}>
|
|
44
|
-
<Heading variant="
|
|
44
|
+
<Heading variant="h2" style={styles.sectionHeading}>
|
|
45
45
|
Group notification settings
|
|
46
46
|
</Heading>
|
|
47
47
|
<Text variant="tertiary" style={styles.sectionSubtitle}>
|
|
48
48
|
The settings are applied to all conversations in{' '}
|
|
49
|
-
<Text style={styles.groupNameBold}>
|
|
49
|
+
<Text variant="tertiary" style={styles.groupNameBold}>
|
|
50
|
+
{group.name || title}
|
|
51
|
+
</Text>
|
|
50
52
|
</Text>
|
|
51
53
|
</View>
|
|
52
54
|
</View>
|
|
@@ -98,10 +100,11 @@ const useStyles = () => {
|
|
|
98
100
|
borderBottomColor: colors.borderColorDefaultBase,
|
|
99
101
|
},
|
|
100
102
|
sectionHeading: {
|
|
101
|
-
paddingBottom:
|
|
103
|
+
paddingBottom: 12,
|
|
102
104
|
},
|
|
103
105
|
sectionSubtitle: {
|
|
104
106
|
color: colors.textColorDefaultSecondary,
|
|
107
|
+
paddingBottom: 2,
|
|
105
108
|
},
|
|
106
109
|
groupNameBold: {
|
|
107
110
|
fontWeight: platformFontWeightBold,
|
|
@@ -90,7 +90,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
90
90
|
...chatTypes.map(type => ({
|
|
91
91
|
type: SectionTypes.link,
|
|
92
92
|
data: {
|
|
93
|
-
title: `${type.title}
|
|
93
|
+
title: `${type.title} conversations`,
|
|
94
94
|
rightLabel: type.preferredApp,
|
|
95
95
|
onPress: () =>
|
|
96
96
|
navigation.navigate('PreferredAppSelection', {
|
|
@@ -284,7 +284,7 @@ function LinkRow({ title, subtitle, rightLabel, onPress }: LinkRowProps) {
|
|
|
284
284
|
<Text style={styles.title} numberOfLines={2}>
|
|
285
285
|
{title}
|
|
286
286
|
</Text>
|
|
287
|
-
{Boolean(subtitle) && <Text variant="
|
|
287
|
+
{Boolean(subtitle) && <Text variant="tertiary">{subtitle}</Text>}
|
|
288
288
|
</View>
|
|
289
289
|
<View style={styles.rightContent}>
|
|
290
290
|
{isSourceType ? (
|
|
@@ -41,12 +41,12 @@ export function PreferredAppSelectionScreen({ route }: PreferredAppSelectionScre
|
|
|
41
41
|
updateChatType(app)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const sectionTitle = `${chatType?.title}
|
|
44
|
+
const sectionTitle = `${chatType?.title} conversations`
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
47
|
<View style={styles.container}>
|
|
48
48
|
<View style={styles.section}>
|
|
49
|
-
<Heading variant="
|
|
49
|
+
<Heading variant="h2" style={styles.sectionHeading}>
|
|
50
50
|
{sectionTitle}
|
|
51
51
|
</Heading>
|
|
52
52
|
{preferredAppOptions?.sort(sortPreferredApp('asc')).map((option, key) => (
|
|
@@ -120,7 +120,7 @@ const styles = StyleSheet.create({
|
|
|
120
120
|
flex: 1,
|
|
121
121
|
},
|
|
122
122
|
section: {
|
|
123
|
-
paddingTop:
|
|
123
|
+
paddingTop: 24,
|
|
124
124
|
},
|
|
125
125
|
sectionHeading: {
|
|
126
126
|
paddingHorizontal: 16,
|
|
@@ -12,6 +12,7 @@ export interface ConversationResource {
|
|
|
12
12
|
conversationMembership?: Partial<ConversationMembershipResource>
|
|
13
13
|
createdAt: string
|
|
14
14
|
deleted?: boolean
|
|
15
|
+
disabled?: boolean
|
|
15
16
|
genderOption?: string | null
|
|
16
17
|
groups?: GroupResource[]
|
|
17
18
|
previewAvatarUrls?: string[]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { canSubmitMessage } from '../can_submit_message'
|
|
2
|
+
|
|
3
|
+
const base = {
|
|
4
|
+
isPending: false,
|
|
5
|
+
conversationDisabled: false,
|
|
6
|
+
pendingUploads: false,
|
|
7
|
+
hasFlaggedAttachments: false,
|
|
8
|
+
hasText: false,
|
|
9
|
+
hasSendableAttachments: false,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('canSubmitMessage', () => {
|
|
13
|
+
it.each([
|
|
14
|
+
['empty form', base, false],
|
|
15
|
+
['has text', { ...base, hasText: true }, true],
|
|
16
|
+
['has attachments', { ...base, hasSendableAttachments: true }, true],
|
|
17
|
+
['pending request blocks submit', { ...base, hasText: true, isPending: true }, false],
|
|
18
|
+
[
|
|
19
|
+
'conversation disabled blocks submit',
|
|
20
|
+
{ ...base, hasText: true, conversationDisabled: true },
|
|
21
|
+
false,
|
|
22
|
+
],
|
|
23
|
+
['pending uploads block submit', { ...base, hasText: true, pendingUploads: true }, false],
|
|
24
|
+
[
|
|
25
|
+
'flagged attachments block submit',
|
|
26
|
+
{ ...base, hasText: true, hasFlaggedAttachments: true },
|
|
27
|
+
false,
|
|
28
|
+
],
|
|
29
|
+
])('%s', (_label, args, expected) => {
|
|
30
|
+
expect(canSubmitMessage(args)).toBe(expected)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isInputEditable } from '../is_input_editable'
|
|
2
|
+
|
|
3
|
+
const base = {
|
|
4
|
+
isPending: false,
|
|
5
|
+
conversationDisabled: false,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('isInputEditable', () => {
|
|
9
|
+
it.each([
|
|
10
|
+
['active conversation with no text', base, true],
|
|
11
|
+
['active conversation with text', base, true],
|
|
12
|
+
['pending request locks input', { ...base, isPending: true }, false],
|
|
13
|
+
['disabled conversation locks input', { ...base, conversationDisabled: true }, false],
|
|
14
|
+
])('%s', (_label, args, expected) => {
|
|
15
|
+
expect(isInputEditable(args)).toBe(expected)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CanSubmitMessageArgs {
|
|
2
|
+
isPending: boolean
|
|
3
|
+
conversationDisabled: boolean
|
|
4
|
+
pendingUploads: boolean
|
|
5
|
+
hasFlaggedAttachments: boolean
|
|
6
|
+
hasText: boolean
|
|
7
|
+
hasSendableAttachments: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function canSubmitMessage({
|
|
11
|
+
isPending,
|
|
12
|
+
conversationDisabled,
|
|
13
|
+
pendingUploads,
|
|
14
|
+
hasFlaggedAttachments,
|
|
15
|
+
hasText,
|
|
16
|
+
hasSendableAttachments,
|
|
17
|
+
}: CanSubmitMessageArgs): boolean {
|
|
18
|
+
if (isPending) return false
|
|
19
|
+
if (conversationDisabled) return false
|
|
20
|
+
if (pendingUploads) return false
|
|
21
|
+
if (hasFlaggedAttachments) return false
|
|
22
|
+
return hasText || hasSendableAttachments
|
|
23
|
+
}
|