@planningcenter/chat-react-native 3.11.0-rc.8 → 3.11.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.
Files changed (202) hide show
  1. package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -1
  2. package/build/components/conversation/attachments/image_attachment.js +9 -2
  3. package/build/components/conversation/attachments/image_attachment.js.map +1 -1
  4. package/build/components/conversation/message.d.ts +1 -1
  5. package/build/components/conversation/message.d.ts.map +1 -1
  6. package/build/components/conversation/message.js +85 -26
  7. package/build/components/conversation/message.js.map +1 -1
  8. package/build/components/conversation/message_form/message_form_attachment_image.d.ts +1 -1
  9. package/build/components/conversation/message_form/message_form_attachment_image.d.ts.map +1 -1
  10. package/build/components/conversation/message_form/message_form_attachment_image.js.map +1 -1
  11. package/build/components/conversation/message_form/message_form_attachment_video.d.ts +1 -1
  12. package/build/components/conversation/message_form/message_form_attachment_video.d.ts.map +1 -1
  13. package/build/components/conversation/message_form/message_form_attachment_video.js.map +1 -1
  14. package/build/components/conversation/message_form.d.ts.map +1 -1
  15. package/build/components/conversation/message_form.js +12 -10
  16. package/build/components/conversation/message_form.js.map +1 -1
  17. package/build/components/conversations/conversations.d.ts.map +1 -1
  18. package/build/components/conversations/conversations.js +5 -1
  19. package/build/components/conversations/conversations.js.map +1 -1
  20. package/build/components/display/spinner.d.ts +6 -1
  21. package/build/components/display/spinner.d.ts.map +1 -1
  22. package/build/components/display/spinner.js +2 -2
  23. package/build/components/display/spinner.js.map +1 -1
  24. package/build/components/display/text.js +10 -2
  25. package/build/components/display/text.js.map +1 -1
  26. package/build/components/primitive/form_sheet.d.ts +1 -0
  27. package/build/components/primitive/form_sheet.d.ts.map +1 -1
  28. package/build/components/primitive/form_sheet.js +2 -2
  29. package/build/components/primitive/form_sheet.js.map +1 -1
  30. package/build/contexts/api_provider.d.ts +1 -0
  31. package/build/contexts/api_provider.d.ts.map +1 -1
  32. package/build/contexts/api_provider.js +24 -2
  33. package/build/contexts/api_provider.js.map +1 -1
  34. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +1 -1
  35. package/build/hooks/groups/use_groups_conversation_create.d.ts.map +1 -1
  36. package/build/hooks/groups/use_groups_conversation_create.js +3 -1
  37. package/build/hooks/groups/use_groups_conversation_create.js.map +1 -1
  38. package/build/hooks/services/use_find_or_create_services_conversation.d.ts +2 -0
  39. package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
  40. package/build/hooks/services/use_find_or_create_services_conversation.js +22 -19
  41. package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
  42. package/build/hooks/{use_services_team.d.ts → services/use_services_team.d.ts} +1 -1
  43. package/build/hooks/services/use_services_team.d.ts.map +1 -0
  44. package/build/hooks/{use_services_team.js → services/use_services_team.js} +1 -1
  45. package/build/hooks/services/use_services_team.js.map +1 -0
  46. package/build/hooks/use_attachment_uploader.d.ts +5 -13
  47. package/build/hooks/use_attachment_uploader.d.ts.map +1 -1
  48. package/build/hooks/use_attachment_uploader.js.map +1 -1
  49. package/build/hooks/use_chat_permissions.d.ts +10 -0
  50. package/build/hooks/use_chat_permissions.d.ts.map +1 -1
  51. package/build/hooks/use_chat_permissions.js +10 -9
  52. package/build/hooks/use_chat_permissions.js.map +1 -1
  53. package/build/hooks/use_conversation.d.ts +1 -1
  54. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  55. package/build/hooks/use_conversation_messages_jolt_events.js +16 -1
  56. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  57. package/build/hooks/use_giphy.d.ts +1 -1
  58. package/build/hooks/use_giphy.d.ts.map +1 -1
  59. package/build/hooks/use_giphy.js.map +1 -1
  60. package/build/hooks/use_message_create_or_update.d.ts +8 -4
  61. package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
  62. package/build/hooks/use_message_create_or_update.js +58 -4
  63. package/build/hooks/use_message_create_or_update.js.map +1 -1
  64. package/build/hooks/use_read_receipts.d.ts +1 -1
  65. package/build/hooks/use_suspense_api.d.ts +2 -2
  66. package/build/index.d.ts +1 -1
  67. package/build/index.d.ts.map +1 -1
  68. package/build/index.js +1 -1
  69. package/build/index.js.map +1 -1
  70. package/build/navigation/index.d.ts +11 -0
  71. package/build/navigation/index.d.ts.map +1 -1
  72. package/build/navigation/index.js +10 -0
  73. package/build/navigation/index.js.map +1 -1
  74. package/build/screens/conversation_details_screen.js +1 -1
  75. package/build/screens/conversation_details_screen.js.map +1 -1
  76. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.d.ts.map +1 -1
  77. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js +81 -17
  78. package/build/screens/conversation_filter_recipients/conversation_filter_recipients_screen.js.map +1 -1
  79. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +171 -4
  80. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  81. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +49 -8
  82. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  83. package/build/screens/conversation_filter_recipients/types.d.ts +7 -0
  84. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  85. package/build/screens/conversation_filter_recipients/types.js +6 -0
  86. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  87. package/build/screens/conversation_filters/components/rows.js +1 -1
  88. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  89. package/build/screens/conversation_new/components/groups_form.js +2 -2
  90. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  91. package/build/screens/conversation_new/components/services_form.d.ts +3 -1
  92. package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
  93. package/build/screens/conversation_new/components/services_form.js +6 -5
  94. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  95. package/build/screens/conversation_new/conversation_new_screen.d.ts +2 -0
  96. package/build/screens/conversation_new/conversation_new_screen.d.ts.map +1 -1
  97. package/build/screens/conversation_new/conversation_new_screen.js +2 -2
  98. package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
  99. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.d.ts.map +1 -1
  100. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js +38 -33
  101. package/build/screens/conversation_select_recipients/conversation_select_recipients_screen.js.map +1 -1
  102. package/build/screens/team_conversation_screen.d.ts +8 -0
  103. package/build/screens/team_conversation_screen.d.ts.map +1 -0
  104. package/build/screens/team_conversation_screen.js +28 -0
  105. package/build/screens/team_conversation_screen.js.map +1 -0
  106. package/build/types/resources/denormalized_attachment_resource.d.ts +9 -32
  107. package/build/types/resources/denormalized_attachment_resource.d.ts.map +1 -1
  108. package/build/types/resources/denormalized_attachment_resource.js.map +1 -1
  109. package/build/types/resources/denormalized_attachment_resource_for_create.d.ts +50 -0
  110. package/build/types/resources/denormalized_attachment_resource_for_create.d.ts.map +1 -0
  111. package/build/types/resources/denormalized_attachment_resource_for_create.js +2 -0
  112. package/build/types/resources/denormalized_attachment_resource_for_create.js.map +1 -0
  113. package/build/types/resources/message.d.ts +4 -0
  114. package/build/types/resources/message.d.ts.map +1 -1
  115. package/build/types/resources/message.js.map +1 -1
  116. package/build/types/resources/services/chat_resource.d.ts +52 -0
  117. package/build/types/resources/services/chat_resource.d.ts.map +1 -0
  118. package/build/types/resources/services/chat_resource.js +7 -0
  119. package/build/types/resources/services/chat_resource.js.map +1 -0
  120. package/build/types/resources/services/index.d.ts +1 -0
  121. package/build/types/resources/services/index.d.ts.map +1 -1
  122. package/build/types/resources/services/index.js +1 -0
  123. package/build/types/resources/services/index.js.map +1 -1
  124. package/build/types/resources/services/team_resource.d.ts +9 -41
  125. package/build/types/resources/services/team_resource.d.ts.map +1 -1
  126. package/build/types/resources/services/team_resource.js +0 -5
  127. package/build/types/resources/services/team_resource.js.map +1 -1
  128. package/build/utils/cache/optimistically_create_message.d.ts +10 -0
  129. package/build/utils/cache/optimistically_create_message.d.ts.map +1 -0
  130. package/build/utils/cache/optimistically_create_message.js +43 -0
  131. package/build/utils/cache/optimistically_create_message.js.map +1 -0
  132. package/build/utils/cache/optimistically_update_message.d.ts +7 -0
  133. package/build/utils/cache/optimistically_update_message.d.ts.map +1 -0
  134. package/build/utils/cache/optimistically_update_message.js +21 -0
  135. package/build/utils/cache/optimistically_update_message.js.map +1 -0
  136. package/build/utils/cache/page_mutations.d.ts +6 -3
  137. package/build/utils/cache/page_mutations.d.ts.map +1 -1
  138. package/build/utils/cache/page_mutations.js +4 -4
  139. package/build/utils/cache/page_mutations.js.map +1 -1
  140. package/build/utils/convert_attachments_for_create.d.ts +12 -0
  141. package/build/utils/convert_attachments_for_create.d.ts.map +1 -0
  142. package/build/utils/convert_attachments_for_create.js +70 -0
  143. package/build/utils/convert_attachments_for_create.js.map +1 -0
  144. package/build/utils/generate_placeholder_ulid.d.ts +10 -0
  145. package/build/utils/generate_placeholder_ulid.d.ts.map +1 -0
  146. package/build/utils/generate_placeholder_ulid.js +28 -0
  147. package/build/utils/generate_placeholder_ulid.js.map +1 -0
  148. package/build/utils/index.d.ts +1 -0
  149. package/build/utils/index.d.ts.map +1 -1
  150. package/build/utils/index.js +1 -0
  151. package/build/utils/index.js.map +1 -1
  152. package/build/utils/response_error.d.ts +1 -0
  153. package/build/utils/response_error.d.ts.map +1 -1
  154. package/build/utils/response_error.js +6 -0
  155. package/build/utils/response_error.js.map +1 -1
  156. package/package.json +2 -2
  157. package/src/components/conversation/attachments/image_attachment.tsx +25 -10
  158. package/src/components/conversation/message.tsx +116 -28
  159. package/src/components/conversation/message_form/message_form_attachment_image.tsx +1 -1
  160. package/src/components/conversation/message_form/message_form_attachment_video.tsx +1 -1
  161. package/src/components/conversation/message_form.tsx +16 -13
  162. package/src/components/conversations/conversations.tsx +8 -1
  163. package/src/components/display/spinner.tsx +7 -2
  164. package/src/components/display/text.tsx +10 -2
  165. package/src/components/primitive/form_sheet.tsx +3 -2
  166. package/src/contexts/api_provider.tsx +37 -3
  167. package/src/hooks/groups/use_groups_conversation_create.ts +3 -1
  168. package/src/hooks/services/use_find_or_create_services_conversation.ts +29 -21
  169. package/src/hooks/{use_services_team.ts → services/use_services_team.ts} +2 -2
  170. package/src/hooks/use_attachment_uploader.ts +9 -25
  171. package/src/hooks/use_chat_permissions.ts +12 -9
  172. package/src/hooks/use_conversation_messages_jolt_events.ts +19 -1
  173. package/src/hooks/use_giphy.ts +1 -1
  174. package/src/hooks/use_message_create_or_update.ts +82 -6
  175. package/src/index.tsx +1 -1
  176. package/src/navigation/index.tsx +10 -0
  177. package/src/screens/conversation_details_screen.tsx +1 -1
  178. package/src/screens/conversation_filter_recipients/conversation_filter_recipients_screen.tsx +118 -17
  179. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +61 -10
  180. package/src/screens/conversation_filter_recipients/types.tsx +8 -0
  181. package/src/screens/conversation_filters/components/rows.tsx +1 -1
  182. package/src/screens/conversation_new/components/groups_form.tsx +2 -2
  183. package/src/screens/conversation_new/components/services_form.tsx +17 -3
  184. package/src/screens/conversation_new/conversation_new_screen.tsx +11 -2
  185. package/src/screens/conversation_select_recipients/conversation_select_recipients_screen.tsx +90 -74
  186. package/src/screens/team_conversation_screen.tsx +46 -0
  187. package/src/types/resources/denormalized_attachment_resource.ts +9 -37
  188. package/src/types/resources/denormalized_attachment_resource_for_create.ts +65 -0
  189. package/src/types/resources/message.ts +6 -0
  190. package/src/types/resources/services/chat_resource.ts +66 -0
  191. package/src/types/resources/services/index.ts +1 -0
  192. package/src/types/resources/services/team_resource.ts +10 -53
  193. package/src/utils/__tests__/convert_attachments_for_create.test.ts +175 -0
  194. package/src/utils/cache/optimistically_create_message.ts +71 -0
  195. package/src/utils/cache/optimistically_update_message.ts +37 -0
  196. package/src/utils/cache/page_mutations.ts +5 -3
  197. package/src/utils/convert_attachments_for_create.ts +92 -0
  198. package/src/utils/generate_placeholder_ulid.ts +32 -0
  199. package/src/utils/index.ts +1 -0
  200. package/src/utils/response_error.ts +8 -0
  201. package/build/hooks/use_services_team.d.ts.map +0 -1
  202. package/build/hooks/use_services_team.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate_placeholder_ulid.d.ts","sourceRoot":"","sources":["../../src/utils/generate_placeholder_ulid.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,wBAAgB,uBAAuB,CAAC,EAAE,QAAY,EAAE,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAa5F"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The actual `ulid` library depends on a secure random number generator, which looks like it
