@planningcenter/chat-react-native 4.4.1-qa-staging.30 → 4.4.1-qa-staging.31
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/hooks/direct_messages/use_direct_message_candidates.d.ts +3 -1
- package/build/hooks/direct_messages/use_direct_message_candidates.d.ts.map +1 -1
- package/build/hooks/direct_messages/use_direct_message_candidates.js +42 -29
- package/build/hooks/direct_messages/use_direct_message_candidates.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/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 +409 -308
- 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 +176 -138
- 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/hooks/direct_messages/use_direct_message_candidates.ts +2 -2
- package/src/navigation/index.tsx +11 -2
- package/src/screens/conversation_details_screen.tsx +1 -0
- package/src/screens/conversation_select_recipients/__tests__/conversation_select_direct_message_recipients_screen.test.tsx +83 -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 +104 -69
- 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 +29 -8
- package/src/screens/search/search_screen.tsx +8 -18
- package/src/screens/settings_screen.tsx +25 -25
|
@@ -1,6 +1,6 @@
|
|
|
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'
|
|
@@ -92,38 +92,31 @@ export function ConversationSelectDirectMessageRecipientsScreen({
|
|
|
92
92
|
|
|
93
93
|
return (
|
|
94
94
|
<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
95
|
<View style={styles.candidateResultsContainer}>
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
<CandidateResults
|
|
97
|
+
term={debouncedTerm}
|
|
98
|
+
selectedPeople={selectedPeople}
|
|
99
|
+
onSelect={selectPerson}
|
|
100
|
+
header={
|
|
101
|
+
<>
|
|
102
|
+
{isError && (
|
|
103
|
+
<Banner
|
|
104
|
+
appearance="error"
|
|
105
|
+
description="There was an issue starting the conversation. Please try again."
|
|
106
|
+
style={styles.errorBanner}
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
<ToField
|
|
110
|
+
selectedPeople={selectedPeople}
|
|
111
|
+
searchTerm={searchTerm}
|
|
112
|
+
onSearchChange={setSearchTerm}
|
|
113
|
+
onRemove={removePerson}
|
|
114
|
+
onClear={() => setSelectedPeople(new Map())}
|
|
115
|
+
searchInputRef={searchInputRef}
|
|
116
|
+
/>
|
|
117
|
+
</>
|
|
119
118
|
}
|
|
120
|
-
|
|
121
|
-
<CandidateResults
|
|
122
|
-
term={debouncedTerm}
|
|
123
|
-
selectedPeople={selectedPeople}
|
|
124
|
-
onSelect={selectPerson}
|
|
125
|
-
/>
|
|
126
|
-
</ComponentErrorBoundary>
|
|
119
|
+
/>
|
|
127
120
|
</View>
|
|
128
121
|
<ActionButton
|
|
129
122
|
title={buttonText}
|
|
@@ -214,53 +207,74 @@ function CandidateResults({
|
|
|
214
207
|
term,
|
|
215
208
|
selectedPeople,
|
|
216
209
|
onSelect,
|
|
210
|
+
header,
|
|
217
211
|
}: {
|
|
218
212
|
term: string
|
|
219
213
|
selectedPeople: Map<string, DirectMessageCandidateResource>
|
|
220
214
|
onSelect: (candidate: DirectMessageCandidateResource) => void
|
|
215
|
+
header: ReactElement
|
|
221
216
|
}) {
|
|
222
217
|
const styles = useStyles()
|
|
223
|
-
const { data, isFetching } = useDirectMessageCandidates(term)
|
|
218
|
+
const { data, isFetching, isError } = useDirectMessageCandidates(term, { throwOnError: false })
|
|
224
219
|
const { groups, teams, resultsDescription } = useDirectMessageAffiliations()
|
|
225
220
|
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
const unselectedCandidates = (data?.data ?? []).filter(c => !selectedPeople.has(c.id))
|
|
222
|
+
const hasTruncated = (data?.meta.totalCount ?? 0) > (data?.data.length ?? 0)
|
|
223
|
+
const isAtLimit = selectedPeople.size >= MAX_RECIPIENTS
|
|
224
|
+
const showsResults = term.length >= 1 && !isAtLimit && !isError && unselectedCandidates.length > 0
|
|
228
225
|
|
|
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
|
-
}
|
|
226
|
+
const emptyState = (() => {
|
|
227
|
+
if (showsResults) return null
|
|
239
228
|
|
|
240
|
-
|
|
229
|
+
if (term.length < 1) {
|
|
230
|
+
if (selectedPeople.size > 0) return null
|
|
241
231
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
232
|
+
return (
|
|
233
|
+
<View style={styles.emptyState}>
|
|
234
|
+
<Icon name="churchCenter.people" size={40} style={styles.emptyStateIcon} />
|
|
235
|
+
<Text style={styles.emptyStateTitle}>Search for people to direct message</Text>
|
|
236
|
+
<Text variant="secondary" style={styles.emptyStateSubtitle}>
|
|
237
|
+
Start a conversation with people in your groups and teams.
|
|
238
|
+
</Text>
|
|
239
|
+
</View>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
251
242
|
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
if (isError) {
|
|
244
|
+
return (
|
|
245
|
+
<ComponentErrorBoundary
|
|
246
|
+
key={term}
|
|
247
|
+
fallback={
|
|
248
|
+
<Banner
|
|
249
|
+
appearance="error"
|
|
250
|
+
description="Couldn't load people right now. Try again."
|
|
251
|
+
style={styles.errorBanner}
|
|
252
|
+
/>
|
|
253
|
+
}
|
|
254
|
+
>
|
|
255
|
+
<CandidateSearchError term={term} />
|
|
256
|
+
</ComponentErrorBoundary>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
254
259
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
if (isAtLimit) {
|
|
261
|
+
return (
|
|
262
|
+
<View style={styles.emptyState}>
|
|
263
|
+
<Text variant="secondary" style={styles.emptyStateSubtitle}>
|
|
264
|
+
You can add up to {MAX_RECIPIENTS} people to a direct message.
|
|
265
|
+
</Text>
|
|
266
|
+
</View>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isFetching) {
|
|
271
|
+
return (
|
|
272
|
+
<View style={styles.emptyState}>
|
|
273
|
+
<Spinner />
|
|
274
|
+
</View>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
262
277
|
|
|
263
|
-
if (!isFetching && unselectedCandidates.length === 0) {
|
|
264
278
|
return (
|
|
265
279
|
<View style={styles.emptyState}>
|
|
266
280
|
<Icon name="general.search" size={32} style={styles.emptyStateIcon} />
|
|
@@ -270,13 +284,16 @@ function CandidateResults({
|
|
|
270
284
|
</Text>
|
|
271
285
|
</View>
|
|
272
286
|
)
|
|
273
|
-
}
|
|
287
|
+
})()
|
|
274
288
|
|
|
275
289
|
return (
|
|
276
290
|
<CandidateList
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
291
|
+
header={header}
|
|
292
|
+
candidates={showsResults ? unselectedCandidates : []}
|
|
293
|
+
hasTruncated={showsResults && hasTruncated}
|
|
294
|
+
description={showsResults ? resultsDescription : null}
|
|
295
|
+
showsHeading={showsResults}
|
|
296
|
+
emptyState={emptyState}
|
|
280
297
|
groups={groups}
|
|
281
298
|
teams={teams}
|
|
282
299
|
onSelect={onSelect}
|
|
@@ -284,6 +301,11 @@ function CandidateResults({
|
|
|
284
301
|
)
|
|
285
302
|
}
|
|
286
303
|
|
|
304
|
+
function CandidateSearchError({ term }: { term: string }) {
|
|
305
|
+
useDirectMessageCandidates(term)
|
|
306
|
+
return null
|
|
307
|
+
}
|
|
308
|
+
|
|
287
309
|
function candidateBadges(candidate: DirectMessageCandidateResource) {
|
|
288
310
|
const badges = []
|
|
289
311
|
if (candidate.directMessagesDisabled) badges.push({ title: 'DMs off' })
|
|
@@ -309,16 +331,22 @@ function candidateAffiliationNames(
|
|
|
309
331
|
}
|
|
310
332
|
|
|
311
333
|
function CandidateList({
|
|
334
|
+
header,
|
|
312
335
|
candidates,
|
|
313
336
|
hasTruncated,
|
|
314
337
|
description,
|
|
338
|
+
showsHeading,
|
|
339
|
+
emptyState,
|
|
315
340
|
groups,
|
|
316
341
|
teams,
|
|
317
342
|
onSelect,
|
|
318
343
|
}: {
|
|
344
|
+
header: ReactElement
|
|
319
345
|
candidates: DirectMessageCandidateResource[]
|
|
320
346
|
hasTruncated: boolean
|
|
321
347
|
description: string | null
|
|
348
|
+
showsHeading: boolean
|
|
349
|
+
emptyState: ReactElement | null
|
|
322
350
|
groups: Map<number, string>
|
|
323
351
|
teams: Map<number, string>
|
|
324
352
|
onSelect: (candidate: DirectMessageCandidateResource) => void
|
|
@@ -330,6 +358,7 @@ function CandidateList({
|
|
|
330
358
|
return (
|
|
331
359
|
<FlatList<DirectMessageCandidateResource>
|
|
332
360
|
data={available}
|
|
361
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
333
362
|
keyboardShouldPersistTaps="handled"
|
|
334
363
|
keyExtractor={item => item.id}
|
|
335
364
|
renderItem={({ item }) => (
|
|
@@ -340,7 +369,13 @@ function CandidateList({
|
|
|
340
369
|
onSelect={() => onSelect(item)}
|
|
341
370
|
/>
|
|
342
371
|
)}
|
|
343
|
-
ListHeaderComponent={
|
|
372
|
+
ListHeaderComponent={
|
|
373
|
+
<>
|
|
374
|
+
{header}
|
|
375
|
+
{showsHeading && <ResultsHeading description={description} />}
|
|
376
|
+
</>
|
|
377
|
+
}
|
|
378
|
+
ListEmptyComponent={emptyState}
|
|
344
379
|
ListFooterComponent={
|
|
345
380
|
<>
|
|
346
381
|
{dmDisabled.length > 0 && (
|
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,16 +74,20 @@ 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
88
|
<ListEmpty
|
|
76
89
|
term={term}
|
|
90
|
+
prompt={prompt}
|
|
77
91
|
isError={isError}
|
|
78
92
|
isPending={isPending}
|
|
79
93
|
noResults={results.length === 0 && failedTypes.length === 0}
|
|
@@ -91,7 +105,7 @@ export function SearchResultsList({ term, onSelect, onSettled }: SearchResultsLi
|
|
|
91
105
|
Try again
|
|
92
106
|
</TextButton>
|
|
93
107
|
)}
|
|
94
|
-
{failedTypes.includes('message') && <SectionFailure type="message" />}
|
|
108
|
+
{searching && failedTypes.includes('message') && <SectionFailure type="message" />}
|
|
95
109
|
</>
|
|
96
110
|
}
|
|
97
111
|
onEndReached={() => {
|
|
@@ -104,12 +118,15 @@ export function SearchResultsList({ term, onSelect, onSettled }: SearchResultsLi
|
|
|
104
118
|
|
|
105
119
|
interface ListEmptyProps {
|
|
106
120
|
term: string
|
|
121
|
+
prompt: ReactElement
|
|
107
122
|
isError: boolean
|
|
108
123
|
isPending: boolean
|
|
109
124
|
noResults: boolean
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
function ListEmpty({ term, isError, isPending, noResults }: ListEmptyProps) {
|
|
127
|
+
function ListEmpty({ term, prompt, isError, isPending, noResults }: ListEmptyProps) {
|
|
128
|
+
if (term.trim().length === 0) return <View style={styles.prompt}>{prompt}</View>
|
|
129
|
+
|
|
113
130
|
if (isError) {
|
|
114
131
|
return <Banner appearance="error" description={SEARCH_ERROR_MESSAGE} style={styles.banner} />
|
|
115
132
|
}
|
|
@@ -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: {
|