@repzo/widget-react-native 1.2.1 → 1.3.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 (161) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +40 -3
  3. package/android/build.gradle +22 -0
  4. package/android/src/main/java/expo/modules/repzowidget/RepzoVoicePlayer.kt +222 -0
  5. package/android/src/main/java/expo/modules/repzowidget/RepzoWidgetAudioModule.kt +68 -0
  6. package/expo-module.config.json +9 -0
  7. package/ios/RepzoVoicePlayer.swift +175 -0
  8. package/ios/RepzoWidgetAudio.podspec +26 -0
  9. package/ios/RepzoWidgetAudioModule.swift +65 -0
  10. package/lib/commonjs/api/client.js +47 -2
  11. package/lib/commonjs/api/client.js.map +1 -1
  12. package/lib/commonjs/audio/native-player.js +97 -0
  13. package/lib/commonjs/audio/native-player.js.map +1 -0
  14. package/lib/commonjs/audio/voice.js +146 -0
  15. package/lib/commonjs/audio/voice.js.map +1 -0
  16. package/lib/commonjs/i18n.js +6 -0
  17. package/lib/commonjs/i18n.js.map +1 -1
  18. package/lib/commonjs/locales.js +4 -0
  19. package/lib/commonjs/locales.js.map +1 -1
  20. package/lib/commonjs/state/use-widget-state.js +31 -0
  21. package/lib/commonjs/state/use-widget-state.js.map +1 -1
  22. package/lib/commonjs/state/widget-store.js +44 -1
  23. package/lib/commonjs/state/widget-store.js.map +1 -1
  24. package/lib/commonjs/ui/WidgetProvider.js +7 -3
  25. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  26. package/lib/commonjs/ui/components/FileCard.js +134 -0
  27. package/lib/commonjs/ui/components/FileCard.js.map +1 -0
  28. package/lib/commonjs/ui/components/ImageViewer.js +17 -35
  29. package/lib/commonjs/ui/components/ImageViewer.js.map +1 -1
  30. package/lib/commonjs/ui/components/Launcher.js +15 -1
  31. package/lib/commonjs/ui/components/Launcher.js.map +1 -1
  32. package/lib/commonjs/ui/components/MessagesSkeleton.js +126 -0
  33. package/lib/commonjs/ui/components/MessagesSkeleton.js.map +1 -0
  34. package/lib/commonjs/ui/components/VoiceNote.js +353 -0
  35. package/lib/commonjs/ui/components/VoiceNote.js.map +1 -0
  36. package/lib/commonjs/ui/components/ZoomableImage.js +299 -0
  37. package/lib/commonjs/ui/components/ZoomableImage.js.map +1 -0
  38. package/lib/commonjs/ui/components/icons.js +57 -0
  39. package/lib/commonjs/ui/components/icons.js.map +1 -1
  40. package/lib/commonjs/ui/screens/chat/ChatView.js +39 -18
  41. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  42. package/lib/commonjs/ui/screens/chat/ChatView.styles.js +5 -16
  43. package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
  44. package/lib/module/api/client.js +48 -2
  45. package/lib/module/api/client.js.map +1 -1
  46. package/lib/module/audio/native-player.js +93 -0
  47. package/lib/module/audio/native-player.js.map +1 -0
  48. package/lib/module/audio/voice.js +136 -0
  49. package/lib/module/audio/voice.js.map +1 -0
  50. package/lib/module/i18n.js +6 -0
  51. package/lib/module/i18n.js.map +1 -1
  52. package/lib/module/locales.js +4 -0
  53. package/lib/module/locales.js.map +1 -1
  54. package/lib/module/state/use-widget-state.js +30 -1
  55. package/lib/module/state/use-widget-state.js.map +1 -1
  56. package/lib/module/state/widget-store.js +44 -1
  57. package/lib/module/state/widget-store.js.map +1 -1
  58. package/lib/module/ui/WidgetProvider.js +7 -3
  59. package/lib/module/ui/WidgetProvider.js.map +1 -1
  60. package/lib/module/ui/components/FileCard.js +130 -0
  61. package/lib/module/ui/components/FileCard.js.map +1 -0
  62. package/lib/module/ui/components/ImageViewer.js +18 -36
  63. package/lib/module/ui/components/ImageViewer.js.map +1 -1
  64. package/lib/module/ui/components/Launcher.js +16 -2
  65. package/lib/module/ui/components/Launcher.js.map +1 -1
  66. package/lib/module/ui/components/MessagesSkeleton.js +124 -0
  67. package/lib/module/ui/components/MessagesSkeleton.js.map +1 -0
  68. package/lib/module/ui/components/VoiceNote.js +349 -0
  69. package/lib/module/ui/components/VoiceNote.js.map +1 -0
  70. package/lib/module/ui/components/ZoomableImage.js +291 -0
  71. package/lib/module/ui/components/ZoomableImage.js.map +1 -0
  72. package/lib/module/ui/components/icons.js +55 -0
  73. package/lib/module/ui/components/icons.js.map +1 -1
  74. package/lib/module/ui/screens/chat/ChatView.js +39 -18
  75. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  76. package/lib/module/ui/screens/chat/ChatView.styles.js +5 -16
  77. package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
  78. package/lib/typescript/commonjs/api/client.d.ts +12 -0
  79. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/audio/native-player.d.ts +36 -0
  81. package/lib/typescript/commonjs/audio/native-player.d.ts.map +1 -0
  82. package/lib/typescript/commonjs/audio/voice.d.ts +42 -0
  83. package/lib/typescript/commonjs/audio/voice.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/i18n.d.ts +4 -0
  85. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/state/use-widget-state.d.ts +8 -0
  88. package/lib/typescript/commonjs/state/use-widget-state.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/state/widget-store.d.ts +13 -0
  90. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/components/FileCard.d.ts +21 -0
  93. package/lib/typescript/commonjs/ui/components/FileCard.d.ts.map +1 -0
  94. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +2 -2
  95. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/components/Launcher.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts +10 -0
  98. package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts.map +1 -0
  99. package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts +32 -0
  100. package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts +45 -0
  102. package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/ui/components/icons.d.ts +4 -0
  104. package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +4 -16
  107. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +4 -0
  109. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  110. package/lib/typescript/module/api/client.d.ts +12 -0
  111. package/lib/typescript/module/api/client.d.ts.map +1 -1
  112. package/lib/typescript/module/audio/native-player.d.ts +36 -0
  113. package/lib/typescript/module/audio/native-player.d.ts.map +1 -0
  114. package/lib/typescript/module/audio/voice.d.ts +42 -0
  115. package/lib/typescript/module/audio/voice.d.ts.map +1 -0
  116. package/lib/typescript/module/i18n.d.ts +4 -0
  117. package/lib/typescript/module/i18n.d.ts.map +1 -1
  118. package/lib/typescript/module/locales.d.ts.map +1 -1
  119. package/lib/typescript/module/state/use-widget-state.d.ts +8 -0
  120. package/lib/typescript/module/state/use-widget-state.d.ts.map +1 -1
  121. package/lib/typescript/module/state/widget-store.d.ts +13 -0
  122. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  123. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  124. package/lib/typescript/module/ui/components/FileCard.d.ts +21 -0
  125. package/lib/typescript/module/ui/components/FileCard.d.ts.map +1 -0
  126. package/lib/typescript/module/ui/components/ImageViewer.d.ts +2 -2
  127. package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -1
  128. package/lib/typescript/module/ui/components/Launcher.d.ts.map +1 -1
  129. package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts +10 -0
  130. package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts.map +1 -0
  131. package/lib/typescript/module/ui/components/VoiceNote.d.ts +32 -0
  132. package/lib/typescript/module/ui/components/VoiceNote.d.ts.map +1 -0
  133. package/lib/typescript/module/ui/components/ZoomableImage.d.ts +45 -0
  134. package/lib/typescript/module/ui/components/ZoomableImage.d.ts.map +1 -0
  135. package/lib/typescript/module/ui/components/icons.d.ts +4 -0
  136. package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
  137. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +4 -16
  139. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  140. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +4 -0
  141. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  142. package/package.json +14 -3
  143. package/react-native.config.js +12 -0
  144. package/src/api/client.ts +56 -2
  145. package/src/audio/native-player.ts +106 -0
  146. package/src/audio/voice.ts +127 -0
  147. package/src/i18n.ts +6 -0
  148. package/src/locales.ts +4 -0
  149. package/src/state/use-widget-state.ts +30 -1
  150. package/src/state/widget-store.ts +45 -1
  151. package/src/ui/WidgetProvider.tsx +7 -3
  152. package/src/ui/components/FileCard.tsx +111 -0
  153. package/src/ui/components/ImageViewer.tsx +11 -30
  154. package/src/ui/components/Launcher.tsx +7 -2
  155. package/src/ui/components/MessagesSkeleton.tsx +92 -0
  156. package/src/ui/components/VoiceNote.tsx +355 -0
  157. package/src/ui/components/ZoomableImage.tsx +250 -0
  158. package/src/ui/components/icons.tsx +33 -0
  159. package/src/ui/screens/chat/ChatView.styles.ts +5 -16
  160. package/src/ui/screens/chat/ChatView.tsx +43 -16
  161. package/src/ui/screens/chat/ChatView.types.ts +4 -0
