@planningcenter/chat-react-native 3.7.0-rc.8 → 3.7.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.
- package/build/components/conversation/message_reaction.d.ts.map +1 -1
- package/build/components/conversation/message_reaction.js +18 -8
- package/build/components/conversation/message_reaction.js.map +1 -1
- package/build/components/display/button.d.ts +5 -5
- package/build/components/display/button.d.ts.map +1 -1
- package/build/components/display/button.js +9 -4
- package/build/components/display/button.js.map +1 -1
- package/build/components/display/icon.d.ts +1 -0
- package/build/components/display/icon.d.ts.map +1 -1
- package/build/components/display/icon.js +3 -0
- package/build/components/display/icon.js.map +1 -1
- package/build/components/primitive/form_sheet.d.ts +9 -2
- package/build/components/primitive/form_sheet.d.ts.map +1 -1
- package/build/components/primitive/form_sheet.js +48 -7
- package/build/components/primitive/form_sheet.js.map +1 -1
- package/build/screens/attachment_actions/attachment_actions_screen.d.ts.map +1 -1
- package/build/screens/attachment_actions/attachment_actions_screen.js +9 -3
- package/build/screens/attachment_actions/attachment_actions_screen.js.map +1 -1
- package/build/screens/conversation/message_read_receipts_screen.d.ts +1 -2
- package/build/screens/conversation/message_read_receipts_screen.d.ts.map +1 -1
- package/build/screens/conversation/message_read_receipts_screen.js +19 -40
- package/build/screens/conversation/message_read_receipts_screen.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +7 -7
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts +1 -2
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +35 -41
- package/build/screens/message_actions_screen.js.map +1 -1
- package/package.json +2 -2
- package/src/components/conversation/message_reaction.tsx +18 -8
- package/src/components/display/button.tsx +15 -9
- package/src/components/display/icon.tsx +3 -0
- package/src/components/primitive/form_sheet.tsx +74 -6
- package/src/screens/attachment_actions/attachment_actions_screen.tsx +14 -3
- package/src/screens/conversation/message_read_receipts_screen.tsx +22 -44
- package/src/screens/conversation_screen.tsx +11 -7
- package/src/screens/message_actions_screen.tsx +65 -50
|
@@ -64,13 +64,13 @@ export interface ButtonProps extends PressableProps {
|
|
|
64
64
|
*/
|
|
65
65
|
appearance?: ButtonAppearanceUnion
|
|
66
66
|
/**
|
|
67
|
-
* Styles the
|
|
67
|
+
* Styles the outer LinearGradient that gives the button its background and outline color
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
colorWrapperStyles?: ViewStyle
|
|
70
70
|
/**
|
|
71
|
-
* Styles the
|
|
71
|
+
* Styles the inner View that wraps the button's content
|
|
72
72
|
*/
|
|
73
|
-
|
|
73
|
+
contentWrapperStyles?: ViewStyle
|
|
74
74
|
/**
|
|
75
75
|
* Generates an icon to the left of the button text
|
|
76
76
|
*/
|
|
@@ -109,8 +109,8 @@ export function Button({
|
|
|
109
109
|
adjustsFontSizeToFit = false,
|
|
110
110
|
allowFontScaling = true,
|
|
111
111
|
appearance = 'interaction',
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
contentWrapperStyles,
|
|
113
|
+
colorWrapperStyles,
|
|
114
114
|
disabled = false,
|
|
115
115
|
iconNameLeft,
|
|
116
116
|
iconNameRight,
|
|
@@ -120,12 +120,14 @@ export function Button({
|
|
|
120
120
|
size = 'md',
|
|
121
121
|
title,
|
|
122
122
|
variant = 'fill',
|
|
123
|
+
style,
|
|
123
124
|
...props
|
|
124
125
|
}: ButtonProps) {
|
|
125
126
|
const styles = useStyles({ appearance, disabled, loading, maxFontSizeMultiplier, size, variant })
|
|
126
127
|
const gradientOptionsMap = useGradientColorMap()
|
|
127
128
|
const colorKey = getColorKey({ disabled, loading, appearance })
|
|
128
129
|
|
|
130
|
+
const overrideStyles = StyleSheet.flatten(style)
|
|
129
131
|
const textStyles = [styles.text, disabled && styles.textDisabled, loading && styles.iconLoading]
|
|
130
132
|
const iconStyles = [styles.icon, disabled && styles.iconDisabled, loading && styles.textLoading]
|
|
131
133
|
|
|
@@ -133,7 +135,11 @@ export function Button({
|
|
|
133
135
|
|
|
134
136
|
return (
|
|
135
137
|
<Pressable
|
|
136
|
-
style={({ pressed }) =>
|
|
138
|
+
style={({ pressed }) => ({
|
|
139
|
+
...styles.pressable,
|
|
140
|
+
...(pressed && platformPressedOpacityStyle),
|
|
141
|
+
...overrideStyles,
|
|
142
|
+
})}
|
|
137
143
|
accessibilityRole="button"
|
|
138
144
|
disabled={disabled || loading}
|
|
139
145
|
accessibilityState={{ busy: loading }}
|
|
@@ -144,7 +150,7 @@ export function Button({
|
|
|
144
150
|
start={{ x: 0.1, y: 0.1 }}
|
|
145
151
|
end={{ x: 0.9, y: 0.9 }}
|
|
146
152
|
colors={gradientOptionsMap[colorKey]}
|
|
147
|
-
style={[styles.colorWrapper,
|
|
153
|
+
style={[styles.colorWrapper, colorWrapperStyles]}
|
|
148
154
|
>
|
|
149
155
|
{loading && (
|
|
150
156
|
<Spinner
|
|
@@ -152,7 +158,7 @@ export function Button({
|
|
|
152
158
|
maxFontSizeMultiplier={maxFontSizeMultiplier || 0}
|
|
153
159
|
/>
|
|
154
160
|
)}
|
|
155
|
-
<View style={[styles.innerWrapper,
|
|
161
|
+
<View style={[styles.innerWrapper, contentWrapperStyles]}>
|
|
156
162
|
{iconNameLeft && (
|
|
157
163
|
<Icon
|
|
158
164
|
name={iconNameLeft}
|
|
@@ -5,6 +5,8 @@ import { SvgXml } from 'react-native-svg'
|
|
|
5
5
|
import type { XmlProps } from 'react-native-svg'
|
|
6
6
|
import { useFontScale, useTheme } from '../../hooks'
|
|
7
7
|
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import * as accounts from '@planningcenter/icons/paths/accounts'
|
|
8
10
|
// @ts-ignore
|
|
9
11
|
import * as api from '@planningcenter/icons/paths/api'
|
|
10
12
|
// @ts-ignore
|
|
@@ -33,6 +35,7 @@ import * as publishing from '@planningcenter/icons/paths/publishing'
|
|
|
33
35
|
const FALLBACK_SIZE = 12
|
|
34
36
|
|
|
35
37
|
const ICONS = {
|
|
38
|
+
accounts,
|
|
36
39
|
api,
|
|
37
40
|
brand,
|
|
38
41
|
calendar,
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
|
2
2
|
import React, { ReactNode } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
useWindowDimensions,
|
|
8
|
+
type AccessibilityRole,
|
|
9
|
+
} from 'react-native'
|
|
4
10
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
11
|
import { useTheme } from '../../hooks'
|
|
6
|
-
import { PlatformPressable } from '@react-navigation/elements'
|
|
7
|
-
import { Icon, IconString, Text } from '../display'
|
|
12
|
+
import { PlatformPressable, useHeaderHeight } from '@react-navigation/elements'
|
|
13
|
+
import { Heading, Icon, IconString, Text } from '../display'
|
|
8
14
|
|
|
9
15
|
// =================================
|
|
10
16
|
// ====== Exports ==================
|
|
@@ -34,15 +40,17 @@ export const getFormSheetScreenOptions = ({
|
|
|
34
40
|
const FormSheet = {
|
|
35
41
|
Root: FormSheetRoot,
|
|
36
42
|
Action: FormSheetAction,
|
|
43
|
+
Header: FormSheetHeader,
|
|
37
44
|
} as const
|
|
38
45
|
|
|
39
46
|
type FormSheetComponents = {
|
|
40
47
|
Root: React.FC<FormSheetRootProps>
|
|
41
48
|
Action: React.FC<FormSheetActionProps>
|
|
49
|
+
Header: React.FC<FormSheetHeaderProps>
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
export default FormSheet as FormSheetComponents
|
|
45
|
-
export type { FormSheetRootProps, FormSheetActionProps }
|
|
53
|
+
export type { FormSheetRootProps, FormSheetActionProps, FormSheetHeaderProps }
|
|
46
54
|
|
|
47
55
|
// ====================================
|
|
48
56
|
// ====== ActionsFormSheetRoot ========
|
|
@@ -56,7 +64,7 @@ export function FormSheetRoot({ children }: FormSheetRootProps) {
|
|
|
56
64
|
const styles = useStyles()
|
|
57
65
|
|
|
58
66
|
return (
|
|
59
|
-
<View style={styles.container}>
|
|
67
|
+
<View style={styles.container} collapsable={false}>
|
|
60
68
|
<AndroidSheetGrabber />
|
|
61
69
|
<View style={styles.content}>{children}</View>
|
|
62
70
|
</View>
|
|
@@ -78,6 +86,36 @@ function AndroidSheetGrabber() {
|
|
|
78
86
|
})
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
// ====================================
|
|
90
|
+
// ====== FormSheetHeader =============
|
|
91
|
+
// ====================================
|
|
92
|
+
|
|
93
|
+
interface FormSheetHeaderProps {
|
|
94
|
+
title: string
|
|
95
|
+
secondaryButton?: ReactNode
|
|
96
|
+
primaryButton?: ReactNode
|
|
97
|
+
}
|
|
98
|
+
function FormSheetHeader({ title, secondaryButton, primaryButton }: FormSheetHeaderProps) {
|
|
99
|
+
const styles = useStyles()
|
|
100
|
+
const hasActions = Boolean(secondaryButton) || Boolean(primaryButton)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<View style={styles.header}>
|
|
104
|
+
<Heading variant="h3" style={styles.headerTitle}>
|
|
105
|
+
{title}
|
|
106
|
+
</Heading>
|
|
107
|
+
{hasActions && (
|
|
108
|
+
<View style={styles.headerActions}>
|
|
109
|
+
{secondaryButton}
|
|
110
|
+
{primaryButton}
|
|
111
|
+
</View>
|
|
112
|
+
)}
|
|
113
|
+
</View>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
FormSheetHeader.displayName = 'FormSheet.Header'
|
|
118
|
+
|
|
81
119
|
// ====================================
|
|
82
120
|
// ====== ActionsFormSheetAction ======
|
|
83
121
|
// ====================================
|
|
@@ -98,6 +136,7 @@ interface FormSheetActionProps {
|
|
|
98
136
|
accessibilityHint?: string
|
|
99
137
|
accessibilityRole?: AccessibilityRole
|
|
100
138
|
appearance?: FormSheetActionAppearanceUnion
|
|
139
|
+
disabled?: boolean
|
|
101
140
|
}
|
|
102
141
|
|
|
103
142
|
function FormSheetAction({
|
|
@@ -108,6 +147,7 @@ function FormSheetAction({
|
|
|
108
147
|
accessibilityHint,
|
|
109
148
|
accessibilityRole = 'button',
|
|
110
149
|
appearance = 'neutral',
|
|
150
|
+
disabled = false,
|
|
111
151
|
}: FormSheetActionProps) {
|
|
112
152
|
const styles = useStyles({ appearance })
|
|
113
153
|
|
|
@@ -118,6 +158,7 @@ function FormSheetAction({
|
|
|
118
158
|
accessibilityHint={accessibilityHint}
|
|
119
159
|
accessibilityRole={accessibilityRole}
|
|
120
160
|
style={styles.actionPressable}
|
|
161
|
+
disabled={disabled}
|
|
121
162
|
>
|
|
122
163
|
<Icon name={iconName} size={16} accessibilityElementsHidden style={styles.actionIcon} />
|
|
123
164
|
<Text style={styles.actionTitle}>{title}</Text>
|
|
@@ -137,7 +178,14 @@ interface Styles {
|
|
|
137
178
|
|
|
138
179
|
const useStyles = ({ appearance = 'neutral' }: Styles = {}) => {
|
|
139
180
|
const { colors } = useTheme()
|
|
140
|
-
const {
|
|
181
|
+
const { height } = useWindowDimensions()
|
|
182
|
+
const { bottom, top } = useSafeAreaInsets()
|
|
183
|
+
const headerHeight = useHeaderHeight()
|
|
184
|
+
|
|
185
|
+
const containerHeight = Platform.select({
|
|
186
|
+
ios: height - top - headerHeight,
|
|
187
|
+
default: null,
|
|
188
|
+
})
|
|
141
189
|
|
|
142
190
|
const appearanceColorsMap = {
|
|
143
191
|
neutral: {
|
|
@@ -157,6 +205,7 @@ const useStyles = ({ appearance = 'neutral' }: Styles = {}) => {
|
|
|
157
205
|
paddingBottom: bottom,
|
|
158
206
|
width: '100%',
|
|
159
207
|
backgroundColor: colors.fillColorNeutral100Inverted,
|
|
208
|
+
height: containerHeight,
|
|
160
209
|
},
|
|
161
210
|
androidSheetGrabber: {
|
|
162
211
|
marginTop: 5,
|
|
@@ -169,6 +218,25 @@ const useStyles = ({ appearance = 'neutral' }: Styles = {}) => {
|
|
|
169
218
|
content: {
|
|
170
219
|
paddingTop: Platform.select({ android: 16, ios: 20 }),
|
|
171
220
|
},
|
|
221
|
+
header: {
|
|
222
|
+
backgroundColor: colors.fillColorNeutral100Inverted,
|
|
223
|
+
paddingHorizontal: 16,
|
|
224
|
+
paddingBottom: 16,
|
|
225
|
+
gap: 16,
|
|
226
|
+
borderBottomWidth: 1,
|
|
227
|
+
borderBottomColor: colors.borderColorDefaultBase,
|
|
228
|
+
flexDirection: 'row',
|
|
229
|
+
alignItems: 'center',
|
|
230
|
+
justifyContent: 'space-between',
|
|
231
|
+
},
|
|
232
|
+
headerTitle: {
|
|
233
|
+
textAlign: 'left',
|
|
234
|
+
},
|
|
235
|
+
headerActions: {
|
|
236
|
+
flexDirection: 'row',
|
|
237
|
+
alignItems: 'center',
|
|
238
|
+
gap: 16,
|
|
239
|
+
},
|
|
172
240
|
actionPressable: {
|
|
173
241
|
flexDirection: 'row',
|
|
174
242
|
alignItems: 'center',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { Linking } from 'react-native'
|
|
2
|
+
import React, { useCallback } from 'react'
|
|
3
|
+
import { Alert, Linking } from 'react-native'
|
|
4
4
|
import FormSheet, { getFormSheetScreenOptions } from '../../components/primitive/form_sheet'
|
|
5
5
|
import { useDeleteAttachment } from './hooks/useDeleteAttachment'
|
|
6
6
|
|
|
@@ -37,6 +37,17 @@ export function AttachmentActionsScreen({ route }: AttachmentActionsScreenProps)
|
|
|
37
37
|
attachmentName,
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
const handleDeleteConfirm = useCallback(() => {
|
|
41
|
+
Alert.alert(
|
|
42
|
+
`Delete ${attachmentName}`,
|
|
43
|
+
'Are you sure you want to permanently delete this attachment?',
|
|
44
|
+
[
|
|
45
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
46
|
+
{ text: 'Delete', style: 'destructive', onPress: () => handleDeleteAttachment() },
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
}, [attachmentName, handleDeleteAttachment])
|
|
50
|
+
|
|
40
51
|
const handleOpenInBrowser = () => {
|
|
41
52
|
Linking.openURL(attachmentUrl)
|
|
42
53
|
navigation.goBack()
|
|
@@ -57,7 +68,7 @@ export function AttachmentActionsScreen({ route }: AttachmentActionsScreenProps)
|
|
|
57
68
|
appearance="danger"
|
|
58
69
|
iconName="publishing.trash"
|
|
59
70
|
title={`Delete ${attachmentName}`}
|
|
60
|
-
onPress={
|
|
71
|
+
onPress={handleDeleteConfirm}
|
|
61
72
|
/>
|
|
62
73
|
)}
|
|
63
74
|
</FormSheet.Root>
|
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { StaticScreenProps } from '@react-navigation/native'
|
|
3
|
-
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
|
1
|
+
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
4
2
|
import React, { memo } from 'react'
|
|
5
|
-
import { Platform, StyleSheet,
|
|
3
|
+
import { Platform, StyleSheet, View } from 'react-native'
|
|
6
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
7
|
-
import { Avatar,
|
|
8
|
-
import { useTheme } from '../../hooks'
|
|
5
|
+
import { Avatar, Text, TextButton } from '../../components'
|
|
9
6
|
import { useReadReceipts } from '../../hooks/use_read_receipts'
|
|
10
7
|
import { FlatList } from 'react-native-gesture-handler'
|
|
11
8
|
import { ReadReceiptResource } from '../../types/resources/read_receipt'
|
|
9
|
+
import FormSheet, { getFormSheetScreenOptions } from '../../components/primitive/form_sheet'
|
|
10
|
+
import { useTheme } from '../../hooks'
|
|
12
11
|
|
|
13
|
-
export const MessageReadReceiptsScreenOptions
|
|
14
|
-
presentation: 'formSheet',
|
|
12
|
+
export const MessageReadReceiptsScreenOptions = getFormSheetScreenOptions({
|
|
15
13
|
sheetAllowedDetents: Platform.select({
|
|
16
|
-
android: [0.
|
|
17
|
-
default: [0.
|
|
14
|
+
android: [0.5, 0.94], // Going straight to full 0.94 preserves height of screen on Android
|
|
15
|
+
default: [0.5, 1],
|
|
18
16
|
}),
|
|
19
|
-
sheetGrabberVisible: true,
|
|
20
|
-
headerShown: false,
|
|
21
|
-
sheetCornerRadius: 16,
|
|
22
17
|
headerTitle: 'Read receipts',
|
|
23
|
-
}
|
|
18
|
+
})
|
|
24
19
|
|
|
25
20
|
export type MessageReadReceiptsScreenProps = StaticScreenProps<{
|
|
26
21
|
conversation_id: number
|
|
@@ -29,6 +24,7 @@ export type MessageReadReceiptsScreenProps = StaticScreenProps<{
|
|
|
29
24
|
|
|
30
25
|
export function MessageReadReceiptsScreen({ route }: MessageReadReceiptsScreenProps) {
|
|
31
26
|
const styles = useStyles()
|
|
27
|
+
const navigation = useNavigation()
|
|
32
28
|
|
|
33
29
|
const { conversation_id, message_id } = route.params
|
|
34
30
|
const {
|
|
@@ -38,22 +34,22 @@ export function MessageReadReceiptsScreen({ route }: MessageReadReceiptsScreenPr
|
|
|
38
34
|
} = useReadReceipts({ conversation_id, message_id })
|
|
39
35
|
|
|
40
36
|
return (
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
Read receipts ({totalCount})
|
|
44
|
-
|
|
37
|
+
<FormSheet.Root>
|
|
38
|
+
<FormSheet.Header
|
|
39
|
+
title={`Read receipts (${totalCount})`}
|
|
40
|
+
secondaryButton={<TextButton onPress={() => navigation.goBack()}>Close</TextButton>}
|
|
41
|
+
/>
|
|
45
42
|
<FlatList
|
|
46
43
|
data={receipts}
|
|
47
|
-
|
|
44
|
+
contentContainerStyle={styles.contentContainer}
|
|
48
45
|
keyExtractor={item => item.id.toString()}
|
|
49
46
|
renderItem={({ item }) => <Receipt receipt={item} />}
|
|
50
47
|
nestedScrollEnabled
|
|
51
|
-
ListFooterComponent={<View style={styles.footer} />}
|
|
52
48
|
ListEmptyComponent={<Text style={styles.emptyText}>No one has read this message yet.</Text>}
|
|
53
49
|
onEndReached={() => fetchNextPage()}
|
|
54
50
|
onEndReachedThreshold={0.2}
|
|
55
51
|
/>
|
|
56
|
-
</
|
|
52
|
+
</FormSheet.Root>
|
|
57
53
|
)
|
|
58
54
|
}
|
|
59
55
|
|
|
@@ -71,29 +67,14 @@ const Receipt = memo(({ receipt }: { receipt: ReadReceiptResource }) => {
|
|
|
71
67
|
})
|
|
72
68
|
|
|
73
69
|
const useStyles = () => {
|
|
74
|
-
const
|
|
75
|
-
const {
|
|
76
|
-
const { bottom, top } = useSafeAreaInsets()
|
|
77
|
-
const headerHeight = useHeaderHeight()
|
|
78
|
-
|
|
79
|
-
const containerHeight = Platform.select({
|
|
80
|
-
android: null,
|
|
81
|
-
ios: height - top - headerHeight,
|
|
82
|
-
})
|
|
70
|
+
const { bottom } = useSafeAreaInsets()
|
|
71
|
+
const { colors } = useTheme()
|
|
83
72
|
|
|
84
73
|
return StyleSheet.create({
|
|
85
|
-
container: {
|
|
86
|
-
paddingTop: 16,
|
|
87
|
-
backgroundColor: theme.colors.fillColorNeutral100Inverted,
|
|
88
|
-
height: containerHeight,
|
|
89
|
-
flex: 1,
|
|
90
|
-
},
|
|
91
74
|
contentContainer: {
|
|
92
75
|
paddingHorizontal: 16,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
paddingHorizontal: 16,
|
|
96
|
-
paddingBottom: 8,
|
|
76
|
+
paddingTop: 8,
|
|
77
|
+
paddingBottom: bottom + Platform.select({ android: 24, default: 16 }),
|
|
97
78
|
},
|
|
98
79
|
receiptRow: {
|
|
99
80
|
flexDirection: 'row',
|
|
@@ -105,12 +86,9 @@ const useStyles = () => {
|
|
|
105
86
|
name: {
|
|
106
87
|
flex: 1,
|
|
107
88
|
},
|
|
108
|
-
footer: {
|
|
109
|
-
height: bottom + 16,
|
|
110
|
-
},
|
|
111
89
|
emptyText: {
|
|
112
90
|
textAlign: 'center',
|
|
113
|
-
color:
|
|
91
|
+
color: colors.textColorDefaultSecondary,
|
|
114
92
|
marginTop: 32,
|
|
115
93
|
fontSize: 16,
|
|
116
94
|
},
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
MemberDisabledRepliesBanner,
|
|
19
19
|
} from '../components/conversation/disabled_replies_banners'
|
|
20
20
|
import { EmptyConversationBlankState } from '../components/conversation/empty_conversation_blank_state'
|
|
21
|
+
import { BlankState } from '../components/display/blank_state'
|
|
21
22
|
import { Message } from '../components/conversation/message'
|
|
22
23
|
import { MessageForm } from '../components/conversation/message_form'
|
|
23
24
|
import { TypingIndicator } from '../components/conversation/typing_indicator'
|
|
@@ -93,9 +94,16 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
93
94
|
if (!conversation || conversation.deleted) {
|
|
94
95
|
return (
|
|
95
96
|
<View style={styles.container}>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
<BlankState
|
|
98
|
+
iconName="general.outlinedTextMessage"
|
|
99
|
+
title="This conversation has been deleted"
|
|
100
|
+
buttonProps={{
|
|
101
|
+
onPress: navigation.goBack,
|
|
102
|
+
title: 'Back to conversations',
|
|
103
|
+
accessibilityHint: 'Navigates back to the conversations list',
|
|
104
|
+
accessibilityRole: 'link',
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
99
107
|
</View>
|
|
100
108
|
)
|
|
101
109
|
}
|
|
@@ -310,10 +318,6 @@ const useStyles = () => {
|
|
|
310
318
|
// Just whitespace to provide space where the typing indicator can be
|
|
311
319
|
height: 16,
|
|
312
320
|
},
|
|
313
|
-
deletedAlert: {
|
|
314
|
-
textAlign: 'center',
|
|
315
|
-
padding: 16,
|
|
316
|
-
},
|
|
317
321
|
})
|
|
318
322
|
}
|
|
319
323
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
2
|
import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
3
|
-
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
|
|
4
3
|
import { useMutation } from '@tanstack/react-query'
|
|
5
4
|
import React, { useCallback } from 'react'
|
|
6
|
-
import { Alert, Platform, StyleSheet,
|
|
7
|
-
import {
|
|
8
|
-
import { Text, TextButton } from '../components'
|
|
5
|
+
import { Alert, Platform, StyleSheet, View } from 'react-native'
|
|
6
|
+
import { Text } from '../components'
|
|
9
7
|
import { REACTION_EMOJIS, useReactionStyles } from '../components/conversation/message_reaction'
|
|
10
8
|
import { useTheme } from '../hooks'
|
|
11
9
|
import { useApiClient } from '../hooks/use_api_client'
|
|
@@ -14,13 +12,12 @@ import { useMessageReactionToggle } from '../hooks/use_message_reaction_toggle'
|
|
|
14
12
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
15
13
|
import { Clipboard } from '../utils/native_adapters'
|
|
16
14
|
import { isNil, omitBy } from 'lodash'
|
|
15
|
+
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
17
16
|
|
|
18
|
-
export const MessageActionsScreenOptions
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
sheetGrabberVisible: true,
|
|
23
|
-
}
|
|
17
|
+
export const MessageActionsScreenOptions = getFormSheetScreenOptions({
|
|
18
|
+
sheetAllowedDetents: [0.5],
|
|
19
|
+
headerTitle: 'Message actions',
|
|
20
|
+
})
|
|
24
21
|
|
|
25
22
|
export type MessageActionsScreenProps = StaticScreenProps<{
|
|
26
23
|
message_id: string
|
|
@@ -92,6 +89,13 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
92
89
|
},
|
|
93
90
|
})
|
|
94
91
|
|
|
92
|
+
const handleDeleteConfirm = useCallback(() => {
|
|
93
|
+
Alert.alert('Delete message', 'Are you sure you want to permanently delete this message?', [
|
|
94
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
95
|
+
{ text: 'Delete', style: 'destructive', onPress: () => handleDeleteMessage() },
|
|
96
|
+
])
|
|
97
|
+
}, [handleDeleteMessage])
|
|
98
|
+
|
|
95
99
|
const handleEditPress = useCallback(() => {
|
|
96
100
|
const state = navigation.getState?.()
|
|
97
101
|
const targetRoute = state?.routes?.find(r => r.name === 'Conversation')
|
|
@@ -118,7 +122,7 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
118
122
|
}, [navigation, conversation_id, message_id])
|
|
119
123
|
|
|
120
124
|
return (
|
|
121
|
-
<
|
|
125
|
+
<FormSheet.Root>
|
|
122
126
|
<View style={styles.reactionList}>
|
|
123
127
|
{availableReactions.map((reaction, index) => (
|
|
124
128
|
<Reaction
|
|
@@ -134,26 +138,40 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
134
138
|
))}
|
|
135
139
|
</View>
|
|
136
140
|
<View style={styles.actions}>
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
141
|
+
<FormSheet.Action
|
|
142
|
+
onPress={handleCopyPress}
|
|
143
|
+
title="Copy text"
|
|
144
|
+
iconName="services.fileCopy"
|
|
145
|
+
accessibilityHint="Copies text and links to clipboard"
|
|
146
|
+
/>
|
|
147
|
+
{message?.mine && (
|
|
148
|
+
<FormSheet.Action
|
|
149
|
+
onPress={() => handleEditPress()}
|
|
150
|
+
title="Edit message"
|
|
151
|
+
iconName="accounts.editor"
|
|
152
|
+
accessibilityHint="Opens existing text in the message form input."
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
{message?.mine && (
|
|
156
|
+
<FormSheet.Action
|
|
157
|
+
onPress={() => handleViewReadReceiptsPress()}
|
|
158
|
+
title="View read receipts"
|
|
159
|
+
iconName="general.checkPerson"
|
|
160
|
+
accessibilityHint="Opens a modal with a list of people who read your message."
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
{(message?.mine || canDeleteNonAuthoredMessages) && (
|
|
164
|
+
<FormSheet.Action
|
|
165
|
+
onPress={() => handleDeleteConfirm()}
|
|
166
|
+
title="Delete message"
|
|
167
|
+
iconName="publishing.trash"
|
|
168
|
+
appearance="danger"
|
|
169
|
+
disabled={isPending}
|
|
170
|
+
accessibilityHint="Opens a confirmation alert to delete this message permanently."
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
155
173
|
</View>
|
|
156
|
-
</
|
|
174
|
+
</FormSheet.Root>
|
|
157
175
|
)
|
|
158
176
|
}
|
|
159
177
|
|
|
@@ -173,15 +191,14 @@ const Reaction = ({
|
|
|
173
191
|
style={[reactionStyles.reaction, styles.reaction]}
|
|
174
192
|
onPress={onPress}
|
|
175
193
|
>
|
|
176
|
-
<Text style={
|
|
194
|
+
<Text style={styles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
|
|
177
195
|
</PlatformPressable>
|
|
178
196
|
)
|
|
179
197
|
}
|
|
180
198
|
|
|
181
199
|
const useStyles = () => {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const { bottom } = useSafeAreaInsets()
|
|
200
|
+
const { colors } = useTheme()
|
|
201
|
+
|
|
185
202
|
const btnBorderWidth = 1
|
|
186
203
|
const baseSize = 44
|
|
187
204
|
const reactionBtnSize = Platform.select({
|
|
@@ -190,13 +207,15 @@ const useStyles = () => {
|
|
|
190
207
|
})
|
|
191
208
|
|
|
192
209
|
return StyleSheet.create({
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
reactionList: {
|
|
211
|
+
flexDirection: 'row',
|
|
212
|
+
justifyContent: 'center',
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
gap: 16,
|
|
215
|
+
paddingTop: 8,
|
|
216
|
+
paddingBottom: 16,
|
|
217
|
+
borderBottomColor: colors.borderColorDefaultBase,
|
|
218
|
+
borderBottomWidth: 1,
|
|
200
219
|
},
|
|
201
220
|
reaction: {
|
|
202
221
|
height: reactionBtnSize,
|
|
@@ -205,15 +224,11 @@ const useStyles = () => {
|
|
|
205
224
|
borderRadius: 32,
|
|
206
225
|
justifyContent: 'center',
|
|
207
226
|
},
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
borderBottomColor: theme.colors.fillColorNeutral040,
|
|
214
|
-
borderBottomWidth: 1,
|
|
227
|
+
reactionEmoji: {
|
|
228
|
+
fontSize: 24,
|
|
229
|
+
},
|
|
230
|
+
actions: {
|
|
231
|
+
paddingTop: 4,
|
|
215
232
|
},
|
|
216
|
-
actions: { flex: 1 },
|
|
217
|
-
actionButton: { padding: 12, paddingBottom: 100, gap: 12 },
|
|
218
233
|
})
|
|
219
234
|
}
|