@planningcenter/chat-react-native 3.32.1-rc.1 → 3.33.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_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +22 -1
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/display/emoji_avatar.d.ts.map +1 -1
- package/build/components/display/emoji_avatar.js +2 -0
- package/build/components/display/emoji_avatar.js.map +1 -1
- package/build/components/display/icon_avatar.d.ts.map +1 -1
- package/build/components/display/icon_avatar.js +2 -0
- package/build/components/display/icon_avatar.js.map +1 -1
- package/build/components/display/utils/avatar_gradient_colors.d.ts +3 -0
- package/build/components/display/utils/avatar_gradient_colors.d.ts.map +1 -1
- package/build/components/display/utils/avatar_gradient_colors.js +8 -3
- package/build/components/display/utils/avatar_gradient_colors.js.map +1 -1
- package/build/components/primitive/avatar_primitive.d.ts +3 -1
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +10 -2
- package/build/components/primitive/avatar_primitive.js.map +1 -1
- package/build/hooks/attachments/fallback_chat_configuration.d.ts +4 -0
- package/build/hooks/attachments/fallback_chat_configuration.d.ts.map +1 -0
- package/build/hooks/attachments/fallback_chat_configuration.js +59 -0
- package/build/hooks/attachments/fallback_chat_configuration.js.map +1 -0
- package/build/hooks/groups/use_groups_conversation_create.d.ts.map +1 -1
- package/build/hooks/groups/use_groups_conversation_create.js +1 -1
- package/build/hooks/groups/use_groups_conversation_create.js.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts +43 -11
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.js +5 -5
- package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
- package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
- package/build/hooks/use_attachment_uploader.js +39 -14
- package/build/hooks/use_attachment_uploader.js.map +1 -1
- package/build/hooks/use_chat_configuration.d.ts +6 -0
- package/build/hooks/use_chat_configuration.d.ts.map +1 -0
- package/build/hooks/use_chat_configuration.js +41 -0
- package/build/hooks/use_chat_configuration.js.map +1 -0
- package/build/hooks/use_conversation_avatar_update.d.ts +26 -0
- package/build/hooks/use_conversation_avatar_update.d.ts.map +1 -0
- package/build/hooks/use_conversation_avatar_update.js +130 -0
- package/build/hooks/use_conversation_avatar_update.js.map +1 -0
- package/build/hooks/use_features.d.ts +1 -0
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +1 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/navigation/index.d.ts +16 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +9 -0
- package/build/navigation/index.js.map +1 -1
- package/build/screens/avatar_picker/avatar_picker_screen.d.ts +12 -0
- package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -0
- package/build/screens/avatar_picker/avatar_picker_screen.js +193 -0
- package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -0
- package/build/screens/avatar_picker/avatar_picker_state.d.ts +38 -0
- package/build/screens/avatar_picker/avatar_picker_state.d.ts.map +1 -0
- package/build/screens/avatar_picker/avatar_picker_state.js +101 -0
- package/build/screens/avatar_picker/avatar_picker_state.js.map +1 -0
- package/build/screens/avatar_picker/avatar_preview.d.ts +9 -0
- package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -0
- package/build/screens/avatar_picker/avatar_preview.js +39 -0
- package/build/screens/avatar_picker/avatar_preview.js.map +1 -0
- package/build/screens/avatar_picker/color_picker.d.ts +9 -0
- package/build/screens/avatar_picker/color_picker.d.ts.map +1 -0
- package/build/screens/avatar_picker/color_picker.js +53 -0
- package/build/screens/avatar_picker/color_picker.js.map +1 -0
- package/build/screens/avatar_picker/constants.d.ts +3 -0
- package/build/screens/avatar_picker/constants.d.ts.map +1 -0
- package/build/screens/avatar_picker/constants.js +53 -0
- package/build/screens/avatar_picker/constants.js.map +1 -0
- package/build/screens/avatar_picker/emoji_tab.d.ts +7 -0
- package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -0
- package/build/screens/avatar_picker/emoji_tab.js +55 -0
- package/build/screens/avatar_picker/emoji_tab.js.map +1 -0
- package/build/screens/avatar_picker/icon_grid.d.ts +8 -0
- package/build/screens/avatar_picker/icon_grid.d.ts.map +1 -0
- package/build/screens/avatar_picker/icon_grid.js +48 -0
- package/build/screens/avatar_picker/icon_grid.js.map +1 -0
- package/build/screens/avatar_picker/upload_tab.d.ts +9 -0
- package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -0
- package/build/screens/avatar_picker/upload_tab.js +39 -0
- package/build/screens/avatar_picker/upload_tab.js.map +1 -0
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +37 -1
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_new/components/avatar_selection_row.d.ts +12 -0
- package/build/screens/conversation_new/components/avatar_selection_row.d.ts.map +1 -0
- package/build/screens/conversation_new/components/avatar_selection_row.js +60 -0
- package/build/screens/conversation_new/components/avatar_selection_row.js.map +1 -0
- package/build/screens/conversation_new/components/gender_filter_toggle.d.ts.map +1 -1
- package/build/screens/conversation_new/components/gender_filter_toggle.js +3 -9
- package/build/screens/conversation_new/components/gender_filter_toggle.js.map +1 -1
- package/build/screens/conversation_new/components/groups_form.d.ts +3 -1
- package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/groups_form.js +22 -8
- package/build/screens/conversation_new/components/groups_form.js.map +1 -1
- package/build/screens/conversation_new/components/services_form.d.ts +3 -1
- package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/services_form.js +22 -8
- package/build/screens/conversation_new/components/services_form.js.map +1 -1
- package/build/screens/conversation_new/conversation_new_screen.d.ts +2 -0
- package/build/screens/conversation_new/conversation_new_screen.d.ts.map +1 -1
- package/build/screens/conversation_new/conversation_new_screen.js +3 -3
- package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
- package/build/screens/team_conversation_screen.d.ts.map +1 -1
- package/build/screens/team_conversation_screen.js +1 -1
- package/build/screens/team_conversation_screen.js.map +1 -1
- package/build/types/resources/chat_configuration_resource.d.ts +8 -0
- package/build/types/resources/chat_configuration_resource.d.ts.map +1 -0
- package/build/types/resources/chat_configuration_resource.js +2 -0
- package/build/types/resources/chat_configuration_resource.js.map +1 -0
- package/build/utils/native_adapters/configuration.d.ts +3 -0
- package/build/utils/native_adapters/configuration.d.ts.map +1 -1
- package/build/utils/native_adapters/configuration.js +8 -0
- package/build/utils/native_adapters/configuration.js.map +1 -1
- package/build/utils/native_adapters/document_picker.d.ts +21 -0
- package/build/utils/native_adapters/document_picker.d.ts.map +1 -0
- package/build/utils/native_adapters/document_picker.js +7 -0
- package/build/utils/native_adapters/document_picker.js.map +1 -0
- package/build/utils/native_adapters/image_picker.d.ts +7 -1
- package/build/utils/native_adapters/image_picker.d.ts.map +1 -1
- package/build/utils/native_adapters/image_picker.js.map +1 -1
- package/build/utils/native_adapters/index.d.ts +1 -0
- package/build/utils/native_adapters/index.d.ts.map +1 -1
- package/build/utils/native_adapters/index.js +1 -0
- package/build/utils/native_adapters/index.js.map +1 -1
- package/build/utils/request/get_chat_configuration.d.ts +10 -0
- package/build/utils/request/get_chat_configuration.d.ts.map +1 -0
- package/build/utils/request/get_chat_configuration.js +21 -0
- package/build/utils/request/get_chat_configuration.js.map +1 -0
- package/package.json +4 -3
- package/src/__tests__/hooks/use_attachment_uploader.test.tsx +219 -0
- package/src/__tests__/hooks/use_chat_configuration.test.tsx +80 -0
- package/src/__tests__/utils/native_adapters/configuration.ts +25 -1
- package/src/components/conversation/message_form.tsx +39 -1
- package/src/components/display/emoji_avatar.tsx +7 -2
- package/src/components/display/icon_avatar.tsx +7 -2
- package/src/components/display/utils/avatar_gradient_colors.ts +10 -3
- package/src/components/primitive/avatar_primitive.tsx +11 -2
- package/src/hooks/attachments/fallback_chat_configuration.ts +61 -0
- package/src/hooks/groups/use_groups_conversation_create.ts +2 -1
- package/src/hooks/services/use_find_or_create_services_conversation.ts +7 -7
- package/src/hooks/use_attachment_uploader.ts +39 -15
- package/src/hooks/use_chat_configuration.ts +54 -0
- package/src/hooks/use_conversation_avatar_update.ts +163 -0
- package/src/hooks/use_features.ts +1 -0
- package/src/navigation/index.tsx +13 -0
- package/src/screens/avatar_picker/__tests__/avatar_picker_state.test.ts +157 -0
- package/src/screens/avatar_picker/avatar_picker_screen.tsx +312 -0
- package/src/screens/avatar_picker/avatar_picker_state.ts +141 -0
- package/src/screens/avatar_picker/avatar_preview.tsx +46 -0
- package/src/screens/avatar_picker/color_picker.tsx +91 -0
- package/src/screens/avatar_picker/constants.ts +53 -0
- package/src/screens/avatar_picker/emoji_tab.tsx +76 -0
- package/src/screens/avatar_picker/icon_grid.tsx +81 -0
- package/src/screens/avatar_picker/upload_tab.tsx +62 -0
- package/src/screens/conversation_details_screen.tsx +60 -1
- package/src/screens/conversation_new/components/avatar_selection_row.tsx +82 -0
- package/src/screens/conversation_new/components/gender_filter_toggle.tsx +3 -9
- package/src/screens/conversation_new/components/groups_form.tsx +33 -6
- package/src/screens/conversation_new/components/services_form.tsx +37 -6
- package/src/screens/conversation_new/conversation_new_screen.tsx +17 -3
- package/src/screens/team_conversation_screen.tsx +2 -1
- package/src/types/resources/chat_configuration_resource.ts +11 -0
- package/src/utils/native_adapters/configuration.ts +10 -0
- package/src/utils/native_adapters/document_picker.ts +26 -0
- package/src/utils/native_adapters/image_picker.ts +8 -1
- package/src/utils/native_adapters/index.ts +1 -0
- package/src/utils/request/get_chat_configuration.ts +23 -0
- package/build/hooks/attachments/supported_extensions.d.ts +0 -2
- package/build/hooks/attachments/supported_extensions.d.ts.map +0 -1
- package/build/hooks/attachments/supported_extensions.js +0 -48
- package/build/hooks/attachments/supported_extensions.js.map +0 -1
- package/src/hooks/attachments/supported_extensions.ts +0 -47
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
|
+
import React, { useCallback } from 'react'
|
|
3
|
+
import { FlatList, StyleSheet, useWindowDimensions } from 'react-native'
|
|
4
|
+
import LinearGradient from 'react-native-linear-gradient'
|
|
5
|
+
import {
|
|
6
|
+
COLOR_KEYS,
|
|
7
|
+
type CustomAvatarColorKey,
|
|
8
|
+
getAvatarGradientProps,
|
|
9
|
+
} from '../../components/display/utils/avatar_gradient_colors'
|
|
10
|
+
import { useTheme } from '../../hooks'
|
|
11
|
+
import { GRID_COLUMNS } from './constants'
|
|
12
|
+
|
|
13
|
+
interface ColorPickerProps {
|
|
14
|
+
selectedColor: string
|
|
15
|
+
onColorSelect: (color: CustomAvatarColorKey) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ColorPicker({ selectedColor, onColorSelect }: ColorPickerProps) {
|
|
19
|
+
const styles = useStyles()
|
|
20
|
+
|
|
21
|
+
const renderItem = useCallback(
|
|
22
|
+
({ item }: { item: CustomAvatarColorKey }) => {
|
|
23
|
+
const gradientProps = getAvatarGradientProps(item)
|
|
24
|
+
const isSelected = item === selectedColor
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<PlatformPressable
|
|
28
|
+
onPress={() => onColorSelect(item)}
|
|
29
|
+
style={[styles.swatchOuter, isSelected && styles.swatchSelected]}
|
|
30
|
+
accessibilityRole="button"
|
|
31
|
+
accessibilityLabel={`${item.replace(/-/g, ' ')} color`}
|
|
32
|
+
accessibilityState={{ selected: isSelected }}
|
|
33
|
+
>
|
|
34
|
+
<LinearGradient {...gradientProps} style={styles.swatch} />
|
|
35
|
+
</PlatformPressable>
|
|
36
|
+
)
|
|
37
|
+
},
|
|
38
|
+
[selectedColor, onColorSelect, styles.swatch, styles.swatchOuter, styles.swatchSelected]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<FlatList
|
|
43
|
+
data={COLOR_KEYS}
|
|
44
|
+
numColumns={GRID_COLUMNS}
|
|
45
|
+
keyExtractor={item => item}
|
|
46
|
+
renderItem={renderItem}
|
|
47
|
+
contentContainerStyle={styles.grid}
|
|
48
|
+
columnWrapperStyle={styles.row}
|
|
49
|
+
scrollEnabled={false}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const PADDING = 16
|
|
55
|
+
const GAP = 8
|
|
56
|
+
|
|
57
|
+
const useStyles = () => {
|
|
58
|
+
const { colors } = useTheme()
|
|
59
|
+
const { width: screenWidth } = useWindowDimensions()
|
|
60
|
+
const swatchSize = Math.floor(
|
|
61
|
+
(screenWidth - PADDING * 2 - (GRID_COLUMNS - 1) * GAP) / GRID_COLUMNS
|
|
62
|
+
)
|
|
63
|
+
const innerSize = swatchSize - 6
|
|
64
|
+
|
|
65
|
+
return StyleSheet.create({
|
|
66
|
+
grid: {
|
|
67
|
+
paddingHorizontal: PADDING,
|
|
68
|
+
paddingVertical: 12,
|
|
69
|
+
},
|
|
70
|
+
row: {
|
|
71
|
+
gap: GAP,
|
|
72
|
+
marginBottom: GAP,
|
|
73
|
+
},
|
|
74
|
+
swatchOuter: {
|
|
75
|
+
width: swatchSize,
|
|
76
|
+
height: swatchSize,
|
|
77
|
+
borderRadius: swatchSize / 2,
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
justifyContent: 'center',
|
|
80
|
+
},
|
|
81
|
+
swatchSelected: {
|
|
82
|
+
borderWidth: 3,
|
|
83
|
+
borderColor: colors.interaction,
|
|
84
|
+
},
|
|
85
|
+
swatch: {
|
|
86
|
+
width: innerSize,
|
|
87
|
+
height: innerSize,
|
|
88
|
+
borderRadius: innerSize / 2,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const AVATAR_ICON_KEYS: string[] = [
|
|
2
|
+
// Church
|
|
3
|
+
'book-bible',
|
|
4
|
+
'church',
|
|
5
|
+
'cross',
|
|
6
|
+
'dove',
|
|
7
|
+
'hands-heart',
|
|
8
|
+
'person-rays',
|
|
9
|
+
'praying-hands',
|
|
10
|
+
'podium',
|
|
11
|
+
'droplet',
|
|
12
|
+
'person-drowning',
|
|
13
|
+
'lighthouse',
|
|
14
|
+
// Services
|
|
15
|
+
'guitar',
|
|
16
|
+
'guitar-electric',
|
|
17
|
+
'violin',
|
|
18
|
+
'calendar-heart',
|
|
19
|
+
'hand-heart',
|
|
20
|
+
'hand-wave',
|
|
21
|
+
'baby',
|
|
22
|
+
'children',
|
|
23
|
+
'mug-hot',
|
|
24
|
+
'video-camera',
|
|
25
|
+
'music',
|
|
26
|
+
'piano-keyboard',
|
|
27
|
+
'drum',
|
|
28
|
+
'user-graduate',
|
|
29
|
+
'comments-question',
|
|
30
|
+
'presentation',
|
|
31
|
+
'microphone-stand',
|
|
32
|
+
'id-badge',
|
|
33
|
+
'book-user',
|
|
34
|
+
'hand-holding-heart',
|
|
35
|
+
'guitars',
|
|
36
|
+
'amp-guitar',
|
|
37
|
+
'school-flag',
|
|
38
|
+
'shield-check',
|
|
39
|
+
'projector',
|
|
40
|
+
// Groups
|
|
41
|
+
'people',
|
|
42
|
+
'people-group',
|
|
43
|
+
'person-dress',
|
|
44
|
+
'person',
|
|
45
|
+
'seedling',
|
|
46
|
+
'comments-alt',
|
|
47
|
+
'globe',
|
|
48
|
+
'book-open-reader',
|
|
49
|
+
'running',
|
|
50
|
+
'leaf',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
export const GRID_COLUMNS = 6
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
|
+
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { EmojiKeyboard, type EmojiType } from 'rn-emoji-keyboard'
|
|
4
|
+
import { useTheme } from '../../hooks'
|
|
5
|
+
|
|
6
|
+
interface EmojiTabProps {
|
|
7
|
+
onEmojiSelect: (emoji: string) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function EmojiTab({ onEmojiSelect }: EmojiTabProps) {
|
|
11
|
+
const styles = useStyles()
|
|
12
|
+
const { colors } = useTheme()
|
|
13
|
+
|
|
14
|
+
const handleEmojiSelected = useCallback(
|
|
15
|
+
(emojiObject: EmojiType) => {
|
|
16
|
+
onEmojiSelect(emojiObject.emoji)
|
|
17
|
+
},
|
|
18
|
+
[onEmojiSelect]
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const emojiTheme = {
|
|
22
|
+
container: colors.fillColorNeutral100Inverted,
|
|
23
|
+
header: colors.textColorDefaultSecondary,
|
|
24
|
+
knob: colors.fillColorNeutral040,
|
|
25
|
+
skinTonesContainer: colors.fillColorNeutral060,
|
|
26
|
+
category: {
|
|
27
|
+
icon: colors.textColorDefaultSecondary,
|
|
28
|
+
iconActive: colors.interaction,
|
|
29
|
+
container: colors.fillColorNeutral100Inverted,
|
|
30
|
+
containerActive: colors.fillColorNeutral080,
|
|
31
|
+
},
|
|
32
|
+
search: {
|
|
33
|
+
background: colors.fillColorNeutral080,
|
|
34
|
+
text: colors.textColorDefaultPrimary,
|
|
35
|
+
placeholder: colors.textColorDefaultSecondary,
|
|
36
|
+
icon: colors.textColorDefaultSecondary,
|
|
37
|
+
},
|
|
38
|
+
emoji: {
|
|
39
|
+
selected: colors.fillColorNeutral080,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<View style={styles.container}>
|
|
45
|
+
<EmojiKeyboard
|
|
46
|
+
categoryPosition="top"
|
|
47
|
+
onEmojiSelected={handleEmojiSelected}
|
|
48
|
+
enableSearchBar
|
|
49
|
+
enableRecentlyUsed
|
|
50
|
+
theme={emojiTheme}
|
|
51
|
+
styles={{
|
|
52
|
+
container: { borderRadius: 0 },
|
|
53
|
+
category: {
|
|
54
|
+
container: {
|
|
55
|
+
borderRadius: 0,
|
|
56
|
+
marginTop: -6,
|
|
57
|
+
borderBottomWidth: 1,
|
|
58
|
+
borderBottomColor: colors.borderColorDefaultDim,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
</View>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const useStyles = () => {
|
|
68
|
+
const { colors } = useTheme()
|
|
69
|
+
|
|
70
|
+
return StyleSheet.create({
|
|
71
|
+
container: {
|
|
72
|
+
flex: 1,
|
|
73
|
+
backgroundColor: colors.fillColorNeutral100Inverted,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { IconName } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'
|
|
3
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
4
|
+
import React, { useCallback } from 'react'
|
|
5
|
+
import { FlatList, StyleSheet, useWindowDimensions } from 'react-native'
|
|
6
|
+
import { useTheme } from '../../hooks'
|
|
7
|
+
import { AVATAR_ICON_KEYS, GRID_COLUMNS } from './constants'
|
|
8
|
+
|
|
9
|
+
interface IconGridProps {
|
|
10
|
+
selectedIconKey: string | null
|
|
11
|
+
onIconSelect: (iconKey: string) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function IconGrid({ selectedIconKey, onIconSelect }: IconGridProps) {
|
|
15
|
+
const styles = useStyles()
|
|
16
|
+
|
|
17
|
+
const renderItem = useCallback(
|
|
18
|
+
({ item }: { item: string }) => {
|
|
19
|
+
const isSelected = item === selectedIconKey
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<PlatformPressable
|
|
23
|
+
onPress={() => onIconSelect(item)}
|
|
24
|
+
style={[styles.cell, isSelected && styles.cellSelected]}
|
|
25
|
+
accessibilityRole="button"
|
|
26
|
+
accessibilityLabel={`${item.replace(/-/g, ' ')} icon`}
|
|
27
|
+
accessibilityState={{ selected: isSelected }}
|
|
28
|
+
>
|
|
29
|
+
<FontAwesomeIcon icon={['fas', item as IconName]} size={20} color="white" />
|
|
30
|
+
</PlatformPressable>
|
|
31
|
+
)
|
|
32
|
+
},
|
|
33
|
+
[selectedIconKey, onIconSelect, styles.cell, styles.cellSelected]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<FlatList
|
|
38
|
+
data={AVATAR_ICON_KEYS}
|
|
39
|
+
numColumns={GRID_COLUMNS}
|
|
40
|
+
keyExtractor={item => item}
|
|
41
|
+
renderItem={renderItem}
|
|
42
|
+
contentContainerStyle={styles.grid}
|
|
43
|
+
columnWrapperStyle={styles.row}
|
|
44
|
+
style={styles.list}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PADDING = 16
|
|
50
|
+
const GAP = 8
|
|
51
|
+
|
|
52
|
+
const useStyles = () => {
|
|
53
|
+
const { colors } = useTheme()
|
|
54
|
+
const { width: screenWidth } = useWindowDimensions()
|
|
55
|
+
const cellSize = Math.floor((screenWidth - PADDING * 2 - (GRID_COLUMNS - 1) * GAP) / GRID_COLUMNS)
|
|
56
|
+
|
|
57
|
+
return StyleSheet.create({
|
|
58
|
+
list: {
|
|
59
|
+
flex: 1,
|
|
60
|
+
},
|
|
61
|
+
grid: {
|
|
62
|
+
padding: PADDING,
|
|
63
|
+
},
|
|
64
|
+
row: {
|
|
65
|
+
gap: GAP,
|
|
66
|
+
marginBottom: GAP,
|
|
67
|
+
},
|
|
68
|
+
cell: {
|
|
69
|
+
width: cellSize,
|
|
70
|
+
height: cellSize,
|
|
71
|
+
borderRadius: cellSize / 2,
|
|
72
|
+
backgroundColor: colors.fillColorNeutral040,
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
justifyContent: 'center',
|
|
75
|
+
},
|
|
76
|
+
cellSelected: {
|
|
77
|
+
borderWidth: 3,
|
|
78
|
+
borderColor: colors.interaction,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
|
+
import { Alert, StyleSheet, View } from 'react-native'
|
|
3
|
+
import { Button } from '../../components'
|
|
4
|
+
import { useTheme } from '../../hooks'
|
|
5
|
+
import { ImagePicker } from '../../utils/native_adapters'
|
|
6
|
+
import type { ImagePickerAsset } from '../../utils/native_adapters/image_picker'
|
|
7
|
+
|
|
8
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
|
|
9
|
+
|
|
10
|
+
interface UploadTabProps {
|
|
11
|
+
imagePreviewUri: string | null
|
|
12
|
+
onImageSelect: (asset: ImagePickerAsset) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function UploadTab({ imagePreviewUri, onImageSelect }: UploadTabProps) {
|
|
16
|
+
const styles = useStyles()
|
|
17
|
+
|
|
18
|
+
const pickImage = useCallback(async () => {
|
|
19
|
+
const result = await ImagePicker.openImageLibraryAsync({
|
|
20
|
+
mediaTypes: ['images'],
|
|
21
|
+
allowsEditing: true,
|
|
22
|
+
allowsMultipleSelection: false,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (result.canceled || !result.assets?.[0]) return
|
|
26
|
+
|
|
27
|
+
const asset = result.assets[0]
|
|
28
|
+
|
|
29
|
+
if (asset.fileSize && asset.fileSize > MAX_FILE_SIZE) {
|
|
30
|
+
Alert.alert('Image too large', 'Please choose an image under 10MB.')
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onImageSelect(asset)
|
|
35
|
+
}, [onImageSelect])
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View style={styles.container}>
|
|
39
|
+
<Button
|
|
40
|
+
title={imagePreviewUri ? 'Change photo' : 'Choose photo'}
|
|
41
|
+
iconNameLeft="general.image"
|
|
42
|
+
onPress={pickImage}
|
|
43
|
+
variant="outline"
|
|
44
|
+
appearance="interaction"
|
|
45
|
+
size="md"
|
|
46
|
+
/>
|
|
47
|
+
</View>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const useStyles = () => {
|
|
52
|
+
const { colors } = useTheme()
|
|
53
|
+
|
|
54
|
+
return StyleSheet.create({
|
|
55
|
+
container: {
|
|
56
|
+
flex: 1,
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
justifyContent: 'center',
|
|
59
|
+
backgroundColor: colors.fillColorNeutral100Inverted,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from 'react-native'
|
|
27
27
|
import {
|
|
28
28
|
Badge,
|
|
29
|
+
Button,
|
|
29
30
|
ChildNotice,
|
|
30
31
|
Heading,
|
|
31
32
|
Icon,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
TextButton,
|
|
36
37
|
type TextStyle,
|
|
37
38
|
} from '../components'
|
|
39
|
+
import { ConversationAvatar } from '../components/display/conversation_avatar'
|
|
38
40
|
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
|
|
39
41
|
import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
|
|
40
42
|
import { useSuspensePaginator, useTheme } from '../hooks'
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
useConversationUpdate,
|
|
47
49
|
} from '../hooks/use_conversation'
|
|
48
50
|
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
49
|
-
import { MemberResource, isDefined } from '../types'
|
|
51
|
+
import { type ConversationResource, MemberResource, isDefined } from '../types'
|
|
50
52
|
import { GroupResource } from '../types/resources/group_resource'
|
|
51
53
|
import { genderDisplayLabel } from '../utils/gender_display_label'
|
|
52
54
|
import { tokens } from '../vendor/tapestry/tokens'
|
|
@@ -102,6 +104,7 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
102
104
|
const { mutate: deleteConversation } = useConversationDelete(route.params)
|
|
103
105
|
const { featureEnabled } = useFeatures()
|
|
104
106
|
const granularNotificationsEnabled = featureEnabled(availableFeatures.granular_notifications_ui)
|
|
107
|
+
const customAvatarsEnabled = featureEnabled(availableFeatures.custom_conversation_avatars)
|
|
105
108
|
const showGenderFilter =
|
|
106
109
|
featureEnabled(availableFeatures.gender_specific_conversations) && !!conversation.genderOption
|
|
107
110
|
const genderLabel = conversation.genderOption ? genderDisplayLabel(conversation.genderOption) : ''
|
|
@@ -234,6 +237,25 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
234
237
|
}, [HeaderRight, HeaderTitle, navigation])
|
|
235
238
|
|
|
236
239
|
const listData = [
|
|
240
|
+
{
|
|
241
|
+
type: canUpdate && customAvatarsEnabled ? SectionTypes.view : SectionTypes.hidden,
|
|
242
|
+
data: {
|
|
243
|
+
children: (
|
|
244
|
+
<AvatarCard
|
|
245
|
+
conversation={conversation}
|
|
246
|
+
onPress={() => navigation.navigate('AvatarPicker', { conversation_id })}
|
|
247
|
+
/>
|
|
248
|
+
),
|
|
249
|
+
},
|
|
250
|
+
sectionOuterStyle: styles.sectionOuterAvatarCard,
|
|
251
|
+
sectionInnerStyle: styles.sectionInnerAvatarCard,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: SectionTypes.header,
|
|
255
|
+
data: { title: 'Basic info' },
|
|
256
|
+
showBottomBorder: true,
|
|
257
|
+
sectionInnerStyle: styles.sectionInnerHeaderWithBottomBorder,
|
|
258
|
+
},
|
|
237
259
|
{
|
|
238
260
|
type: SectionTypes.view,
|
|
239
261
|
data: {
|
|
@@ -595,6 +617,30 @@ function NavigableSettingRow({ title, subtitle, onPress }: NavigableSettingRowPr
|
|
|
595
617
|
)
|
|
596
618
|
}
|
|
597
619
|
|
|
620
|
+
function AvatarCard({
|
|
621
|
+
conversation,
|
|
622
|
+
onPress,
|
|
623
|
+
}: {
|
|
624
|
+
conversation: ConversationResource
|
|
625
|
+
onPress: () => void
|
|
626
|
+
}) {
|
|
627
|
+
const styles = useStyles()
|
|
628
|
+
|
|
629
|
+
return (
|
|
630
|
+
<View style={styles.avatarCard}>
|
|
631
|
+
<ConversationAvatar conversation={conversation} size="2xl" />
|
|
632
|
+
<Button
|
|
633
|
+
title="Update avatar"
|
|
634
|
+
iconNameLeft="general.pencil"
|
|
635
|
+
onPress={onPress}
|
|
636
|
+
variant="outline"
|
|
637
|
+
appearance="interaction"
|
|
638
|
+
size="sm"
|
|
639
|
+
/>
|
|
640
|
+
</View>
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
|
|
598
644
|
function TeamsGroup({ teams }: { teams: GroupResource[] }) {
|
|
599
645
|
const styles = useStyles()
|
|
600
646
|
|
|
@@ -725,6 +771,19 @@ const useStyles = ({ isStart, isEnd }: { isStart?: boolean; isEnd?: boolean } =
|
|
|
725
771
|
navigableSettingChevron: {
|
|
726
772
|
color: colors.iconColorDefaultDisabled,
|
|
727
773
|
},
|
|
774
|
+
avatarCard: {
|
|
775
|
+
alignItems: 'center',
|
|
776
|
+
gap: 16,
|
|
777
|
+
},
|
|
778
|
+
sectionOuterAvatarCard: {
|
|
779
|
+
paddingLeft: 0,
|
|
780
|
+
},
|
|
781
|
+
sectionInnerAvatarCard: {
|
|
782
|
+
paddingTop: 24,
|
|
783
|
+
paddingBottom: 24,
|
|
784
|
+
paddingHorizontal: 16,
|
|
785
|
+
alignItems: 'center',
|
|
786
|
+
},
|
|
728
787
|
teamGroup: {
|
|
729
788
|
flexDirection: 'row',
|
|
730
789
|
gap: 4,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useNavigation, useRoute } from '@react-navigation/native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { Pressable, StyleSheet, View } from 'react-native'
|
|
4
|
+
import { Icon, Text } from '../../../components'
|
|
5
|
+
import { EmojiAvatar } from '../../../components/display/emoji_avatar'
|
|
6
|
+
import { IconAvatar } from '../../../components/display/icon_avatar'
|
|
7
|
+
import AvatarPrimitive from '../../../components/primitive/avatar_primitive'
|
|
8
|
+
import { useTheme } from '../../../hooks'
|
|
9
|
+
import type { AvatarUpdatePayload } from '../../../hooks/use_conversation_avatar_update'
|
|
10
|
+
|
|
11
|
+
interface AvatarSelectionRowProps {
|
|
12
|
+
avatarSelection?: AvatarUpdatePayload
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AvatarSelectionRow({ avatarSelection }: AvatarSelectionRowProps) {
|
|
16
|
+
const styles = useStyles()
|
|
17
|
+
const navigation = useNavigation()
|
|
18
|
+
const route = useRoute()
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Pressable
|
|
22
|
+
style={styles.avatarSection}
|
|
23
|
+
onPress={() =>
|
|
24
|
+
navigation.navigate('AvatarPicker', {
|
|
25
|
+
source_params: route.params as Record<string, unknown>,
|
|
26
|
+
...(avatarSelection && { avatar_selection: avatarSelection }),
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
>
|
|
30
|
+
<Text style={styles.avatarLabel}>Customize avatar</Text>
|
|
31
|
+
<View style={styles.avatarSectionTrailing}>
|
|
32
|
+
<AvatarSelectionPreview avatarSelection={avatarSelection} />
|
|
33
|
+
<Icon name="general.rightChevron" size={12} />
|
|
34
|
+
</View>
|
|
35
|
+
</Pressable>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface AvatarSelectionPreviewProps {
|
|
40
|
+
avatarSelection?: AvatarUpdatePayload
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function AvatarSelectionPreview({ avatarSelection }: AvatarSelectionPreviewProps) {
|
|
44
|
+
if (!avatarSelection || avatarSelection.kind === 'clear') return null
|
|
45
|
+
|
|
46
|
+
switch (avatarSelection.kind) {
|
|
47
|
+
case 'icon':
|
|
48
|
+
return <IconAvatar iconKey={avatarSelection.key} color={avatarSelection.color} size="md" />
|
|
49
|
+
case 'emoji':
|
|
50
|
+
return <EmojiAvatar emoji={avatarSelection.key} color={avatarSelection.color} size="md" />
|
|
51
|
+
case 'image':
|
|
52
|
+
return (
|
|
53
|
+
<AvatarPrimitive.Root size="md">
|
|
54
|
+
<AvatarPrimitive.Mask>
|
|
55
|
+
<AvatarPrimitive.Image sourceUri={avatarSelection.imageAsset.uri} />
|
|
56
|
+
</AvatarPrimitive.Mask>
|
|
57
|
+
</AvatarPrimitive.Root>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const useStyles = () => {
|
|
63
|
+
const { colors } = useTheme()
|
|
64
|
+
|
|
65
|
+
return StyleSheet.create({
|
|
66
|
+
avatarSection: {
|
|
67
|
+
padding: 16,
|
|
68
|
+
flexDirection: 'row',
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
justifyContent: 'space-between',
|
|
71
|
+
},
|
|
72
|
+
avatarSectionTrailing: {
|
|
73
|
+
flexDirection: 'row',
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
gap: 12,
|
|
76
|
+
},
|
|
77
|
+
avatarLabel: {
|
|
78
|
+
fontSize: 18,
|
|
79
|
+
color: colors.textColorDefaultPrimary,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ActivityIndicator, StyleSheet, View } from 'react-native'
|
|
3
|
-
import { Heading, Switch, Text
|
|
3
|
+
import { Heading, Switch, Text } from '../../../components'
|
|
4
4
|
import { useTheme } from '../../../hooks'
|
|
5
5
|
import { genderDisplayLabel } from '../../../utils/gender_display_label'
|
|
6
6
|
|
|
@@ -26,10 +26,7 @@ function FilterableGenderContent({ genderValue, enabled, onToggle }: FilterableG
|
|
|
26
26
|
/>
|
|
27
27
|
</View>
|
|
28
28
|
<Text style={{ color: colors.textColorDefaultSecondary }}>
|
|
29
|
-
Filter limited to your profile's set gender.
|
|
30
|
-
<TextInlineButton nativeID="gender-filter-learn-more" onPress={() => {}}>
|
|
31
|
-
Learn more.
|
|
32
|
-
</TextInlineButton>
|
|
29
|
+
Filter limited to your profile's set gender.
|
|
33
30
|
</Text>
|
|
34
31
|
</>
|
|
35
32
|
)
|
|
@@ -45,10 +42,7 @@ function NoGenderContent({ isFetching }: { isFetching: boolean }) {
|
|
|
45
42
|
<ActivityIndicator size="small" />
|
|
46
43
|
) : (
|
|
47
44
|
<Text style={{ color: colors.textColorDefaultSecondary }}>
|
|
48
|
-
Set a gender in your Church Center profile to enable gender filtering.
|
|
49
|
-
<TextInlineButton nativeID="gender-filter-learn-more" onPress={() => {}}>
|
|
50
|
-
Learn more.
|
|
51
|
-
</TextInlineButton>
|
|
45
|
+
Set a gender in your Church Center profile to enable gender filtering.
|
|
52
46
|
</Text>
|
|
53
47
|
)}
|
|
54
48
|
</>
|