@@ -66,6 +66,39 @@ export function ChevronIcon({
66
66
  );
67
67
  }
68
68
 
69
+ /** A right-pointing triangle, drawn with borders — a voice note's play button. */
70
+ export function PlayIcon({ size = 16, color = palette.white, style }: IconProps) {
71
+ return (
72
+ <View style={[{ width: size, height: size }, styles.center, style]}>
73
+ <View
74
+ style={{
75
+ width: 0,
76
+ height: 0,
77
+ // The triangle's weight sits left of its box; nudge it to look centred.
78
+ marginLeft: size * 0.12,
79
+ borderLeftWidth: size * 0.72,
80
+ borderTopWidth: size * 0.42,
81
+ borderBottomWidth: size * 0.42,
82
+ borderLeftColor: color,
83
+ borderTopColor: 'transparent',
84
+ borderBottomColor: 'transparent',
85
+ }}
86
+ />
87
+ </View>
88
+ );
89
+ }
90
+
91
+ /** Two upright bars — a playing voice note's pause button. */
92
+ export function PauseIcon({ size = 16, color = palette.white, style }: IconProps) {
93
+ const bar: ViewStyle = { width: size * 0.26, height: size * 0.8, borderRadius: 1.5, backgroundColor: color };
94
+ return (
95
+ <View style={[{ width: size, height: size, flexDirection: 'row', gap: size * 0.2 }, styles.center, style]}>
96
+ <View style={bar} />
97
+ <View style={bar} />
98
+ </View>
99
+ );
100
+ }
101
+
69
102
  /** Arrow with a shaft — used for the "start a conversation" affordance. */