3
+ * may require a native dependency to polyfill in React Native.
4
+ * Since this is only used as a local temporary ID, I think we can get away with a simpler
5
+ * implementation that doesn't require any native dependencies.
6
+ */
7
+ const ULID_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // Crockford's Base32
8
+ function encodeBase32(value, length) {
9
+ let result = '';
10
+ for (let i = 0; i < length; i++) {
11
+ result = ULID_CHARS[value % 32] + result;
12
+ value = Math.floor(value / 32);
13
+ }
14
+ return result;
15
+ }
16
+ export function generatePlaceholderUlid({ offsetMs = 0 } = {}) {
17
+ const timestamp = Date.now() + offsetMs; // Milliseconds since epoch
18
+ // 48 bits for timestamp (6 Base32 characters)
19
+ const timeComponent = encodeBase32(timestamp, 10); // 48 bits = 6 bytes = 10 Base32 characters (approx, needs more precise bit shifting)
20
+ // 80 bits for randomness (16 Base32 characters)
21
+ let randomComponent = '';
22
+ for (let i = 0; i < 16; i++) {
23
+ // Generate 16 random Base32 chars
24
+ randomComponent += ULID_CHARS[Math.floor(Math.random() * 32)];
25
+ }
26
+ return timeComponent + randomComponent;
27
+ }
28
+ //# sourceMappingURL=generate_placeholder_ulid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate_placeholder_ulid.js","sourceRoot":"","sources":["../../src/utils/generate_placeholder_ulid.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,UAAU,GAAG,kCAAkC,CAAA,CAAC,qBAAqB;AAE3E,SAAS,YAAY,CAAC,KAAa,EAAE,MAAc;IACjD,IAAI,MAAM,GAAG,EAAE,CAAA;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,GAAG,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,MAAM,CAAA;QACxC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,EAAE,QAAQ,GAAG,CAAC,KAA4B,EAAE;IAClF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAA,CAAC,2BAA2B;IACnE,8CAA8C;IAC9C,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA,CAAC,qFAAqF;IAEvI,gDAAgD;IAChD,IAAI,eAAe,GAAG,EAAE,CAAA;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,kCAAkC;QAClC,eAAe,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,aAAa,GAAG,eAAe,CAAA;AACxC,CAAC","sourcesContent":["/**\n * The actual `ulid` library depends on a secure random number generator, which looks like it\n * may require a native dependency to polyfill in React Native.\n * Since this is only used as a local temporary ID, I think we can get away with a simpler\n * implementation that doesn't require any native dependencies.\n */\n\nconst ULID_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' // Crockford's Base32\n\nfunction encodeBase32(value: number, length: number) {\n let result = ''\n for (let i = 0; i < length; i++) {\n result = ULID_CHARS[value % 32] + result\n value = Math.floor(value / 32)\n }\n return result\n}\n\nexport function generatePlaceholderUlid({ offsetMs = 0 }: { offsetMs?: number } = {}): string {\n const timestamp = Date.now() + offsetMs // Milliseconds since epoch\n // 48 bits for timestamp (6 Base32 characters)\n const timeComponent = encodeBase32(timestamp, 10) // 48 bits = 6 bytes = 10 Base32 characters (approx, needs more precise bit shifting)\n\n // 80 bits for randomness (16 Base32 characters)\n let randomComponent = ''\n for (let i = 0; i < 16; i++) {\n // Generate 16 random Base32 chars\n randomComponent += ULID_CHARS[Math.floor(Math.random() * 32)]\n }\n\n return timeComponent + randomComponent\n}\n"]}
@@ -8,4 +8,5 @@ export * from './cache';
8
8
  export * from './native_adapters';
