@planningcenter/chat-react-native 1.5.0 → 1.6.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/conversations.d.ts.map +1 -1
- package/build/components/conversations.js +29 -8
- package/build/components/conversations.js.map +1 -1
- package/build/components/display/button.d.ts +71 -0
- package/build/components/display/button.d.ts.map +1 -0
- package/build/components/display/button.js +136 -0
- package/build/components/display/button.js.map +1 -0
- package/build/components/display/button_color_utils.d.ts +24 -0
- package/build/components/display/button_color_utils.d.ts.map +1 -0
- package/build/components/display/button_color_utils.js +43 -0
- package/build/components/display/button_color_utils.js.map +1 -0
- package/build/components/display/heading.d.ts +4 -0
- package/build/components/display/heading.d.ts.map +1 -1
- package/build/components/display/heading.js +3 -0
- package/build/components/display/heading.js.map +1 -1
- package/build/components/display/icon.d.ts +8 -4
- package/build/components/display/icon.d.ts.map +1 -1
- package/build/components/display/icon.js +21 -13
- package/build/components/display/icon.js.map +1 -1
- package/build/components/display/image.d.ts +7 -2
- package/build/components/display/image.d.ts.map +1 -1
- package/build/components/display/image.js +5 -5
- package/build/components/display/image.js.map +1 -1
- package/build/components/display/index.d.ts +10 -7
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +10 -7
- package/build/components/display/index.js.map +1 -1
- package/build/components/display/spinner.d.ts +5 -1
- package/build/components/display/spinner.d.ts.map +1 -1
- package/build/components/display/spinner.js +19 -13
- package/build/components/display/spinner.js.map +1 -1
- package/build/components/display/text.d.ts +13 -3
- package/build/components/display/text.d.ts.map +1 -1
- package/build/components/display/text.js +17 -5
- package/build/components/display/text.js.map +1 -1
- package/build/components/display/text_button.d.ts +37 -0
- package/build/components/display/text_button.d.ts.map +1 -0
- package/build/components/display/text_button.js +36 -0
- package/build/components/display/text_button.js.map +1 -0
- package/build/components/display/text_inline_button.d.ts +12 -0
- package/build/components/display/text_inline_button.d.ts.map +1 -0
- package/build/components/display/text_inline_button.js +53 -0
- package/build/components/display/text_inline_button.js.map +1 -0
- package/build/components/index.d.ts +1 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +1 -0
- package/build/components/index.js.map +1 -1
- package/build/components/primitive/avatar_primitive.d.ts +1 -1
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +6 -9
- package/build/components/primitive/avatar_primitive.js.map +1 -1
- package/build/contexts/api_provider.d.ts +4 -6
- package/build/contexts/api_provider.d.ts.map +1 -1
- package/build/contexts/api_provider.js +13 -20
- package/build/contexts/api_provider.js.map +1 -1
- package/build/contexts/chat_context.d.ts +7 -5
- package/build/contexts/chat_context.d.ts.map +1 -1
- package/build/contexts/chat_context.js +40 -4
- package/build/contexts/chat_context.js.map +1 -1
- package/build/hooks/index.d.ts +4 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +4 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_create_android_ripple_color.d.ts +4 -0
- package/build/hooks/use_create_android_ripple_color.d.ts.map +1 -0
- package/build/hooks/use_create_android_ripple_color.js +15 -0
- package/build/hooks/use_create_android_ripple_color.js.map +1 -0
- package/build/hooks/use_current_person.d.ts +3 -0
- package/build/hooks/use_current_person.d.ts.map +1 -0
- package/build/hooks/use_current_person.js +13 -0
- package/build/hooks/use_current_person.js.map +1 -0
- package/build/hooks/use_font_scale.d.ts +4 -0
- package/build/hooks/use_font_scale.d.ts.map +1 -0
- package/build/hooks/use_font_scale.js +8 -0
- package/build/hooks/use_font_scale.js.map +1 -0
- package/build/hooks/use_suspense_api.d.ts +61 -0
- package/build/hooks/use_suspense_api.d.ts.map +1 -0
- package/build/hooks/use_suspense_api.js +39 -0
- package/build/hooks/use_suspense_api.js.map +1 -0
- package/build/navigation/index.d.ts +1 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +7 -4
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +59 -6
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/display.d.ts.map +1 -1
- package/build/screens/display.js +277 -51
- package/build/screens/display.js.map +1 -1
- package/build/screens/not_found.js +1 -1
- package/build/screens/not_found.js.map +1 -1
- package/build/types/api_primitives.d.ts +23 -0
- package/build/types/api_primitives.d.ts.map +1 -0
- package/build/types/api_primitives.js +2 -0
- package/build/types/api_primitives.js.map +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +4 -0
- package/build/types/index.js.map +1 -0
- package/build/types/resources/conversation.d.ts +15 -0
- package/build/types/resources/conversation.d.ts.map +1 -0
- package/build/types/resources/conversation.js +2 -0
- package/build/types/resources/conversation.js.map +1 -0
- package/build/types/resources/index.d.ts +5 -0
- package/build/types/resources/index.d.ts.map +1 -0
- package/build/types/resources/index.js +5 -0
- package/build/types/resources/index.js.map +1 -0
- package/build/types/resources/message.d.ts +16 -0
- package/build/types/resources/message.d.ts.map +1 -0
- package/build/types/resources/message.js +2 -0
- package/build/types/resources/message.js.map +1 -0
- package/build/types/resources/oauth_token.d.ts +9 -0
- package/build/types/resources/oauth_token.d.ts.map +1 -0
- package/build/types/resources/oauth_token.js +2 -0
- package/build/types/resources/oauth_token.js.map +1 -0
- package/build/types/resources/person.d.ts +9 -0
- package/build/types/resources/person.d.ts.map +1 -0
- package/build/types/resources/person.js +2 -0
- package/build/types/resources/person.js.map +1 -0
- package/build/types/resources/reaction.d.ts +10 -0
- package/build/types/resources/reaction.d.ts.map +1 -0
- package/build/types/resources/reaction.js +2 -0
- package/build/types/resources/reaction.js.map +1 -0
- package/build/types/utils/index.d.ts +4 -0
- package/build/types/utils/index.d.ts.map +1 -0
- package/build/types/utils/index.js +4 -0
- package/build/types/utils/index.js.map +1 -0
- package/build/utils/client/client.d.ts +21 -12
- package/build/utils/client/client.d.ts.map +1 -1
- package/build/utils/client/client.js +24 -22
- package/build/utils/client/client.js.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/session.d.ts +0 -5
- package/build/utils/session.d.ts.map +1 -1
- package/build/utils/session.js +0 -10
- package/build/utils/session.js.map +1 -1
- package/build/utils/styles.d.ts +5 -0
- package/build/utils/styles.d.ts.map +1 -1
- package/build/utils/styles.js +9 -0
- package/build/utils/styles.js.map +1 -1
- package/build/utils/theme.d.ts +3 -1
- package/build/utils/theme.d.ts.map +1 -1
- package/build/utils/theme.js +6 -2
- package/build/utils/theme.js.map +1 -1
- package/build/vendor/tapestry/alias_tokens_color_map.d.ts +8 -0
- package/build/vendor/tapestry/alias_tokens_color_map.d.ts.map +1 -1
- package/build/vendor/tapestry/alias_tokens_color_map.js +8 -0
- package/build/vendor/tapestry/alias_tokens_color_map.js.map +1 -1
- package/build/vendor/tapestry/tokens.d.ts +21 -0
- package/build/vendor/tapestry/tokens.d.ts.map +1 -1
- package/build/vendor/tapestry/tokens.js +21 -0
- package/build/vendor/tapestry/tokens.js.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/client.ts +72 -19
- package/src/__tests__/session.ts +0 -11
- package/src/__utils__/handlers.ts +1 -1
- package/src/components/conversations.tsx +33 -11
- package/src/components/display/button.tsx +293 -0
- package/src/components/display/button_color_utils.ts +72 -0
- package/src/components/display/heading.tsx +12 -0
- package/src/components/display/icon.tsx +35 -16
- package/src/components/display/image.tsx +29 -7
- package/src/components/display/index.ts +10 -7
- package/src/components/display/spinner.tsx +42 -13
- package/src/components/display/text.tsx +34 -13
- package/src/components/display/text_button.tsx +102 -0
- package/src/components/display/text_inline_button.tsx +91 -0
- package/src/components/index.tsx +1 -0
- package/src/components/primitive/avatar_primitive.tsx +12 -6
- package/src/contexts/api_provider.tsx +18 -26
- package/src/contexts/chat_context.tsx +61 -7
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use_create_android_ripple_color.ts +18 -0
- package/src/hooks/use_current_person.ts +15 -0
- package/src/hooks/use_font_scale.ts +9 -0
- package/src/hooks/use_suspense_api.ts +60 -0
- package/src/navigation/index.tsx +14 -4
- package/src/screens/conversation_screen.tsx +83 -7
- package/src/screens/display.tsx +447 -51
- package/src/screens/not_found.tsx +1 -1
- package/src/types/api_primitives.ts +24 -0
- package/src/types/index.ts +3 -0
- package/src/types/resources/conversation.ts +15 -0
- package/src/types/resources/index.ts +4 -0
- package/src/types/resources/message.ts +18 -0
- package/src/types/resources/oauth_token.ts +8 -0
- package/src/types/resources/person.ts +9 -0
- package/src/types/resources/reaction.ts +9 -0
- package/src/types/utils/index.ts +6 -0
- package/src/utils/client/client.ts +41 -34
- package/src/utils/client/types.d.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/session.ts +0 -13
- package/src/utils/styles.ts +12 -0
- package/src/utils/theme.ts +9 -3
- package/src/vendor/tapestry/alias_tokens_color_map.ts +12 -0
- package/src/vendor/tapestry/tokens.ts +21 -0
- package/build/utils/api.d.ts +0 -9
- package/build/utils/api.d.ts.map +0 -1
- package/build/utils/api.js +0 -36
- package/build/utils/api.js.map +0 -1
- package/src/types.d.ts +0 -35
- package/src/utils/api.ts +0 -47
|
@@ -1,24 +1,41 @@
|
|
|
1
1
|
import { useTheme } from '../../hooks'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
StyleSheet,
|
|
6
|
-
Text as ReactNativeText,
|
|
7
|
-
TextProps as ReactNativeTextProps,
|
|
8
|
-
} from 'react-native'
|
|
3
|
+
import { Platform, StyleSheet, Text as ReactNativeText } from 'react-native'
|
|
4
|
+
import type { TextStyle, TextProps as ReactNativeTextProps } from 'react-native'
|
|
9
5
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
// =================================
|
|
8
|
+
// ====== Constants ================
|
|
9
|
+
// =================================
|
|
10
|
+
|
|
11
|
+
const VARIANTS = {
|
|
12
|
+
plain: 'plain',
|
|
13
|
+
secondary: 'secondary',
|
|
14
|
+
tertiary: 'tertiary',
|
|
15
|
+
footnote: 'footnote',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
type VariantUnion = (typeof VARIANTS)[keyof typeof VARIANTS]
|
|
19
|
+
type VariantStyles = Record<VariantUnion, TextStyle>
|
|
20
|
+
|
|
21
|
+
// =================================
|
|
22
|
+
// ====== Component ================
|
|
23
|
+
// =================================
|
|
24
|
+
|
|
25
|
+
export interface TextProps extends ReactNativeTextProps {
|
|
26
|
+
/**
|
|
27
|
+
* Changes the styles and size of the text.
|
|
28
|
+
*/
|
|
29
|
+
variant?: VariantUnion
|
|
13
30
|
}
|
|
14
31
|
|
|
15
32
|
export function Text({ style, variant = 'plain', children, ...props }: TextProps) {
|
|
16
33
|
const styles = useStyles()
|
|
17
|
-
const variantStyleMap = {
|
|
18
|
-
plain: styles.plain,
|
|
19
|
-
secondary: styles.secondary,
|
|
20
|
-
tertiary: styles.tertiary,
|
|
21
|
-
footnote: styles.footnote,
|
|
34
|
+
const variantStyleMap: VariantStyles = {
|
|
35
|
+
[VARIANTS.plain]: styles.plain,
|
|
36
|
+
[VARIANTS.secondary]: styles.secondary,
|
|
37
|
+
[VARIANTS.tertiary]: styles.tertiary,
|
|
38
|
+
[VARIANTS.footnote]: styles.footnote,
|
|
22
39
|
}
|
|
23
40
|
|
|
24
41
|
return (
|
|
@@ -28,6 +45,10 @@ export function Text({ style, variant = 'plain', children, ...props }: TextProps
|
|
|
28
45
|
)
|
|
29
46
|
}
|
|
30
47
|
|
|
48
|
+
// =================================
|
|
49
|
+
// ====== Styles ===================
|
|
50
|
+
// =================================
|
|
51
|
+
|
|
31
52
|
const useStyles = () => {
|
|
32
53
|
const { colors } = useTheme()
|
|
33
54
|
return StyleSheet.create({
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Pressable, StyleSheet } from 'react-native'
|
|
3
|
+
import type { PressableProps } from 'react-native'
|
|
4
|
+
import { useTheme } from '../../hooks'
|
|
5
|
+
import { platformFontWeightMedium } from '../../utils'
|
|
6
|
+
import { Text } from './text'
|
|
7
|
+
import type { TextProps } from './text'
|
|
8
|
+
import { getColorKey, useColorOptionMap } from './button_color_utils'
|
|
9
|
+
import type { AppearanceUnion } from './button_color_utils'
|
|
10
|
+
|
|
11
|
+
// =================================
|
|
12
|
+
// ====== Component ================
|
|
13
|
+
// =================================
|
|
14
|
+
|
|
15
|
+
interface TextButtonProps extends PressableProps {
|
|
16
|
+
/**
|
|
17
|
+
* Specifies whether fonts should be scaled down automatically to fit given style constraints.
|
|
18
|
+
*/
|
|
19
|
+
adjustsFontSizeToFit?: boolean
|
|
20
|
+
/**
|
|
21
|
+
* Specifies whether fonts should scale to respect the device's text size accessibility settings. The default is true.
|
|
22
|
+
*/
|
|
23
|
+
allowFontScaling?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* Updates the button's colors
|
|
26
|
+
*/
|
|
27
|
+
appearance?: AppearanceUnion
|
|
28
|
+
/**
|
|
29
|
+
* Renders text within a `Text`
|
|
30
|
+
*/
|
|
31
|
+
children?: TextProps['children']
|
|
32
|
+
/**
|
|
33
|
+
* Specifies the maximum size a font can reach when allowFontScaling is enabled.
|
|
34
|
+
*/
|
|
35
|
+
maxFontSizeMultiplier?: number
|
|
36
|
+
/**
|
|
37
|
+
* Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).
|
|
38
|
+
*/
|
|
39
|
+
minimumFontScale?: number
|
|
40
|
+
/**
|
|
41
|
+
* Fill buttons are visually more propmonitent then outline buttons
|
|
42
|
+
*/
|
|
43
|
+
variant?: TextProps['variant']
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function TextButton({
|
|
47
|
+
adjustsFontSizeToFit = false,
|
|
48
|
+
allowFontScaling = true,
|
|
49
|
+
appearance = 'primary',
|
|
50
|
+
disabled = false,
|
|
51
|
+
children,
|
|
52
|
+
maxFontSizeMultiplier,
|
|
53
|
+
minimumFontScale,
|
|
54
|
+
variant = 'plain',
|
|
55
|
+
...props
|
|
56
|
+
}: TextButtonProps) {
|
|
57
|
+
const styles = useStyles({ appearance, disabled })
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Pressable
|
|
61
|
+
accessibilityRole="button"
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
style={({ pressed }) => pressed && styles.pressed} // Android & iOS intentionally get the same pressed style
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
<Text
|
|
67
|
+
adjustsFontSizeToFit={adjustsFontSizeToFit}
|
|
68
|
+
allowFontScaling={allowFontScaling}
|
|
69
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
70
|
+
minimumFontScale={minimumFontScale}
|
|
71
|
+
numberOfLines={1}
|
|
72
|
+
style={[styles.text, disabled && styles.disabled]}
|
|
73
|
+
variant={variant}
|
|
74
|
+
>
|
|
75
|
+
{children}
|
|
76
|
+
</Text>
|
|
77
|
+
</Pressable>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// =================================
|
|
82
|
+
// ====== Styles ===================
|
|
83
|
+
// =================================
|
|
84
|
+
|
|
85
|
+
const useStyles = ({ appearance = 'primary', disabled = false }: Partial<TextButtonProps>) => {
|
|
86
|
+
const { colors } = useTheme()
|
|
87
|
+
const colorKey = getColorKey({ disabled, appearance })
|
|
88
|
+
const colorOptionMap = useColorOptionMap()
|
|
89
|
+
|
|
90
|
+
return StyleSheet.create({
|
|
91
|
+
text: {
|
|
92
|
+
fontWeight: platformFontWeightMedium,
|
|
93
|
+
color: colorOptionMap[colorKey],
|
|
94
|
+
},
|
|
95
|
+
disabled: {
|
|
96
|
+
color: colors.textColorDefaultDisabled,
|
|
97
|
+
},
|
|
98
|
+
pressed: {
|
|
99
|
+
opacity: 0.5,
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Platform, StyleSheet } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../hooks'
|
|
4
|
+
import { Text } from './text'
|
|
5
|
+
import type { TextProps } from './text'
|
|
6
|
+
import { platformFontWeightMedium } from '../../utils'
|
|
7
|
+
import colorFunction from 'color'
|
|
8
|
+
import { getColorKey, useColorOptionMap } from './button_color_utils'
|
|
9
|
+
import type { AppearanceUnion } from './button_color_utils'
|
|
10
|
+
|
|
11
|
+
// =================================
|
|
12
|
+
// ====== Component ================
|
|
13
|
+
// =================================
|
|
14
|
+
|
|
15
|
+
interface TextInlineButtonProps extends TextProps {
|
|
16
|
+
/**
|
|
17
|
+
* Updates the button's colors
|
|
18
|
+
*/
|
|
19
|
+
appearance?: AppearanceUnion
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function TextInlineButton({
|
|
23
|
+
appearance = 'primary',
|
|
24
|
+
children,
|
|
25
|
+
disabled = false,
|
|
26
|
+
onPress,
|
|
27
|
+
...props
|
|
28
|
+
}: TextInlineButtonProps) {
|
|
29
|
+
const styles = useStyles({ appearance, disabled })
|
|
30
|
+
const [isPressed, setIsPressed] = useState(false)
|
|
31
|
+
|
|
32
|
+
const platformPressedStyle = Platform.select({
|
|
33
|
+
ios: null,
|
|
34
|
+
android: styles.pressedAndroid,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Text
|
|
39
|
+
accessibilityRole="button"
|
|
40
|
+
numberOfLines={1}
|
|
41
|
+
style={[
|
|
42
|
+
styles.text,
|
|
43
|
+
styles.underline,
|
|
44
|
+
disabled && styles.disabled,
|
|
45
|
+
isPressed && platformPressedStyle,
|
|
46
|
+
]}
|
|
47
|
+
onPress={!disabled ? onPress : undefined}
|
|
48
|
+
onPressIn={() => setIsPressed(true)}
|
|
49
|
+
onPressOut={() => setIsPressed(false)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</Text>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// =================================
|
|
58
|
+
// ====== Styles ===================
|
|
59
|
+
// =================================
|
|
60
|
+
|
|
61
|
+
const useStyles = ({
|
|
62
|
+
appearance = 'primary',
|
|
63
|
+
disabled = false,
|
|
64
|
+
}: Partial<TextInlineButtonProps>) => {
|
|
65
|
+
const { colors } = useTheme()
|
|
66
|
+
const colorKey = getColorKey({ disabled, appearance })
|
|
67
|
+
const colorOptionMap = useColorOptionMap()
|
|
68
|
+
|
|
69
|
+
// Android doesn't show a "pressed" state and so we need to simulate it.
|
|
70
|
+
// Lowering the Text's opacity doesn't work as it inherits it's parent's Text's opacity.
|
|
71
|
+
// We can simulate the effect by changing the color's opacity instead.
|
|
72
|
+
const pressedColor = colorFunction(colorOptionMap[colorKey]).alpha(0.5).string()
|
|
73
|
+
|
|
74
|
+
return StyleSheet.create({
|
|
75
|
+
text: {
|
|
76
|
+
fontWeight: platformFontWeightMedium,
|
|
77
|
+
color: colorOptionMap[colorKey],
|
|
78
|
+
},
|
|
79
|
+
underline: {
|
|
80
|
+
textDecorationLine: 'underline',
|
|
81
|
+
textDecorationColor: colorOptionMap[colorKey],
|
|
82
|
+
textDecorationStyle: 'solid',
|
|
83
|
+
},
|
|
84
|
+
disabled: {
|
|
85
|
+
color: colors.textColorDefaultDisabled,
|
|
86
|
+
},
|
|
87
|
+
pressedAndroid: {
|
|
88
|
+
color: pressedColor,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
}
|
package/src/components/index.tsx
CHANGED
|
@@ -94,7 +94,7 @@ interface AvatarRootProps {
|
|
|
94
94
|
|
|
95
95
|
function AvatarRoot({ children, size = 'md' }: AvatarRootProps) {
|
|
96
96
|
const [allImagesLoaded, setAllImagesLoaded] = useState(false)
|
|
97
|
-
const styles = useStyles(size)
|
|
97
|
+
const styles = useStyles({ size })
|
|
98
98
|
|
|
99
99
|
return (
|
|
100
100
|
<AvatarContext.Provider value={{ size, allImagesLoaded, setAllImagesLoaded }}>
|
|
@@ -127,14 +127,14 @@ AvatarMask.displayName = 'Avatar.Mask'
|
|
|
127
127
|
// ====== AvatarImage ============
|
|
128
128
|
// =================================
|
|
129
129
|
|
|
130
|
-
interface AvatarImageProps extends Omit<ImageProps, 'source'> {
|
|
130
|
+
interface AvatarImageProps extends Omit<ImageProps, 'source' | 'alt'> {
|
|
131
131
|
sourceUri: string
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
function AvatarImage({ sourceUri, ...props }: AvatarImageProps) {
|
|
135
135
|
const { size } = useAvatarContext()
|
|
136
136
|
|
|
137
|
-
return <Image source={{ uri: sourceUri }} loaderSize={AVATAR_PX[size]} {...props} />
|
|
137
|
+
return <Image source={{ uri: sourceUri }} loaderSize={AVATAR_PX[size]} {...props} alt="" />
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
AvatarImage.displayName = 'Avatar.Image'
|
|
@@ -152,6 +152,7 @@ function AvatarGroupImage({ sourceUri, style, onLoad }: AvatarGroupImageProps) {
|
|
|
152
152
|
loadingEnabled={false}
|
|
153
153
|
wrapperStyle={style}
|
|
154
154
|
onLoad={onLoad}
|
|
155
|
+
alt=""
|
|
155
156
|
/>
|
|
156
157
|
)
|
|
157
158
|
}
|
|
@@ -279,7 +280,7 @@ AvatarGroup.displayName = 'Avatar.Group'
|
|
|
279
280
|
|
|
280
281
|
function AvatarGroupLoader() {
|
|
281
282
|
const { size, allImagesLoaded } = useAvatarContext()
|
|
282
|
-
const styles = useStyles(size)
|
|
283
|
+
const styles = useStyles({ size })
|
|
283
284
|
|
|
284
285
|
if (allImagesLoaded) return null
|
|
285
286
|
|
|
@@ -302,7 +303,7 @@ interface AvatarPresenceProps extends ViewProps {
|
|
|
302
303
|
|
|
303
304
|
function AvatarPresence({ presence, ...props }: AvatarPresenceProps) {
|
|
304
305
|
const { size } = useAvatarContext()
|
|
305
|
-
const styles = useStyles(size, presence)
|
|
306
|
+
const styles = useStyles({ size, presence })
|
|
306
307
|
|
|
307
308
|
return <View style={styles.presence} {...props} />
|
|
308
309
|
}
|
|
@@ -313,7 +314,12 @@ AvatarPresence.displayName = 'Avatar.Presence'
|
|
|
313
314
|
// ====== Styles ===================
|
|
314
315
|
// =================================
|
|
315
316
|
|
|
316
|
-
|
|
317
|
+
interface Styles {
|
|
318
|
+
size?: AvatarSize
|
|
319
|
+
presence?: AvatarPresenceType
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const useStyles = ({ size = 'md', presence = 'offline' }: Styles = {}) => {
|
|
317
323
|
const { colors } = useTheme()
|
|
318
324
|
const PRESENCE_COLOR = {
|
|
319
325
|
online: colors.fillColorInteractionOnlineDefault,
|
|
@@ -1,53 +1,45 @@
|
|
|
1
|
-
import { JSONAPIResponse } from '@planningcenter/chat-core'
|
|
2
1
|
import { QueryClient, QueryClientProvider, QueryKey } from '@tanstack/react-query'
|
|
3
2
|
import React, { useEffect } from 'react'
|
|
4
3
|
import { ViewProps } from 'react-native'
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { ENV, session } from '../utils/session'
|
|
8
|
-
import Uri from '../utils/uri'
|
|
4
|
+
import { Client } from '../utils'
|
|
5
|
+
import { GetRequest } from '../utils/client/types'
|
|
9
6
|
|
|
10
|
-
let
|
|
7
|
+
let apiClient: Client | undefined
|
|
11
8
|
|
|
12
9
|
const defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {
|
|
13
|
-
if (!
|
|
10
|
+
if (!apiClient) {
|
|
14
11
|
throw new Error('No token present')
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return apiRequest<JSONAPIResponse>(url)
|
|
21
|
-
.then(r => r.json)
|
|
22
|
-
.catch(error => {
|
|
23
|
-
if (error.message === 'Token expired') {
|
|
24
|
-
handleTokenExpired()
|
|
25
|
-
}
|
|
26
|
-
return null
|
|
27
|
-
})
|
|
14
|
+
const data = queryKey[0] as GetRequest
|
|
15
|
+
|
|
16
|
+
return apiClient.get(data)
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
export const queryClient = new QueryClient({
|
|
31
20
|
defaultOptions: {
|
|
32
21
|
queries: {
|
|
33
22
|
queryFn: defaultQueryFn,
|
|
23
|
+
retry: 3,
|
|
34
24
|
},
|
|
35
25
|
},
|
|
36
26
|
})
|
|
37
27
|
|
|
38
28
|
export function ApiProvider({
|
|
39
29
|
children,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
handleTokenExpired = onTokenExpired
|
|
30
|
+
client,
|
|
31
|
+
sessionChanged,
|
|
32
|
+
}: ViewProps & { client: Client; sessionChanged: boolean }) {
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
apiClient = client
|
|
35
|
+
}, [client])
|
|
47
36
|
|
|
37
|
+
// TODO: CREATE CALLBACK TO INVALIDATE QUERIES
|
|
48
38
|
useEffect(() => {
|
|
39
|
+
if (!sessionChanged) return
|
|
40
|
+
|
|
49
41
|
queryClient.invalidateQueries()
|
|
50
|
-
}, [
|
|
42
|
+
}, [sessionChanged])
|
|
51
43
|
|
|
52
44
|
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
53
45
|
}
|
|
@@ -1,39 +1,93 @@
|
|
|
1
1
|
import { merge } from 'lodash'
|
|
2
|
-
import React, { createContext, useMemo } from 'react'
|
|
2
|
+
import React, { createContext, useEffect, useMemo, useRef } from 'react'
|
|
3
3
|
import { ColorSchemeName, useColorScheme } from 'react-native'
|
|
4
4
|
import { DeepPartial, OAuthToken } from '../types'
|
|
5
5
|
import { defaultTheme, DefaultTheme } from '../utils/theme'
|
|
6
6
|
import { aliasTokensColorMap } from '../vendor/tapestry/alias_tokens_color_map'
|
|
7
7
|
import { ApiProvider } from './api_provider'
|
|
8
|
+
import { Client, ENV, Session } from '../utils'
|
|
8
9
|
|
|
9
|
-
type
|
|
10
|
+
type ChatContextValue = {
|
|
10
11
|
token?: OAuthToken
|
|
11
12
|
onTokenExpired: () => void
|
|
12
13
|
theme: any
|
|
13
|
-
env?:
|
|
14
|
+
env?: ENV
|
|
15
|
+
client: Client
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
const defaultChatClient = new Client({
|
|
19
|
+
app: 'chat',
|
|
20
|
+
session: new Session(),
|
|
21
|
+
version: '2018-11-01',
|
|
22
|
+
onTokenExpired: () => null,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export const ChatContext = createContext<ChatContextValue>({
|
|
17
26
|
theme: undefined,
|
|
18
27
|
token: undefined,
|
|
19
28
|
env: undefined,
|
|
20
29
|
onTokenExpired: () => {},
|
|
30
|
+
client: defaultChatClient,
|
|
21
31
|
})
|
|
22
32
|
|
|
23
|
-
export function ChatProvider({
|
|
33
|
+
export function ChatProvider({
|
|
34
|
+
children,
|
|
35
|
+
value,
|
|
36
|
+
}: {
|
|
37
|
+
children: any
|
|
38
|
+
value: Omit<ChatContextValue, 'client'>
|
|
39
|
+
}) {
|
|
40
|
+
const { env, token, onTokenExpired } = value
|
|
24
41
|
const theme = useCreateChatTheme(value.theme)
|
|
42
|
+
const session = useMemo(() => new Session({ token, env }), [env, token])
|
|
43
|
+
const client = useMemo(
|
|
44
|
+
() =>
|
|
45
|
+
new Client({
|
|
46
|
+
app: 'chat',
|
|
47
|
+
session,
|
|
48
|
+
version: '2018-11-01',
|
|
49
|
+
onTokenExpired,
|
|
50
|
+
}),
|
|
51
|
+
[onTokenExpired, session]
|
|
52
|
+
)
|
|
53
|
+
const sessionChanged = useSessionChanged({ token, env })
|
|
54
|
+
|
|
55
|
+
const contextValue: ChatContextValue = {
|
|
56
|
+
env,
|
|
57
|
+
token,
|
|
58
|
+
onTokenExpired,
|
|
59
|
+
theme,
|
|
60
|
+
client,
|
|
61
|
+
}
|
|
25
62
|
|
|
26
63
|
if (!Object.keys(value.token || {}).length) return null
|
|
27
64
|
|
|
28
65
|
return (
|
|
29
|
-
<ChatContext.Provider value={
|
|
30
|
-
<ApiProvider
|
|
66
|
+
<ChatContext.Provider value={contextValue}>
|
|
67
|
+
<ApiProvider sessionChanged={sessionChanged} client={client}>
|
|
31
68
|
{children}
|
|
32
69
|
</ApiProvider>
|
|
33
70
|
</ChatContext.Provider>
|
|
34
71
|
)
|
|
35
72
|
}
|
|
36
73
|
|
|
74
|
+
function useSessionChanged(value: Pick<ChatContextValue, 'token' | 'env'>): boolean {
|
|
75
|
+
const { token: newToken, env: newEnv } = value
|
|
76
|
+
const { token: prevToken, env: prevEnv } = usePrevious<typeof value>(value)
|
|
77
|
+
|
|
78
|
+
return Boolean(prevToken && newToken !== prevToken) || Boolean(prevEnv && newEnv !== prevEnv)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function usePrevious<T>(value) {
|
|
82
|
+
const ref = useRef<T>(value)
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
ref.current = value
|
|
86
|
+
}, [value])
|
|
87
|
+
|
|
88
|
+
return ref.current
|
|
89
|
+
}
|
|
90
|
+
|
|
37
91
|
interface CreateChatThemeProps {
|
|
38
92
|
theme?: DeepPartial<DefaultTheme>
|
|
39
93
|
colorScheme?: ColorSchemeName
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import colorFunction from 'color'
|
|
3
|
+
import { useTheme } from './use_theme'
|
|
4
|
+
|
|
5
|
+
export const useCreateAndroidRippleColor = ({ color }: { color: string }) => {
|
|
6
|
+
const { colors } = useTheme()
|
|
7
|
+
const intensity = colors.name === 'dark' ? 0.4 : 0.35
|
|
8
|
+
|
|
9
|
+
const androidRippleColor = useMemo(() => {
|
|
10
|
+
return colorFunction(color)
|
|
11
|
+
.hsl()
|
|
12
|
+
[colors.name === 'dark' ? 'lighten' : 'darken'](intensity)
|
|
13
|
+
.alpha(intensity)
|
|
14
|
+
.string()
|
|
15
|
+
}, [color, colors.name, intensity])
|
|
16
|
+
|
|
17
|
+
return androidRippleColor
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PersonResource } from '../types'
|
|
2
|
+
import { useSuspenseGet } from './use_suspense_api'
|
|
3
|
+
|
|
4
|
+
export const useCurrentPerson = () => {
|
|
5
|
+
const { data: person } = useSuspenseGet<PersonResource>({
|
|
6
|
+
url: '/me',
|
|
7
|
+
data: {
|
|
8
|
+
fields: {
|
|
9
|
+
Person: ['id', 'name', 'avatar', 'unread_count', 'pco_chat_enabled'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
return person
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useWindowDimensions } from 'react-native'
|
|
2
|
+
|
|
3
|
+
export const useFontScale = ({ maxFontSizeMultiplier }: { maxFontSizeMultiplier?: number }) => {
|
|
4
|
+
const { fontScale: nativeFontScale } = useWindowDimensions()
|
|
5
|
+
const scaleLimit = maxFontSizeMultiplier || nativeFontScale
|
|
6
|
+
const fontScale = Math.min(scaleLimit, nativeFontScale)
|
|
7
|
+
|
|
8
|
+
return fontScale
|
|
9
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { InfiniteData, useSuspenseInfiniteQuery, useSuspenseQuery } from '@tanstack/react-query'
|
|
2
|
+
import { useContext } from 'react'
|
|
3
|
+
import { ChatContext } from '../contexts'
|
|
4
|
+
import { ApiCollection, ApiResource, ResourceObject } from '../types'
|
|
5
|
+
import { GetRequest, RequestData } from '../utils/client/types'
|
|
6
|
+
|
|
7
|
+
export const useSuspenseGet = <T extends ResourceObject | ResourceObject[]>(args: GetRequest) => {
|
|
8
|
+
type Resource = T extends ResourceObject ? ApiResource<T> : ApiCollection<T>
|
|
9
|
+
|
|
10
|
+
const { data, ...query } = useSuspenseQuery<Resource, Response>({
|
|
11
|
+
queryKey: [args],
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
return { ...data, ...query }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type NextMeta = Partial<{
|
|
18
|
+
offset: string
|
|
19
|
+
idLt: string
|
|
20
|
+
}>
|
|
21
|
+
|
|
22
|
+
export const useSuspensePaginator = <T extends ResourceObject>(args: GetRequest) => {
|
|
23
|
+
const { client } = useContext(ChatContext)
|
|
24
|
+
const query = useSuspenseInfiniteQuery<
|
|
25
|
+
ApiCollection<T>,
|
|
26
|
+
Response,
|
|
27
|
+
InfiniteData<ApiCollection<T>>,
|
|
28
|
+
any,
|
|
29
|
+
Partial<RequestData> | undefined
|
|
30
|
+
>({
|
|
31
|
+
queryKey: [args.url, args.data],
|
|
32
|
+
queryFn: ({ pageParam }) => {
|
|
33
|
+
const pageParmWhere = pageParam?.where || {}
|
|
34
|
+
const argsWhere = args.data.where || {}
|
|
35
|
+
const where = { ...argsWhere, ...pageParmWhere }
|
|
36
|
+
|
|
37
|
+
const offset = pageParam?.offset || args.data.offset
|
|
38
|
+
const data = { ...args.data, where, offset }
|
|
39
|
+
|
|
40
|
+
return client.get({
|
|
41
|
+
url: args.url,
|
|
42
|
+
data,
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
initialPageParam: {} as Partial<RequestData>,
|
|
46
|
+
getNextPageParam: lastPage => {
|
|
47
|
+
const next: NextMeta = lastPage.meta?.next || {}
|
|
48
|
+
const { offset, idLt } = next
|
|
49
|
+
|
|
50
|
+
if (idLt) return { where: { id_lt: idLt } }
|
|
51
|
+
if (offset) return { offset: Number(offset) }
|
|
52
|
+
|
|
53
|
+
return undefined
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const data: T[] = query.data?.pages.flatMap(page => page.data) || []
|
|
58
|
+
|
|
59
|
+
return { ...query, data }
|
|
60
|
+
}
|
package/src/navigation/index.tsx
CHANGED
|
@@ -5,7 +5,8 @@ import { NotFound } from '../screens/not_found'
|
|
|
5
5
|
import { ScreenLayout } from './screenLayout'
|
|
6
6
|
import { ConversationsScreen } from '../screens/conversations_screen'
|
|
7
7
|
import { ConversationScreen } from '../screens/conversation_screen'
|
|
8
|
-
import { HeaderBackButton } from '@react-navigation/elements'
|
|
8
|
+
import { HeaderBackButton, PlatformPressable } from '@react-navigation/elements'
|
|
9
|
+
import { Icon } from '../components'
|
|
9
10
|
|
|
10
11
|
export const ChatStack = createNativeStackNavigator({
|
|
11
12
|
screenLayout: ScreenLayout,
|
|
@@ -13,9 +14,18 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
13
14
|
Conversations: {
|
|
14
15
|
screen: ConversationsScreen,
|
|
15
16
|
options: ({ route, navigation }) => ({
|
|
16
|
-
headerTitle: (route.params as { title?: string })?.title ?? '
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
headerTitle: (route.params as { title?: string })?.title ?? 'Chat',
|
|
18
|
+
headerLeft: () => (
|
|
19
|
+
<PlatformPressable onPress={() => null}>
|
|
20
|
+
<Icon name="general.threeReducingHorizontalBars" size={18} />
|
|
21
|
+
</PlatformPressable>
|
|
22
|
+
),
|
|
23
|
+
headerRight: () => (
|
|
24
|
+
<HeaderBackButton
|
|
25
|
+
onPress={navigation.goBack}
|
|
26
|
+
backImage={() => <Icon name="general.x" size={18} />}
|
|
27
|
+
/>
|
|
28
|
+
),
|
|
19
29
|
}),
|
|
20
30
|
},
|
|
21
31
|
Conversation: {
|