70
103
  export function ArrowRightIcon({ size = 16, color = palette.textMuted, style }: IconProps) {
71
104
  const thickness = Math.max(2, size * 0.12);
@@ -84,6 +84,11 @@ export const styles = StyleSheet.create({
84
84
  flexGrow: 1,
85
85
  justifyContent: 'center',
86
86
  },
87
+ // Placeholders sit at the bottom, where the newest messages will land.
88
+ messagesContentLoading: {
89
+ flexGrow: 1,
90
+ justifyContent: 'flex-end',
91
+ },
87
92
  emptyState: {
88
93
  alignItems: 'center',
89
94
  paddingHorizontal: spacing.xl,
@@ -117,13 +122,6 @@ export const styles = StyleSheet.create({
117
122
  fontSize: fontSize.meta,
118
123
  fontWeight: '600',
119
124
  },
120
- stateText: {
121
- textAlign: 'center',
122
- paddingVertical: 40,
123
- paddingHorizontal: spacing.xl,
124
- color: palette.textSubtle,
125
- fontSize: fontSize.meta,
126
- },
127
125
  messageRow: {
128
126
  maxWidth: '100%',
129
127
  },
@@ -214,15 +212,6 @@ export const styles = StyleSheet.create({
214
212
  height: 140,
215
213
  borderRadius: radius.md,
216
214
  },
217
- attachmentFile: {
218
- borderWidth: StyleSheet.hairlineWidth,
219
- borderRadius: radius.md,
220
- paddingHorizontal: spacing.sm + 1,
221
- paddingVertical: spacing.sm - 1,
222
- },
223
- attachmentName: {
224
- fontSize: fontSize.meta,
225
- },
226
215
  failedText: {
227
216
  fontSize: fontSize.caption,
228
217
  marginTop: spacing.xs,
@@ -59,6 +59,10 @@ import { contrastingTextColor, mixHex, palette, spacing, withAlpha } from '../..
59
59
  import { formatClockTime, formatDayLabel, isSameDay } from '../../utils/conversations';
60
60
  import { stripHtml } from '../../utils/html';
61
61
  import { isHttpUrl, openExternalUrl } from '../../utils/safe-url';
62
+ import { isPlayableAudio } from '../../../audio/voice';
63
+ import { VoiceNote, type VoiceNoteLabels } from '../../components/VoiceNote';
64
+ import { FileCard } from '../../components/FileCard';
65
+ import { MessagesSkeleton } from '../../components/MessagesSkeleton';
62
66
  import { backDirection, isRTL } from '../../utils/rtl';
63
67
  import { styles } from './ChatView.styles';
64
68
  import type { ChatLabels, ChatViewProps } from './ChatView.types';
@@ -328,6 +332,7 @@ export function ChatView({
328
332
  const headerTextColor = contrastingTextColor(color);
329
333
  const onDarkHeader = headerTextColor === palette.white;
330
334
  const isEmptyThread = !loading && messages.length === 0;
335
+ const isLoadingThread = loading && messages.length === 0;
331
336
  const agentIsActive = agentOnline ?? isOnline;
332
337
  // "Waiting for a teammate" until someone replies: no teammate in the header
333
338
  // and the visitor spoke last (bot replies before that still count as waiting).
@@ -436,6 +441,7 @@ export function ChatView({
436
441
  contentContainerStyle={[
437
442
  styles.messagesContent,
438
443
  isEmptyThread && styles.messagesContentEmpty,
444
+ isLoadingThread && styles.messagesContentLoading,
439
445
  ]}
440
446
  keyboardShouldPersistTaps="handled"
441
447
  // Interactive dismissal drags the keyboard down under the finger while
@@ -452,9 +458,7 @@ export function ChatView({
452
458
  </Pressable>
453
459
  ) : null}
454
460
 
455
- {loading && messages.length === 0 ? (
456
- <Text style={styles.stateText}>{labels.loading}</Text>
457
- ) : null}
461
+ {isLoadingThread ? <MessagesSkeleton color={color} label={labels.loading} /> : null}
458
462
 
459
463
  {isEmptyThread ? (
460
464
  <View style={styles.emptyState}>
@@ -759,6 +763,9 @@ const MessageBubble = memo(function MessageBubble({
759
763
  key={attachment.id}
760
764
  attachment={attachment}
761
765
  isVisitor={isVisitor}
766
+ color={color}
767
+ voiceLabels={labels.voice}
768
+ openLabel={labels.openAttachment}
762
769
  onOpenImage={onOpenImage}
763
770
  />
764
771
  ))}
@@ -818,16 +825,24 @@ function receiptLabel(message: Message, labels: ChatLabels): string | undefined
818
825
  function AttachmentPreview({
819
826
  attachment,
820
827
  isVisitor,
828
+ color,
829
+ voiceLabels,
830
+ openLabel,
821
831
  onOpenImage,
822
832
  }: {
823
833
  attachment: WidgetAttachment;
824
834
  isVisitor: boolean;
835
+ color: string;
836
+ voiceLabels: VoiceNoteLabels;
837
+ /** Keeps `{name}`. */
838
+ openLabel: string;
825
839
  onOpenImage: (image: { url: string; name: string }) => void;
826
840
  }) {
827
841
  // `url` is empty when the server sent a non-http(s) URL (see resolveUrl).
828
842
  // An unsent attachment shows its local file (or data URL): it renders, but
829
843
  // there is nothing to open until the server copy arrives.
830
844
  const [imageFailed, setImageFailed] = useState(false);
845
+ const [audioFailed, setAudioFailed] = useState(false);
831
846
  const isImage = isImageAttachment(attachment.contentType) && !!attachment.url && !imageFailed;
832
847
  const openable = isHttpUrl(attachment.url);
833
848
  // Images open in the in-app viewer; other files (PDF, sheets) need the OS.
@@ -837,6 +852,23 @@ function AttachmentPreview({
837
852
  ? () => onOpenImage({ url: attachment.url, name: attachment.name })
838
853
  : () => void openExternalUrl(attachment.url);
839
854
 
855
+ // Voice notes from the team (and any other audio) play in place, like the
856
+ // web Messenger — when the app has expo-audio; otherwise the file row below.
857
+ // One the player can't load (expired link, a format the OS can't decode)
858
+ // falls back to that row, which opens it in the browser.
859
+ if (isPlayableAudio(attachment.contentType, attachment.url) && !audioFailed) {
860
+ return (
861
+ <VoiceNote
862
+ url={attachment.url}
863
+ seed={attachment.id}
864
+ color={color}
865
+ onBrandBubble={isVisitor}
866
+ labels={voiceLabels}
867
+ onError={() => setAudioFailed(true)}
868
+ />
869
+ );
870
+ }
871
+
840
872
  if (isImage) {
841
873
  return (
842
874
  <Pressable
@@ -858,20 +890,15 @@ function AttachmentPreview({
858
890
  }
859
891
 
860
892
  return (
861
- <Pressable
893
+ <FileCard
894
+ name={attachment.name}
895
+ contentType={attachment.contentType}
896
+ size={attachment.size}
897
+ color={color}
898
+ onBrandBubble={isVisitor}
899
+ openLabel={openLabel.replace('{name}', attachment.name)}
862
900
  onPress={open}
863
- disabled={!openable}
864
- accessibilityRole="button"
865
- style={[
866
- styles.attachmentFile,
867
- { borderColor: isVisitor ? withAlpha(palette.white, 0.35) : palette.border },
868
- ]}>
869
- <Text
870
- style={[styles.attachmentName, { color: isVisitor ? palette.white : palette.ink }]}
871
- numberOfLines={1}>
872
- {attachment.name}
873
- </Text>
874
- </Pressable>
901
+ />
875
902
  );
876
903
  }
877
904
 
@@ -12,6 +12,7 @@ import type {
12
12
  import type { AgentUiBlockLabels } from '../../components/AgentUiBlocks';
13
13
  import type { ImageViewerLabels } from '../../components/ImageViewer';
14
14
  import type { RatingCardLabels } from '../../components/RatingCard';
15
+ import type { VoiceNoteLabels } from '../../components/VoiceNote';
15
16
 
16
17
  export interface ChatLabels {
17
18
  back: string;
@@ -49,6 +50,9 @@ export interface ChatLabels {
49
50
  rating: RatingCardLabels;
50
51
  attach: AttachLabels;
51
52
  viewer: ImageViewerLabels;
53
+ voice: VoiceNoteLabels;
54
+ /** Screen-reader label for a file card; keeps `{name}`. */
55
+ openAttachment: string;
52
56
  }
53
57
 
54
58
  /** Composer attachment copy. Limits are already filled in; `remove` keeps `{name}`. */