9
9
  export * from './pluralize';
10
10
  export * from './destructure_chat_group_graph_id';
11
+ export * from './convert_attachments_for_create';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -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,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,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,SAAS,CAAA;AACvB,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"}
@@ -8,4 +8,5 @@ export * from './cache';
8
8
  export * from './native_adapters';
9
9
  export * from './pluralize';
10
10
  export * from './destructure_chat_group_graph_id';
11
+ export * from './convert_attachments_for_create';
11
12
  //# sourceMappingURL=index.js.map
@@ -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,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './space'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './destructure_chat_group_graph_id'\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,SAAS,CAAA;AACvB,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 './space'\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"]}
@@ -6,4 +6,5 @@ export declare class ResponseError extends Error {
6
6
  response: ApiError;
7
7
  constructor(response: ApiError);
8
8
  }
9
+ export declare const throwResponseError: (error: unknown) => Promise<never>;
9
10
  //# sourceMappingURL=response_error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response_error.d.ts","sourceRoot":"","sources":["../../src/utils/response_error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAElD,qBAAa,aAAc,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC1B,QAAQ,EAAE,QAAQ,CAAA;gBAEN,QAAQ,EAAE,QAAQ;CAQ/B"}
1
+ {"version":3,"file":"response_error.d.ts","sourceRoot":"","sources":["../../src/utils/response_error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAElD,qBAAa,aAAc,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAC1B,QAAQ,EAAE,QAAQ,CAAA;gBAEN,QAAQ,EAAE,QAAQ;CAQ/B;AAED,eAAO,MAAM,kBAAkB,UAAW,OAAO,mBAMhD,CAAA"}
@@ -12,4 +12,10 @@ export class ResponseError extends Error {
12
12
  this.response = response;
13
13
  }
14
14
  }
