@planningcenter/chat-react-native 3.23.0 → 3.23.1-qa-538.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/build/components/display/toggle_button.d.ts +11 -4
  2. package/build/components/display/toggle_button.d.ts.map +1 -1
  3. package/build/components/display/toggle_button.js +6 -5
  4. package/build/components/display/toggle_button.js.map +1 -1
  5. package/build/components/primitive/form_sheet.d.ts +1 -1
  6. package/build/components/primitive/form_sheet.d.ts.map +1 -1
  7. package/build/components/primitive/form_sheet.js +1 -1
  8. package/build/components/primitive/form_sheet.js.map +1 -1
  9. package/build/hooks/index.d.ts +1 -0
  10. package/build/hooks/index.d.ts.map +1 -1
  11. package/build/hooks/index.js +1 -0
  12. package/build/hooks/index.js.map +1 -1
  13. package/build/hooks/use_conversation.d.ts.map +1 -1
  14. package/build/hooks/use_conversation.js +6 -1
  15. package/build/hooks/use_conversation.js.map +1 -1
  16. package/build/hooks/use_conversation_membership.d.ts +5 -0
  17. package/build/hooks/use_conversation_membership.d.ts.map +1 -0
  18. package/build/hooks/use_conversation_membership.js +54 -0
  19. package/build/hooks/use_conversation_membership.js.map +1 -0
  20. package/build/hooks/use_features.d.ts +1 -0
  21. package/build/hooks/use_features.d.ts.map +1 -1
  22. package/build/hooks/use_features.js +1 -0
  23. package/build/hooks/use_features.js.map +1 -1
  24. package/build/hooks/use_new_conversation_entry.d.ts +3 -0
  25. package/build/hooks/use_new_conversation_entry.d.ts.map +1 -0
  26. package/build/hooks/use_new_conversation_entry.js +20 -0
  27. package/build/hooks/use_new_conversation_entry.js.map +1 -0
  28. package/build/hooks/use_report_message.d.ts +6 -0
  29. package/build/hooks/use_report_message.d.ts.map +1 -0
  30. package/build/hooks/use_report_message.js +28 -0
  31. package/build/hooks/use_report_message.js.map +1 -0
  32. package/build/navigation/index.d.ts +18 -8
  33. package/build/navigation/index.d.ts.map +1 -1
  34. package/build/navigation/index.js +16 -6
  35. package/build/navigation/index.js.map +1 -1
  36. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  37. package/build/screens/conversation_details_screen.js +18 -10
  38. package/build/screens/conversation_details_screen.js.map +1 -1
  39. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js +5 -3
  40. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js.map +1 -1
  41. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts +4 -0
  42. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.d.ts.map +1 -0
  43. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js +67 -0
  44. package/build/screens/conversation_select_recipients/conversation_new_entry_screen.js.map +1 -0
  45. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
  46. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +17 -2
  47. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
  48. package/build/screens/conversation_select_type_screen.d.ts +11 -0
  49. package/build/screens/conversation_select_type_screen.d.ts.map +1 -0
  50. package/build/screens/conversation_select_type_screen.js +32 -0
  51. package/build/screens/conversation_select_type_screen.js.map +1 -0
  52. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  53. package/build/screens/conversations/components/list_header_component.js +54 -25
  54. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  55. package/build/screens/index.d.ts +1 -1
  56. package/build/screens/index.d.ts.map +1 -1
  57. package/build/screens/index.js +1 -1
  58. package/build/screens/index.js.map +1 -1
  59. package/build/screens/message_actions_screen.js +14 -2
  60. package/build/screens/message_actions_screen.js.map +1 -1
  61. package/build/screens/message_report/components/message_preview.d.ts +10 -0
  62. package/build/screens/message_report/components/message_preview.d.ts.map +1 -0
  63. package/build/screens/message_report/components/message_preview.js +81 -0
  64. package/build/screens/message_report/components/message_preview.js.map +1 -0
  65. package/build/screens/message_report/components/report_reason_list.d.ts +9 -0
  66. package/build/screens/message_report/components/report_reason_list.d.ts.map +1 -0
  67. package/build/screens/message_report/components/report_reason_list.js +67 -0
  68. package/build/screens/message_report/components/report_reason_list.js.map +1 -0
  69. package/build/screens/message_report_screen.d.ts +10 -0
  70. package/build/screens/message_report_screen.d.ts.map +1 -0
  71. package/build/screens/message_report_screen.js +214 -0
  72. package/build/screens/message_report_screen.js.map +1 -0
  73. package/build/types/resources/conversation.d.ts +2 -3
  74. package/build/types/resources/conversation.d.ts.map +1 -1
  75. package/build/types/resources/conversation.js.map +1 -1
  76. package/build/types/resources/conversation_membership.d.ts +14 -0
  77. package/build/types/resources/conversation_membership.d.ts.map +1 -0
  78. package/build/types/resources/conversation_membership.js +2 -0
  79. package/build/types/resources/conversation_membership.js.map +1 -0
  80. package/build/types/resources/index.d.ts +1 -0
  81. package/build/types/resources/index.d.ts.map +1 -1
  82. package/build/types/resources/index.js +1 -0
  83. package/build/types/resources/index.js.map +1 -1
  84. package/build/types/resources/message_report.d.ts +19 -0
  85. package/build/types/resources/message_report.d.ts.map +1 -0
  86. package/build/types/resources/message_report.js +9 -0
  87. package/build/types/resources/message_report.js.map +1 -0
  88. package/build/utils/deep_snake_case_keys.d.ts +4 -0
  89. package/build/utils/deep_snake_case_keys.d.ts.map +1 -0
  90. package/build/utils/deep_snake_case_keys.js +13 -0
  91. package/build/utils/deep_snake_case_keys.js.map +1 -0
  92. package/package.json +2 -2
  93. package/src/components/display/toggle_button.tsx +27 -15
  94. package/src/components/primitive/form_sheet.tsx +4 -2
  95. package/src/hooks/index.ts +1 -0
  96. package/src/hooks/use_conversation.ts +6 -1
  97. package/src/hooks/use_conversation_membership.ts +82 -0
  98. package/src/hooks/use_features.ts +1 -0
  99. package/src/hooks/use_new_conversation_entry.ts +31 -0
  100. package/src/hooks/use_report_message.ts +37 -0
  101. package/src/navigation/index.tsx +19 -6
  102. package/src/screens/conversation_details_screen.tsx +34 -16
  103. package/src/screens/conversation_filter_recipients/conversation_filter_recipients_screen.tsx +10 -3
  104. package/src/screens/conversation_select_recipients/conversation_new_entry_screen.tsx +100 -0
  105. package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +23 -3
  106. package/src/screens/conversation_select_type_screen.tsx +53 -0
  107. package/src/screens/conversations/components/list_header_component.tsx +103 -67
  108. package/src/screens/index.ts +1 -1
  109. package/src/screens/message_actions_screen.tsx +24 -2
  110. package/src/screens/message_report/components/message_preview.tsx +99 -0
  111. package/src/screens/message_report/components/report_reason_list.tsx +106 -0
  112. package/src/screens/message_report_screen.tsx +278 -0
  113. package/src/types/resources/conversation.ts +2 -3
  114. package/src/types/resources/conversation_membership.ts +15 -0
  115. package/src/types/resources/index.ts +1 -0
  116. package/src/types/resources/message_report.ts +20 -0
  117. package/src/utils/deep_snake_case_keys.ts +16 -0
  118. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts +0 -4
  119. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts.map +0 -1
  120. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js +0 -146
  121. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js.map +0 -1
  122. package/src/screens/conversation_select_recipients/conversation_select_recipients_screen.tsx +0 -215
