@planningcenter/chat-react-native 3.16.0-rc.2 → 3.16.0-rc.3
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/attachments/giphy_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/giphy_attachment.js +1 -11
- package/build/components/conversation/attachments/giphy_attachment.js.map +1 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +5 -21
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/shadow_message.d.ts +7 -0
- package/build/components/conversation/shadow_message.d.ts.map +1 -0
- package/build/components/conversation/shadow_message.js +156 -0
- package/build/components/conversation/shadow_message.js.map +1 -0
- package/build/components/primitive/avatar_primitive.d.ts +1 -0
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +4 -0
- package/build/components/primitive/avatar_primitive.js.map +1 -1
- package/build/hooks/index.d.ts +1 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +1 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_animated_message_background_color.d.ts +8 -0
- package/build/hooks/use_animated_message_background_color.d.ts.map +1 -0
- package/build/hooks/use_animated_message_background_color.js +29 -0
- package/build/hooks/use_animated_message_background_color.js.map +1 -0
- package/build/utils/assert_keys_are_numbers.d.ts +2 -0
- package/build/utils/assert_keys_are_numbers.d.ts.map +1 -0
- package/build/utils/assert_keys_are_numbers.js +12 -0
- package/build/utils/assert_keys_are_numbers.js.map +1 -0
- 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/package.json +2 -2
- package/src/components/conversation/attachments/giphy_attachment.tsx +1 -14
- package/src/components/conversation/message.tsx +11 -35
- package/src/components/conversation/shadow_message.tsx +266 -0
- package/src/components/primitive/avatar_primitive.tsx +4 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use_animated_message_background_color.ts +44 -0
- package/src/utils/assert_keys_are_numbers.ts +13 -0
- package/src/utils/index.ts +1 -0
package/build/hooks/index.js
CHANGED
package/build/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA","sourcesContent":["export * from './use_async_storage'\nexport * from './use_theme'\nexport * from './use_suspense_api'\nexport * from './use_current_person'\nexport * from './use_font_scale'\nexport * from './use_create_android_ripple_color'\nexport * from './use_chat_permissions'\nexport * from './use_api_client'\nexport * from './use_groups_groups'\nexport * from './use_groups'\nexport * from './use_api'\nexport * from './use_api_client'\nexport * from './use_message_reaction_toggle'\nexport * from './use_interaction_ghost_color'\nexport * from './use_at_font_scale_breakpoint'\nexport * from './use_scalable_number_of_lines'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,yCAAyC,CAAA;AACvD,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA","sourcesContent":["export * from './use_async_storage'\nexport * from './use_animated_message_background_color'\nexport * from './use_theme'\nexport * from './use_suspense_api'\nexport * from './use_current_person'\nexport * from './use_font_scale'\nexport * from './use_create_android_ripple_color'\nexport * from './use_chat_permissions'\nexport * from './use_api_client'\nexport * from './use_groups_groups'\nexport * from './use_groups'\nexport * from './use_api'\nexport * from './use_api_client'\nexport * from './use_message_reaction_toggle'\nexport * from './use_interaction_ghost_color'\nexport * from './use_at_font_scale_breakpoint'\nexport * from './use_scalable_number_of_lines'\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function useAnimatedMessageBackgroundColor(): {
|
|
2
|
+
animatedBackgroundColor: {
|
|
3
|
+
backgroundColor: string;
|
|
4
|
+
};
|
|
5
|
+
handleMessagePressIn: () => void;
|
|
6
|
+
handleMessagePressOut: () => void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=use_animated_message_background_color.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use_animated_message_background_color.d.ts","sourceRoot":"","sources":["../../src/hooks/use_animated_message_background_color.ts"],"names":[],"mappings":"AAUA,wBAAgB,iCAAiC;;;;;;EAiChD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { useSharedValue, useAnimatedStyle, withTiming, interpolateColor, Easing, } from 'react-native-reanimated';
|
|
3
|
+
import { useTheme } from '../hooks';
|
|
4
|
+
export function useAnimatedMessageBackgroundColor() {
|
|
5
|
+
const { colors } = useTheme();
|
|
6
|
+
const bgFadeProgress = useSharedValue(0);
|
|
7
|
+
const pressedColor = Platform.select({
|
|
8
|
+
ios: colors.fillColorNeutral050Base,
|
|
9
|
+
default: 'transparent',
|
|
10
|
+
});
|
|
11
|
+
const animatedBackgroundColor = useAnimatedStyle(() => {
|
|
12
|
+
const backgroundColor = interpolateColor(bgFadeProgress.value, [0, 1], ['transparent', pressedColor]);
|
|
13
|
+
return {
|
|
14
|
+
backgroundColor,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
const handleMessagePressIn = () => {
|
|
18
|
+
bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) });
|
|
19
|
+
};
|
|
20
|
+
const handleMessagePressOut = () => {
|
|
21
|
+
bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) });
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
animatedBackgroundColor,
|
|
25
|
+
handleMessagePressIn,
|
|
26
|
+
handleMessagePressOut,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=use_animated_message_background_color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use_animated_message_background_color.js","sourceRoot":"","sources":["../../src/hooks/use_animated_message_background_color.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,gBAAgB,EAChB,MAAM,GACP,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC,MAAM,UAAU,iCAAiC;IAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC7B,MAAM,cAAc,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;IAExC,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC;QACnC,GAAG,EAAE,MAAM,CAAC,uBAAuB;QACnC,OAAO,EAAE,aAAa;KACvB,CAAC,CAAA;IAEF,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,GAAG,EAAE;QACpD,MAAM,eAAe,GAAG,gBAAgB,CACtC,cAAc,CAAC,KAAK,EACpB,CAAC,CAAC,EAAE,CAAC,CAAC,EACN,CAAC,aAAa,EAAE,YAAY,CAAC,CAC9B,CAAA;QACD,OAAO;YACL,eAAe;SAChB,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,oBAAoB,GAAG,GAAG,EAAE;QAChC,cAAc,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC5F,CAAC,CAAA;IAED,MAAM,qBAAqB,GAAG,GAAG,EAAE;QACjC,cAAc,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC5F,CAAC,CAAA;IAED,OAAO;QACL,uBAAuB;QACvB,oBAAoB;QACpB,qBAAqB;KACtB,CAAA;AACH,CAAC","sourcesContent":["import { Platform } from 'react-native'\nimport {\n useSharedValue,\n useAnimatedStyle,\n withTiming,\n interpolateColor,\n Easing,\n} from 'react-native-reanimated'\nimport { useTheme } from '../hooks'\n\nexport function useAnimatedMessageBackgroundColor() {\n const { colors } = useTheme()\n const bgFadeProgress = useSharedValue(0)\n\n const pressedColor = Platform.select({\n ios: colors.fillColorNeutral050Base,\n default: 'transparent',\n })\n\n const animatedBackgroundColor = useAnimatedStyle(() => {\n const backgroundColor = interpolateColor(\n bgFadeProgress.value,\n [0, 1],\n ['transparent', pressedColor]\n )\n return {\n backgroundColor,\n }\n })\n\n const handleMessagePressIn = () => {\n bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })\n }\n\n const handleMessagePressOut = () => {\n bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) })\n }\n\n return {\n animatedBackgroundColor,\n handleMessagePressIn,\n handleMessagePressOut,\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert_keys_are_numbers.d.ts","sourceRoot":"","sources":["../../src/utils/assert_keys_are_numbers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,QAAS,MAAM,KAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAEpE,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const assertKeysAreNumbers = (obj) => {
|
|
2
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]));
|
|
3
|
+
};
|
|
4
|
+
const assertNumber = (value) => {
|
|
5
|
+
if (typeof value === 'number')
|
|
6
|
+
return value;
|
|
7
|
+
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
8
|
+
return Number(value);
|
|
9
|
+
}
|
|
10
|
+
return 0;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=assert_keys_are_numbers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert_keys_are_numbers.js","sourceRoot":"","sources":["../../src/utils/assert_keys_are_numbers.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAW,EAAuB,EAAE;IACvE,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAClG,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC;IAED,OAAO,CAAC,CAAA;AACV,CAAC,CAAA","sourcesContent":["export const assertKeysAreNumbers = (obj: Object): Record<any, number> => {\n return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))\n}\n\nconst assertNumber = (value: string): number => {\n if (typeof value === 'number') return value\n\n if (typeof value === 'string' && !isNaN(Number(value))) {\n return Number(value)\n }\n\n return 0\n}\n"]}
|
package/build/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA"}
|
package/build/utils/index.js
CHANGED
package/build/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './destructure_chat_group_graph_id'\nexport * from './convert_attachments_for_create'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './destructure_chat_group_graph_id'\nexport * from './convert_attachments_for_create'\nexport * from './assert_keys_are_numbers'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.16.0-rc.
|
|
3
|
+
"version": "3.16.0-rc.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"react-native-url-polyfill": "^2.0.0",
|
|
56
56
|
"typescript": "<5.6.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "41458eebf252d441367b1c30a6b35e5422486bf2"
|
|
59
59
|
}
|
|
@@ -5,6 +5,7 @@ import { useTheme } from '../../../hooks'
|
|
|
5
5
|
import { DenormalizedGiphyAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
6
6
|
import { PlatformPressable } from '@react-navigation/elements'
|
|
7
7
|
import { Image } from '../../display'
|
|
8
|
+
import { assertKeysAreNumbers } from '../../../utils'
|
|
8
9
|
|
|
9
10
|
export function GiphyAttachment({
|
|
10
11
|
attachment,
|
|
@@ -46,20 +47,6 @@ export function GiphyAttachment({
|
|
|
46
47
|
)
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
const assertKeysAreNumbers = (obj: Object): Record<any, number> => {
|
|
50
|
-
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const assertNumber = (value: string): number => {
|
|
54
|
-
if (typeof value === 'number') return value
|
|
55
|
-
|
|
56
|
-
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
57
|
-
return Number(value)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return 0
|
|
61
|
-
}
|
|
62
|
-
|
|
63
50
|
const useStyles = ({ imageWidth, imageHeight }: { imageWidth: number; imageHeight: number }) => {
|
|
64
51
|
const { colors } = useTheme()
|
|
65
52
|
return StyleSheet.create({
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
2
|
import React, { useEffect } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
4
4
|
import { MessageReaction } from '../../components/conversation/message_reaction'
|
|
5
5
|
import { Avatar, Icon, Spinner, Text, TextInlineButton } from '../../components/display'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
useAnimatedMessageBackgroundColor,
|
|
8
|
+
useInteractionGhostBackgroundColor,
|
|
9
|
+
useTheme,
|
|
10
|
+
} from '../../hooks'
|
|
7
11
|
import { MessageResource } from '../../types'
|
|
8
12
|
import { ReactionCountResource } from '../../types/resources/reaction'
|
|
9
13
|
import { MessageAttachments } from './message_attachments'
|
|
@@ -15,13 +19,7 @@ import {
|
|
|
15
19
|
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
16
20
|
platformFontWeightMedium,
|
|
17
21
|
} from '../../utils/styles'
|
|
18
|
-
import Animated
|
|
19
|
-
useSharedValue,
|
|
20
|
-
useAnimatedStyle,
|
|
21
|
-
withTiming,
|
|
22
|
-
interpolateColor,
|
|
23
|
-
Easing,
|
|
24
|
-
} from 'react-native-reanimated'
|
|
22
|
+
import Animated from 'react-native-reanimated'
|
|
25
23
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
26
24
|
import { MessageReadReceipts } from './message_read_receipts'
|
|
27
25
|
import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
@@ -56,6 +54,8 @@ export function Message({
|
|
|
56
54
|
const isPersisted = !isNewMessage(message)
|
|
57
55
|
const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)
|
|
58
56
|
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
57
|
+
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
58
|
+
useAnimatedMessageBackgroundColor()
|
|
59
59
|
|
|
60
60
|
useEffect(() => {
|
|
61
61
|
if (pending) {
|
|
@@ -81,30 +81,6 @@ export function Message({
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const bgFadeProgress = useSharedValue(0)
|
|
85
|
-
const pressedColor = Platform.select({
|
|
86
|
-
ios: colors.fillColorNeutral050Base,
|
|
87
|
-
default: 'transparent',
|
|
88
|
-
})
|
|
89
|
-
const animatedBackgroundColor = useAnimatedStyle(() => {
|
|
90
|
-
const backgroundColor = interpolateColor(
|
|
91
|
-
bgFadeProgress.value,
|
|
92
|
-
[0, 1],
|
|
93
|
-
['transparent', pressedColor]
|
|
94
|
-
)
|
|
95
|
-
return {
|
|
96
|
-
backgroundColor,
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const handlePressIn = () => {
|
|
101
|
-
bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const handlePressOut = () => {
|
|
105
|
-
bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
106
|
-
}
|
|
107
|
-
|
|
108
84
|
const handleMessageLongPress = () => {
|
|
109
85
|
if (!isPersisted) return
|
|
110
86
|
|
|
@@ -149,8 +125,8 @@ export function Message({
|
|
|
149
125
|
<Pressable
|
|
150
126
|
onLongPress={handleMessageLongPress}
|
|
151
127
|
onPress={() => setShowMessageMetaToggle(!showMessageMetaToggle)}
|
|
152
|
-
onPressIn={
|
|
153
|
-
onPressOut={
|
|
128
|
+
onPressIn={handleMessagePressIn}
|
|
129
|
+
onPressOut={handleMessagePressOut}
|
|
154
130
|
android_ripple={{ color: colors.androidRippleNeutral }}
|
|
155
131
|
accessibilityHint="Long press to view message actions like reacting and copying."
|
|
156
132
|
>
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
|
|
3
|
+
import { Avatar, Icon, IconProps, Image, Text } from '../display'
|
|
4
|
+
import {
|
|
5
|
+
useAnimatedMessageBackgroundColor,
|
|
6
|
+
useFontScale,
|
|
7
|
+
useScalableNumberOfLines,
|
|
8
|
+
useTheme,
|
|
9
|
+
} from '../../hooks'
|
|
10
|
+
import { MessageResource } from '../../types'
|
|
11
|
+
import {
|
|
12
|
+
DenormalizedAttachmentResource,
|
|
13
|
+
DenormalizedGiphyAttachmentResource,
|
|
14
|
+
DenormalizedExpandedLinkAttachmentResource,
|
|
15
|
+
DenormalizedMessageAttachmentResource,
|
|
16
|
+
} from '../../types/resources/denormalized_attachment_resource'
|
|
17
|
+
import {
|
|
18
|
+
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
19
|
+
MAX_FONT_SIZE_MULTIPLIER,
|
|
20
|
+
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
21
|
+
platformFontWeightMedium,
|
|
22
|
+
} from '../../utils/styles'
|
|
23
|
+
import Animated from 'react-native-reanimated'
|
|
24
|
+
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
25
|
+
import { assertKeysAreNumbers } from '../../utils'
|
|
26
|
+
|
|
27
|
+
interface ShadowMessageProps extends MessageResource {}
|
|
28
|
+
|
|
29
|
+
export function ShadowMessage({ ...message }: ShadowMessageProps) {
|
|
30
|
+
const { text } = message
|
|
31
|
+
const styles = useStyles(message)
|
|
32
|
+
const { colors } = useTheme()
|
|
33
|
+
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
34
|
+
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
35
|
+
useAnimatedMessageBackgroundColor()
|
|
36
|
+
const scalableNumberOfLines = useScalableNumberOfLines(2)
|
|
37
|
+
|
|
38
|
+
const replyCount = 0 // TODO: Get reply count from message object
|
|
39
|
+
|
|
40
|
+
const handleNavigateToReplies = () => {
|
|
41
|
+
console.log('Navigate to replies') // TODO: Implement navigate to a reply screen
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Pressable
|
|
46
|
+
android_ripple={{ color: colors.androidRippleNeutral }}
|
|
47
|
+
onPress={handleNavigateToReplies}
|
|
48
|
+
onPressIn={handleMessagePressIn}
|
|
49
|
+
onPressOut={handleMessagePressOut}
|
|
50
|
+
accessibilityHint="Navigate to all replies for this message."
|
|
51
|
+
>
|
|
52
|
+
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
53
|
+
{!message.mine && (
|
|
54
|
+
<View>
|
|
55
|
+
<View style={styles.avatarWrapper}>
|
|
56
|
+
<Avatar
|
|
57
|
+
size="xs"
|
|
58
|
+
sourceUri={message.author.avatar}
|
|
59
|
+
style={styles.avatar}
|
|
60
|
+
maxFontSizeMultiplier={1}
|
|
61
|
+
/>
|
|
62
|
+
</View>
|
|
63
|
+
<TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
64
|
+
</View>
|
|
65
|
+
)}
|
|
66
|
+
<View style={styles.messageContent}>
|
|
67
|
+
<View
|
|
68
|
+
style={styles.messageBubble}
|
|
69
|
+
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
70
|
+
>
|
|
71
|
+
<MessageAttachmentImagery attachments={message.attachments} />
|
|
72
|
+
{text && (
|
|
73
|
+
<Text
|
|
74
|
+
variant="footnote"
|
|
75
|
+
style={styles.messageText}
|
|
76
|
+
numberOfLines={scalableNumberOfLines}
|
|
77
|
+
>
|
|
78
|
+
{text}
|
|
79
|
+
</Text>
|
|
80
|
+
)}
|
|
81
|
+
</View>
|
|
82
|
+
<View style={styles.messageMeta}>
|
|
83
|
+
<Text variant="footnote" style={styles.replyCountText}>
|
|
84
|
+
{replyCount} replies
|
|
85
|
+
</Text>
|
|
86
|
+
</View>
|
|
87
|
+
</View>
|
|
88
|
+
{message.mine && (
|
|
89
|
+
<MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
90
|
+
)}
|
|
91
|
+
</Animated.View>
|
|
92
|
+
</Pressable>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function MessageAttachmentImagery({
|
|
97
|
+
attachments,
|
|
98
|
+
}: {
|
|
99
|
+
attachments: DenormalizedAttachmentResource[]
|
|
100
|
+
}) {
|
|
101
|
+
if (!attachments || attachments.length === 0) return null
|
|
102
|
+
|
|
103
|
+
const attachment = attachments[0]
|
|
104
|
+
|
|
105
|
+
if (attachment.type === 'giphy') {
|
|
106
|
+
return <GiphyImage attachment={attachment} />
|
|
107
|
+
}
|
|
108
|
+
if (attachment.type === 'ExpandedLink') {
|
|
109
|
+
return <ExpandedLinkImage attachment={attachment} />
|
|
110
|
+
}
|
|
111
|
+
if (attachment.type === 'MessageAttachment') {
|
|
112
|
+
const contentType = attachment.attributes?.contentType
|
|
113
|
+
const basicType = contentType?.split('/')[0]
|
|
114
|
+
|
|
115
|
+
switch (basicType) {
|
|
116
|
+
case 'image':
|
|
117
|
+
return <MessageAttachmentImage attachment={attachment} />
|
|
118
|
+
case 'video':
|
|
119
|
+
return <MessageAttachmentIcon iconName="general.outlinedVideoFile" />
|
|
120
|
+
case 'audio':
|
|
121
|
+
return <MessageAttachmentIcon iconName="general.outlinedMusicFile" />
|
|
122
|
+
case 'application':
|
|
123
|
+
return <MessageAttachmentIcon iconName="general.outlinedGenericFile" />
|
|
124
|
+
default:
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function GiphyImage({ attachment }: { attachment: DenormalizedGiphyAttachmentResource }) {
|
|
133
|
+
const { title, giphy } = attachment
|
|
134
|
+
const { url } = giphy.fixedWidth
|
|
135
|
+
const { width, height } = assertKeysAreNumbers(giphy.fixedWidth)
|
|
136
|
+
const styles = useStyles({ imageWidth: width, imageHeight: height })
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<Image
|
|
140
|
+
source={{ uri: url }}
|
|
141
|
+
wrapperStyle={styles.imageWrapper}
|
|
142
|
+
style={styles.image}
|
|
143
|
+
alt={title}
|
|
144
|
+
loaderSize={16}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function ExpandedLinkImage({
|
|
150
|
+
attachment,
|
|
151
|
+
}: {
|
|
152
|
+
attachment: DenormalizedExpandedLinkAttachmentResource
|
|
153
|
+
}) {
|
|
154
|
+
const { title = '', imageUrl, imageHeight, imageWidth } = attachment.attributes
|
|
155
|
+
const styles = useStyles({ imageWidth, imageHeight })
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Image
|
|
159
|
+
source={{ uri: imageUrl }}
|
|
160
|
+
wrapperStyle={styles.imageWrapper}
|
|
161
|
+
style={styles.image}
|
|
162
|
+
alt={title}
|
|
163
|
+
loaderSize={16}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function MessageAttachmentImage({
|
|
169
|
+
attachment,
|
|
170
|
+
}: {
|
|
171
|
+
attachment: DenormalizedMessageAttachmentResource
|
|
172
|
+
}) {
|
|
173
|
+
const { url, urlMedium, filename, metadata = {} } = attachment.attributes
|
|
174
|
+
const styles = useStyles({ imageWidth: metadata.width, imageHeight: metadata.height })
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<Image
|
|
178
|
+
source={{ uri: urlMedium || url }}
|
|
179
|
+
style={styles.image}
|
|
180
|
+
wrapperStyle={styles.imageWrapper}
|
|
181
|
+
alt={filename}
|
|
182
|
+
loaderSize={16}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function MessageAttachmentIcon({ iconName }: { iconName: IconProps['name'] }) {
|
|
188
|
+
const styles = useStyles()
|
|
189
|
+
return (
|
|
190
|
+
<Icon
|
|
191
|
+
name={iconName}
|
|
192
|
+
style={styles.attachmentIcon}
|
|
193
|
+
maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface StylesProps {
|
|
199
|
+
imageWidth?: number
|
|
200
|
+
imageHeight?: number
|
|
201
|
+
mine?: boolean
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}) => {
|
|
205
|
+
const { colors } = useTheme()
|
|
206
|
+
const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })
|
|
207
|
+
const { width } = useWindowDimensions()
|
|
208
|
+
const tabletWidth = width >= 744 // Smallest iPad Mini's width
|
|
209
|
+
|
|
210
|
+
return StyleSheet.create({
|
|
211
|
+
message: {
|
|
212
|
+
gap: 8,
|
|
213
|
+
flexDirection: mine ? 'row-reverse' : 'row',
|
|
214
|
+
paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
215
|
+
},
|
|
216
|
+
messageContent: {
|
|
217
|
+
flex: 1,
|
|
218
|
+
gap: 4,
|
|
219
|
+
marginBottom: 12,
|
|
220
|
+
},
|
|
221
|
+
avatarWrapper: {
|
|
222
|
+
width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
},
|
|
225
|
+
avatar: {
|
|
226
|
+
marginBottom: 8,
|
|
227
|
+
opacity: 0.5,
|
|
228
|
+
},
|
|
229
|
+
messageBubble: {
|
|
230
|
+
flexDirection: 'row',
|
|
231
|
+
alignSelf: mine ? 'flex-end' : 'flex-start',
|
|
232
|
+
alignItems: 'center',
|
|
233
|
+
gap: 8,
|
|
234
|
+
borderColor: colors.borderColorDefaultBase,
|
|
235
|
+
borderWidth: 1,
|
|
236
|
+
borderRadius: 8,
|
|
237
|
+
maxWidth: tabletWidth ? 360 : '80%',
|
|
238
|
+
paddingVertical: 6,
|
|
239
|
+
paddingHorizontal: 8,
|
|
240
|
+
},
|
|
241
|
+
messageText: {
|
|
242
|
+
color: colors.textColorDefaultPlaceholder,
|
|
243
|
+
flexShrink: 1,
|
|
244
|
+
},
|
|
245
|
+
messageMeta: {
|
|
246
|
+
flexDirection: 'row',
|
|
247
|
+
justifyContent: mine ? 'flex-end' : 'flex-start',
|
|
248
|
+
},
|
|
249
|
+
replyCountText: {
|
|
250
|
+
color: colors.interaction,
|
|
251
|
+
fontWeight: platformFontWeightMedium,
|
|
252
|
+
},
|
|
253
|
+
imageWrapper: {
|
|
254
|
+
width: 32 * fontScale,
|
|
255
|
+
aspectRatio: imageWidth / imageHeight,
|
|
256
|
+
opacity: 0.5,
|
|
257
|
+
},
|
|
258
|
+
image: {
|
|
259
|
+
borderRadius: 4,
|
|
260
|
+
},
|
|
261
|
+
attachmentIcon: {
|
|
262
|
+
color: colors.iconColorDefaultDim,
|
|
263
|
+
fontSize: 16,
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
}
|
|
@@ -45,6 +45,7 @@ export type {
|
|
|
45
45
|
// =================================
|
|
46
46
|
|
|
47
47
|
const AVATAR_SIZES = {
|
|
48
|
+
xs: 'xs',
|
|
48
49
|
sm: 'sm',
|
|
49
50
|
md: 'md',
|
|
50
51
|
lg: 'lg',
|
|
@@ -60,18 +61,21 @@ type AvatarSize = (typeof AVATAR_SIZES)[keyof typeof AVATAR_SIZES]
|
|
|
60
61
|
type AvatarPresenceType = (typeof AVATAR_PRESENCE_TYPES)[keyof typeof AVATAR_PRESENCE_TYPES]
|
|
61
62
|
|
|
62
63
|
const AVATAR_PX: Record<AvatarSize, number> = {
|
|
64
|
+
[AVATAR_SIZES.xs]: 20,
|
|
63
65
|
[AVATAR_SIZES.sm]: 24,
|
|
64
66
|
[AVATAR_SIZES.md]: 32,
|
|
65
67
|
[AVATAR_SIZES.lg]: 40,
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
const AVATAR_PRESENCE_PX: Record<AvatarSize, number> = {
|
|
71
|
+
[AVATAR_SIZES.xs]: 8,
|
|
69
72
|
[AVATAR_SIZES.sm]: 10,
|
|
70
73
|
[AVATAR_SIZES.md]: 12,
|
|
71
74
|
[AVATAR_SIZES.lg]: 14,
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const AVATAR_FALLBACK_ICON_PX: Record<AvatarSize, number> = {
|
|
78
|
+
[AVATAR_SIZES.xs]: 10,
|
|
75
79
|
[AVATAR_SIZES.sm]: 12,
|
|
76
80
|
[AVATAR_SIZES.md]: 16,
|
|
77
81
|
[AVATAR_SIZES.lg]: 20,
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Platform } from 'react-native'
|
|
2
|
+
import {
|
|
3
|
+
useSharedValue,
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
withTiming,
|
|
6
|
+
interpolateColor,
|
|
7
|
+
Easing,
|
|
8
|
+
} from 'react-native-reanimated'
|
|
9
|
+
import { useTheme } from '../hooks'
|
|
10
|
+
|
|
11
|
+
export function useAnimatedMessageBackgroundColor() {
|
|
12
|
+
const { colors } = useTheme()
|
|
13
|
+
const bgFadeProgress = useSharedValue(0)
|
|
14
|
+
|
|
15
|
+
const pressedColor = Platform.select({
|
|
16
|
+
ios: colors.fillColorNeutral050Base,
|
|
17
|
+
default: 'transparent',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const animatedBackgroundColor = useAnimatedStyle(() => {
|
|
21
|
+
const backgroundColor = interpolateColor(
|
|
22
|
+
bgFadeProgress.value,
|
|
23
|
+
[0, 1],
|
|
24
|
+
['transparent', pressedColor]
|
|
25
|
+
)
|
|
26
|
+
return {
|
|
27
|
+
backgroundColor,
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const handleMessagePressIn = () => {
|
|
32
|
+
bgFadeProgress.value = withTiming(1, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleMessagePressOut = () => {
|
|
36
|
+
bgFadeProgress.value = withTiming(0, { duration: 300, easing: Easing.inOut(Easing.ease) })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
animatedBackgroundColor,
|
|
41
|
+
handleMessagePressIn,
|
|
42
|
+
handleMessagePressOut,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const assertKeysAreNumbers = (obj: Object): Record<any, number> => {
|
|
2
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, assertNumber(value)]))
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const assertNumber = (value: string): number => {
|
|
6
|
+
if (typeof value === 'number') return value
|
|
7
|
+
|
|
8
|
+
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
9
|
+
return Number(value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return 0
|
|
13
|
+
}
|
package/src/utils/index.ts
CHANGED