@planningcenter/chat-react-native 3.32.1-rc.1 → 3.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +22 -1
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/display/emoji_avatar.d.ts.map +1 -1
- package/build/components/display/emoji_avatar.js +2 -0
- package/build/components/display/emoji_avatar.js.map +1 -1
- package/build/components/display/icon_avatar.d.ts.map +1 -1
- package/build/components/display/icon_avatar.js +2 -0
- package/build/components/display/icon_avatar.js.map +1 -1
- package/build/components/display/utils/avatar_gradient_colors.d.ts +3 -0
- package/build/components/display/utils/avatar_gradient_colors.d.ts.map +1 -1
- package/build/components/display/utils/avatar_gradient_colors.js +8 -3
- package/build/components/display/utils/avatar_gradient_colors.js.map +1 -1
- package/build/components/primitive/avatar_primitive.d.ts +3 -1
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +10 -2
- package/build/components/primitive/avatar_primitive.js.map +1 -1
- package/build/hooks/attachments/fallback_chat_configuration.d.ts +4 -0
- package/build/hooks/attachments/fallback_chat_configuration.d.ts.map +1 -0
- package/build/hooks/attachments/fallback_chat_configuration.js +59 -0
- package/build/hooks/attachments/fallback_chat_configuration.js.map +1 -0
- package/build/hooks/groups/use_groups_conversation_create.d.ts.map +1 -1
- package/build/hooks/groups/use_groups_conversation_create.js +1 -1
- package/build/hooks/groups/use_groups_conversation_create.js.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts +43 -11
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.js +5 -5
- package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
- package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
- package/build/hooks/use_attachment_uploader.js +39 -14
- package/build/hooks/use_attachment_uploader.js.map +1 -1
- package/build/hooks/use_chat_configuration.d.ts +6 -0
- package/build/hooks/use_chat_configuration.d.ts.map +1 -0
- package/build/hooks/use_chat_configuration.js +41 -0
- package/build/hooks/use_chat_configuration.js.map +1 -0
- package/build/hooks/use_conversation_avatar_update.d.ts +26 -0
- package/build/hooks/use_conversation_avatar_update.d.ts.map +1 -0
- package/build/hooks/use_conversation_avatar_update.js +130 -0
- package/build/hooks/use_conversation_avatar_update.js.map +1 -0
- package/build/hooks/use_features.d.ts +1 -0
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +1 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/navigation/index.d.ts +16 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +9 -0
- package/build/navigation/index.js.map +1 -1
- package/build/screens/avatar_picker/avatar_picker_screen.d.ts +12 -0
- package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -0
- package/build/screens/avatar_picker/avatar_picker_screen.js +193 -0
- package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -0
- package/build/screens/avatar_picker/avatar_picker_state.d.ts +38 -0
- package/build/screens/avatar_picker/avatar_picker_state.d.ts.map +1 -0
- package/build/screens/avatar_picker/avatar_picker_state.js +101 -0
- package/build/screens/avatar_picker/avatar_picker_state.js.map +1 -0
- package/build/screens/avatar_picker/avatar_preview.d.ts +9 -0
- package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -0
- package/build/screens/avatar_picker/avatar_preview.js +39 -0
- package/build/screens/avatar_picker/avatar_preview.js.map +1 -0
- package/build/screens/avatar_picker/color_picker.d.ts +9 -0
- package/build/screens/avatar_picker/color_picker.d.ts.map +1 -0
- package/build/screens/avatar_picker/color_picker.js +53 -0
- package/build/screens/avatar_picker/color_picker.js.map +1 -0
- package/build/screens/avatar_picker/constants.d.ts +3 -0
- package/build/screens/avatar_picker/constants.d.ts.map +1 -0
- package/build/screens/avatar_picker/constants.js +53 -0
- package/build/screens/avatar_picker/constants.js.map +1 -0
- package/build/screens/avatar_picker/emoji_tab.d.ts +7 -0
- package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -0
- package/build/screens/avatar_picker/emoji_tab.js +55 -0
- package/build/screens/avatar_picker/emoji_tab.js.map +1 -0
- package/build/screens/avatar_picker/icon_grid.d.ts +8 -0
- package/build/screens/avatar_picker/icon_grid.d.ts.map +1 -0
- package/build/screens/avatar_picker/icon_grid.js +48 -0
- package/build/screens/avatar_picker/icon_grid.js.map +1 -0
- package/build/screens/avatar_picker/upload_tab.d.ts +9 -0
- package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -0
- package/build/screens/avatar_picker/upload_tab.js +39 -0
- package/build/screens/avatar_picker/upload_tab.js.map +1 -0
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +37 -1
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_new/components/avatar_selection_row.d.ts +12 -0
- package/build/screens/conversation_new/components/avatar_selection_row.d.ts.map +1 -0
- package/build/screens/conversation_new/components/avatar_selection_row.js +60 -0
- package/build/screens/conversation_new/components/avatar_selection_row.js.map +1 -0
- package/build/screens/conversation_new/components/gender_filter_toggle.d.ts.map +1 -1
- package/build/screens/conversation_new/components/gender_filter_toggle.js +3 -9
- package/build/screens/conversation_new/components/gender_filter_toggle.js.map +1 -1
- package/build/screens/conversation_new/components/groups_form.d.ts +3 -1
- package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/groups_form.js +22 -8
- package/build/screens/conversation_new/components/groups_form.js.map +1 -1
- package/build/screens/conversation_new/components/services_form.d.ts +3 -1
- package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/services_form.js +22 -8
- package/build/screens/conversation_new/components/services_form.js.map +1 -1
- package/build/screens/conversation_new/conversation_new_screen.d.ts +2 -0
- package/build/screens/conversation_new/conversation_new_screen.d.ts.map +1 -1
- package/build/screens/conversation_new/conversation_new_screen.js +3 -3
- package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
- package/build/screens/team_conversation_screen.d.ts.map +1 -1
- package/build/screens/team_conversation_screen.js +1 -1
- package/build/screens/team_conversation_screen.js.map +1 -1
- package/build/types/resources/chat_configuration_resource.d.ts +8 -0
- package/build/types/resources/chat_configuration_resource.d.ts.map +1 -0
- package/build/types/resources/chat_configuration_resource.js +2 -0
- package/build/types/resources/chat_configuration_resource.js.map +1 -0
- package/build/utils/native_adapters/configuration.d.ts +3 -0
- package/build/utils/native_adapters/configuration.d.ts.map +1 -1
- package/build/utils/native_adapters/configuration.js +8 -0
- package/build/utils/native_adapters/configuration.js.map +1 -1
- package/build/utils/native_adapters/document_picker.d.ts +21 -0
- package/build/utils/native_adapters/document_picker.d.ts.map +1 -0
- package/build/utils/native_adapters/document_picker.js +7 -0
- package/build/utils/native_adapters/document_picker.js.map +1 -0
- package/build/utils/native_adapters/image_picker.d.ts +7 -1
- package/build/utils/native_adapters/image_picker.d.ts.map +1 -1
- package/build/utils/native_adapters/image_picker.js.map +1 -1
- package/build/utils/native_adapters/index.d.ts +1 -0
- package/build/utils/native_adapters/index.d.ts.map +1 -1
- package/build/utils/native_adapters/index.js +1 -0
- package/build/utils/native_adapters/index.js.map +1 -1
- package/build/utils/request/get_chat_configuration.d.ts +10 -0
- package/build/utils/request/get_chat_configuration.d.ts.map +1 -0
- package/build/utils/request/get_chat_configuration.js +21 -0
- package/build/utils/request/get_chat_configuration.js.map +1 -0
- package/package.json +4 -3
- package/src/__tests__/hooks/use_attachment_uploader.test.tsx +219 -0
- package/src/__tests__/hooks/use_chat_configuration.test.tsx +80 -0
- package/src/__tests__/utils/native_adapters/configuration.ts +25 -1
- package/src/components/conversation/message_form.tsx +39 -1
- package/src/components/display/emoji_avatar.tsx +7 -2
- package/src/components/display/icon_avatar.tsx +7 -2
- package/src/components/display/utils/avatar_gradient_colors.ts +10 -3
- package/src/components/primitive/avatar_primitive.tsx +11 -2
- package/src/hooks/attachments/fallback_chat_configuration.ts +61 -0
- package/src/hooks/groups/use_groups_conversation_create.ts +2 -1
- package/src/hooks/services/use_find_or_create_services_conversation.ts +7 -7
- package/src/hooks/use_attachment_uploader.ts +39 -15
- package/src/hooks/use_chat_configuration.ts +54 -0
- package/src/hooks/use_conversation_avatar_update.ts +163 -0
- package/src/hooks/use_features.ts +1 -0
- package/src/navigation/index.tsx +13 -0
- package/src/screens/avatar_picker/__tests__/avatar_picker_state.test.ts +157 -0
- package/src/screens/avatar_picker/avatar_picker_screen.tsx +312 -0
- package/src/screens/avatar_picker/avatar_picker_state.ts +141 -0
- package/src/screens/avatar_picker/avatar_preview.tsx +46 -0
- package/src/screens/avatar_picker/color_picker.tsx +91 -0
- package/src/screens/avatar_picker/constants.ts +53 -0
- package/src/screens/avatar_picker/emoji_tab.tsx +76 -0
- package/src/screens/avatar_picker/icon_grid.tsx +81 -0
- package/src/screens/avatar_picker/upload_tab.tsx +62 -0
- package/src/screens/conversation_details_screen.tsx +60 -1
- package/src/screens/conversation_new/components/avatar_selection_row.tsx +82 -0
- package/src/screens/conversation_new/components/gender_filter_toggle.tsx +3 -9
- package/src/screens/conversation_new/components/groups_form.tsx +33 -6
- package/src/screens/conversation_new/components/services_form.tsx +37 -6
- package/src/screens/conversation_new/conversation_new_screen.tsx +17 -3
- package/src/screens/team_conversation_screen.tsx +2 -1
- package/src/types/resources/chat_configuration_resource.ts +11 -0
- package/src/utils/native_adapters/configuration.ts +10 -0
- package/src/utils/native_adapters/document_picker.ts +26 -0
- package/src/utils/native_adapters/image_picker.ts +8 -1
- package/src/utils/native_adapters/index.ts +1 -0
- package/src/utils/request/get_chat_configuration.ts +23 -0
- package/build/hooks/attachments/supported_extensions.d.ts +0 -2
- package/build/hooks/attachments/supported_extensions.d.ts.map +0 -1
- package/build/hooks/attachments/supported_extensions.js +0 -48
- package/build/hooks/attachments/supported_extensions.js.map +0 -1
- package/src/hooks/attachments/supported_extensions.ts +0 -47
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configuration.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/configuration.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,2CAA2C,CAAA;AACpE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"configuration.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/configuration.ts"],"names":[],"mappings":"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","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\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 openAsync: async () => {\n methodMissing()\n return { canceled: true, assets: null }\n },\n})\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"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type DocumentPickerAsset = {
|
|
2
|
+
uri: string;
|
|
3
|
+
name: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
mimeType?: string;
|
|
6
|
+
};
|
|
7
|
+
type DocumentPickerSuccessResult = {
|
|
8
|
+
canceled: false;
|
|
9
|
+
assets: DocumentPickerAsset[];
|
|
10
|
+
};
|
|
11
|
+
type DocumentPickerCanceledResult = {
|
|
12
|
+
canceled: true;
|
|
13
|
+
assets: null;
|
|
14
|
+
};
|
|
15
|
+
export type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult;
|
|
16
|
+
export declare class DocumentPickerAdapter {
|
|
17
|
+
openAsync: () => Promise<DocumentPickerResult>;
|
|
18
|
+
constructor(methods: DocumentPickerAdapter);
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=document_picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document_picker.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/document_picker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,2BAA2B,GAAG;IACjC,QAAQ,EAAE,KAAK,CAAA;IACf,MAAM,EAAE,mBAAmB,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;CACb,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,4BAA4B,CAAA;AAE7F,qBAAa,qBAAqB;IAChC,SAAS,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAA;gBAElC,OAAO,EAAE,qBAAqB;CAG3C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document_picker.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/document_picker.ts"],"names":[],"mappings":"AAmBA,MAAM,OAAO,qBAAqB;IAChC,SAAS,CAAqC;IAE9C,YAAY,OAA8B;QACxC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;IACpC,CAAC;CACF","sourcesContent":["export type DocumentPickerAsset = {\n uri: string\n name: string\n size?: number\n mimeType?: string\n}\n\ntype DocumentPickerSuccessResult = {\n canceled: false\n assets: DocumentPickerAsset[]\n}\n\ntype DocumentPickerCanceledResult = {\n canceled: true\n assets: null\n}\n\nexport type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult\n\nexport class DocumentPickerAdapter {\n openAsync: () => Promise<DocumentPickerResult>\n\n constructor(methods: DocumentPickerAdapter) {\n this.openAsync = methods.openAsync\n }\n}\n"]}
|
|
@@ -16,9 +16,15 @@ type ImagePickerCanceledResult = {
|
|
|
16
16
|
assets: null;
|
|
17
17
|
};
|
|
18
18
|
export type ImagePickerResult = ImagePickerSuccessResult | ImagePickerCanceledResult;
|
|
19
|
+
export interface ImagePickerOptions {
|
|
20
|
+
selectionLimit?: number;
|
|
21
|
+
allowsEditing?: boolean;
|
|
22
|
+
allowsMultipleSelection?: boolean;
|
|
23
|
+
mediaTypes?: Array<'images' | 'videos' | 'livePhotos'>;
|
|
24
|
+
}
|
|
19
25
|
export declare class ImagePickerAdapter {
|
|
20
26
|
openCameraAsync: () => Promise<ImagePickerResult>;
|
|
21
|
-
openImageLibraryAsync: () => Promise<ImagePickerResult>;
|
|
27
|
+
openImageLibraryAsync: (options?: ImagePickerOptions) => Promise<ImagePickerResult>;
|
|
22
28
|
constructor(methods: ImagePickerAdapter);
|
|
23
29
|
}
|
|
24
30
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image_picker.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/image_picker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,wBAAwB,GAAG;IAC9B,QAAQ,EAAE,KAAK,CAAA;IACf,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;CACb,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,wBAAwB,GAAG,yBAAyB,CAAA;AAEpF,qBAAa,kBAAkB;IAC7B,eAAe,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACjD,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"image_picker.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/image_picker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,wBAAwB,GAAG;IAC9B,QAAQ,EAAE,KAAK,CAAA;IACf,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;CACb,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,wBAAwB,GAAG,yBAAyB,CAAA;AAEpF,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,UAAU,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC,CAAA;CACvD;AAED,qBAAa,kBAAkB;IAC7B,eAAe,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACjD,qBAAqB,EAAE,CAAC,OAAO,CAAC,EAAE,kBAAkB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;gBAEvE,OAAO,EAAE,kBAAkB;CAIxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image_picker.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/image_picker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image_picker.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/image_picker.ts"],"names":[],"mappings":"AA6BA,MAAM,OAAO,kBAAkB;IAC7B,eAAe,CAAkC;IACjD,qBAAqB,CAA8D;IAEnF,YAAY,OAA2B;QACrC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAA;QAC9C,IAAI,CAAC,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAA;IAC5D,CAAC;CACF","sourcesContent":["export type ImagePickerAsset = {\n uri: string\n assetId?: string | null\n width: number\n height: number\n mimeType?: string\n fileName?: string | null\n fileSize?: number\n}\n\ntype ImagePickerSuccessResult = {\n canceled: false\n assets: ImagePickerAsset[]\n}\n\ntype ImagePickerCanceledResult = {\n canceled: true\n assets: null\n}\n\nexport type ImagePickerResult = ImagePickerSuccessResult | ImagePickerCanceledResult\n\nexport interface ImagePickerOptions {\n selectionLimit?: number\n allowsEditing?: boolean\n allowsMultipleSelection?: boolean\n mediaTypes?: Array<'images' | 'videos' | 'livePhotos'>\n}\n\nexport class ImagePickerAdapter {\n openCameraAsync: () => Promise<ImagePickerResult>\n openImageLibraryAsync: (options?: ImagePickerOptions) => Promise<ImagePickerResult>\n\n constructor(methods: ImagePickerAdapter) {\n this.openCameraAsync = methods.openCameraAsync\n this.openImageLibraryAsync = methods.openImageLibraryAsync\n }\n}\n"]}
|
|
@@ -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,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,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,mBAAmB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","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,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,mBAAmB,CAAA","sourcesContent":["export * from './audio'\nexport * from './clipboard'\nexport * from './configuration'\nexport * from './image_picker'\nexport * from './linking'\nexport * from './log'\nexport * from './video'\nexport * from './haptic'\nexport * from './storage_adapter'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","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","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"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const getChatConfigurationRequestArgs: () => {
|
|
2
|
+
url: string;
|
|
3
|
+
data: {
|
|
4
|
+
fields: {
|
|
5
|
+
ChatConfiguration: string[];
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare const getChatConfigurationQueryKey: () => import("../../hooks/use_suspense_api").RequestQueryKey;
|
|
10
|
+
//# sourceMappingURL=get_chat_configuration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_chat_configuration.d.ts","sourceRoot":"","sources":["../../../src/utils/request/get_chat_configuration.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,+BAA+B;;;;;;;CAe3C,CAAA;AAED,eAAO,MAAM,4BAA4B,8DAGxC,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api';
|
|
2
|
+
export const getChatConfigurationRequestArgs = () => {
|
|
3
|
+
const url = '/me/chat_configuration';
|
|
4
|
+
return {
|
|
5
|
+
url,
|
|
6
|
+
data: {
|
|
7
|
+
fields: {
|
|
8
|
+
ChatConfiguration: [
|
|
9
|
+
'allowed_file_extensions',
|
|
10
|
+
'max_file_size_in_bytes',
|
|
11
|
+
'max_attachments_per_message',
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export const getChatConfigurationQueryKey = () => {
|
|
18
|
+
const requestArgs = getChatConfigurationRequestArgs();
|
|
19
|
+
return getRequestQueryKey(requestArgs);
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=get_chat_configuration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_chat_configuration.js","sourceRoot":"","sources":["../../../src/utils/request/get_chat_configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAEjE,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,EAAE;IAClD,MAAM,GAAG,GAAG,wBAAwB,CAAA;IAEpC,OAAO;QACL,GAAG;QACH,IAAI,EAAE;YACJ,MAAM,EAAE;gBACN,iBAAiB,EAAE;oBACjB,yBAAyB;oBACzB,wBAAwB;oBACxB,6BAA6B;iBAC9B;aACF;SACF;KACF,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,EAAE;IAC/C,MAAM,WAAW,GAAG,+BAA+B,EAAE,CAAA;IACrD,OAAO,kBAAkB,CAAC,WAAW,CAAC,CAAA;AACxC,CAAC,CAAA","sourcesContent":["import { getRequestQueryKey } from '../../hooks/use_suspense_api'\n\nexport const getChatConfigurationRequestArgs = () => {\n const url = '/me/chat_configuration'\n\n return {\n url,\n data: {\n fields: {\n ChatConfiguration: [\n 'allowed_file_extensions',\n 'max_file_size_in_bytes',\n 'max_attachments_per_message',\n ],\n },\n },\n }\n}\n\nexport const getChatConfigurationQueryKey = () => {\n const requestArgs = getChatConfigurationRequestArgs()\n return getRequestQueryKey(requestArgs)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
|
27
27
|
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
|
28
28
|
"lodash-inflection": "^1.5.0",
|
|
29
|
-
"react-compiler-runtime": "^1.0.0"
|
|
29
|
+
"react-compiler-runtime": "^1.0.0",
|
|
30
|
+
"rn-emoji-keyboard": "^1.7.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@planningcenter/datetime-fmt": ">=2.0.0",
|
|
@@ -64,5 +65,5 @@
|
|
|
64
65
|
"react-native-url-polyfill": "^2.0.0",
|
|
65
66
|
"typescript": "<5.6.0"
|
|
66
67
|
},
|
|
67
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "97036ad047060e6943f5d80b07618053382a7e77"
|
|
68
69
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
+
import { renderHook, act } from '@testing-library/react-hooks'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { useApiClient } from '../../hooks/use_api_client'
|
|
5
|
+
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
6
|
+
import { useChatConfiguration } from '../../hooks/use_chat_configuration'
|
|
7
|
+
import { useUploadClient } from '../../hooks/use_upload_client'
|
|
8
|
+
import { FileAttachment } from '../../types/resources/denormalized_attachment_resource_for_create'
|
|
9
|
+
|
|
10
|
+
jest.mock('../../hooks/use_api_client')
|
|
11
|
+
jest.mock('../../hooks/use_upload_client')
|
|
12
|
+
jest.mock('../../hooks/use_chat_configuration')
|
|
13
|
+
|
|
14
|
+
const mockedUseApiClient = useApiClient as jest.MockedFunction<typeof useApiClient>
|
|
15
|
+
const mockedUseUploadClient = useUploadClient as jest.MockedFunction<typeof useUploadClient>
|
|
16
|
+
const mockedUseChatConfiguration = useChatConfiguration as jest.MockedFunction<
|
|
17
|
+
typeof useChatConfiguration
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
const createWrapper = () => {
|
|
21
|
+
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
|
22
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
23
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const setChatConfiguration = (overrides: Partial<ReturnType<typeof useChatConfiguration>> = {}) => {
|
|
28
|
+
mockedUseChatConfiguration.mockReturnValue({
|
|
29
|
+
allowedFileExtensions: ['.pdf', '.jpg'],
|
|
30
|
+
maxFileSizeInBytes: 50 * 1024 * 1024,
|
|
31
|
+
maxAttachmentsPerMessage: 10,
|
|
32
|
+
...overrides,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const renderUploader = (opts?: { draftAttachments?: FileAttachment[] }) =>
|
|
37
|
+
renderHook(() => useAttachmentUploader({ conversationId: 1, ...opts }), {
|
|
38
|
+
wrapper: createWrapper(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const makeFile = (
|
|
42
|
+
overrides: Partial<
|
|
43
|
+
Parameters<ReturnType<typeof useAttachmentUploader>['handleFilesAttached']>[0][number]
|
|
44
|
+
> = {}
|
|
45
|
+
) => ({
|
|
46
|
+
uri: 'file:///tmp/example.pdf',
|
|
47
|
+
name: 'example.pdf',
|
|
48
|
+
type: 'application/pdf',
|
|
49
|
+
size: 1024,
|
|
50
|
+
...overrides,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const draftAttachment = (name: string): FileAttachment => ({
|
|
54
|
+
file: { uri: `file:///tmp/${name}`, name, type: 'application/pdf', size: 1024 },
|
|
55
|
+
status: 'success',
|
|
56
|
+
uploadedAt: 0,
|
|
57
|
+
id: `att-${name}`,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
setChatConfiguration()
|
|
62
|
+
mockedUseApiClient.mockReturnValue({
|
|
63
|
+
chat: { post: jest.fn().mockResolvedValue({ data: { id: 'msg-attachment-1' } }) },
|
|
64
|
+
} as any)
|
|
65
|
+
mockedUseUploadClient.mockReturnValue({
|
|
66
|
+
uploadFile: jest.fn().mockResolvedValue({ id: 'uploaded-1' }),
|
|
67
|
+
} as any)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.clearAllMocks()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('useAttachmentUploader', () => {
|
|
75
|
+
describe('extension validation', () => {
|
|
76
|
+
it('rejects a disallowed extension and lists it in the error', () => {
|
|
77
|
+
const { result } = renderUploader()
|
|
78
|
+
|
|
79
|
+
act(() => {
|
|
80
|
+
result.current.handleFilesAttached([makeFile({ name: 'evil.exe' })])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(result.current.errorMessage).toContain(
|
|
84
|
+
'The following file types are not supported: exe'
|
|
85
|
+
)
|
|
86
|
+
expect(result.current.attachments).toHaveLength(0)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('accepts a matching extension regardless of casing', async () => {
|
|
90
|
+
const { result } = renderUploader()
|
|
91
|
+
|
|
92
|
+
await act(async () => {
|
|
93
|
+
result.current.handleFilesAttached([makeFile({ name: 'Shouting.PDF' })])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(result.current.errorMessage).toBeNull()
|
|
97
|
+
expect(result.current.attachments).toHaveLength(1)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('file size validation', () => {
|
|
102
|
+
it('rejects files that exceed maxFileSizeInBytes', () => {
|
|
103
|
+
setChatConfiguration({ maxFileSizeInBytes: 50 * 1024 * 1024 })
|
|
104
|
+
const { result } = renderUploader()
|
|
105
|
+
|
|
106
|
+
act(() => {
|
|
107
|
+
result.current.handleFilesAttached([makeFile({ size: 50 * 1024 * 1024 + 1 })])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(result.current.errorMessage).toBe('File size exceeds 50 MB')
|
|
111
|
+
expect(result.current.attachments).toHaveLength(0)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('formats an integer MB limit without a trailing decimal', () => {
|
|
115
|
+
setChatConfiguration({ maxFileSizeInBytes: 25 * 1024 * 1024 })
|
|
116
|
+
const { result } = renderUploader()
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
result.current.handleFilesAttached([makeFile({ size: 25 * 1024 * 1024 + 1 })])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
expect(result.current.errorMessage).toBe('File size exceeds 25 MB')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('formats a fractional MB limit to one decimal', () => {
|
|
126
|
+
// 50.5 MB — server could in principle return a non-multiple-of-MB cap.
|
|
127
|
+
setChatConfiguration({ maxFileSizeInBytes: Math.round(50.5 * 1024 * 1024) })
|
|
128
|
+
const { result } = renderUploader()
|
|
129
|
+
|
|
130
|
+
act(() => {
|
|
131
|
+
result.current.handleFilesAttached([makeFile({ size: 51 * 1024 * 1024 })])
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
expect(result.current.errorMessage).toBe('File size exceeds 50.5 MB')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('attachment count validation', () => {
|
|
139
|
+
it('rejects a batch that would push past maxAttachmentsPerMessage', () => {
|
|
140
|
+
setChatConfiguration({ maxAttachmentsPerMessage: 2 })
|
|
141
|
+
const { result } = renderUploader()
|
|
142
|
+
|
|
143
|
+
act(() => {
|
|
144
|
+
result.current.handleFilesAttached([
|
|
145
|
+
makeFile({ uri: 'file:///a.pdf', name: 'a.pdf' }),
|
|
146
|
+
makeFile({ uri: 'file:///b.pdf', name: 'b.pdf' }),
|
|
147
|
+
makeFile({ uri: 'file:///c.pdf', name: 'c.pdf' }),
|
|
148
|
+
])
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
expect(result.current.errorMessage).toBe("You can't attach more than 2 files at once.")
|
|
152
|
+
expect(result.current.attachments).toHaveLength(0)
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('remainingAttachable', () => {
|
|
157
|
+
it('reports how many more files can be attached', () => {
|
|
158
|
+
setChatConfiguration({ maxAttachmentsPerMessage: 5 })
|
|
159
|
+
const { result } = renderUploader({
|
|
160
|
+
draftAttachments: [draftAttachment('a.pdf'), draftAttachment('b.pdf')],
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
expect(result.current.remainingAttachable).toBe(3)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('clamps to 0 when the draft already exceeds the server limit', () => {
|
|
167
|
+
// Simulates a saved draft whose attachments outnumber a since-lowered
|
|
168
|
+
// server cap — the value must never go negative.
|
|
169
|
+
setChatConfiguration({ maxAttachmentsPerMessage: 1 })
|
|
170
|
+
const { result } = renderUploader({
|
|
171
|
+
draftAttachments: [draftAttachment('a.pdf'), draftAttachment('b.pdf')],
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
expect(result.current.remainingAttachable).toBe(0)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('upload errors', () => {
|
|
179
|
+
const rejectingResponse = (body: unknown, status = 500) => {
|
|
180
|
+
const response = { status, clone: () => response, json: async () => body }
|
|
181
|
+
return response as unknown as Response
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
it('shows a generic error message when the upload fails', async () => {
|
|
185
|
+
mockedUseApiClient.mockReturnValue({
|
|
186
|
+
chat: { post: jest.fn().mockRejectedValue(rejectingResponse({})) },
|
|
187
|
+
} as any)
|
|
188
|
+
|
|
189
|
+
const { result } = renderUploader()
|
|
190
|
+
|
|
191
|
+
await act(async () => {
|
|
192
|
+
result.current.handleFilesAttached([makeFile()])
|
|
193
|
+
})
|
|
194
|
+
await act(async () => {
|
|
195
|
+
await Promise.resolve()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(result.current.errorMessage).toBe('This file could not be uploaded.')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('does not overwrite the error message when the upload is flagged', async () => {
|
|
202
|
+
mockedUseUploadClient.mockReturnValue({
|
|
203
|
+
uploadFile: jest.fn().mockRejectedValue({ code: 'image_flagged' }),
|
|
204
|
+
} as any)
|
|
205
|
+
|
|
206
|
+
const { result } = renderUploader()
|
|
207
|
+
|
|
208
|
+
await act(async () => {
|
|
209
|
+
result.current.handleFilesAttached([makeFile()])
|
|
210
|
+
})
|
|
211
|
+
await act(async () => {
|
|
212
|
+
await Promise.resolve()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
expect(result.current.errorMessage).toBeNull()
|
|
216
|
+
expect(result.current.flaggedAttachmentCount).toBe(1)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
+
import { renderHook, act } from '@testing-library/react-hooks'
|
|
3
|
+
import React, { Suspense } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
FALLBACK_ALLOWED_FILE_EXTENSIONS,
|
|
6
|
+
FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
|
|
7
|
+
FALLBACK_MAX_FILE_SIZE_IN_BYTES,
|
|
8
|
+
} from '../../hooks/attachments/fallback_chat_configuration'
|
|
9
|
+
import * as useApiClientModule from '../../hooks/use_api_client'
|
|
10
|
+
import { useChatConfiguration } from '../../hooks/use_chat_configuration'
|
|
11
|
+
|
|
12
|
+
const createWrapper = () => {
|
|
13
|
+
const queryClient = new QueryClient({
|
|
14
|
+
defaultOptions: { queries: { retry: false } },
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
18
|
+
<QueryClientProvider client={queryClient}>
|
|
19
|
+
<Suspense fallback={null}>{children}</Suspense>
|
|
20
|
+
</QueryClientProvider>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const waitForQuery = async () => {
|
|
25
|
+
await act(async () => {
|
|
26
|
+
await Promise.resolve()
|
|
27
|
+
await Promise.resolve()
|
|
28
|
+
await Promise.resolve()
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const mockApiClient = (getImpl: () => Promise<unknown>) => {
|
|
33
|
+
jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
|
|
34
|
+
chat: { get: jest.fn(getImpl) },
|
|
35
|
+
} as any)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('useChatConfiguration', () => {
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.restoreAllMocks()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('returns server-provided values when the API succeeds', async () => {
|
|
44
|
+
mockApiClient(() =>
|
|
45
|
+
Promise.resolve({
|
|
46
|
+
data: {
|
|
47
|
+
type: 'ChatConfiguration',
|
|
48
|
+
id: 'current',
|
|
49
|
+
allowedFileExtensions: ['.pdf', '.jpg'],
|
|
50
|
+
maxFileSizeInBytes: 1000,
|
|
51
|
+
maxAttachmentsPerMessage: 3,
|
|
52
|
+
},
|
|
53
|
+
links: {},
|
|
54
|
+
meta: {},
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const { result } = renderHook(() => useChatConfiguration(), { wrapper: createWrapper() })
|
|
59
|
+
await waitForQuery()
|
|
60
|
+
|
|
61
|
+
expect(result.current).toEqual({
|
|
62
|
+
allowedFileExtensions: ['.pdf', '.jpg'],
|
|
63
|
+
maxFileSizeInBytes: 1000,
|
|
64
|
+
maxAttachmentsPerMessage: 3,
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('returns fallback values when the API rejects', async () => {
|
|
69
|
+
mockApiClient(() => Promise.reject(new Error('boom')))
|
|
70
|
+
|
|
71
|
+
const { result } = renderHook(() => useChatConfiguration(), { wrapper: createWrapper() })
|
|
72
|
+
await waitForQuery()
|
|
73
|
+
|
|
74
|
+
expect(result.current).toEqual({
|
|
75
|
+
allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,
|
|
76
|
+
maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,
|
|
77
|
+
maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -4,11 +4,17 @@ import {
|
|
|
4
4
|
AudioAdapter,
|
|
5
5
|
Clipboard,
|
|
6
6
|
ClipboardAdapter,
|
|
7
|
+
DocumentPickerAdapter,
|
|
7
8
|
ImagePickerAdapter,
|
|
8
9
|
LinkingAdapter,
|
|
9
10
|
VideoAdapter,
|
|
10
11
|
} from '../../../utils/native_adapters'
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ChatAdapters,
|
|
14
|
+
DocumentPicker,
|
|
15
|
+
Linking,
|
|
16
|
+
Haptic,
|
|
17
|
+
} from '../../../utils/native_adapters/configuration'
|
|
12
18
|
import { HapticAdapter } from '../../../utils/native_adapters/haptic'
|
|
13
19
|
import { VideoPlayerHandle, VideoPlayerProps } from '../../../utils/native_adapters/video'
|
|
14
20
|
|
|
@@ -104,6 +110,24 @@ describe('ChatAdapters', () => {
|
|
|
104
110
|
})
|
|
105
111
|
})
|
|
106
112
|
|
|
113
|
+
describe('document picker adapter', () => {
|
|
114
|
+
it('uses the configured adapter when provided', () => {
|
|
115
|
+
const documentPicker = new DocumentPickerAdapter({
|
|
116
|
+
openAsync: jest.fn(async () => ({ canceled: true, assets: null })),
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
ChatAdapters.configure({
|
|
120
|
+
clipboard,
|
|
121
|
+
audio,
|
|
122
|
+
video,
|
|
123
|
+
imagePicker,
|
|
124
|
+
documentPicker,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
expect(DocumentPicker).toEqual(documentPicker)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
107
131
|
describe('haptic adapter', () => {
|
|
108
132
|
it('should configure the haptic adapter', () => {
|
|
109
133
|
ChatAdapters.configure({
|
|
@@ -36,7 +36,13 @@ import {
|
|
|
36
36
|
platformFontWeightMedium,
|
|
37
37
|
platformPressedOpacityStyle,
|
|
38
38
|
} from '../../utils'
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
DocumentPicker,
|
|
41
|
+
DocumentPickerResult,
|
|
42
|
+
Haptic,
|
|
43
|
+
ImagePicker,
|
|
44
|
+
ImagePickerResult,
|
|
45
|
+
} from '../../utils/native_adapters'
|
|
40
46
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
41
47
|
import { Button } from '../display/button'
|
|
42
48
|
import BannerPrimitive from '../primitive/banner_primitive'
|
|
@@ -522,6 +528,21 @@ function MessageFormAttachmentPicker() {
|
|
|
522
528
|
attachmentUploader?.handleFilesAttached(filteredAssets)
|
|
523
529
|
}
|
|
524
530
|
|
|
531
|
+
function uploadDocumentPickerResult(result: DocumentPickerResult) {
|
|
532
|
+
if (result.canceled) return
|
|
533
|
+
|
|
534
|
+
const filteredAssets = result.assets
|
|
535
|
+
.filter(asset => asset.size != null && asset.uri)
|
|
536
|
+
.map(asset => ({
|
|
537
|
+
uri: asset.uri,
|
|
538
|
+
name: asset.name,
|
|
539
|
+
type: asset.mimeType || 'application/octet-stream',
|
|
540
|
+
size: asset.size as number,
|
|
541
|
+
}))
|
|
542
|
+
|
|
543
|
+
attachmentUploader?.handleFilesAttached(filteredAssets)
|
|
544
|
+
}
|
|
545
|
+
|
|
525
546
|
const openCamera = async () => {
|
|
526
547
|
setIsOpen(false)
|
|
527
548
|
let result = await ImagePicker.openCameraAsync()
|
|
@@ -538,6 +559,14 @@ function MessageFormAttachmentPicker() {
|
|
|
538
559
|
}
|
|
539
560
|
}
|
|
540
561
|
|
|
562
|
+
const pickFile = async () => {
|
|
563
|
+
setIsOpen(false)
|
|
564
|
+
let result = await DocumentPicker.openAsync()
|
|
565
|
+
if (!result.canceled) {
|
|
566
|
+
uploadDocumentPickerResult(result)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
541
570
|
if (usingGiphy || currentlyEditingMessage) {
|
|
542
571
|
return null
|
|
543
572
|
}
|
|
@@ -564,6 +593,15 @@ function MessageFormAttachmentPicker() {
|
|
|
564
593
|
onPress={pickImage}
|
|
565
594
|
style={styles.attachmentPickerButton}
|
|
566
595
|
/>
|
|
596
|
+
<IconButton
|
|
597
|
+
accessibilityLabel="Attach a file"
|
|
598
|
+
accessibilityHint="Opens your files to attach documents"
|
|
599
|
+
size="lg"
|
|
600
|
+
appearance="neutral"
|
|
601
|
+
name="general.blankFile"
|
|
602
|
+
onPress={pickFile}
|
|
603
|
+
style={styles.attachmentPickerButton}
|
|
604
|
+
/>
|
|
567
605
|
</View>
|
|
568
606
|
)}
|
|
569
607
|
<IconButton
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { StyleSheet, Text, View } from 'react-native'
|
|
3
3
|
import LinearGradient from 'react-native-linear-gradient'
|
|
4
|
-
import AvatarPrimitive, {
|
|
4
|
+
import AvatarPrimitive, {
|
|
5
|
+
type AvatarRootProps,
|
|
6
|
+
type AvatarSize,
|
|
7
|
+
} from '../primitive/avatar_primitive'
|
|
5
8
|
import { getAvatarGradientProps } from './utils/avatar_gradient_colors'
|
|
6
9
|
|
|
7
|
-
const EMOJI_SIZE: Record<
|
|
10
|
+
const EMOJI_SIZE: Record<AvatarSize, number> = {
|
|
8
11
|
xs: 8,
|
|
9
12
|
sm: 10,
|
|
10
13
|
md: 14,
|
|
11
14
|
lg: 20,
|
|
15
|
+
xl: 28,
|
|
16
|
+
'2xl': 40,
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
interface EmojiAvatarProps {
|
|
@@ -7,14 +7,19 @@ import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'
|
|
|
7
7
|
import React from 'react'
|
|
8
8
|
import { StyleSheet, Text, View } from 'react-native'
|
|
9
9
|
import LinearGradient from 'react-native-linear-gradient'
|
|
10
|
-
import AvatarPrimitive, {
|
|
10
|
+
import AvatarPrimitive, {
|
|
11
|
+
type AvatarRootProps,
|
|
12
|
+
type AvatarSize,
|
|
13
|
+
} from '../primitive/avatar_primitive'
|
|
11
14
|
import { getAvatarGradientProps } from './utils/avatar_gradient_colors'
|
|
12
15
|
|
|
13
|
-
const ICON_SIZE: Record<
|
|
16
|
+
const ICON_SIZE: Record<AvatarSize, number> = {
|
|
14
17
|
xs: 10,
|
|
15
18
|
sm: 12,
|
|
16
19
|
md: 16,
|
|
17
20
|
lg: 20,
|
|
21
|
+
xl: 28,
|
|
22
|
+
'2xl': 40,
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
interface IconAvatarProps {
|