@planningcenter/chat-react-native 4.4.1-qa-staging.13 → 4.4.1-qa-staging.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/build/components/conversation/message.d.ts +2 -1
  2. package/build/components/conversation/message.d.ts.map +1 -1
  3. package/build/components/conversation/message.js +3 -1
  4. package/build/components/conversation/message.js.map +1 -1
  5. package/build/components/conversation/message_list.d.ts.map +1 -1
  6. package/build/components/conversation/message_list.js +35 -31
  7. package/build/components/conversation/message_list.js.map +1 -1
  8. package/build/components/conversation/message_reaction.d.ts +2 -1
  9. package/build/components/conversation/message_reaction.d.ts.map +1 -1
  10. package/build/components/conversation/message_reaction.js +67 -60
  11. package/build/components/conversation/message_reaction.js.map +1 -1
  12. package/build/components/conversation/messages_disabled_banners.d.ts +7 -5
  13. package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
  14. package/build/components/conversation/messages_disabled_banners.js +15 -14
  15. package/build/components/conversation/messages_disabled_banners.js.map +1 -1
  16. package/build/components/conversation/system_message.d.ts +2 -1
  17. package/build/components/conversation/system_message.d.ts.map +1 -1
  18. package/build/components/conversation/system_message.js +87 -65
  19. package/build/components/conversation/system_message.js.map +1 -1
  20. package/build/components/display/filter_dropdown.d.ts +13 -0
  21. package/build/components/display/filter_dropdown.d.ts.map +1 -0
  22. package/build/components/display/filter_dropdown.js +188 -0
  23. package/build/components/display/filter_dropdown.js.map +1 -0
  24. package/build/components/display/index.d.ts +1 -0
  25. package/build/components/display/index.d.ts.map +1 -1
  26. package/build/components/display/index.js +1 -0
  27. package/build/components/display/index.js.map +1 -1
  28. package/build/hooks/use_conversation.d.ts +73 -1
  29. package/build/hooks/use_conversation.d.ts.map +1 -1
  30. package/build/hooks/use_conversation.js +122 -3
  31. package/build/hooks/use_conversation.js.map +1 -1
  32. package/build/hooks/use_unarchive_conversation_action.d.ts +7 -0
  33. package/build/hooks/use_unarchive_conversation_action.d.ts.map +1 -0
  34. package/build/hooks/use_unarchive_conversation_action.js +52 -0
  35. package/build/hooks/use_unarchive_conversation_action.js.map +1 -0
  36. package/build/screens/attachment_actions/attachment_actions_screen.d.ts.map +1 -1
  37. package/build/screens/attachment_actions/attachment_actions_screen.js +84 -61
  38. package/build/screens/attachment_actions/attachment_actions_screen.js.map +1 -1
  39. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  40. package/build/screens/conversation_details_screen.js +487 -393
  41. package/build/screens/conversation_details_screen.js.map +1 -1
  42. package/build/screens/conversation_screen.d.ts.map +1 -1
  43. package/build/screens/conversation_screen.js +12 -9
  44. package/build/screens/conversation_screen.js.map +1 -1
  45. package/build/screens/design_system_screen.d.ts.map +1 -1
  46. package/build/screens/design_system_screen.js +275 -215
  47. package/build/screens/design_system_screen.js.map +1 -1
  48. package/build/screens/message_actions_screen.d.ts.map +1 -1
  49. package/build/screens/message_actions_screen.js +112 -92
  50. package/build/screens/message_actions_screen.js.map +1 -1
  51. package/build/types/resources/member_ability.d.ts +2 -0
  52. package/build/types/resources/member_ability.d.ts.map +1 -1
  53. package/build/types/resources/member_ability.js.map +1 -1
  54. package/build/utils/archived_conversation.d.ts +5 -0
  55. package/build/utils/archived_conversation.d.ts.map +1 -0
  56. package/build/utils/archived_conversation.js +5 -0
  57. package/build/utils/archived_conversation.js.map +1 -0
  58. package/build/utils/index.d.ts +1 -0
  59. package/build/utils/index.d.ts.map +1 -1
  60. package/build/utils/index.js +1 -0
  61. package/build/utils/index.js.map +1 -1
  62. package/build/utils/native_adapters/configuration.d.ts +3 -0
  63. package/build/utils/native_adapters/configuration.d.ts.map +1 -1
  64. package/build/utils/native_adapters/configuration.js +3 -0
  65. package/build/utils/native_adapters/configuration.js.map +1 -1
  66. package/build/utils/native_adapters/index.d.ts +1 -0
  67. package/build/utils/native_adapters/index.d.ts.map +1 -1
  68. package/build/utils/native_adapters/index.js +1 -0
  69. package/build/utils/native_adapters/index.js.map +1 -1
  70. package/build/utils/native_adapters/menu.d.ts +40 -0
  71. package/build/utils/native_adapters/menu.d.ts.map +1 -0
  72. package/build/utils/native_adapters/menu.js +55 -0
  73. package/build/utils/native_adapters/menu.js.map +1 -0
  74. package/package.json +3 -3
  75. package/src/__tests__/screens/message_actions_screen.test.tsx +65 -2
  76. package/src/components/conversation/__tests__/message_list.test.tsx +68 -2
  77. package/src/components/conversation/__tests__/system_message.test.tsx +3 -1
  78. package/src/components/conversation/message.tsx +3 -0
  79. package/src/components/conversation/message_list.tsx +9 -1
  80. package/src/components/conversation/message_reaction.tsx +7 -2
  81. package/src/components/conversation/messages_disabled_banners.tsx +17 -18
  82. package/src/components/conversation/system_message.tsx +24 -10
  83. package/src/components/display/__tests__/filter_dropdown.test.tsx +106 -0
  84. package/src/components/display/filter_dropdown.tsx +123 -0
  85. package/src/components/display/index.ts +1 -0
  86. package/src/hooks/__tests__/use_conversation_archive.test.tsx +130 -0
  87. package/src/hooks/__tests__/use_conversation_jolt_events.test.tsx +74 -0
  88. package/src/hooks/__tests__/use_unarchive_conversation_action.test.tsx +67 -0
  89. package/src/hooks/use_conversation.ts +41 -3
  90. package/src/hooks/use_unarchive_conversation_action.ts +24 -0
  91. package/src/screens/__tests__/conversation_details_screen.test.tsx +289 -0
  92. package/src/screens/attachment_actions/__tests__/attachment_actions_screen.test.tsx +72 -0
  93. package/src/screens/attachment_actions/attachment_actions_screen.tsx +3 -1
  94. package/src/screens/conversation_details_screen.tsx +78 -14
  95. package/src/screens/conversation_screen.tsx +3 -1
  96. package/src/screens/design_system_screen.tsx +26 -0
  97. package/src/screens/message_actions_screen.tsx +29 -20
  98. package/src/types/resources/member_ability.ts +2 -0
  99. package/src/utils/archived_conversation.ts +13 -0
  100. package/src/utils/index.ts +1 -0
  101. package/src/utils/native_adapters/__tests__/menu.test.tsx +42 -0
  102. package/src/utils/native_adapters/configuration.ts +5 -0
  103. package/src/utils/native_adapters/index.ts +1 -0
  104. package/src/utils/native_adapters/menu.tsx +68 -0
