@planningcenter/chat-react-native 4.4.1-qa-staging.30 → 4.4.1-qa-staging.32
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_list.d.ts.map +1 -1
- package/build/components/conversation/message_list.js +155 -123
- package/build/components/conversation/message_list.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +1 -0
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/components/display/keyboard_scroll_view.d.ts.map +1 -1
- package/build/components/display/keyboard_scroll_view.js +1 -0
- package/build/components/display/keyboard_scroll_view.js.map +1 -1
- package/build/components/page/page_error_boundary.d.ts +9 -1
- package/build/components/page/page_error_boundary.d.ts.map +1 -1
- package/build/components/page/page_error_boundary.js +56 -31
- package/build/components/page/page_error_boundary.js.map +1 -1
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +11 -3
- package/build/navigation/index.js.map +1 -1
- package/build/navigation/page_error_fallbacks.d.ts +7 -0
- package/build/navigation/page_error_fallbacks.d.ts.map +1 -0
- package/build/navigation/page_error_fallbacks.js +7 -0
- package/build/navigation/page_error_fallbacks.js.map +1 -0
- package/build/navigation/screenLayout.d.ts.map +1 -1
- package/build/navigation/screenLayout.js +10 -6
- package/build/navigation/screenLayout.js.map +1 -1
- package/build/screens/age_check/age_check_required_screen.d.ts.map +1 -1
- package/build/screens/age_check/age_check_required_screen.js +1 -0
- package/build/screens/age_check/age_check_required_screen.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +1 -0
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js +1 -0
- package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js +332 -291
- package/build/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js +1 -0
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +1 -0
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
- package/build/screens/get_help_screen.js +56 -62
- package/build/screens/get_help_screen.js.map +1 -1
- package/build/screens/group_notification_settings_screen.js +3 -2
- package/build/screens/group_notification_settings_screen.js.map +1 -1
- package/build/screens/notification_settings_screen.d.ts.map +1 -1
- package/build/screens/notification_settings_screen.js +1 -0
- package/build/screens/notification_settings_screen.js.map +1 -1
- package/build/screens/preferred_app_selection_screen.js +45 -39
- package/build/screens/preferred_app_selection_screen.js.map +1 -1
- package/build/screens/search/components/search_results_list.d.ts +4 -1
- package/build/screens/search/components/search_results_list.d.ts.map +1 -1
- package/build/screens/search/components/search_results_list.js +136 -110
- package/build/screens/search/components/search_results_list.js.map +1 -1
- package/build/screens/search/search_screen.d.ts.map +1 -1
- package/build/screens/search/search_screen.js +21 -28
- package/build/screens/search/search_screen.js.map +1 -1
- package/build/screens/settings_screen.d.ts.map +1 -1
- package/build/screens/settings_screen.js +7 -6
- package/build/screens/settings_screen.js.map +1 -1
- package/package.json +3 -3
- package/src/components/conversation/__tests__/message_list.test.tsx +4 -0
- package/src/components/conversation/message_list.tsx +6 -0
- package/src/components/conversations/conversations.tsx +1 -0
- package/src/components/display/keyboard_scroll_view.tsx +1 -0
- package/src/components/page/__tests__/page_error_boundary.test.tsx +33 -0
- package/src/components/page/page_error_boundary.tsx +43 -12
- package/src/navigation/index.tsx +11 -2
- package/src/navigation/page_error_fallbacks.ts +14 -0
- package/src/navigation/screenLayout.tsx +3 -1
- package/src/screens/age_check/age_check_required_screen.tsx +5 -1
- package/src/screens/conversation_details_screen.tsx +1 -0
- package/src/screens/conversation_select_recipients/__tests__/conversation_select_direct_message_recipients_screen.test.tsx +100 -0
- package/src/screens/conversation_select_recipients/conversation_new_entry_screen.tsx +4 -1
- package/src/screens/conversation_select_recipients/conversation_select_direct_message_recipients_screen.tsx +78 -68
- package/src/screens/conversation_select_recipients/conversation_select_group_recipients_screen.tsx +1 -0
- package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +1 -0
- package/src/screens/get_help_screen.tsx +4 -4
- package/src/screens/group_notification_settings_screen.tsx +3 -3
- package/src/screens/notification_settings_screen.tsx +1 -0
- package/src/screens/preferred_app_selection_screen.tsx +4 -4
- package/src/screens/search/__tests__/search_screen.test.tsx +5 -7
- package/src/screens/search/components/__tests__/search_results_list.test.tsx +6 -2
- package/src/screens/search/components/search_results_list.tsx +34 -13
- package/src/screens/search/search_screen.tsx +8 -18
- package/src/screens/settings_screen.tsx +25 -25
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
2
|
import { StackActions, useNavigation } from '@react-navigation/native'
|
|
3
|
-
import { RefObject, useRef, useState } from 'react'
|
|
3
|
+
import { ReactElement, RefObject, useRef, useState } from 'react'
|
|
4
4
|
import { FlatList, Pressable, StyleSheet, TextInput, View } from 'react-native'
|
|
5
5
|
import { Avatar, Badge, Banner, Icon, KeyboardView, Spinner, Text } from '../../components'
|
|
6
6
|
import { ActionButton } from '../../components/display/action_button'
|
|
7
7
|
import { AutoFocusAwareTextInput } from '../../components/display/text_input'
|
|
8
|
-
import { ComponentErrorBoundary } from '../../components/page/component_error_boundary'
|
|
9
8
|
import { useTheme } from '../../hooks'
|
|
10
9
|
import { useDirectMessageAffiliations } from '../../hooks/direct_messages/use_direct_message_affiliations'
|
|
11
10
|
import { useDirectMessageCandidates } from '../../hooks/direct_messages/use_direct_message_candidates'
|
|
@@ -92,38 +91,31 @@ export function ConversationSelectDirectMessageRecipientsScreen({
|
|
|
92
91
|
|
|
93
92
|
return (
|
|
94
93
|
<KeyboardView>
|
|
95
|
-
{isError && (
|
|
96
|
-
<Banner
|
|
97
|
-
appearance="error"
|
|
98
|
-
description="There was an issue starting the conversation. Please try again."
|
|
99
|
-
style={styles.errorBanner}
|
|
100
|
-
/>
|
|
101
|
-
)}
|
|
102
|
-
<ToField
|
|
103
|
-
selectedPeople={selectedPeople}
|
|
104
|
-
searchTerm={searchTerm}
|
|
105
|
-
onSearchChange={setSearchTerm}
|
|
106
|
-
onRemove={removePerson}
|
|
107
|
-
onClear={() => setSelectedPeople(new Map())}
|
|
108
|
-
searchInputRef={searchInputRef}
|
|
109
|
-
/>
|
|
110
94
|
<View style={styles.candidateResultsContainer}>
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
95
|
+
<CandidateResults
|
|
96
|
+
term={debouncedTerm}
|
|
97
|
+
selectedPeople={selectedPeople}
|
|
98
|
+
onSelect={selectPerson}
|
|
99
|
+
header={
|
|
100
|
+
<>
|
|
101
|
+
{isError && (
|
|
102
|
+
<Banner
|
|
103
|
+
appearance="error"
|
|
104
|
+
description="There was an issue starting the conversation. Please try again."
|
|
105
|
+
style={styles.errorBanner}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
<ToField
|
|
109
|
+
selectedPeople={selectedPeople}
|
|
110
|
+
searchTerm={searchTerm}
|
|
111
|
+
onSearchChange={setSearchTerm}
|
|
112
|
+
onRemove={removePerson}
|
|
113
|
+
onClear={() => setSelectedPeople(new Map())}
|
|
114
|
+
searchInputRef={searchInputRef}
|
|
115
|
+
/>
|
|
116
|
+
</>
|
|
119
117
|
}
|
|
120
|
-
|
|
121
|
-
<CandidateResults
|
|
122
|
-
term={debouncedTerm}
|
|
123
|
-
selectedPeople={selectedPeople}
|
|
124
|
-
onSelect={selectPerson}
|
|
125
|
-
/>
|
|
126
|
-
</ComponentErrorBoundary>
|
|
118
|
+
/>
|
|
127
119
|
</View>
|
|
128
120
|
<ActionButton
|
|
129
121
|
title={buttonText}
|
|
@@ -214,53 +206,57 @@ function CandidateResults({
|
|
|
214
206
|
term,
|
|
215
207
|
selectedPeople,
|
|
216
208
|
onSelect,
|
|
209
|
+
header,
|
|
217
210
|
}: {
|
|
218
211
|
term: string
|
|
219
212
|
selectedPeople: Map<string, DirectMessageCandidateResource>
|
|
220
213
|
onSelect: (candidate: DirectMessageCandidateResource) => void
|
|
214
|
+
header: ReactElement
|
|
221
215
|
}) {
|
|
222
216
|
const styles = useStyles()
|
|
223
217
|
const { data, isFetching } = useDirectMessageCandidates(term)
|
|
224
218
|
const { groups, teams, resultsDescription } = useDirectMessageAffiliations()
|
|
225
219
|
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
const unselectedCandidates = (data?.data ?? []).filter(c => !selectedPeople.has(c.id))
|
|
221
|
+
const hasTruncated = (data?.meta.totalCount ?? 0) > (data?.data.length ?? 0)
|
|
222
|
+
const isAtLimit = selectedPeople.size >= MAX_RECIPIENTS
|
|
223
|
+
const showsResults = term.length >= 1 && !isAtLimit && unselectedCandidates.length > 0
|
|
228
224
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<Icon name="churchCenter.people" size={40} style={styles.emptyStateIcon} />
|
|
232
|
-
<Text style={styles.emptyStateTitle}>Search for people to direct message</Text>
|
|
233
|
-
<Text variant="secondary" style={styles.emptyStateSubtitle}>
|
|
234
|
-
Start a conversation with people in your groups and teams.
|
|
235
|
-
</Text>
|
|
236
|
-
</View>
|
|
237
|
-
)
|
|
238
|
-
}
|
|
225
|
+
const emptyState = (() => {
|
|
226
|
+
if (showsResults) return null
|
|
239
227
|
|
|
240
|
-
|
|
228
|
+
if (term.length < 1) {
|
|
229
|
+
if (selectedPeople.size > 0) return null
|
|
241
230
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
231
|
+
return (
|
|
232
|
+
<View style={styles.emptyState}>
|
|
233
|
+
<Icon name="churchCenter.people" size={40} style={styles.emptyStateIcon} />
|
|
234
|
+
<Text style={styles.emptyStateTitle}>Search for people to direct message</Text>
|
|
235
|
+
<Text variant="secondary" style={styles.emptyStateSubtitle}>
|
|
236
|
+
Start a conversation with people in your groups and teams.
|
|
237
|
+
</Text>
|
|
238
|
+
</View>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
251
241
|
|
|
252
|
-
|
|
253
|
-
|
|
242
|
+
if (isAtLimit) {
|
|
243
|
+
return (
|
|
244
|
+
<View style={styles.emptyState}>
|
|
245
|
+
<Text variant="secondary" style={styles.emptyStateSubtitle}>
|
|
246
|
+
You can add up to {MAX_RECIPIENTS} people to a direct message.
|
|
247
|
+
</Text>
|
|
248
|
+
</View>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
254
251
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
if (isFetching) {
|
|
253
|
+
return (
|
|
254
|
+
<View style={styles.emptyState}>
|
|
255
|
+
<Spinner />
|
|
256
|
+
</View>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
262
259
|
|
|
263
|
-
if (!isFetching && unselectedCandidates.length === 0) {
|
|
264
260
|
return (
|
|
265
261
|
<View style={styles.emptyState}>
|
|
266
262
|
<Icon name="general.search" size={32} style={styles.emptyStateIcon} />
|
|
@@ -270,13 +266,15 @@ function CandidateResults({
|
|
|
270
266
|
</Text>
|
|
271
267
|
</View>
|
|
272
268
|
)
|
|
273
|
-
}
|
|
269
|
+
})()
|
|
274
270
|
|
|
275
271
|
return (
|
|
276
272
|
<CandidateList
|
|
277
|
-
|
|
273
|
+
header={header}
|
|
274
|
+
candidates={showsResults ? unselectedCandidates : []}
|
|
278
275
|
hasTruncated={hasTruncated}
|
|
279
276
|
description={resultsDescription}
|
|
277
|
+
emptyState={emptyState}
|
|
280
278
|
groups={groups}
|
|
281
279
|
teams={teams}
|
|
282
280
|
onSelect={onSelect}
|
|
@@ -309,27 +307,33 @@ function candidateAffiliationNames(
|
|
|
309
307
|
}
|
|
310
308
|
|
|
311
309
|
function CandidateList({
|
|
310
|
+
header,
|
|
312
311
|
candidates,
|
|
313
312
|
hasTruncated,
|
|
314
313
|
description,
|
|
314
|
+
emptyState,
|
|
315
315
|
groups,
|
|
316
316
|
teams,
|
|
317
317
|
onSelect,
|
|
318
318
|
}: {
|
|
319
|
+
header: ReactElement
|
|
319
320
|
candidates: DirectMessageCandidateResource[]
|
|
320
321
|
hasTruncated: boolean
|
|
321
322
|
description: string | null
|
|
323
|
+
emptyState: ReactElement | null
|
|
322
324
|
groups: Map<number, string>
|
|
323
325
|
teams: Map<number, string>
|
|
324
326
|
onSelect: (candidate: DirectMessageCandidateResource) => void
|
|
325
327
|
}) {
|
|
326
328
|
const styles = useStyles()
|
|
329
|
+
const hasCandidates = candidates.length > 0
|
|
327
330
|
const available = candidates.filter(c => !c.directMessagesDisabled)
|
|
328
331
|
const dmDisabled = candidates.filter(c => c.directMessagesDisabled)
|
|
329
332
|
|
|
330
333
|
return (
|
|
331
334
|
<FlatList<DirectMessageCandidateResource>
|
|
332
335
|
data={available}
|
|
336
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
333
337
|
keyboardShouldPersistTaps="handled"
|
|
334
338
|
keyExtractor={item => item.id}
|
|
335
339
|
renderItem={({ item }) => (
|
|
@@ -340,7 +344,13 @@ function CandidateList({
|
|
|
340
344
|
onSelect={() => onSelect(item)}
|
|
341
345
|
/>
|
|
342
346
|
)}
|
|
343
|
-
ListHeaderComponent={
|
|
347
|
+
ListHeaderComponent={
|
|
348
|
+
<>
|
|
349
|
+
{header}
|
|
350
|
+
{hasCandidates && <ResultsHeading description={description} />}
|
|
351
|
+
</>
|
|
352
|
+
}
|
|
353
|
+
ListEmptyComponent={emptyState}
|
|
344
354
|
ListFooterComponent={
|
|
345
355
|
<>
|
|
346
356
|
{dmDisabled.length > 0 && (
|
|
@@ -361,7 +371,7 @@ function CandidateList({
|
|
|
361
371
|
))}
|
|
362
372
|
</>
|
|
363
373
|
)}
|
|
364
|
-
{hasTruncated && (
|
|
374
|
+
{hasCandidates && hasTruncated && (
|
|
365
375
|
<View style={styles.truncatedFooter}>
|
|
366
376
|
<Text variant="secondary" style={styles.truncatedText}>
|
|
367
377
|
Keep typing to narrow your results.
|
package/src/screens/conversation_select_recipients/conversation_select_group_recipients_screen.tsx
CHANGED
|
@@ -60,6 +60,7 @@ export const ConversationSelectGroupRecipientsScreen = ({
|
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<SectionList<GroupsGroupResource, GroupsSection>
|
|
63
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
63
64
|
sections={sections}
|
|
64
65
|
keyExtractor={item => item.id.toString()}
|
|
65
66
|
stickySectionHeadersEnabled={false}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
2
|
import { useCallback, useMemo } from 'react'
|
|
3
|
-
import { Alert, Linking, StyleSheet, View } from 'react-native'
|
|
3
|
+
import { Alert, Linking, ScrollView, 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'
|
|
@@ -56,7 +56,7 @@ export const GetHelpScreen = ({ route }: GetHelpScreenProps) => {
|
|
|
56
56
|
}, [navigation])
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
|
-
<
|
|
59
|
+
<ScrollView contentInsetAdjustmentBehavior="automatic" contentContainerStyle={styles.container}>
|
|
60
60
|
{appSpecificHelpUrls.length > 0 && (
|
|
61
61
|
<Heading variant="h2" style={[styles.heading, styles.headingBottomBorder]}>
|
|
62
62
|
How-to articles
|
|
@@ -90,7 +90,7 @@ export const GetHelpScreen = ({ route }: GetHelpScreenProps) => {
|
|
|
90
90
|
to the development team.
|
|
91
91
|
</Text>
|
|
92
92
|
</View>
|
|
93
|
-
</
|
|
93
|
+
</ScrollView>
|
|
94
94
|
)
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -145,7 +145,7 @@ const useStyles = () => {
|
|
|
145
145
|
|
|
146
146
|
return StyleSheet.create({
|
|
147
147
|
container: {
|
|
148
|
-
|
|
148
|
+
flexGrow: 1,
|
|
149
149
|
},
|
|
150
150
|
text: {
|
|
151
151
|
paddingVertical: 4,
|
|
@@ -2,7 +2,7 @@ import { PlatformPressable } from '@react-navigation/elements'
|
|
|
2
2
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
3
3
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
4
4
|
import React, { useEffect } from 'react'
|
|
5
|
-
import { Platform, StyleSheet, View } from 'react-native'
|
|
5
|
+
import { Platform, ScrollView, StyleSheet, View } from 'react-native'
|
|
6
6
|
import { Heading, Icon, Text } from '../components'
|
|
7
7
|
import { useTheme } from '../hooks'
|
|
8
8
|
import { platformFontWeightBold } from '../utils/styles'
|
|
@@ -38,7 +38,7 @@ export function GroupNotificationSettingsScreen({ route }: GroupNotificationSett
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
|
-
<
|
|
41
|
+
<ScrollView contentInsetAdjustmentBehavior="automatic" style={styles.container}>
|
|
42
42
|
<View style={styles.sectionOuter}>
|
|
43
43
|
<View style={styles.sectionInner}>
|
|
44
44
|
<Heading variant="h2" style={styles.sectionHeading}>
|
|
@@ -76,7 +76,7 @@ export function GroupNotificationSettingsScreen({ route }: GroupNotificationSett
|
|
|
76
76
|
)}
|
|
77
77
|
</View>
|
|
78
78
|
</PlatformPressable>
|
|
79
|
-
</
|
|
79
|
+
</ScrollView>
|
|
80
80
|
)
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -123,6 +123,7 @@ export function NotificationSettingsScreen({}: NotificationSettingsScreenProps)
|
|
|
123
123
|
return (
|
|
124
124
|
<View style={styles.listContainer}>
|
|
125
125
|
<FlatList
|
|
126
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
126
127
|
data={listData as SectionListData}
|
|
127
128
|
contentContainerStyle={styles.contentContainer}
|
|
128
129
|
renderItem={({ item, index }) => {
|
|
@@ -2,7 +2,7 @@ import { PlatformPressable } from '@react-navigation/elements'
|
|
|
2
2
|
import { StaticScreenProps } from '@react-navigation/native'
|
|
3
3
|
import { useMutation } from '@tanstack/react-query'
|
|
4
4
|
import React, { type PropsWithChildren } from 'react'
|
|
5
|
-
import { Image, StyleSheet, View, type ViewStyle } from 'react-native'
|
|
5
|
+
import { Image, ScrollView, StyleSheet, View, type ViewStyle } from 'react-native'
|
|
6
6
|
import { Heading, Icon, Text } from '../components'
|
|
7
7
|
import { useApiClient, useTheme } from '../hooks'
|
|
8
8
|
import { useAppName } from '../hooks/use_app_name'
|
|
@@ -45,7 +45,7 @@ export function PreferredAppSelectionScreen({ route }: PreferredAppSelectionScre
|
|
|
45
45
|
const sectionTitle = chatType ? chatTypeLabel(chatType.title) : undefined
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
|
-
<
|
|
48
|
+
<ScrollView contentInsetAdjustmentBehavior="automatic" contentContainerStyle={styles.container}>
|
|
49
49
|
<View style={styles.section}>
|
|
50
50
|
<Heading variant="h2" style={styles.sectionHeading}>
|
|
51
51
|
{sectionTitle}
|
|
@@ -64,7 +64,7 @@ export function PreferredAppSelectionScreen({ route }: PreferredAppSelectionScre
|
|
|
64
64
|
</PressableRow>
|
|
65
65
|
))}
|
|
66
66
|
</View>
|
|
67
|
-
</
|
|
67
|
+
</ScrollView>
|
|
68
68
|
)
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -118,7 +118,7 @@ const PressableRow = ({
|
|
|
118
118
|
|
|
119
119
|
const styles = StyleSheet.create({
|
|
120
120
|
container: {
|
|
121
|
-
|
|
121
|
+
flexGrow: 1,
|
|
122
122
|
},
|
|
123
123
|
section: {
|
|
124
124
|
paddingTop: 24,
|
|
@@ -106,12 +106,13 @@ describe('SearchScreen', () => {
|
|
|
106
106
|
fireEvent.changeText(input, ' budget ')
|
|
107
107
|
|
|
108
108
|
expect(view.getByText('Search chat')).toBeTruthy()
|
|
109
|
-
|
|
109
|
+
const searchedTerms = () =>
|
|
110
|
+
new Set(mockUseSearchResults.mock.calls.map(([term]) => term).filter(term => term.trim()))
|
|
111
|
+
expect([...searchedTerms()]).toEqual([])
|
|
110
112
|
|
|
111
113
|
act(() => jest.advanceTimersByTime(200))
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
expect([...searchedTerms]).toEqual(['budget'])
|
|
115
|
+
expect([...searchedTerms()]).toEqual(['budget'])
|
|
115
116
|
expect(view.queryByText('Search chat')).toBeNull()
|
|
116
117
|
expect(view.getByLabelText('Loading results')).toBeTruthy()
|
|
117
118
|
})
|
|
@@ -121,10 +122,7 @@ describe('SearchScreen', () => {
|
|
|
121
122
|
|
|
122
123
|
const scrollView = view.UNSAFE_getByType(ScrollView)
|
|
123
124
|
expect(scrollView.props.keyboardShouldPersistTaps).toBe('handled')
|
|
124
|
-
expect(scrollView.props.contentContainerStyle).toMatchObject({
|
|
125
|
-
flexGrow: 1,
|
|
126
|
-
justifyContent: 'center',
|
|
127
|
-
})
|
|
125
|
+
expect(scrollView.props.contentContainerStyle).toMatchObject({ flexGrow: 1 })
|
|
128
126
|
expect(view.getByText('Search chat')).toBeTruthy()
|
|
129
127
|
})
|
|
130
128
|
|
|
@@ -49,9 +49,13 @@ const messageResult = (id: string): MessageSearchResultResource => ({
|
|
|
49
49
|
},
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
+
const slots = { header: <></>, prompt: <></> }
|
|
53
|
+
|
|
52
54
|
const renderList = (state: Partial<SearchState>, onSettled = jest.fn()) => {
|
|
53
55
|
mockUseSearchResults.mockReturnValue(searchState(state))
|
|
54
|
-
const list = () =>
|
|
56
|
+
const list = () => (
|
|
57
|
+
<SearchResultsList term="potluck" onSelect={jest.fn()} onSettled={onSettled} {...slots} />
|
|
58
|
+
)
|
|
55
59
|
const view = render(list())
|
|
56
60
|
const rerenderWith = (next: Partial<SearchState>) => {
|
|
57
61
|
mockUseSearchResults.mockReturnValue(searchState(next))
|
|
@@ -130,7 +134,7 @@ describe('SearchResultsList', () => {
|
|
|
130
134
|
const results = [conversationResult('1'), messageResult('1'), messageResult('2')]
|
|
131
135
|
mockUseSearchResults.mockReturnValue(searchState({ results }))
|
|
132
136
|
const view = render(
|
|
133
|
-
<SearchResultsList term="potluck" onSelect={onSelect} onSettled={jest.fn()} />
|
|
137
|
+
<SearchResultsList term="potluck" onSelect={onSelect} onSettled={jest.fn()} {...slots} />
|
|
134
138
|
)
|
|
135
139
|
|
|
136
140
|
fireEvent.press(view.getByLabelText(/^Message 2/))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react'
|
|
1
|
+
import React, { ReactElement, useEffect, useRef } from 'react'
|
|
2
2
|
import { FlatList, StyleSheet, View } from 'react-native'
|
|
3
3
|
import { Banner, Spinner, Text, TextButton } from '../../../components'
|
|
4
4
|
import { useSearchResults } from '../../../hooks/use_search_results'
|
|
@@ -23,9 +23,17 @@ interface SearchResultsListProps {
|
|
|
23
23
|
term: string
|
|
24
24
|
onSelect: (result: SearchResultResource, position: number) => void
|
|
25
25
|
onSettled: (term: string, counts: SearchResultCounts) => void
|
|
26
|
+
header: ReactElement
|
|
27
|
+
prompt: ReactElement
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
export function SearchResultsList({
|
|
30
|
+
export function SearchResultsList({
|
|
31
|
+
term,
|
|
32
|
+
onSelect,
|
|
33
|
+
onSettled,
|
|
34
|
+
header,
|
|
35
|
+
prompt,
|
|
36
|
+
}: SearchResultsListProps) {
|
|
29
37
|
const {
|
|
30
38
|
results,
|
|
31
39
|
failedTypes,
|
|
@@ -36,9 +44,10 @@ export function SearchResultsList({ term, onSelect, onSettled }: SearchResultsLi
|
|
|
36
44
|
hasNextPage,
|
|
37
45
|
isFetchingNextPage,
|
|
38
46
|
} = useSearchResults(term)
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
47
|
+
const searching = term.trim().length > 0
|
|
48
|
+
const conversationResults = searching ? results.filter(isConversationResult) : []
|
|
49
|
+
const messageResults = searching ? results.filter(isMessageResult) : []
|
|
50
|
+
const settled = searching && !isPending && !isError
|
|
42
51
|
|
|
43
52
|
const settledTermRef = useRef<string | undefined>(undefined)
|
|
44
53
|
|
|
@@ -56,6 +65,7 @@ export function SearchResultsList({ term, onSelect, onSettled }: SearchResultsLi
|
|
|
56
65
|
<FlatList
|
|
57
66
|
data={messageResults}
|
|
58
67
|
keyExtractor={item => item.id}
|
|
68
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
59
69
|
keyboardShouldPersistTaps="handled"
|
|
60
70
|
keyboardDismissMode="on-drag"
|
|
61
71
|
contentContainerStyle={styles.content}
|
|
@@ -64,20 +74,27 @@ export function SearchResultsList({ term, onSelect, onSettled }: SearchResultsLi
|
|
|
64
74
|
)}
|
|
65
75
|
ListHeaderComponent={
|
|
66
76
|
<>
|
|
77
|
+
{header}
|
|
67
78
|
{conversationResults.length > 0 && (
|
|
68
79
|
<ConversationResults results={conversationResults} onSelect={onSelect} />
|
|
69
80
|
)}
|
|
70
|
-
{failedTypes.includes('conversation') &&
|
|
81
|
+
{searching && failedTypes.includes('conversation') && (
|
|
82
|
+
<SectionFailure type="conversation" />
|
|
83
|
+
)}
|
|
71
84
|
{messageResults.length > 0 && <ResultsSection title="Messages" />}
|
|
72
85
|
</>
|
|
73
86
|
}
|
|
74
87
|
ListEmptyComponent={
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
searching ? (
|
|
89
|
+
<ListEmpty
|
|
90
|
+
term={term}
|
|
91
|
+
isError={isError}
|
|
92
|
+
isPending={isPending}
|
|
93
|
+
noResults={results.length === 0 && failedTypes.length === 0}
|
|
94
|
+
/>
|
|
95
|
+
) : (
|
|
96
|
+
<View style={styles.prompt}>{prompt}</View>
|
|
97
|
+
)
|
|
81
98
|
}
|
|
82
99
|
ListFooterComponent={
|
|
83
100
|
<>
|
|
@@ -91,7 +108,7 @@ export function SearchResultsList({ term, onSelect, onSettled }: SearchResultsLi
|
|
|
91
108
|
Try again
|
|
92
109
|
</TextButton>
|
|
93
110
|
)}
|
|
94
|
-
{failedTypes.includes('message') && <SectionFailure type="message" />}
|
|
111
|
+
{searching && failedTypes.includes('message') && <SectionFailure type="message" />}
|
|
95
112
|
</>
|
|
96
113
|
}
|
|
97
114
|
onEndReached={() => {
|
|
@@ -149,6 +166,10 @@ const styles = StyleSheet.create({
|
|
|
149
166
|
content: {
|
|
150
167
|
flexGrow: 1,
|
|
151
168
|
},
|
|
169
|
+
prompt: {
|
|
170
|
+
flexGrow: 1,
|
|
171
|
+
justifyContent: 'center',
|
|
172
|
+
},
|
|
152
173
|
banner: {
|
|
153
174
|
margin: 16,
|
|
154
175
|
},
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
NativeStackScreenProps,
|
|
5
5
|
} from '@react-navigation/native-stack'
|
|
6
6
|
import React, { useRef, useState } from 'react'
|
|
7
|
-
import { Keyboard,
|
|
7
|
+
import { Keyboard, StyleSheet, View } from 'react-native'
|
|
8
8
|
import { KeyboardView } from '../../components'
|
|
9
9
|
import {
|
|
10
10
|
composeOptions,
|
|
@@ -64,19 +64,13 @@ export function SearchScreen() {
|
|
|
64
64
|
return (
|
|
65
65
|
<KeyboardView>
|
|
66
66
|
<View style={styles.container} testID="search-screen">
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
/>
|
|
75
|
-
) : (
|
|
76
|
-
<ScrollView contentContainerStyle={styles.prompt} keyboardShouldPersistTaps="handled">
|
|
77
|
-
<SearchPromptBlankState />
|
|
78
|
-
</ScrollView>
|
|
79
|
-
)}
|
|
67
|
+
<SearchResultsList
|
|
68
|
+
term={debouncedTerm}
|
|
69
|
+
onSelect={openResult}
|
|
70
|
+
onSettled={publishSearch}
|
|
71
|
+
header={<SearchField term={term} onChange={setTerm} />}
|
|
72
|
+
prompt={<SearchPromptBlankState />}
|
|
73
|
+
/>
|
|
80
74
|
</View>
|
|
81
75
|
</KeyboardView>
|
|
82
76
|
)
|
|
@@ -86,8 +80,4 @@ const styles = StyleSheet.create({
|
|
|
86
80
|
container: {
|
|
87
81
|
flex: 1,
|
|
88
82
|
},
|
|
89
|
-
prompt: {
|
|
90
|
-
flexGrow: 1,
|
|
91
|
-
justifyContent: 'center',
|
|
92
|
-
},
|
|
93
83
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
2
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
3
3
|
import React, { useCallback } from 'react'
|
|
4
|
-
import { Platform, StyleSheet, View } from 'react-native'
|
|
4
|
+
import { Platform, ScrollView, StyleSheet, View } from 'react-native'
|
|
5
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
6
6
|
import { Heading, Icon, Text, VersionFooter } from '../components'
|
|
7
7
|
import { Switch } from '../components/display/switch'
|
|
@@ -28,29 +28,31 @@ export function SettingsScreen({}: SettingsScreenProps) {
|
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
30
|
<View style={styles.container}>
|
|
31
|
-
<
|
|
32
|
-
<View style={styles.
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
{directMessagesEligible && (
|
|
36
|
-
<View style={styles.settingRow}>
|
|
37
|
-
<View style={styles.settingRowText}>
|
|
38
|
-
<Text>Allow direct messages</Text>
|
|
39
|
-
<Text variant="tertiary">
|
|
40
|
-
When off, you can't be added to new direct messages, and any you're already in will
|
|
41
|
-
be turned off.
|
|
42
|
-
</Text>
|
|
43
|
-
</View>
|
|
44
|
-
<Switch
|
|
45
|
-
value={!directMessagesDisabled}
|
|
46
|
-
onValueChange={enabled => setDirectMessagesDisabled(!enabled)}
|
|
47
|
-
disabled={isMutating}
|
|
48
|
-
/>
|
|
31
|
+
<ScrollView contentInsetAdjustmentBehavior="automatic">
|
|
32
|
+
<View style={styles.sectionOuter}>
|
|
33
|
+
<View style={styles.sectionHeader}>
|
|
34
|
+
<Heading variant="h2">Messages</Heading>
|
|
49
35
|
</View>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
{directMessagesEligible && (
|
|
37
|
+
<View style={styles.settingRow}>
|
|
38
|
+
<View style={styles.settingRowText}>
|
|
39
|
+
<Text>Allow direct messages</Text>
|
|
40
|
+
<Text variant="tertiary">
|
|
41
|
+
When off, you can't be added to new direct messages, and any you're already in
|
|
42
|
+
will be turned off.
|
|
43
|
+
</Text>
|
|
44
|
+
</View>
|
|
45
|
+
<Switch
|
|
46
|
+
value={!directMessagesDisabled}
|
|
47
|
+
onValueChange={enabled => setDirectMessagesDisabled(!enabled)}
|
|
48
|
+
disabled={isMutating}
|
|
49
|
+
/>
|
|
50
|
+
</View>
|
|
51
|
+
)}
|
|
52
|
+
<NavRow label="Notification settings" onPress={handleNotificationSettings} />
|
|
53
|
+
{appName !== 'services' && <NavRow label="Get help" onPress={handleGetHelp} />}
|
|
54
|
+
</View>
|
|
55
|
+
</ScrollView>
|
|
54
56
|
<View style={styles.footer}>
|
|
55
57
|
<VersionFooter />
|
|
56
58
|
</View>
|
|
@@ -90,8 +92,6 @@ const useStyles = () => {
|
|
|
90
92
|
flex: 1,
|
|
91
93
|
},
|
|
92
94
|
footer: {
|
|
93
|
-
flex: 1,
|
|
94
|
-
justifyContent: 'flex-end',
|
|
95
95
|
paddingBottom: Math.max(insets.bottom, 8),
|
|
96
96
|
},
|
|
97
97
|
sectionOuter: {
|