@@ -1,25 +1,25 @@
1
+ import { HeaderTitle as ElementsHeaderTitle, HeaderTitleProps } from '@react-navigation/elements'
1
2
  import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
2
3
  import React, {
3
4
  useCallback,
4
5
  useEffect,
6
+ useRef,
5
7
  useState,
6
- type SetStateAction,
7
8
  type Dispatch,
8
9
  type ReactNode,
9
- useRef,
10
+ type SetStateAction,
10
11
  } from 'react'
11
12
  import {
13
+ Alert,
12
14
  FlatList,
15
+ Platform,
16
+ Pressable,
13
17
  StyleSheet,
14
18
  TextInput,
15
19
  View,
16
- type ViewStyle,
17
20
  type ViewProps,
18
- Pressable,
19
- Alert,
20
- Platform,
21
+ type ViewStyle,
21
22
  } from 'react-native'
22
- import { HeaderTitle as ElementsHeaderTitle, HeaderTitleProps } from '@react-navigation/elements'
23
23
  import {
24
24
  Badge,
25
25
  ChildNotice,
@@ -31,21 +31,20 @@ import {
31
31
  TextButton,
32
32
  type TextStyle,
33
33
  } from '../components'
34
+ import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
35
+ import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
34
36
  import { useSuspensePaginator, useTheme } from '../hooks'
35
37
  import {
36
38
  useConversation,
37
39
  useConversationDelete,
38
40
  useConversationDisableReplies,
39
- useConversationMute,
40
41
  useConversationUpdate,
41
42
  } from '../hooks/use_conversation'
43
+ import { useConversationMembershipUpdate } from '../hooks/use_conversation_membership'
44
+ import { availableFeatures, useFeatures } from '../hooks/use_features'
42
45
  import { MemberResource, isDefined } from '../types'
43
- import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
44
- import { tokens } from '../vendor/tapestry/tokens'
45
- import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
46
46
  import { GroupResource } from '../types/resources/group_resource'
47
- import { useFeatures } from '../hooks/use_features'
48
- import { availableFeatures } from '../hooks/use_features'
47
+ import { tokens } from '../vendor/tapestry/tokens'
49
48
 
50
49
  // =========================================
51
50
  // ====== Factory Constants & Types ========
@@ -91,7 +90,7 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
91
90
 
92
91
  const { data: conversation } = useConversation(route.params)
93
92
  const [title, setTitle] = useState(conversation.title)
94
- const { muted, setMuted } = useConversationMute(route.params)
93
+ const { mutate: updateMembership } = useConversationMembershipUpdate(route.params)
95
94
  const { repliesDisabled, setRepliesDisabled } = useConversationDisableReplies(route.params)
96
95
  const { mutate: saveTitle } = useConversationUpdate(route.params)
97
96
  const { mutate: deleteConversation } = useConversationDelete(route.params)
@@ -101,6 +100,10 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
101
100
  const trimmedTitle = title.trim()
102
101
  const emptyTitle = trimmedTitle === '' || title === null
103
102
 
103
+ const notificationsEnabled =
104
+ conversation.conversationMembership?.notificationLevel === 'everything'
105
+ const notificationLevelDescription =
106
+ conversation?.conversationMembership?.notificationLevelDescription
104
107
  const canUpdate = conversation.memberAbility?.canUpdate || false
105
108
  const canDelete = conversation.memberAbility?.canDelete || false
106
109
  const isLeader = conversation.memberAbility?.leader || false
@@ -163,6 +166,15 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
163
166
  const productName = badge?.appName
164
167
  const name = badge?.text || undefined
165
168
 
169
+ const handleToggleNotificationPreferences = useCallback(
170
+ (value: boolean) => {
171
+ updateMembership({
172
+ notificationLevel: value ? 'everything' : 'nothing',
173
+ })
174
+ },
175
+ [updateMembership]
176
+ )
177
+
166
178
  const handleDelete = useCallback(() => {
167
179
  Alert.alert(
168
180
  'Delete conversation',
@@ -246,8 +258,14 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
246
258
  {
247
259
  type: SectionTypes.setting,
248
260
  data: {
249
- title: 'Mute',
250
- rightItem: <Switch value={muted} onChange={() => setMuted(!muted)} />,
261
+ title: 'Enable notifications',
262
+ subtitle: notificationLevelDescription,
263
+ rightItem: (
264
+ <Switch
265
+ value={notificationsEnabled}
266
+ onValueChange={handleToggleNotificationPreferences}
267
+ />
268
+ ),
251
269
  },
252
270
  showBottomBorder: true,
253
271
  },
@@ -1,7 +1,7 @@
1
1
  import { RouteProp, StackActions, useNavigation, useRoute } from '@react-navigation/native'
2
2
  import { NativeStackNavigationProp } from '@react-navigation/native-stack'
3
3
  import React, { useCallback, useMemo, useState } from 'react'
4
- import { LayoutChangeEvent, Platform, StyleSheet, View } from 'react-native'
4
+ import { LayoutChangeEvent, Platform, ScrollView, StyleSheet, View } from 'react-native'
5
5
  import { FlatList, TextInput } from 'react-native-gesture-handler'
6
6
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
7
7
  import { Heading, ToggleButton } from '../../components'
@@ -192,7 +192,12 @@ const TeamFilters = ({ onLayout }: { onLayout?: (event: LayoutChangeEvent) => vo
192
192
  })
193
193
  }}
194
194
  />
195
- <View style={styles.filterToggleContainer}>
195
+ <ScrollView
196
+ horizontal
197
+ showsHorizontalScrollIndicator={false}
198
+ contentContainerStyle={styles.filterToggleContainer}
199
+ accessibilityRole="toolbar"
200
+ >
196
201
  <ToggleButton
197
202
  active={active === TeamFilterTypes.TeamsIlead}
198
203
  title={TeamFilterTypes.TeamsIlead}
@@ -211,7 +216,7 @@ const TeamFilters = ({ onLayout }: { onLayout?: (event: LayoutChangeEvent) => vo
211
216
  accessibilityLabel="Show all teams"
212
217
  onPress={() => handleFilterChange(TeamFilterTypes.All)}
213
218
  />
214
- </View>
219
+ </ScrollView>
215
220
  </View>
216
221
  )
217
222
  }
@@ -253,7 +258,9 @@ const useStyles = () => {
253
258
  },
254
259
  filterToggleContainer: {
255
260
  flexDirection: 'row',
261
+ justifyContent: 'flex-start',
256
262
  gap: 8,
263
+ flexGrow: 1,
257
264
  },
258
265
  searchInput: {
259
266
  paddingVertical: 16,
@@ -0,0 +1,100 @@
1
+ import { useNavigation } from '@react-navigation/native'
2
+ import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
3
+ import React, { useEffect } from 'react'
4
+ import { ScrollView, StyleSheet, View } from 'react-native'
5
+ import { Button, Heading } from '../../components'
6
+ import { DefaultLoading } from '../../components/page/loading'
7
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
8
+ import type {
9
+ ConversationSelectRecipientsParams,
10
+ ConversationSelectRecipientsScreenProps,
11
+ } from './types/screen_props'
12
+ import { useNewConversationEntry } from '../../hooks/use_new_conversation_entry'
13
+ import { Haptic } from '../../utils/native_adapters'
14
+
15
+ type NewConversationStackParamList = {
16
+ ConversationNewEntry: ConversationSelectRecipientsParams | undefined
17
+ ConversationSelectGroupRecipients: ConversationSelectRecipientsParams | undefined
18
+ ConversationSelectTeamsILeadRecipients: ConversationSelectRecipientsParams | undefined
19
+ }
20
+
21
+ export const ConversationNewEntryScreen = ({ route }: ConversationSelectRecipientsScreenProps) => {
22
+ const styles = useStyles()
23
+ const navigation = useNavigation<NativeStackNavigationProp<NewConversationStackParamList>>()
24
+ const entryMode = useNewConversationEntry()
25
+
26
+ useEffect(() => {
27
+ if (entryMode === 'groups') {
28
+ navigation.replace('ConversationSelectGroupRecipients', route.params)
29
+ return
30
+ }
31
+
32
+ if (entryMode === 'teams') {
33
+ navigation.replace('ConversationSelectTeamsILeadRecipients', route.params)
34
+ return
35
+ }
36
+
37
+ if (entryMode === 'none') {
38
+ navigation.goBack()
39
+ return
40
+ }
41
+ }, [entryMode, navigation, route.params])
42
+
43
+ if (entryMode !== 'select_type') return <DefaultLoading />
44
+
45
+ const handleGroupPress = () => {
46
+ Haptic.impactLight()
47
+ navigation.navigate('ConversationSelectGroupRecipients', route.params)
48
+ }
49
+
50
+ const handleTeamPress = () => {
51
+ Haptic.impactLight()
52
+ navigation.navigate('ConversationSelectTeamsILeadRecipients', route.params)
53
+ }
54
+
55
+ return (
56
+ <ScrollView contentContainerStyle={styles.contentContainer}>
57
+ <View style={styles.header}>
58
+ <Heading variant="h2">Start a conversation by</Heading>
59
+ </View>
60
+ <View style={styles.actions}>
61
+ <Button
62
+ title="Group"
63
+ onPress={handleGroupPress}
64
+ size="lg"
65
+ variant="outline"
66
+ accessibilityHint="Opens group selection screen"
67
+ />
68
+ <Button
69
+ title="Team"
70
+ onPress={handleTeamPress}
71
+ size="lg"
72
+ variant="outline"
73
+ accessibilityHint="Opens team selection screen"
74
+ />
75
+ </View>
76
+ </ScrollView>
77
+ )
78
+ }
79
+
80
+ const useStyles = () => {
81
+ const { bottom } = useSafeAreaInsets()
82
+
83
+ return StyleSheet.create({
84
+ contentContainer: {
85
+ flexGrow: 1,
86
+ padding: 16,
87
+ paddingBottom: 16 + bottom,
88
+ justifyContent: 'center',
89
+ },
90
+ header: {
91
+ marginBottom: 24,
92
+ alignItems: 'center',
93
+ },
94
+ actions: {
95
+ gap: 12,
96
+ flexDirection: 'row',
97
+ justifyContent: 'center',
98
+ },
99
+ })
100
+ }
@@ -1,12 +1,13 @@
1
1
  import { useNavigation } from '@react-navigation/native'
2
2
  import React from 'react'
3
3
  import { FlatList, StyleSheet, View } from 'react-native'
4
- import { Heading } from '../../components'
4
+ import { Button, Heading } from '../../components'
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
6
6
  import { ConversationSelectRecipientsScreenProps } from './types/screen_props'
7
7
  import { useServiceTypesWithTeams } from '../conversation_filter_recipients/hooks/use_service_types_with_teams'
8
- import { ServiceTypeWithTeams } from '../conversation_filter_recipients/types'
8
+ import { ServiceTypeWithTeams, TeamFilterTypes } from '../conversation_filter_recipients/types'
9
9
  import { TeamRecipientRow } from './components/team_recipient_row'
10
+ import { Haptic } from '../../utils/native_adapters'
10
11
 
11
12
  export const ConversationSelectTeamsILeadRecipientsScreen = ({
12
13
  route,
@@ -14,6 +15,7 @@ export const ConversationSelectTeamsILeadRecipientsScreen = ({
14
15
  const styles = useStyles()
15
16
  const navigation = useNavigation()
16
17
  const { serviceTypes } = useServiceTypesWithTeams()
18
+ const hasServiceTypes = serviceTypes.length > 0
17
19
 
18
20
  const handleNavigateToConversationNew = (teams: ServiceTypeWithTeams['teams']) => {
19
21
  const teamIds = teams.map(team => team.id)
@@ -35,7 +37,25 @@ export const ConversationSelectTeamsILeadRecipientsScreen = ({
35
37
  contentContainerStyle={styles.contentContainer}
36
38
  ListHeaderComponent={
37
39
  <View style={styles.sectionHeader}>
38
- <Heading variant="h3">All teams I lead</Heading>
40
+ <Heading variant="h3">Teams I lead</Heading>
41
+ <Button
42
+ onPress={() => {
43
+ Haptic.impactLight()
44
+ navigation.navigate('New', {
45
+ screen: 'ConversationFilterRecipients',
46
+ params: {
47
+ source_app_name: 'Services',
48
+ team_filter_type: hasServiceTypes
49
+ ? TeamFilterTypes.TeamsIlead
50
+ : TeamFilterTypes.All,
51
+ },
52
+ })
53
+ }}
54
+ title="Select teams"
55
+ variant="outline"
56
+ iconNameLeft="general.search"
57
+ size="sm"
58
+ />
39
59
  </View>
40
60
  }
41
61
  renderItem={({ item: serviceType }: { item: ServiceTypeWithTeams }) => {
@@ -0,0 +1,53 @@
1
+ import React from 'react'
2
+ import { StaticScreenProps, useNavigation } from '@react-navigation/native'
3
+ import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
4
+ import { Haptic } from '../utils/native_adapters'
5
+ import { AppName } from '../types/resources/app_name'
6
+ import { GraphId } from '../types/resources/group_resource'
7
+
8
+ export const ConversationSelectTypeScreenOptions = getFormSheetScreenOptions({
9
+ headerTitle: 'New conversation',
10
+ })
11
+
12
+ export type ConversationSelectTypeScreenProps = StaticScreenProps<{
13
+ chat_group_graph_id?: GraphId
14
+ group_source_app_name?: AppName
15
+ }>
16
+
17
+ export function ConversationSelectTypeScreen({ route }: ConversationSelectTypeScreenProps) {
18
+ const navigation = useNavigation()
19
+
20
+ const handleGroupPress = () => {
21
+ Haptic.impactLight()
22
+ navigation.navigate('New', {
23
+ screen: 'ConversationSelectGroupRecipients',
24
+ params: { ...route.params },
25
+ })
26
+ }
27
+
28
+ const handleTeamPress = () => {
29
+ Haptic.impactLight()
30
+ navigation.navigate('New', {
31
+ screen: 'ConversationSelectTeamsILeadRecipients',
32
+ params: { ...route.params },
33
+ })
34
+ }
35
+
36
+ return (
37
+ <FormSheet.Root>
38
+ <FormSheet.Header>
39
+ <FormSheet.HeaderTitle>Start a conversation by</FormSheet.HeaderTitle>
40
+ </FormSheet.Header>
41
+ <FormSheet.Action
42
+ title="Group"
43
+ onPress={handleGroupPress}
44
+ accessibilityHint="Opens group selection screen"
45
+ />
46
+ <FormSheet.Action
47
+ title="Team"
48
+ onPress={handleTeamPress}
49
+ accessibilityHint="Opens team selection screen"
50
+ />
51
+ </FormSheet.Root>
52
+ )
53
+ }
@@ -6,6 +6,7 @@ import { useAtFontScaleBreakpoint, useCurrentPersonCache, useTheme } from '../..
6
6
  import { useMarkAllRead } from '../../../hooks/use_conversations_actions'
7
7
  import { useCanDisplayGroups } from '../../../hooks/use_groups'
8
8
  import { useCanCreateConversations } from '../../../hooks'
9
+ import { useNewConversationEntry } from '../../../hooks/use_new_conversation_entry'
9
10
  import { useAppName } from '../../../hooks/use_app_name'
10
11
  import { ConversationsScreenProps } from '../conversations_screen'
11
12
  import { ChatGroupBadge } from './chat_group_badge'
@@ -23,7 +24,6 @@ enum FilterTypes {
23
24
  }
24
25
 
25
26
  export const ListHeaderComponent = () => {
26
- const styles = useStyles()
27
27
  const navigation = useNavigation()
28
28
  const canFilterByTeams = useCanDisplayGroups({ source_app_name: 'Services', source_type: 'Team' })
29
29
  const canFilterByGroups = useCanDisplayGroups({ source_app_name: 'Groups', source_type: 'Group' })
@@ -32,6 +32,7 @@ export const ListHeaderComponent = () => {
32
32
  const currentPersonCache = useCurrentPersonCache()
33
33
  const { markAllRead, isPending } = useMarkAllRead()
34
34
  const canCreateConversations = useCanCreateConversations()
35
+ const entryMode = useNewConversationEntry()
35
36
  const appName = useAppName()
36
37
 
37
38
  const active: FilterTypes = useMemo(() => {
@@ -47,18 +48,36 @@ export const ListHeaderComponent = () => {
47
48
  }, [chat_group_graph_id, group_source_app_name])
48
49
 
49
50
  const handleNewConversationNavigation = useCallback(() => {
50
- return navigation.navigate('New', {
51
- screen: 'ConversationSelectRecipients',
52
- params: {
53
- ...route.params,
54
- },
55
- })
56
- }, [navigation, route.params])
51
+ if (entryMode === 'select_type') {
52
+ return navigation.navigate('ConversationSelectType', { ...route.params })
53
+ }
54
+
55
+ if (entryMode === 'groups') {
56
+ return navigation.navigate('New', {
57
+ screen: 'ConversationSelectGroupRecipients',
58
+ params: { ...route.params },
59
+ })
60
+ }
61
+
62
+ if (entryMode === 'teams') {
63
+ return navigation.navigate('New', {
64
+ screen: 'ConversationSelectTeamsILeadRecipients',
65
+ params: { ...route.params },
66
+ })
67
+ }
68
+ }, [navigation, route.params, entryMode])
57
69
 
58
70
  const handleGetHelp = useCallback(() => {
59
71
  navigation.navigate('GetHelp', { type: 'chat' })
60
72
  }, [navigation])
61
73
 
74
+ const handleOpenConversationFilters = useCallback(() => {
75
+ navigation.navigate('ConversationFilters', {
76
+ chat_group_graph_id,
77
+ group_source_app_name,
78
+ })
79
+ }, [navigation, chat_group_graph_id, group_source_app_name])
80
+
62
81
  const handleMarkAllRead = useCallback(() => {
63
82
  currentPersonCache.update({ unreadCount: 0 })
64
83
  Haptic.notificationSuccess()
@@ -74,7 +93,22 @@ export const ListHeaderComponent = () => {
74
93
  ])
75
94
  }, [active, handleMarkAllRead])
76
95
 
77
- const showAppFilters = canFilterByGroups && canFilterByTeams
96
+ const hasSomeSourceFilterCapability = canFilterByTeams || canFilterByGroups
97
+ const hasMultiSourceFilterCapabilities = canFilterByTeams && canFilterByGroups
98
+ const shouldShowHeaderFiltersButton =
99
+ hasSomeSourceFilterCapability && !hasMultiSourceFilterCapabilities
100
+ const moreFiltersButtonA11yLabel = shouldShowHeaderFiltersButton
101
+ ? 'View filters'
102
+ : 'View more filters'
103
+
104
+ const moreFiltersButtonProps = {
105
+ onPress: handleOpenConversationFilters,
106
+ active: active === FilterTypes.More,
107
+ iconNameRight: 'general.threeReducingHorizontalBars' as const,
108
+ accessibilityLabel: moreFiltersButtonA11yLabel,
109
+ }
110
+
111
+ const styles = useStyles({ shouldShowHeaderFiltersButton })
78
112
 
79
113
  return (
80
114
  <View style={styles.listHeader}>
@@ -111,73 +145,70 @@ export const ListHeaderComponent = () => {
111
145
  maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
112
146
  />
113
147
  )}
148
+ {shouldShowHeaderFiltersButton && <ToggleButton {...moreFiltersButtonProps} />}
114
149
  </View>
115
150
  </View>
116
- <ScrollView
117
- horizontal
118
- showsHorizontalScrollIndicator={false}
119
- contentContainerStyle={styles.filterRow}
120
- accessibilityRole="toolbar"
121
- >
122
- {showAppFilters && (
123
- <>
124
- <ToggleButton
125
- title={FilterTypes.All}
126
- active={active === FilterTypes.All}
127
- onPress={() =>
128
- navigation.setParams({
129
- chat_group_graph_id: undefined,
130
- group_source_app_name: undefined,
131
- })
132
- }
133
- accessibilityLabel="Show all conversations"
134
- />
135
- <ToggleButton
136
- title={FilterTypes.Groups}
137
- active={active === FilterTypes.Groups}
138
- onPress={() =>
139
- navigation.setParams({
140
- chat_group_graph_id: undefined,
141
- group_source_app_name: 'Groups',
142
- })
143
- }
144
- accessibilityLabel="Filter to group conversations"
145
- />
151
+ {hasMultiSourceFilterCapabilities && (
152
+ <ScrollView
153
+ horizontal
154
+ showsHorizontalScrollIndicator={false}
155
+ contentContainerStyle={styles.filterRow}
156
+ accessibilityRole="toolbar"
157
+ >
158
+ <ToggleButton
159
+ title={FilterTypes.All}
160
+ active={active === FilterTypes.All}
161
+ onPress={() =>
162
+ navigation.setParams({
163
+ chat_group_graph_id: undefined,
164
+ group_source_app_name: undefined,
165
+ })
166
+ }
167
+ accessibilityLabel="Show all conversations"
168
+ style={styles.filterButton}
169
+ />
170
+ <ToggleButton
171
+ title={FilterTypes.Groups}
172
+ active={active === FilterTypes.Groups}
173
+ onPress={() =>
174
+ navigation.setParams({
175
+ chat_group_graph_id: undefined,
176
+ group_source_app_name: 'Groups',
177
+ })
178
+ }
179
+ accessibilityLabel="Filter to group conversations"
180
+ style={styles.filterButton}
181
+ />
146
182
 
147
- <ToggleButton
148
- title={FilterTypes.Teams}
149
- active={active === FilterTypes.Teams}
150
- onPress={() =>
151
- navigation.setParams({
152
- chat_group_graph_id: undefined,
153
- group_source_app_name: 'Services',
154
- })
155
- }
156
- accessibilityLabel="Filter to team conversations"
157
- />
158
- </>
159
- )}
160
- <ToggleButton
161
- title={showAppFilters ? FilterTypes.More : 'Filter'}
162
- onPress={() =>
163
- navigation.navigate('ConversationFilters', {
164
- chat_group_graph_id,
165
- group_source_app_name,
166
- })
167
- }
168
- active={active === FilterTypes.More}
169
- iconNameRight="general.threeReducingHorizontalBars"
170
- accessibilityLabel={showAppFilters ? 'View all filters' : 'View more filters'}
171
- />
172
- </ScrollView>
183
+ <ToggleButton
184
+ title={FilterTypes.Teams}
185
+ active={active === FilterTypes.Teams}
186
+ onPress={() =>
187
+ navigation.setParams({
188
+ chat_group_graph_id: undefined,
189
+ group_source_app_name: 'Services',
190
+ })
191
+ }
192
+ accessibilityLabel="Filter to team conversations"
193
+ style={styles.filterButton}
194
+ />
195
+ <ToggleButton {...moreFiltersButtonProps} />
196
+ </ScrollView>
197
+ )}
173
198
  <ChatGroupBadge rowPaddingHorizontal={ROW_PADDING_HORIZONTAL} />
174
199
  </View>
175
200
  )
176
201
  }
177
202
 
178
- const useStyles = () => {
203
+ const useStyles = ({
204
+ shouldShowHeaderFiltersButton,
205
+ }: {
206
+ shouldShowHeaderFiltersButton: boolean
207
+ }) => {
179
208
  const theme = useTheme()
180
- const atFontScaleBreakpoint = useAtFontScaleBreakpoint()
209
+ const atFontScaleBreakpoint = useAtFontScaleBreakpoint(
210
+ shouldShowHeaderFiltersButton ? 1.2 : undefined
211
+ )
181
212
 
182
213
  return StyleSheet.create({
183
214
  listHeader: {
@@ -210,6 +241,11 @@ const useStyles = () => {
210
241
  justifyContent: 'flex-start',
211
242
  gap: 8,
212
243
  paddingHorizontal: ROW_PADDING_HORIZONTAL,
244
+ flexGrow: 1,
245
+ },
246
+ filterButton: {
247
+ flex: 1,
248
+ minWidth: 65,
213
249
  },
214
250
  })
215
251
  }
@@ -3,8 +3,8 @@ export * from './conversation_details_screen'
3
3
  export * from './conversation_screen'
4
4
  export * from './conversation_new/conversation_new_screen'
5
5
  export * from './conversation_filter_recipients/conversation_filter_recipients_screen'
6
- export * from './conversation_select_recipients/conversation_select_recipients_screen'
7
6
  export * from './message_actions_screen'
7
+ export * from './message_report_screen'
8
8
  export * from './send_giphy_screen'
9
9
  export * from './not_found'
10
10
  export * from './reactions_screen'
@@ -120,6 +120,14 @@ function MessageActionsScreenContent({
120
120
  navigation.goBack()
121
121
  }
122
122
 
123
+ const handleReportPress = useCallback(() => {
124
+ Haptic.impactLight()
125
+ navigation.navigate('MessageReport', {
126
+ conversation_id,
127
+ message_id: message.id,
128
+ })
129
+ }, [navigation, conversation_id, message.id])
130
+
123
131
  const { handleReactionToggle, isPending } = useMessageReactionToggle({
124
132
  conversation_id,
125
133
  message,
@@ -212,6 +220,9 @@ function MessageActionsScreenContent({
212
220
  ])
213
221
  }, [handleRemoveLinkPreview])
214
222
 
223
+ const showReportMessageAction =
224
+ !message?.mine && featureEnabled(availableFeatures.message_reporting)
225
+
215
226
  return (
216
227
  <FormSheet.Root style={styles.formSheetContent}>
217
228
  <View style={styles.reactionList}>
@@ -246,6 +257,14 @@ function MessageActionsScreenContent({
246
257
  iconName="services.fileCopy"
247
258
  accessibilityHint="Copies text and links to clipboard"
248
259
  />
260
+ {showReportMessageAction && (
261
+ <FormSheet.Action
262
+ onPress={handleReportPress}
263
+ title="Report message"
264
+ iconName="chat.reportMessageO"
265
+ accessibilityHint="Opens a form to report this message"
266
+ />
267
+ )}
249
268
  {message?.mine && (
250
269
  <FormSheet.Action
251
270
  onPress={() => handleEditPress()}
@@ -306,7 +325,9 @@ const Reaction = ({
306
325
  android_ripple={{ color: androidRippleColor, borderless: false, foreground: true }}
307
326
  onPress={onPress}
308
327
  >
309
- <Text style={styles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
328
+ <Text style={styles.reactionEmoji} allowFontScaling={false}>
329
+ {REACTION_EMOJIS[reaction.value]}
330
+ </Text>
310
331
  </PlatformPressable>
311
332
  )
312
333
  }
@@ -316,7 +337,7 @@ const useStyles = () => {
316
337
  const fontScale = useFontScale({ maxFontSizeMultiplier: 1.3 })
317
338
 
318
339
  const btnBorderWidth = 1
319
- const baseSize = 46 * fontScale
340
+ const baseSize = 46 * Math.max(1, fontScale)
320
341
  const reactionBtnSize = Platform.select({
321
342
  ios: baseSize,
322
343
  android: baseSize + btnBorderWidth * 2,
@@ -346,6 +367,7 @@ const useStyles = () => {
346
367
  },
347
368
  reactionEmoji: {
348
369
  fontSize: 24,
370
+ textAlign: 'center',
349
371
  },
350
372
  actions: {
351
373
  paddingTop: 4,