@@ -1 +1 @@
1
- {"version":3,"file":"configuration.js","names":["AsyncStorage","Linking","RNLinking","AudioAdapter","ClipboardAdapter","DocumentPickerAdapter","HapticAdapter","ImagePickerAdapter","LinkingAdapter","LogAdapter","StorageAdapter","VideoAdapter","ChatAdapters","configure","configurations","Clipboard","clipboard","Audio","audio","Video","video","ImagePicker","imagePicker","DocumentPicker","documentPicker","Log","log","linking","Haptic","haptic","methodMissing","console","warn","getStringAsync","setStringAsync","_","useAudio","Player","Object","assign","$$typeof","Symbol","for","openCameraAsync","canceled","assets","openImageLibraryAsync","Storage","getItem","setItem","removeItem"],"sources":["../../../src/utils/native_adapters/configuration.ts"],"sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage'\nimport { Linking as RNLinking } from 'react-native'\nimport { AudioAdapter } from './audio'\nimport { ClipboardAdapter } from './clipboard'\nimport { DocumentPickerAdapter } from './document_picker'\nimport { HapticAdapter } from './haptic'\nimport { ImagePickerAdapter } from './image_picker'\nimport { LinkingAdapter } from './linking'\nimport { LogAdapter } from './log'\nimport { StorageAdapter } from './storage_adapter'\nimport { VideoAdapter } from './video'\n\ntype ChatConfigurations = {\n clipboard: ClipboardAdapter\n audio: AudioAdapter\n video: VideoAdapter\n imagePicker: ImagePickerAdapter\n documentPicker?: DocumentPickerAdapter\n log?: LogAdapter\n linking?: LinkingAdapter\n haptic?: HapticAdapter\n}\n\nexport class ChatAdapters {\n static configure(configurations: ChatConfigurations) {\n Clipboard = configurations.clipboard\n Audio = configurations.audio\n Video = configurations.video\n ImagePicker = configurations.imagePicker\n DocumentPicker = configurations.documentPicker || new DocumentPickerAdapter()\n Log = configurations.log || new LogAdapter()\n Linking = configurations.linking || new LinkingAdapter(RNLinking)\n Haptic = configurations.haptic || new HapticAdapter()\n }\n}\n\nconst methodMissing = () => {\n console.warn('ChatAdapters.configure() must be called before using any adapters')\n}\n\nexport let Clipboard: ClipboardAdapter = new ClipboardAdapter({\n getStringAsync: async () => {\n methodMissing()\n return ''\n },\n setStringAsync: async (_: string) => methodMissing(),\n})\n\nexport let Audio: AudioAdapter = new AudioAdapter({\n useAudio: (_: string) => {\n methodMissing()\n return {} as any\n },\n})\n\nexport let Video: VideoAdapter = new VideoAdapter({\n Player: Object.assign(\n () => {\n methodMissing()\n return null\n },\n { $$typeof: Symbol.for('react.forward_ref') }\n ),\n})\n\nexport let ImagePicker: ImagePickerAdapter = new ImagePickerAdapter({\n openCameraAsync: async () => {\n methodMissing()\n return { canceled: true, assets: null }\n },\n openImageLibraryAsync: async () => {\n methodMissing()\n return { canceled: true, assets: null }\n },\n})\n\nexport let DocumentPicker: DocumentPickerAdapter = new DocumentPickerAdapter()\n\nexport let Log: LogAdapter = new LogAdapter()\n\nexport let Linking: LinkingAdapter = new LinkingAdapter(RNLinking)\n\nexport let Haptic: HapticAdapter = new HapticAdapter()\n\nexport let Storage = new StorageAdapter({\n getItem: AsyncStorage.getItem,\n setItem: AsyncStorage.setItem,\n removeItem: AsyncStorage.removeItem,\n})\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"configuration.js","names":["AsyncStorage","Linking","RNLinking","AudioAdapter","ClipboardAdapter","DocumentPickerAdapter","HapticAdapter","ImagePickerAdapter","LinkingAdapter","LogAdapter","MenuAdapter","StorageAdapter","VideoAdapter","ChatAdapters","configure","configurations","Clipboard","clipboard","Audio","audio","Video","video","ImagePicker","imagePicker","DocumentPicker","documentPicker","Log","log","linking","Haptic","haptic","Menu","menu","methodMissing","console","warn","getStringAsync","setStringAsync","_","useAudio","Player","Object","assign","$$typeof","Symbol","for","openCameraAsync","canceled","assets","openImageLibraryAsync","Storage","getItem","setItem","removeItem"],"sources":["../../../src/utils/native_adapters/configuration.ts"],"sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage'\nimport { Linking as RNLinking } from 'react-native'\nimport { AudioAdapter } from './audio'\nimport { ClipboardAdapter } from './clipboard'\nimport { DocumentPickerAdapter } from './document_picker'\nimport { HapticAdapter } from './haptic'\nimport { ImagePickerAdapter } from './image_picker'\nimport { LinkingAdapter } from './linking'\nimport { LogAdapter } from './log'\nimport { MenuAdapter } from './menu'\nimport { StorageAdapter } from './storage_adapter'\nimport { VideoAdapter } from './video'\n\ntype ChatConfigurations = {\n clipboard: ClipboardAdapter\n audio: AudioAdapter\n video: VideoAdapter\n imagePicker: ImagePickerAdapter\n documentPicker?: DocumentPickerAdapter\n log?: LogAdapter\n linking?: LinkingAdapter\n haptic?: HapticAdapter\n menu?: MenuAdapter\n}\n\nexport class ChatAdapters {\n static configure(configurations: ChatConfigurations) {\n Clipboard = configurations.clipboard\n Audio = configurations.audio\n Video = configurations.video\n ImagePicker = configurations.imagePicker\n DocumentPicker = configurations.documentPicker || new DocumentPickerAdapter()\n Log = configurations.log || new LogAdapter()\n Linking = configurations.linking || new LinkingAdapter(RNLinking)\n Haptic = configurations.haptic || new HapticAdapter()\n Menu = configurations.menu || new MenuAdapter()\n }\n}\n\nconst methodMissing = () => {\n console.warn('ChatAdapters.configure() must be called before using any adapters')\n}\n\nexport let Clipboard: ClipboardAdapter = new ClipboardAdapter({\n getStringAsync: async () => {\n methodMissing()\n return ''\n },\n setStringAsync: async (_: string) => methodMissing(),\n})\n\nexport let Audio: AudioAdapter = new AudioAdapter({\n useAudio: (_: string) => {\n methodMissing()\n return {} as any\n },\n})\n\nexport let Video: VideoAdapter = new VideoAdapter({\n Player: Object.assign(\n () => {\n methodMissing()\n return null\n },\n { $$typeof: Symbol.for('react.forward_ref') }\n ),\n})\n\nexport let ImagePicker: ImagePickerAdapter = new ImagePickerAdapter({\n openCameraAsync: async () => {\n methodMissing()\n return { canceled: true, assets: null }\n },\n openImageLibraryAsync: async () => {\n methodMissing()\n return { canceled: true, assets: null }\n },\n})\n\nexport let DocumentPicker: DocumentPickerAdapter = new DocumentPickerAdapter()\n\nexport let Log: LogAdapter = new LogAdapter()\n\nexport let Linking: LinkingAdapter = new LinkingAdapter(RNLinking)\n\nexport let Haptic: HapticAdapter = new HapticAdapter()\n\nexport let Menu: MenuAdapter = new MenuAdapter()\n\nexport let Storage = new StorageAdapter({\n getItem: AsyncStorage.getItem,\n setItem: AsyncStorage.setItem,\n removeItem: AsyncStorage.removeItem,\n})\n"],"mappings":"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","ignoreList":[]}
@@ -5,6 +5,7 @@ export * from './document_picker';
5
5
  export * from './image_picker';
6
6
  export * from './linking';
7
7
  export * from './log';
8
+ export * from './menu';
8
9
  export * from './video';
9
10
  export * from './haptic';
10
11
  export * from './storage_adapter';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,OAAO,CAAA;AACrB,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,mBAAmB,CAAA"}
@@ -5,6 +5,7 @@ export * from './document_picker';
5
5
  export * from './image_picker';
6
6
  export * from './linking';
7
7
  export * from './log';
8
+ export * from './menu';
8
9
  export * from './video';
9
10
  export * from './haptic';
10
11
  export * from './storage_adapter';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/native_adapters/index.ts"],"sourcesContent":["export * from './audio'\nexport * from './clipboard'\nexport * from './configuration'\nexport * from './document_picker'\nexport * from './image_picker'\nexport * from './linking'\nexport * from './log'\nexport * from './video'\nexport * from './haptic'\nexport * from './storage_adapter'\n"],"mappings":"AAAA,cAAc,SAAS;AACvB,cAAc,aAAa;AAC3B,cAAc,iBAAiB;AAC/B,cAAc,mBAAmB;AACjC,cAAc,gBAAgB;AAC9B,cAAc,WAAW;AACzB,cAAc,OAAO;AACrB,cAAc,SAAS;AACvB,cAAc,UAAU;AACxB,cAAc,mBAAmB","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/utils/native_adapters/index.ts"],"sourcesContent":["export * from './audio'\nexport * from './clipboard'\nexport * from './configuration'\nexport * from './document_picker'\nexport * from './image_picker'\nexport * from './linking'\nexport * from './log'\nexport * from './menu'\nexport * from './video'\nexport * from './haptic'\nexport * from './storage_adapter'\n"],"mappings":"AAAA,cAAc,SAAS;AACvB,cAAc,aAAa;AAC3B,cAAc,iBAAiB;AAC/B,cAAc,mBAAmB;AACjC,cAAc,gBAAgB;AAC9B,cAAc,WAAW;AACzB,cAAc,OAAO;AACrB,cAAc,QAAQ;AACtB,cAAc,SAAS;AACvB,cAAc,UAAU;AACxB,cAAc,mBAAmB","ignoreList":[]}
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ export type MenuActionState = 'on' | 'off';
4
+ export interface MenuActionAttributes {
5
+ destructive?: boolean;
6
+ disabled?: boolean;
7
+ hidden?: boolean;
8
+ }
9
+ export interface MenuAction {
10
+ id?: string;
11
+ title: string;
12
+ state?: MenuActionState;
13
+ attributes?: MenuActionAttributes;
14
+ subactions?: MenuAction[];
15
+ displayInline?: boolean;
16
+ }
17
+ export interface MenuActionEvent {
18
+ nativeEvent: {
19
+ event: string;
20
+ };
21
+ }
22
+ export interface MenuViewProps {
23
+ actions: MenuAction[];
24
+ onPressAction?: (event: MenuActionEvent) => void;
25
+ onOpenMenu?: () => void;
26
+ onCloseMenu?: () => void;
27
+ shouldOpenOnLongPress?: boolean;
28
+ title?: string;
29
+ style?: StyleProp<ViewStyle>;
30
+ testID?: string;
31
+ children?: React.ReactNode;
32
+ }
33
+ export type MenuView = React.ComponentType<MenuViewProps>;
34
+ export declare class MenuAdapter {
35
+ View: MenuView;
36
+ constructor(methods?: {
37
+ View: MenuView;
38
+ });
39
+ }
40
+ //# sourceMappingURL=menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/menu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAA;AACxC,OAAO,EAAE,SAAS,EAAQ,SAAS,EAAE,MAAM,cAAc,CAAA;AASzD,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,KAAK,CAAA;AAE1C,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,UAAU,CAAC,EAAE,oBAAoB,CAAA;IACjC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IAChD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,CAAA;AAkBzD,qBAAa,WAAW;IACtB,IAAI,EAAE,QAAQ,CAAA;gBAEF,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE;CAGzC"}
@@ -0,0 +1,55 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import React, { useEffect } from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ // =================================
6
+ // ====== Types ====================
7
+ // =================================
8
+
9
+ // Mirrors the props shared by `@expo/ui/community/menu` and
10
+ // `@react-native-menu/menu`, so a host can pass either `MenuView` straight in.
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ // =================================
13
+ // ====== Adapter ==================
14
+ // =================================
15
+
16
+ function UnconfiguredMenuView(t0) {
17
+ const $ = _c(5);
18
+ const {
19
+ children,
20
+ style,
21
+ testID
22
+ } = t0;
23
+ let t1;
24
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25
+ t1 = [];
26
+ $[0] = t1;
27
+ } else {
28
+ t1 = $[0];
29
+ }
30
+ useEffect(_temp, t1);
31
+ let t2;
32
+ if ($[1] !== children || $[2] !== style || $[3] !== testID) {
33
+ t2 = /*#__PURE__*/_jsx(View, {
34
+ style: style,
35
+ testID: testID,
36
+ children: children
37
+ });
38
+ $[1] = children;
39
+ $[2] = style;
40
+ $[3] = testID;
41
+ $[4] = t2;
42
+ } else {
43
+ t2 = $[4];
44
+ }
45
+ return t2;
46
+ }
47
+ function _temp() {
48
+ console.warn("ChatAdapters.configure() must include a menu adapter before rendering a menu");
49
+ }
50
+ export class MenuAdapter {
51
+ constructor(methods) {
52
+ this.View = methods?.View ?? UnconfiguredMenuView;
53
+ }
54
+ }
55
+ //# sourceMappingURL=menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.js","names":["React","useEffect","View","jsx","_jsx","UnconfiguredMenuView","t0","$","_c","children","style","testID","t1","Symbol","for","_temp","t2","console","warn","MenuAdapter","constructor","methods"],"sources":["../../../src/utils/native_adapters/menu.tsx"],"sourcesContent":["import React, { useEffect } from 'react'\nimport { StyleProp, View, ViewStyle } from 'react-native'\n\n// =================================\n// ====== Types ====================\n// =================================\n\n// Mirrors the props shared by `@expo/ui/community/menu` and\n// `@react-native-menu/menu`, so a host can pass either `MenuView` straight in.\n\nexport type MenuActionState = 'on' | 'off'\n\nexport interface MenuActionAttributes {\n destructive?: boolean\n disabled?: boolean\n hidden?: boolean\n}\n\nexport interface MenuAction {\n id?: string\n title: string\n state?: MenuActionState\n attributes?: MenuActionAttributes\n subactions?: MenuAction[]\n displayInline?: boolean\n}\n\nexport interface MenuActionEvent {\n nativeEvent: { event: string }\n}\n\nexport interface MenuViewProps {\n actions: MenuAction[]\n onPressAction?: (event: MenuActionEvent) => void\n onOpenMenu?: () => void\n onCloseMenu?: () => void\n shouldOpenOnLongPress?: boolean\n title?: string\n style?: StyleProp<ViewStyle>\n testID?: string\n children?: React.ReactNode\n}\n\nexport type MenuView = React.ComponentType<MenuViewProps>\n\n// =================================\n// ====== Adapter ==================\n// =================================\n\nfunction UnconfiguredMenuView({ children, style, testID }: MenuViewProps) {\n useEffect(() => {\n console.warn('ChatAdapters.configure() must include a menu adapter before rendering a menu')\n }, [])\n\n return (\n <View style={style} testID={testID}>\n {children}\n </View>\n )\n}\n\nexport class MenuAdapter {\n View: MenuView\n\n constructor(methods?: { View: MenuView }) {\n this.View = methods?.View ?? UnconfiguredMenuView\n }\n}\n"],"mappings":";AAAA,OAAOA,KAAK,IAAIC,SAAS,QAAQ,OAAO;AACxC,SAAoBC,IAAI,QAAmB,cAAc;;AAEzD;AACA;AACA;;AAEA;AACA;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAqCA;AACA;AACA;;AAEA,SAAAC,qBAAAC,EAAA;EAAA,MAAAC,CAAA,GAAAC,EAAA;EAA8B;IAAAC,QAAA;IAAAC,KAAA;IAAAC;EAAA,IAAAL,EAA0C;EAAA,IAAAM,EAAA;EAAA,IAAAL,CAAA,QAAAM,MAAA,CAAAC,GAAA;IAGnEF,EAAA,KAAE;IAAAL,CAAA,MAAAK,EAAA;EAAA;IAAAA,EAAA,GAAAL,CAAA;EAAA;EAFLN,SAAS,CAACc,KAET,EAAEH,EAAE,CAAC;EAAA,IAAAI,EAAA;EAAA,IAAAT,CAAA,QAAAE,QAAA,IAAAF,CAAA,QAAAG,KAAA,IAAAH,CAAA,QAAAI,MAAA;IAGJK,EAAA,gBAAAZ,IAAA,CAACF,IAAI;MAAQQ,KAAK,EAALA,KAAK;MAAUC,MAAM,EAANA,MAAM;MAAAF,QAAA,EAC/BA;IAAQ,CACL,CAAC;IAAAF,CAAA,MAAAE,QAAA;IAAAF,CAAA,MAAAG,KAAA;IAAAH,CAAA,MAAAI,MAAA;IAAAJ,CAAA,MAAAS,EAAA;EAAA;IAAAA,EAAA,GAAAT,CAAA;EAAA;EAAA,OAFPS,EAEO;AAAA;AARX,SAAAD,MAAA;EAEIE,OAAO,CAAAC,IAAK,CAAC,8EAA8E,CAAC;AAAA;AAUhG,OAAO,MAAMC,WAAW,CAAC;EAGvBC,WAAWA,CAACC,OAA4B,EAAE;IACxC,IAAI,CAACnB,IAAI,GAAGmB,OAAO,EAAEnB,IAAI,IAAIG,oBAAoB;EACnD;AACF","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "4.4.1-qa-staging.13",
3
+ "version": "4.4.1-qa-staging.14",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@fortawesome/fontawesome-svg-core": "^7.2.0",
35
35
  "@fortawesome/react-native-fontawesome": "^0.3.2",
