@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.
- package/CHANGELOG.md +69 -0
- package/README.md +40 -3
- package/android/build.gradle +22 -0
- package/android/src/main/java/expo/modules/repzowidget/RepzoVoicePlayer.kt +222 -0
- package/android/src/main/java/expo/modules/repzowidget/RepzoWidgetAudioModule.kt +68 -0
- package/expo-module.config.json +9 -0
- package/ios/RepzoVoicePlayer.swift +175 -0
- package/ios/RepzoWidgetAudio.podspec +26 -0
- package/ios/RepzoWidgetAudioModule.swift +65 -0
- package/lib/commonjs/api/client.js +47 -2
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/audio/native-player.js +97 -0
- package/lib/commonjs/audio/native-player.js.map +1 -0
- package/lib/commonjs/audio/voice.js +146 -0
- package/lib/commonjs/audio/voice.js.map +1 -0
- package/lib/commonjs/i18n.js +6 -0
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/locales.js +4 -0
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/use-widget-state.js +31 -0
- package/lib/commonjs/state/use-widget-state.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +44 -1
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +7 -3
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/FileCard.js +134 -0
- package/lib/commonjs/ui/components/FileCard.js.map +1 -0
- package/lib/commonjs/ui/components/ImageViewer.js +17 -35
- package/lib/commonjs/ui/components/ImageViewer.js.map +1 -1
- package/lib/commonjs/ui/components/Launcher.js +15 -1
- package/lib/commonjs/ui/components/Launcher.js.map +1 -1
- package/lib/commonjs/ui/components/MessagesSkeleton.js +126 -0
- package/lib/commonjs/ui/components/MessagesSkeleton.js.map +1 -0
- package/lib/commonjs/ui/components/VoiceNote.js +353 -0
- package/lib/commonjs/ui/components/VoiceNote.js.map +1 -0
- package/lib/commonjs/ui/components/ZoomableImage.js +299 -0
- package/lib/commonjs/ui/components/ZoomableImage.js.map +1 -0
- package/lib/commonjs/ui/components/icons.js +57 -0
- package/lib/commonjs/ui/components/icons.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +39 -18
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +5 -16
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/api/client.js +48 -2
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/audio/native-player.js +93 -0
- package/lib/module/audio/native-player.js.map +1 -0
- package/lib/module/audio/voice.js +136 -0
- package/lib/module/audio/voice.js.map +1 -0
- package/lib/module/i18n.js +6 -0
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/locales.js +4 -0
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/use-widget-state.js +30 -1
- package/lib/module/state/use-widget-state.js.map +1 -1
- package/lib/module/state/widget-store.js +44 -1
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +7 -3
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/FileCard.js +130 -0
- package/lib/module/ui/components/FileCard.js.map +1 -0
- package/lib/module/ui/components/ImageViewer.js +18 -36
- package/lib/module/ui/components/ImageViewer.js.map +1 -1
- package/lib/module/ui/components/Launcher.js +16 -2
- package/lib/module/ui/components/Launcher.js.map +1 -1
- package/lib/module/ui/components/MessagesSkeleton.js +124 -0
- package/lib/module/ui/components/MessagesSkeleton.js.map +1 -0
- package/lib/module/ui/components/VoiceNote.js +349 -0
- package/lib/module/ui/components/VoiceNote.js.map +1 -0
- package/lib/module/ui/components/ZoomableImage.js +291 -0
- package/lib/module/ui/components/ZoomableImage.js.map +1 -0
- package/lib/module/ui/components/icons.js +55 -0
- package/lib/module/ui/components/icons.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +39 -18
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +5 -16
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +12 -0
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/audio/native-player.d.ts +36 -0
- package/lib/typescript/commonjs/audio/native-player.d.ts.map +1 -0
- package/lib/typescript/commonjs/audio/voice.d.ts +42 -0
- package/lib/typescript/commonjs/audio/voice.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n.d.ts +4 -0
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/use-widget-state.d.ts +8 -0
- package/lib/typescript/commonjs/state/use-widget-state.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +13 -0
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/FileCard.d.ts +21 -0
- package/lib/typescript/commonjs/ui/components/FileCard.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Launcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts +10 -0
- package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts +32 -0
- package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts +45 -0
- package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts +4 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +4 -16
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +4 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +12 -0
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/audio/native-player.d.ts +36 -0
- package/lib/typescript/module/audio/native-player.d.ts.map +1 -0
- package/lib/typescript/module/audio/voice.d.ts +42 -0
- package/lib/typescript/module/audio/voice.d.ts.map +1 -0
- package/lib/typescript/module/i18n.d.ts +4 -0
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/state/use-widget-state.d.ts +8 -0
- package/lib/typescript/module/state/use-widget-state.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +13 -0
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FileCard.d.ts +21 -0
- package/lib/typescript/module/ui/components/FileCard.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts +2 -2
- package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Launcher.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts +10 -0
- package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/VoiceNote.d.ts +32 -0
- package/lib/typescript/module/ui/components/VoiceNote.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ZoomableImage.d.ts +45 -0
- package/lib/typescript/module/ui/components/ZoomableImage.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/icons.d.ts +4 -0
- package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +4 -16
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +4 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/package.json +14 -3
- package/react-native.config.js +12 -0
- package/src/api/client.ts +56 -2
- package/src/audio/native-player.ts +106 -0
- package/src/audio/voice.ts +127 -0
- package/src/i18n.ts +6 -0
- package/src/locales.ts +4 -0
- package/src/state/use-widget-state.ts +30 -1
- package/src/state/widget-store.ts +45 -1
- package/src/ui/WidgetProvider.tsx +7 -3
- package/src/ui/components/FileCard.tsx +111 -0
- package/src/ui/components/ImageViewer.tsx +11 -30
- package/src/ui/components/Launcher.tsx +7 -2
- package/src/ui/components/MessagesSkeleton.tsx +92 -0
- package/src/ui/components/VoiceNote.tsx +355 -0
- package/src/ui/components/ZoomableImage.tsx +250 -0
- package/src/ui/components/icons.tsx +33 -0
- package/src/ui/screens/chat/ChatView.styles.ts +5 -16
- package/src/ui/screens/chat/ChatView.tsx +43 -16
- 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
|
-
{
|
|
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
|
-
<
|
|
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
|
-
|
|
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}`. */
|