15
+ export const throwResponseError = (error) => {
16
+ if (error instanceof Response) {
17
+ throw new ResponseError(error);
18
+ }
19
+ return Promise.reject(error);
20
+ };
15
21
  //# sourceMappingURL=response_error.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"response_error.js","sourceRoot":"","sources":["../../src/utils/response_error.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,MAAM,CAAQ;IACd,UAAU,CAAQ;IAClB,MAAM,CAAoB;IAC1B,QAAQ,CAAU;IAElB,YAAY,QAAkB;QAC5B,KAAK,CAAC,kBAAkB,QAAQ,EAAE,MAAM,IAAI,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;QACnE,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,MAAM,CAAA;QAC9B,IAAI,CAAC,UAAU,GAAG,QAAQ,EAAE,UAAU,CAAA;QACtC,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAA;QACpC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;CACF","sourcesContent":["import { ApiError } from '../types/api_primitives'\n\nexport class ResponseError extends Error {\n status: number\n statusText: string\n errors: ApiError['errors']\n response: ApiError\n\n constructor(response: ApiError) {\n super(`ResponseError: ${response?.status} ${response?.statusText}`)\n this.name = 'ResponseError'\n this.status = response?.status\n this.statusText = response?.statusText\n this.errors = response?.errors || []\n this.response = response\n }\n}\n"]}