36
- "@planningcenter/emoji-keyboard": "4.4.1-qa-staging.13",
36
+ "@planningcenter/emoji-keyboard": "4.4.1-qa-staging.14",
37
37
  "lodash-inflection": "^1.5.0"
38
38
  },
39
39
  "peerDependencies": {
@@ -84,5 +84,5 @@
84
84
  "sf-symbols-typescript": "^2.2.0",
85
85
  "typescript": "~6.0.3"
86
86
  },
87
- "gitHead": "b6fa6a1c2ed0ab85c232050525ec3253be762dff"
87
+ "gitHead": "cae9a5592e733dfe3eb5453dfe1252b75878eff4"
88
88
  }
@@ -9,7 +9,7 @@ import {
9
9
  MessageActionsScreen,
10
10
  MessageActionsScreenProps,
11
11
  } from '../../screens/message_actions_screen'
12
- import { MessageResource } from '../../types'
12
+ import { ConversationResource, MessageResource } from '../../types'
13
13
  import {
14
14
  AudioAdapter,
15
15
  ChatAdapters,
@@ -36,7 +36,15 @@ const mockMessage: MessageResource = {
36
36
  messageType: 'user',
37
37
  }
38
38
 
39
+ const myMessageWithLinkPreview = {
40
+ ...mockMessage,
41
+ mine: true,
42
+ attachments: [{ type: 'ExpandedLink', id: '9' }],
43
+ } as MessageResource
44
+
39
45
  let mockCurrentMessage = mockMessage
46
+ let mockConversation = { id: 1, disabled: false } as ConversationResource
47
+ let mockEnabledFeatures: string[] = []
40
48
 
41
49
  const mockGoBack = jest.fn()
42
50
  jest.mock('@react-navigation/native', () => ({
@@ -44,9 +52,15 @@ jest.mock('@react-navigation/native', () => ({
44
52
  useNavigation: () => ({ navigate: jest.fn(), dispatch: jest.fn(), goBack: mockGoBack }),
45
53
  }))
46
54
  jest.mock('../../hooks/use_api_client', () => ({ useApiClient: () => ({}) }))
55
+ jest.mock('../../hooks/use_conversation', () => ({
56
+ useConversation: () => ({ data: mockConversation }),
57
+ }))
47
58
  jest.mock('../../hooks/use_features', () => ({
48
59
  ...jest.requireActual('../../hooks/use_features'),
49
- useFeatures: () => ({ features: [], featureEnabled: () => false }),
60
+ useFeatures: () => ({
61
+ features: [],
62
+ featureEnabled: (name: string) => mockEnabledFeatures.includes(name),
63
+ }),
50
64
  }))
51
65
  jest.mock('../../hooks/use_message_reaction_toggle', () => ({
52
66
  useMessageReactionToggle: () => ({ handleReactionToggle: jest.fn(), isPending: false }),
@@ -108,6 +122,55 @@ describe('MessageActionsScreen', () => {
108
122
  beforeEach(() => {
109
123
  jest.clearAllMocks()
110
124
  mockCurrentMessage = mockMessage
125
+ mockConversation = { id: 1, disabled: false } as ConversationResource
126
+ mockEnabledFeatures = []
127
+ })
128
+
129
+ it('offers Delete on my own message', () => {
130
+ mockCurrentMessage = { ...mockMessage, mine: true }
131
+ const { getByText } = renderScreen()
132
+
133
+ expect(getByText('Delete message')).toBeTruthy()
134
+ })
135
+
136
+ it('hides Delete when the conversation is disabled', () => {
137
+ mockCurrentMessage = { ...mockMessage, mine: true }
138
+ mockConversation = { id: 1, disabled: true, disabledReason: 'locked' } as ConversationResource
139
+ const { queryByText, getByText } = renderScreen()
140
+
141
+ expect(queryByText('Delete message')).toBeNull()
142
+ expect(getByText('Copy text')).toBeTruthy()
143
+ })
144
+
145
+ it('offers reactions, reply, edit, and link preview removal on my message', () => {
146
+ mockCurrentMessage = myMessageWithLinkPreview
147
+ const { getByText } = renderScreen()
148
+
149
+ expect(getByText('👍')).toBeTruthy()
150
+ expect(getByText('Reply to message')).toBeTruthy()
151
+ expect(getByText('Edit message')).toBeTruthy()
152
+ expect(getByText('Remove link preview')).toBeTruthy()
153
+ })
154
+
155
+ it('hides reactions, reply, edit, and link preview removal when the conversation is disabled', () => {
156
+ mockCurrentMessage = myMessageWithLinkPreview
157
+ mockConversation = { id: 1, disabled: true, disabledReason: 'archived' } as ConversationResource
158
+ const { getByText, queryByText } = renderScreen()
159
+
160
+ expect(queryByText('👍')).toBeNull()
161
+ expect(queryByText('Reply to message')).toBeNull()
162
+ expect(queryByText('Edit message')).toBeNull()
163
+ expect(queryByText('Remove link preview')).toBeNull()
164
+ expect(getByText('Copy text')).toBeTruthy()
165
+ expect(getByText('View read receipts')).toBeTruthy()
166
+ })
167
+
168
+ it('keeps Report when the conversation is disabled', () => {
169
+ mockEnabledFeatures = ['ROLLOUT_MOBILE_message_reporting']
170
+ mockConversation = { id: 1, disabled: true, disabledReason: 'archived' } as ConversationResource
171
+ const { getByText } = renderScreen()
172
+
173
+ expect(getByText('Report message')).toBeTruthy()
111
174
  })
112
175
 
113
176
  it('copies the message text and gives success haptic feedback before closing', async () => {
@@ -5,7 +5,7 @@ import {
5
5
  QueryClient,
6
6
  QueryClientProvider,
7
7
  } from '@tanstack/react-query'
8
- import { act, render, screen } from '@testing-library/react-native'
8
+ import { act, fireEvent, render, screen } from '@testing-library/react-native'
9
9
  import React from 'react'
10
10
  import { FlatList } from 'react-native'
11
11
  import { buildTestQueryClient } from '../../../__utils__/query_client'
@@ -37,9 +37,18 @@ jest.mock('../../../hooks/use_animated_message_background_color', () => ({
37
37
  handleMessagePressOut: jest.fn(),
38
38
  }),
39
39
  }))
40
+ const mockNavigate = jest.fn()
40
41
  jest.mock('@react-navigation/native', () => ({
41
42
  ...jest.requireActual('@react-navigation/native'),
42
- useNavigation: () => ({ navigate: jest.fn() }),
43
+ useNavigation: () => ({ navigate: mockNavigate }),
44
+ }))
45
+ const mockToggleReaction = jest.fn()
46
+ jest.mock('../../../hooks/use_message_reaction_toggle', () => ({
47
+ useMessageReactionToggle: () => ({ handleReactionToggle: mockToggleReaction, isPending: false }),
48
+ }))
49
+ jest.mock('../../../utils/native_adapters', () => ({
50
+ ...jest.requireActual('../../../utils/native_adapters'),
51
+ Haptic: { impactLight: jest.fn() },
43
52
  }))
44
53
  // Every row calls this hook on each render, so its calls count row renders.
45
54
  jest.mock('../../../hooks/use_live_relative_time', () => {
@@ -293,6 +302,63 @@ describe('MessageList', () => {
293
302
  expect(renderedRowIds()).toEqual([])
294
303
  })
295
304
 
305
+ describe('in a disabled conversation', () => {
306
+ const reacted = buildMessage(1, {
307
+ reactionCounts: [
308
+ {
309
+ type: 'ReactionCount',
310
+ id: 'heart',
311
+ messageId: messageId(1),
312
+ value: 'heart',
313
+ count: 1,
314
+ mine: 0,
315
+ authorIds: [OTHER],
316
+ },
317
+ ],
318
+ })
319
+ const archived = buildMessage(2, {
320
+ text: 'This conversation was archived by Ann',
321
+ systemTextFormat: 'actor',
322
+ })
323
+ const disable = () => act(() => setHarnessConversation({ ...conversation, disabled: true }))
324
+
325
+ it('toggles a reaction pill while the conversation is active', () => {
326
+ renderList([reacted])
327
+
328
+ fireEvent.press(screen.getByTestId(`reaction-${reacted.id}-heart`))
329
+
330
+ expect(mockToggleReaction).toHaveBeenCalledWith({ value: 'heart', mine: false })
331
+ })
332
+
333
+ it('keeps reaction pills read-only but still shows who reacted', () => {
334
+ renderList([reacted])
335
+ disable()
336
+ const pill = screen.getByTestId(`reaction-${reacted.id}-heart`)
337
+
338
+ fireEvent.press(pill)
339
+ fireEvent(pill, 'longPress')
340
+
341
+ expect(mockToggleReaction).not.toHaveBeenCalled()
342
+ expect(mockNavigate).toHaveBeenCalledWith('Reactions', expect.anything())
343
+ })
344
+
345
+ it('opens message actions on a system message only while the conversation is active', () => {
346
+ queryClient.setQueryData<MessagesQueryData>(messagesQueryKey, {
347
+ pages: [{ data: [archived], links: {}, meta: { count: 1, totalCount: 1 } }],
348
+ pageParams: [{}],
349
+ })
350
+ render(<Harness />, { wrapper: Wrapper })
351
+ fireEvent(screen.getByText(/archived by Ann/), 'longPress')
352
+ expect(mockNavigate).toHaveBeenCalledWith('MessageActions', expect.anything())
353
+
354
+ mockNavigate.mockClear()
355
+ disable()
356
+ fireEvent(screen.getByText(/archived by Ann/), 'longPress')
357
+
358
+ expect(mockNavigate).not.toHaveBeenCalled()
359
+ })
360
+ })
361
+
296
362
  describe('opened at an anchor message', () => {
297
363
  const anchor = messages[4]
298
364
 
@@ -65,7 +65,9 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
65
65
  )
66
66
 
67
67
  const renderSystemMessage = (message: SystemMessageResource) =>
68
- render(<SystemMessage message={message} conversationId={1} />, { wrapper: Wrapper })
68
+ render(<SystemMessage message={message} conversationId={1} conversationDisabled={false} />, {
69
+ wrapper: Wrapper,
70
+ })
69
71
 
70
72
  beforeEach(() => {
71
73
  jest.useFakeTimers({ now: new Date(2026, 5, 15, 12, 0) })
@@ -44,6 +44,7 @@ import { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from
44
44
  */
45
45
  interface MessageProps extends MessageResource {
46
46
  canDeleteNonAuthoredMessages: boolean
47
+ conversationDisabled: boolean
47
48
  conversation_id: number
48
49
  readByOthers?: boolean
49
50
  inReplyScreen?: boolean
@@ -53,6 +54,7 @@ interface MessageProps extends MessageResource {
53
54
 
54
55
  export const Message = memo(function MessageRow({
55
56
  canDeleteNonAuthoredMessages,
57
+ conversationDisabled,
56
58
  conversation_id,
57
59
  readByOthers,
58
60
  inReplyScreen,
@@ -304,6 +306,7 @@ export const Message = memo(function MessageRow({
304
306
  onLongPress={handleReactionLongPress}
305
307
  message={message}
306
308
  conversationId={conversation_id}
309
+ disabled={conversationDisabled}
307
310
  />
308
311
  ))}
309
312
  </View>
@@ -72,6 +72,7 @@ export function MessageList({
72
72
  const inReplyScreen = !!replyRootId
73
73
  const canDeleteNonAuthoredMessages =
74
74
  conversation.memberAbility?.canDeleteNonAuthoredMessages ?? false
75
+ const conversationDisabled = conversation.disabled ?? false
75
76
  const latestRead = conversation.latestReadMessageSortKey
76
77
  const messagesWithSeparators = groupMessages({ ms: messages, inReplyScreen })
77
78
  const anchorIndex = anchorMessageId
@@ -193,7 +194,13 @@ export function MessageList({
193
194
  }
194
195
 
195
196
  if (isSystemMessage(item)) {
196
- return <SystemMessage message={item} conversationId={conversationId} />
197
+ return (
198
+ <SystemMessage
199
+ message={item}
200
+ conversationId={conversationId}
201
+ conversationDisabled={conversationDisabled}
202
+ />
203
+ )
197
204
  }
198
205
 
199
206
  const readByOthers = item.mine && !!latestRead && item.id <= latestRead
@@ -201,6 +208,7 @@ export function MessageList({
201
208
  <Message
202
209
  {...item}
203
210
  canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages}
211
+ conversationDisabled={conversationDisabled}
204
212
  conversation_id={conversationId}
205
213
  readByOthers={readByOthers}
206
214
  inReplyScreen={inReplyScreen}
@@ -16,11 +16,13 @@ export function MessageReaction({
16
16
  onLongPress,
17
17
  message,
18
18
  conversationId,
19
+ disabled = false,
19
20
  }: {
20
21
  reaction: ReactionCountResource
21
22
  onLongPress: (_reaction: ReactionCountResource) => void
22
23
  message: MessageResource
23
24
  conversationId: number
25
+ disabled?: boolean
24
26
  }) {
25
27
  const styles = useReactionStyles(reaction)
26
28
  const { handleReactionToggle } = useMessageReactionToggle({
@@ -46,8 +48,11 @@ export function MessageReaction({
46
48
  testID={`reaction-${message.id}-${reaction.value}`}
47
49
  style={styles.reaction}
48
50
  onLongPress={() => onLongPress(reaction)}
49
- onPress={handlePress}
50
- android_ripple={{ color: androidRippleColor, borderless: false, foreground: true }}
51
+ onPress={disabled ? undefined : handlePress}
52
+ pressOpacity={disabled ? 1 : undefined}
53
+ android_ripple={
54
+ disabled ? undefined : { color: androidRippleColor, borderless: false, foreground: true }
55
+ }
51
56
  >
52
57
  <Text style={styles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
53
58
  <Text style={styles.reactionText}>{reaction.count}</Text>
@@ -22,38 +22,36 @@ export const MemberMessagesDisabledBanner = () => {
22
22
 
23
23
  const DEFAULT_DISABLED_TEXT = 'This conversation has been disabled.'
24
24
 
25
+ interface BannerAction {
26
+ label: string
27
+ onPress: () => void
28
+ loading?: boolean
29
+ }
30
+
25
31
  interface ConversationDisabledBannerProps {
26
32
  message: string | null
27
- messageLink?: { label: string; onPress: () => void }
33
+ action?: BannerAction
28
34
  }
29
35
 
30
36
  export const ConversationDisabledBanner = ({
31
37
  message,
32
- messageLink,
38
+ action,
33
39
  }: ConversationDisabledBannerProps) => {
34
40
  const styles = useStyles()
35
41
  const description = message ?? DEFAULT_DISABLED_TEXT
36
42
 
37
43
  return (
38
- <MessagesDisabledBanner
39
- description={description}
40
- style={styles.memberBanner}
41
- messageLink={messageLink}
42
- />
44
+ <MessagesDisabledBanner description={description} style={styles.memberBanner} action={action} />
43
45
  )
44
46
  }
45
47
 
46
48
  interface MessagesDisabledBannerProps {
47
49
  description: string
48
50
  style?: ViewStyle
49
- messageLink?: { label: string; onPress: () => void }
51
+ action?: BannerAction
50
52
  }
51
53
 
52
- const MessagesDisabledBanner = ({
53
- description,
54
- style,
55
- messageLink,
56
- }: MessagesDisabledBannerProps) => {
54
+ const MessagesDisabledBanner = ({ description, style, action }: MessagesDisabledBannerProps) => {
57
55
  const styles = useStyles()
58
56
 
59
57
  return (
@@ -61,13 +59,14 @@ const MessagesDisabledBanner = ({
61
59
  <Text style={styles.text} variant="tertiary">
62
60
  {description}
63
61
  </Text>
64
- {messageLink && (
62
+ {action && (
65
63
  <Button
66
64
  variant="fill"
67
65
  size="lg"
68
- title={messageLink.label}
69
- onPress={messageLink.onPress}
70
- style={styles.messageButton}
66
+ title={action.label}
67
+ onPress={action.onPress}
68
+ loading={action.loading}
69
+ style={styles.actionButton}
71
70
  />
72
71
  )}
73
72
  </View>
@@ -94,7 +93,7 @@ const useStyles = () => {
94
93
  paddingBottom: 16 + bottom,
95
94
  marginBottom: -bottom,
96
95
  },
97
- messageButton: {
96
+ actionButton: {
98
97
  marginTop: 12,
99
98
  },
100
99
  })
@@ -13,9 +13,14 @@ import { MessageReaction } from './message_reaction'
13
13
  interface SystemMessageProps {
14
14
  message: SystemMessageResource
15
15
  conversationId: number
16
+ conversationDisabled: boolean
16
17
  }
17
18
 
18
- export function SystemMessage({ message, conversationId }: SystemMessageProps) {
19
+ export function SystemMessage({
20
+ message,
21
+ conversationId,
22
+ conversationDisabled,
23
+ }: SystemMessageProps) {
19
24
  const { reactionCounts } = message
20
25
  const styles = useStyles()
21
26
  const navigation = useNavigation()
@@ -23,6 +28,7 @@ export function SystemMessage({ message, conversationId }: SystemMessageProps) {
23
28
  const text = systemMessageText(message)
24
29
  const isTruncated =
25
30
  message.systemTextFormat === 'people' && message.systemTextParts.overflowCount > 0
31
+ const pressable = isTruncated || !conversationDisabled
26
32
  const hasReactions = reactionCounts.length > 0
27
33
  const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
28
34
  useAnimatedMessageBackgroundColor()
@@ -57,16 +63,16 @@ export function SystemMessage({ message, conversationId }: SystemMessageProps) {
57
63
  return (
58
64
  <Pressable
59
65
  onPress={isTruncated ? handlePress : undefined}
60
- onLongPress={handleLongPress}
61
- onPressIn={handleMessagePressIn}
62
- onPressOut={handleMessagePressOut}
63
- android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}
64
- accessibilityRole="button"
65
- accessibilityHint={
66
- isTruncated
67
- ? 'Tap to see people. Long press for message actions.'
68
- : 'Long press for message actions.'
66
+ onLongPress={conversationDisabled ? undefined : handleLongPress}
67
+ onPressIn={pressable ? handleMessagePressIn : undefined}
68
+ onPressOut={pressable ? handleMessagePressOut : undefined}
69
+ android_ripple={
70
+ pressable
71
+ ? { color: colors.androidRippleNeutral, borderless: false, foreground: true }
72
+ : undefined
69
73
  }
74
+ accessibilityRole={pressable ? 'button' : undefined}
75
+ accessibilityHint={systemMessageHint(isTruncated, conversationDisabled)}
70
76
  >
71
77
  <Animated.View style={[styles.container, animatedBackgroundColor]}>
72
78
  <Text variant="tertiary" style={styles.text}>
@@ -81,6 +87,7 @@ export function SystemMessage({ message, conversationId }: SystemMessageProps) {
81
87
  onLongPress={handleReactionLongPress}
82
88
  message={message}
83
89
  conversationId={conversationId}
90
+ disabled={conversationDisabled}
84
91
  />
85
92
  ))}
86
93
  </View>
@@ -90,6 +97,13 @@ export function SystemMessage({ message, conversationId }: SystemMessageProps) {
90
97
  )
91
98
  }
92
99
 
100
+ function systemMessageHint(isTruncated: boolean, conversationDisabled: boolean) {
101
+ if (conversationDisabled) return isTruncated ? 'Tap to see people.' : undefined
102
+ return isTruncated
103
+ ? 'Tap to see people. Long press for message actions.'
104
+ : 'Long press for message actions.'
105
+ }
106
+
93
107
  function systemMessageText(message: SystemMessageResource): string {
94
108
  switch (message.systemTextFormat) {
95
109
  case 'people': {