1
+ {"version":3,"file":"response_error.js","sourceRoot":"","sources":["../../src/utils/response_error.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,MAAM,CAAQ;IACd,UAAU,CAAQ;IAClB,MAAM,CAAoB;IAC1B,QAAQ,CAAU;IAElB,YAAY,QAAkB;QAC5B,KAAK,CAAC,kBAAkB,QAAQ,EAAE,MAAM,IAAI,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;QACnE,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,MAAM,CAAA;QAC9B,IAAI,CAAC,UAAU,GAAG,QAAQ,EAAE,UAAU,CAAA;QACtC,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAA;QACpC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;CACF;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAE,EAAE;IACnD,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,aAAa,CAAC,KAAiB,CAAC,CAAA;IAC5C,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAA","sourcesContent":["import { ApiError } from '../types/api_primitives'\n\nexport class ResponseError extends Error {\n status: number\n statusText: string\n errors: ApiError['errors']\n response: ApiError\n\n constructor(response: ApiError) {\n super(`ResponseError: ${response?.status} ${response?.statusText}`)\n this.name = 'ResponseError'\n this.status = response?.status\n this.statusText = response?.statusText\n this.errors = response?.errors || []\n this.response = response\n }\n}\n\nexport const throwResponseError = (error: unknown) => {\n if (error instanceof Response) {\n throw new ResponseError(error as ApiError)\n }\n\n return Promise.reject(error)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.11.0-rc.8",
3
+ "version": "3.11.0",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "prettier": "^3.4.2",
56
56
  "typescript": "<5.6.0"
57
57
  },
58
- "gitHead": "93e08dbdc5b76a8fbbc6cf5d7ad95c750ab70ac6"
58
+ "gitHead": "d68607004fe3e05b3e6519564f39953f67c457cb"
59
59
  }
@@ -1,7 +1,12 @@
1
1
  import React, { useMemo, useState, Dispatch, SetStateAction } from 'react'
2
2
  import { StyleSheet, Modal, SafeAreaView, View, Linking, Dimensions } from 'react-native'
3
3
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
4
- import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
4
+ import {
5
+ Gesture,
6
+ GestureDetector,
7
+ GestureHandlerRootView,
8
+ Pressable,
9
+ } from 'react-native-gesture-handler'
5
10
  import {
6
11
  runOnJS,
7
12
  useAnimatedStyle,
@@ -509,15 +514,17 @@ const LightboxModal = ({
509
514
  <SafeAreaView style={styles.modal}>
510
515
  <GestureHandlerRootView>
511
516
  <GestureDetector gesture={composedGesture}>
512
- <Image
513
- source={{ uri }}
514
- loadingBackgroundStyles={styles.lightboxImageLoading}
515
- style={styles.lightboxImage}
516
- animatedImageStyle={animatedImageStyles}
517
- resizeMode="contain"
518
- animated={true}
519
- alt=""
520
- />
517
+ <PreventPressEventsBubbling>
518
+ <Image
519
+ source={{ uri }}
520
+ loadingBackgroundStyles={styles.lightboxImageLoading}
521
+ style={styles.lightboxImage}
522
+ animatedImageStyle={animatedImageStyles}
523
+ resizeMode="contain"
524
+ animated={true}
525
+ alt=""
526
+ />
527
+ </PreventPressEventsBubbling>
521
528
  </GestureDetector>
522
529
  <View style={styles.actionToolbar} accessibilityRole="toolbar">
523
530
  <View style={styles.actionToolbarTextMeta}>
@@ -552,6 +559,14 @@ const LightboxModal = ({
552
559
  )
553
560
  }
554
561
 
562
+ const PreventPressEventsBubbling = ({ children }: { children: React.ReactNode }) => {
563
+ return (
564
+ <Pressable onLongPress={() => {}} onPress={() => {}}>
565
+ {children}
566
+ </Pressable>
567
+ )
568
+ }
569
+
555
570
  interface UseStylesProps {
556
571
  imageWidth?: number
557
572
  imageHeight?: number
@@ -1,8 +1,8 @@
1
1
  import { useNavigation } from '@react-navigation/native'
2
- import React from 'react'
2
+ import React, { useEffect } from 'react'
3
3
  import { Platform, Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
4
4
  import { MessageReaction } from '../../components/conversation/message_reaction'
5
- import { Avatar, Text } from '../../components/display'
5
+ import { Avatar, Icon, Spinner, Text, TextInlineButton } from '../../components/display'
6
6
  import { useInteractionGhostBackgroundColor, useTheme } from '../../hooks'
7
7
  import { MessageResource } from '../../types'
8
8
  import { ReactionCountResource } from '../../types/resources/reaction'
@@ -23,6 +23,7 @@ import Animated, {
23
23
  } from 'react-native-reanimated'
24
24
  import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
25
25
  import { MessageReadReceipts } from './message_read_receipts'
26
+ import { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
26
27
 
27
28
  /** Message
28
29
  * Component for display of a message within a conversation list
@@ -33,16 +34,48 @@ interface MessageProps extends MessageResource {
33
34
  latestReadMessageSortKey?: string
34
35
  }
35
36
 
36
- export function Message(props: MessageProps) {
37
- const { conversation_id, canDeleteNonAuthoredMessages, text, reactionCounts } = props
38
- const styles = useMessageStyles(props)
37
+ export function Message({
38
+ canDeleteNonAuthoredMessages,
39
+ conversation_id,
40
+ latestReadMessageSortKey,
41
+ ...message
42
+ }: MessageProps) {
43
+ const { text, reactionCounts, pending, error } = message
44
+ const styles = useMessageStyles(message)
39
45
  const navigation = useNavigation()
40
46
  const { colors } = useTheme()
41
47
  const hasReactions = reactionCounts.length > 0
42
48
  const [showMessageMetaToggle, setShowMessageMetaToggle] = React.useState(false)
43
- const showMessageMeta = showMessageMetaToggle || props.myLatestInConversation || false
44
- const timestamp = useLiveRelativeTime(props.createdAt)
45
- const wasEdited = Boolean(props.textEditedAt)
49
+ const showMessageMeta =
50
+ showMessageMetaToggle || message.myLatestInConversation || !!pending || !!error || false
51
+ const timestamp = useLiveRelativeTime(message.createdAt)
52
+ const wasEdited = Boolean(message.textEditedAt)
53
+ const isPersisted = !isNewMessage(message)
54
+ const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)
55
+
56
+ useEffect(() => {
57
+ if (pending) {
58
+ const timer = setTimeout(() => {
59
+ setTemporarilyHidePendingState(false)
60
+ }, 2000)
61
+ return () => clearTimeout(timer)
62
+ }
63
+ return () => {}
64
+ }, [pending])
65
+
66
+ const retryFailedSaveMutation = useMessageCreateOrUpdate({
67
+ conversationId: conversation_id,
68
+ message,
69
+ })
70
+
71
+ const handleRetry = () => {
72
+ if (text && error) {
73
+ retryFailedSaveMutation.mutateAsync({
74
+ text,
75
+ attachments: message.attachmentsForCreate,
76
+ })
77
+ }
78
+ }
46
79
 
47
80
  const bgFadeProgress = useSharedValue(0)
48
81
  const pressedColor = Platform.select({
@@ -69,15 +102,17 @@ export function Message(props: MessageProps) {
69
102
  }
70
103
 
71
104
  const handleMessageLongPress = () => {
105
+ if (!isPersisted) return
106
+
72
107
  navigation.navigate('MessageActions', {
73
- message_id: props.id,
108
+ message_id: message.id,
74
109
  conversation_id,
75
110
  canDeleteNonAuthoredMessages,
76
111
  })
77
112
  }
78
113
  const handleReactionLongPress = (reaction: ReactionCountResource) => {
79
114
  navigation.navigate('Reactions', {
80
- message_id: props.id,
115
+ message_id: message.id,
81
116
  conversation_id,
82
117
  reaction_value: reaction.value,
83
118
  })
@@ -90,17 +125,18 @@ export function Message(props: MessageProps) {
90
125
  attachmentUrl: attachment?.attributes.url,
91
126
  conversation_id,
92
127
  canDeleteNonAuthoredMessages,
93
- myMessage: props.mine,
128
+ myMessage: message.mine,
94
129
  })
95
130
  }
96
131
 
97
132
  const metaProps = {
98
- authorName: props.author.name,
99
- createdAt: props.createdAt,
133
+ authorName: message.author.name,
134
+ createdAt: message.createdAt,
100
135
  }
101
136
 
102
- const renderAuthor = (!props.mine && props.renderAuthor) || false
103
- const messageBottomMargin = props.lastInGroup ? 12 : hasReactions || showMessageMeta ? 8 : 4
137
+ const renderAuthor = (!message.mine && message.renderAuthor) || false
138
+ const messageBottomMargin = message.lastInGroup ? 12 : hasReactions || showMessageMeta ? 8 : 4
139
+ const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
104
140
 
105
141
  return (
106
142
  <Pressable
@@ -112,21 +148,21 @@ export function Message(props: MessageProps) {
112
148
  accessibilityHint="Long press to view message actions like reacting and copying."
113
149
  >
114
150
  <Animated.View style={[styles.message, animatedBackgroundColor]}>
115
- {!props.mine && (
151
+ {!message.mine && (
116
152
  <View style={styles.messageAuthor}>
117
153
  {renderAuthor ? (
118
- <Avatar size={'md'} sourceUri={props.author.avatar} />
154
+ <Avatar size={'md'} sourceUri={message.author.avatar} />
119
155
  ) : (
120
156
  <View style={styles.avatarPlaceholder} />
121
157
  )}
122
158
  </View>
123
159
  )}
124
160
  <View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>
125
- {renderAuthor && <Text variant="tertiary">{props.author.name}</Text>}
161
+ {renderAuthor && <Text variant="tertiary">{message.author.name}</Text>}
126
162
  <View style={styles.messageBubble}>
127
163
  <ErrorBoundary>
128
164
  <MessageAttachments
129
- attachments={props.attachments}
165
+ attachments={message.attachments}
130
166
  metaProps={metaProps}
131
167
  onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
132
168
  onMessageLongPress={handleMessageLongPress}
@@ -145,7 +181,7 @@ export function Message(props: MessageProps) {
145
181
  key={reaction.value}
146
182
  reaction={reaction}
147
183
  onLongPress={handleReactionLongPress}
148
- message={props}
184
+ message={message}
149
185
  conversationId={conversation_id}
150
186
  />
151
187
  ))}
@@ -153,14 +189,44 @@ export function Message(props: MessageProps) {
153
189
  )}
154
190
  {showMessageMeta && (
155
191
  <View style={styles.messageMeta}>
156
- {props.mine && (
192
+ {message.mine && !pending && !error && (
157
193
  <MessageReadReceipts
158
- message={props}
159
- latestReadMessageSortKey={props.latestReadMessageSortKey}
194
+ message={message}
195
+ latestReadMessageSortKey={latestReadMessageSortKey}
160
196
  />
161
197
  )}
162
- <Text variant="footnote">{timestamp}</Text>
163
- {wasEdited && (
198
+ {error ? (
199
+ <View style={styles.errorContainer}>
200
+ {retryFailedSaveMutation.isPending ? (
201
+ <Spinner size={12} style={styles.errorSpinner} />
202
+ ) : (
203
+ <Icon name="general.exclamationTriangle" size={12} style={styles.errorIcon} />
204
+ )}
205
+ <Text variant="footnote" style={styles.errorText}>
206
+ Failed to {isPersisted ? 'edit' : 'send'} |{' '}
207
+ <TextInlineButton
208
+ onPress={handleRetry}
209
+ variant="footnote"
210
+ appearance="danger"
211
+ disabled={retryFailedSaveMutation.isPending}
212
+ >
213
+ Try again
214
+ </TextInlineButton>
215
+ </Text>
216
+ </View>
217
+ ) : pending && (!temporarilyHidePendingState || isPersisted) ? (
218
+ <View style={styles.pendingIndicatorContainer}>
219
+ <Icon
220
+ name="general.outlinedClock"
221
+ size={12}
222
+ color={colors.iconColorDefaultSecondary}
223
+ />
224
+ <Text variant="footnote">{messagePendingLabel}</Text>
225
+ </View>
226
+ ) : isPersisted ? (
227
+ <Text variant="footnote">{timestamp}</Text>
228
+ ) : null}
229
+ {!pending && !error && wasEdited && (
164
230
  <Text variant="footnote">
165
231
  |{' '}
166
232
  <Text variant="footnote" style={styles.editedText}>
@@ -176,13 +242,14 @@ export function Message(props: MessageProps) {
176
242
  )
177
243
  }
178
244
 
179
- const useMessageStyles = (props: MessageResource) => {
180
- const { mine } = props
245
+ const useMessageStyles = ({ mine }: MessageResource) => {
181
246
  const { colors } = useTheme()
182
247
  const myMessageBackgroundColor = useInteractionGhostBackgroundColor()
183
248
  const { width } = useWindowDimensions()
184
249
  const tabletWidth = width >= 744 // Smallest iPad Mini's width
185
250
 
251
+ const messageBubbleBackgroundColor = mine ? myMessageBackgroundColor : colors.fillColorNeutral070
252
+
186
253
  return StyleSheet.create({
187
254
  message: {
188
255
  gap: 8,
@@ -202,7 +269,7 @@ const useMessageStyles = (props: MessageResource) => {
202
269
  height: 32, // Same height as avatar
203
270
  },
204
271
  messageBubble: {
205
- backgroundColor: mine ? myMessageBackgroundColor : colors.fillColorNeutral070,
272
+ backgroundColor: messageBubbleBackgroundColor,
206
273
  borderRadius: 8,
207
274
  maxWidth: tabletWidth ? 360 : '80%',
208
275
  alignSelf: mine ? 'flex-end' : 'flex-start',
@@ -225,5 +292,26 @@ const useMessageStyles = (props: MessageResource) => {
225
292
  editedText: {
226
293
  fontWeight: platformFontWeightMedium,
227
294
  },
295
+ pendingIndicatorContainer: {
296
+ flexDirection: 'row',
297
+ alignItems: 'center',
298
+ gap: 4,
299
+ },
300
+ errorContainer: {
301
+ flexDirection: 'row',
302
+ alignItems: 'center',
303
+ gap: 4,
304
+ },
305
+ errorIcon: {
306
+ color: colors.statusErrorIcon,
307
+ },
308
+ errorText: {
309
+ color: colors.statusErrorText,
310
+ },
311
+ errorSpinner: {
312
+ position: 'relative',
313
+ width: 'auto',
314
+ height: 'auto',
315
+ },
228
316
  })
229
317
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
- import { FileAttachment } from '../../../hooks/use_attachment_uploader'
3
2
  import { ImageAttachmentPreview } from '../../display'
3
+ import { FileAttachment } from '../../../types/resources/denormalized_attachment_resource_for_create'
4
4
 
5
5
  interface Props {
6
6
  uri: string
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
- import { FileAttachment } from '../../../hooks/use_attachment_uploader'
3
2
  import { VideoAttachmentPreview } from '../../display'
3
+ import { FileAttachment } from '../../../types/resources/denormalized_attachment_resource_for_create'
4
4
 
5
5
  interface Props {
6
6
  status: FileAttachment['status']
@@ -32,7 +32,7 @@ import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
32
32
  import {
33
33
  DenormalizedAttachmentResourceForCreate,
34
34
  DenormalizedMessageAttachmentResourceForCreate,
35
- } from '../../types/resources/denormalized_attachment_resource'
35
+ } from '../../types/resources/denormalized_attachment_resource_for_create'
36
36
  import { MessageFormAttachmentImage } from './message_form/message_form_attachment_image'
37
37
  import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
38
38
  import { useBroadcastTypingStatus } from '../../hooks/use_broadcast_typing_status'
@@ -99,7 +99,7 @@ function MessageFormRoot({
99
99
  mutateAsync,
100
100
  } = useMessageCreateOrUpdate({
101
101
  conversationId: conversation.id,
102
- messageId: currentlyEditingMessage?.id || null,
102
+ message: currentlyEditingMessage || undefined,
103
103
  })
104
104
  const attachmentUploader = useAttachmentUploader({
105
105
  conversationId: conversation.id,
@@ -175,19 +175,22 @@ function MessageFormRoot({
175
175
  TextInput.State.blurTextInput(TextInput.State.currentlyFocusedInput())
176
176
  navigateToSendGiphyAfterKeyboardHides()
177
177
  } else {
178
- let attachmentsForSubmit: DenormalizedAttachmentResourceForCreate[] = []
179
- if (attachmentUploader?.attachmentIds) {
180
- attachmentsForSubmit = attachmentUploader.attachmentIds.map(
181
- (id: string): DenormalizedMessageAttachmentResourceForCreate => ({
182
- type: 'MessageAttachment',
183
- id,
184
- })
185
- )
186
- }
178
+ let attachmentsForSubmit: DenormalizedAttachmentResourceForCreate[] =
179
+ attachmentUploader.attachments
180
+ .filter(a => a.status === 'success' && a.id)
181
+ .map(
182
+ (attachment): DenormalizedMessageAttachmentResourceForCreate => ({
183
+ type: 'MessageAttachment',
184
+ id: attachment.id as string,
185
+ file: attachment.file,
186
+ })
187
+ )
187
188
  if (currentlyEditingMessage) {
188
- mutateAsync({ text }).then(reset)
189
+ mutateAsync({ text })
190
+ reset()
189
191
  } else {
190
- mutateAsync({ text, attachments: attachmentsForSubmit }).then(reset)
192
+ mutateAsync({ text, attachments: attachmentsForSubmit })
193
+ reset()
191
194
  }
192
195
  }
193
196
  }
@@ -4,9 +4,10 @@ import React, { useMemo } from 'react'
4
4
  import { StyleSheet, View } from 'react-native'
5
5
  import { useConversationsContext } from '../../contexts/conversations_context'
6
6
  import { useTheme } from '../../hooks'
7
+ import { ConversationResource } from '../../types'
8
+ import { throwResponseError } from '../../utils/response_error'
7
9
  import { BlankState } from '../display'
8
10
  import { ConversationPreview, ConversationPreviewSkeleton } from './conversation_preview'
9
- import { ConversationResource } from '../../types'
10
11
 
11
12
  interface ConversationsProps {
12
13
  ListHeaderComponent?:
@@ -26,6 +27,8 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
26
27
  refetch,
27
28
  isRefetching,
28
29
  isFetched,
30
+ isError,
31
+ error,
29
32
  args: { chat_group_graph_id },
30
33
  } = useConversationsContext()
31
34
  const navigation = useNavigation()
@@ -43,6 +46,10 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
43
46
  }))
44
47
  }, [conversations, isLoading])
45
48
 
49
+ if (isError) {
50
+ throwResponseError(error)
51
+ }
52
+
46
53
  return (
47
54
  <View style={styles.container}>
48
55
  <FlashList
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef } from 'react'
2
- import { Animated, Easing, StyleSheet, View } from 'react-native'
2
+ import { Animated, Easing, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
3
3
  import { useFontScale, useTheme } from '../../hooks'
4
4
 
5
5
  // =================================
@@ -21,11 +21,16 @@ interface SpinnerProps {
21
21
  * Specifies the maximum size spinner can scale to if the device's font-size is increased.
22
22
  */
23
23
  maxFontSizeMultiplier?: number
24
+ /**
25
+ * Style to apply to the spinner
26
+ */
27
+ style?: StyleProp<ViewStyle>
24
28
  }
25
29
 
26
30
  export function Spinner({
27
31
  size = 20,
28
32
  maxFontSizeMultiplier = PREVENT_SCALING_DEFAULT,
33
+ style,
29
34
  }: SpinnerProps) {
30
35
  const rotation = useRef(new Animated.Value(0)).current
31
36
 
@@ -53,7 +58,7 @@ export function Spinner({
53
58
  useEffect(() => () => rotation.setValue(0), [rotation])
54
59
 
55
60
  return (
56
- <View style={styles.container}>
61
+ <View style={[styles.container, style]}>
57
62
  <Animated.View style={styles.animatedContainer}>
58
63
  <View style={styles.clipping}>
59
64
  <View style={[styles.circle, styles.spinner]} />
@@ -1,6 +1,6 @@
1
1
  import { useTheme } from '../../hooks'
2
2
  import React from 'react'
3
- import { StyleSheet, Text as ReactNativeText } from 'react-native'
3
+ import { StyleSheet, Text as ReactNativeText, Platform } from 'react-native'
4
4
  import type { TextStyle, TextProps as ReactNativeTextProps } from 'react-native'
5
5
  import { tokens } from '../../vendor/tapestry/tokens'
6
6
 
@@ -39,7 +39,7 @@ export function Text({ style, variant = 'plain', children, ...props }: TextProps
39
39
  }
40
40
 
41
41
  return (
42
- <ReactNativeText style={[variantStyleMap[variant], style]} {...props}>
42
+ <ReactNativeText style={[styles.base, variantStyleMap[variant], style]} {...props}>
43
43
  {children}
44
44
  </ReactNativeText>
45
45
  )
@@ -52,6 +52,14 @@ export function Text({ style, variant = 'plain', children, ...props }: TextProps
52
52
  const useStyles = () => {
53
53
  const { colors } = useTheme()
54
54
  return StyleSheet.create({
55
+ base: {
56
+ ...Platform.select({
57
+ android: {
58
+ fontFamily: 'normal', // This forces the text to be normal weight when the "Bold font" accessibility setting is enabled. While this is not ideal, it fixes a bug where the text without a hard coded fontWeight gets cut off on Android. https://github.com/facebook/react-native/issues/15114
59
+ },
60
+ default: null, // iOS needs to specify its own font family otherwise the italic style won't work. (Used in message_markdown.tsx) It's "Bold font" accessibility setting works as expected.
61
+ }),
62
+ },
55
63
  plain: {
56
64
  color: colors.textColorDefaultPrimary,
57
65
  fontSize: tokens.fontSizeMd,
@@ -113,13 +113,14 @@ function AndroidSheetGrabber() {
113
113
 
114
114
  interface FormSheetHeaderProps {
115
115
  children: ReactNode
116
+ style?: StyleProp<ViewStyle>
116
117
  }
117
118
 
118
- function FormSheetHeader({ children }: FormSheetHeaderProps) {
119
+ function FormSheetHeader({ children, style }: FormSheetHeaderProps) {
119
120
  const styles = useStyles()
120
121
 
121
122
  return (
122
- <View style={styles.header}>
123
+ <View style={[styles.header, style]}>
123
124
  <View style={styles.headerContent}>{children}</View>
124
125
  </View>
125
126
  )