@repzo/widget-react-native 1.3.1 → 1.4.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 +137 -5
- package/README.md +64 -12
- package/lib/commonjs/api/client.js +96 -23
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/attachments/file-size.js +52 -0
- package/lib/commonjs/attachments/file-size.js.map +1 -0
- package/lib/commonjs/attachments/local-previews.js +40 -0
- package/lib/commonjs/attachments/local-previews.js.map +1 -0
- package/lib/commonjs/attachments/pickers.js +20 -29
- package/lib/commonjs/attachments/pickers.js.map +1 -1
- package/lib/commonjs/i18n.js +22 -3
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/locales.js +12 -3
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +277 -30
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/storage.js +3 -1
- package/lib/commonjs/storage.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +33 -13
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/AttachmentChips.js +109 -40
- package/lib/commonjs/ui/components/AttachmentChips.js.map +1 -1
- package/lib/commonjs/ui/components/ConversationRow.js +65 -10
- package/lib/commonjs/ui/components/ConversationRow.js.map +1 -1
- package/lib/commonjs/ui/components/ImageViewer.js +229 -24
- package/lib/commonjs/ui/components/ImageViewer.js.map +1 -1
- package/lib/commonjs/ui/components/RatingCard.js +5 -2
- package/lib/commonjs/ui/components/RatingCard.js.map +1 -1
- package/lib/commonjs/ui/components/ZoomableImage.js +58 -10
- package/lib/commonjs/ui/components/ZoomableImage.js.map +1 -1
- package/lib/commonjs/ui/hooks/use-retained-state.js +39 -0
- package/lib/commonjs/ui/hooks/use-retained-state.js.map +1 -0
- package/lib/commonjs/ui/screens/chat/ChatView.js +637 -164
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +138 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/commonjs/ui/screens/conversations/ConversationsList.js +1 -0
- package/lib/commonjs/ui/screens/conversations/ConversationsList.js.map +1 -1
- package/lib/commonjs/ui/screens/home/HomeView.js +38 -55
- package/lib/commonjs/ui/screens/home/HomeView.js.map +1 -1
- package/lib/commonjs/ui/screens/home/HomeView.styles.js +36 -45
- package/lib/commonjs/ui/screens/home/HomeView.styles.js.map +1 -1
- package/lib/commonjs/ui/utils/html.js +13 -0
- package/lib/commonjs/ui/utils/html.js.map +1 -1
- package/lib/commonjs/ui/utils/image-load-queue.js +114 -0
- package/lib/commonjs/ui/utils/image-load-queue.js.map +1 -0
- package/lib/commonjs/ui/utils/inline-attachments.js +88 -0
- package/lib/commonjs/ui/utils/inline-attachments.js.map +1 -0
- package/lib/module/api/client.js +96 -23
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/attachments/file-size.js +47 -0
- package/lib/module/attachments/file-size.js.map +1 -0
- package/lib/module/attachments/local-previews.js +33 -0
- package/lib/module/attachments/local-previews.js.map +1 -0
- package/lib/module/attachments/pickers.js +15 -25
- package/lib/module/attachments/pickers.js.map +1 -1
- package/lib/module/i18n.js +22 -3
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/locales.js +12 -3
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/widget-store.js +279 -32
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/storage.js +2 -0
- package/lib/module/storage.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +33 -13
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/AttachmentChips.js +111 -42
- package/lib/module/ui/components/AttachmentChips.js.map +1 -1
- package/lib/module/ui/components/ConversationRow.js +65 -10
- package/lib/module/ui/components/ConversationRow.js.map +1 -1
- package/lib/module/ui/components/ImageViewer.js +231 -26
- package/lib/module/ui/components/ImageViewer.js.map +1 -1
- package/lib/module/ui/components/RatingCard.js +5 -2
- package/lib/module/ui/components/RatingCard.js.map +1 -1
- package/lib/module/ui/components/ZoomableImage.js +58 -10
- package/lib/module/ui/components/ZoomableImage.js.map +1 -1
- package/lib/module/ui/hooks/use-retained-state.js +34 -0
- package/lib/module/ui/hooks/use-retained-state.js.map +1 -0
- package/lib/module/ui/screens/chat/ChatView.js +641 -168
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +140 -2
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/ui/screens/conversations/ConversationsList.js +1 -0
- package/lib/module/ui/screens/conversations/ConversationsList.js.map +1 -1
- package/lib/module/ui/screens/home/HomeView.js +40 -57
- package/lib/module/ui/screens/home/HomeView.js.map +1 -1
- package/lib/module/ui/screens/home/HomeView.styles.js +36 -45
- package/lib/module/ui/screens/home/HomeView.styles.js.map +1 -1
- package/lib/module/ui/utils/html.js +12 -0
- package/lib/module/ui/utils/html.js.map +1 -1
- package/lib/module/ui/utils/image-load-queue.js +109 -0
- package/lib/module/ui/utils/image-load-queue.js.map +1 -0
- package/lib/module/ui/utils/inline-attachments.js +85 -0
- package/lib/module/ui/utils/inline-attachments.js.map +1 -0
- package/lib/typescript/commonjs/api/client.d.ts +12 -4
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/attachments/file-size.d.ts +15 -0
- package/lib/typescript/commonjs/attachments/file-size.d.ts.map +1 -0
- package/lib/typescript/commonjs/attachments/local-previews.d.ts +14 -0
- package/lib/typescript/commonjs/attachments/local-previews.d.ts.map +1 -0
- package/lib/typescript/commonjs/attachments/pickers.d.ts +15 -2
- package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -1
- package/lib/typescript/commonjs/i18n.d.ts +12 -3
- 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/widget-store.d.ts +40 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage.d.ts +2 -0
- package/lib/typescript/commonjs/storage.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +41 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts +13 -3
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ConversationRow.d.ts +15 -1
- package/lib/typescript/commonjs/ui/components/ConversationRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +26 -6
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/RatingCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts +10 -1
- package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/use-retained-state.d.ts +11 -0
- package/lib/typescript/commonjs/ui/hooks/use-retained-state.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +4 -4
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +144 -0
- 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 +26 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/conversations/ConversationsList.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/conversations/ConversationsList.types.d.ts +2 -0
- package/lib/typescript/commonjs/ui/screens/conversations/ConversationsList.types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/home/HomeView.d.ts +7 -5
- package/lib/typescript/commonjs/ui/screens/home/HomeView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/home/HomeView.styles.d.ts +30 -35
- package/lib/typescript/commonjs/ui/screens/home/HomeView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/home/HomeView.types.d.ts +5 -6
- package/lib/typescript/commonjs/ui/screens/home/HomeView.types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/html.d.ts +5 -0
- package/lib/typescript/commonjs/ui/utils/html.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/image-load-queue.d.ts +44 -0
- package/lib/typescript/commonjs/ui/utils/image-load-queue.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/inline-attachments.d.ts +29 -0
- package/lib/typescript/commonjs/ui/utils/inline-attachments.d.ts.map +1 -0
- package/lib/typescript/module/api/client.d.ts +12 -4
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/attachments/file-size.d.ts +15 -0
- package/lib/typescript/module/attachments/file-size.d.ts.map +1 -0
- package/lib/typescript/module/attachments/local-previews.d.ts +14 -0
- package/lib/typescript/module/attachments/local-previews.d.ts.map +1 -0
- package/lib/typescript/module/attachments/pickers.d.ts +15 -2
- package/lib/typescript/module/attachments/pickers.d.ts.map +1 -1
- package/lib/typescript/module/i18n.d.ts +12 -3
- 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/widget-store.d.ts +40 -1
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/storage.d.ts +2 -0
- package/lib/typescript/module/storage.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +41 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts +13 -3
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ConversationRow.d.ts +15 -1
- package/lib/typescript/module/ui/components/ConversationRow.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ImageViewer.d.ts +26 -6
- package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/RatingCard.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ZoomableImage.d.ts +10 -1
- package/lib/typescript/module/ui/components/ZoomableImage.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/use-retained-state.d.ts +11 -0
- package/lib/typescript/module/ui/hooks/use-retained-state.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +4 -4
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +144 -0
- 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 +26 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/conversations/ConversationsList.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/conversations/ConversationsList.types.d.ts +2 -0
- package/lib/typescript/module/ui/screens/conversations/ConversationsList.types.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/home/HomeView.d.ts +7 -5
- package/lib/typescript/module/ui/screens/home/HomeView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/home/HomeView.styles.d.ts +30 -35
- package/lib/typescript/module/ui/screens/home/HomeView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/home/HomeView.types.d.ts +5 -6
- package/lib/typescript/module/ui/screens/home/HomeView.types.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/html.d.ts +5 -0
- package/lib/typescript/module/ui/utils/html.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/image-load-queue.d.ts +44 -0
- package/lib/typescript/module/ui/utils/image-load-queue.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/inline-attachments.d.ts +29 -0
- package/lib/typescript/module/ui/utils/inline-attachments.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/api/client.ts +116 -34
- package/src/attachments/file-size.ts +42 -0
- package/src/attachments/local-previews.ts +32 -0
- package/src/attachments/pickers.ts +23 -25
- package/src/i18n.ts +22 -3
- package/src/locales.ts +12 -3
- package/src/state/widget-store.ts +322 -50
- package/src/storage.ts +2 -0
- package/src/types.ts +41 -0
- package/src/ui/WidgetProvider.tsx +35 -8
- package/src/ui/components/AttachmentChips.tsx +100 -29
- package/src/ui/components/ConversationRow.tsx +90 -9
- package/src/ui/components/ImageViewer.tsx +242 -24
- package/src/ui/components/RatingCard.tsx +5 -2
- package/src/ui/components/ZoomableImage.tsx +58 -5
- package/src/ui/hooks/use-retained-state.ts +35 -0
- package/src/ui/screens/chat/ChatView.styles.ts +139 -1
- package/src/ui/screens/chat/ChatView.tsx +721 -197
- package/src/ui/screens/chat/ChatView.types.ts +26 -1
- package/src/ui/screens/conversations/ConversationsList.tsx +1 -0
- package/src/ui/screens/conversations/ConversationsList.types.ts +2 -0
- package/src/ui/screens/home/HomeView.styles.ts +36 -45
- package/src/ui/screens/home/HomeView.tsx +54 -70
- package/src/ui/screens/home/HomeView.types.ts +5 -6
- package/src/ui/utils/html.ts +26 -0
- package/src/ui/utils/image-load-queue.ts +108 -0
- package/src/ui/utils/inline-attachments.ts +71 -0
|
@@ -104,9 +104,9 @@ export function RepzoWidgetProvider({
|
|
|
104
104
|
[state.config, state.locale]
|
|
105
105
|
);
|
|
106
106
|
|
|
107
|
-
// Home previews the
|
|
108
|
-
const
|
|
109
|
-
() => pickRecentConversations(state.conversations,
|
|
107
|
+
// Home previews the newest thread and credits the agents behind the recent ones.
|
|
108
|
+
const recentConversation = useMemo(
|
|
109
|
+
() => pickRecentConversations(state.conversations, 1)[0],
|
|
110
110
|
[state.conversations]
|
|
111
111
|
);
|
|
112
112
|
const teamAgents = useMemo(() => pickTeamAgents(state.conversations, 3), [state.conversations]);
|
|
@@ -132,6 +132,15 @@ export function RepzoWidgetProvider({
|
|
|
132
132
|
}),
|
|
133
133
|
[labels]
|
|
134
134
|
);
|
|
135
|
+
const youPrefix = store.showsYouPrefix();
|
|
136
|
+
const lastMessageLabels = useMemo(
|
|
137
|
+
() => ({
|
|
138
|
+
photo: labels.lastMessagePhoto,
|
|
139
|
+
file: labels.lastMessageFile,
|
|
140
|
+
you: youPrefix ? labels.lastMessageYou : '',
|
|
141
|
+
}),
|
|
142
|
+
[labels, youPrefix]
|
|
143
|
+
);
|
|
135
144
|
|
|
136
145
|
const blockLabels = useMemo(
|
|
137
146
|
() => ({
|
|
@@ -171,6 +180,10 @@ export function RepzoWidgetProvider({
|
|
|
171
180
|
tooLargeTogether: labels.attachmentsTooLargeTogether,
|
|
172
181
|
failed: labels.attachmentFailed,
|
|
173
182
|
cameraDenied: labels.cameraPermissionDenied,
|
|
183
|
+
uploading: labels.attachmentUploading,
|
|
184
|
+
uploadFailed: labels.attachmentUploadFailed,
|
|
185
|
+
uploadFailedNotice: labels.attachmentsUploadFailed,
|
|
186
|
+
retry: labels.tapToRetry,
|
|
174
187
|
}),
|
|
175
188
|
[labels, theme.maxAttachmentBytes]
|
|
176
189
|
);
|
|
@@ -184,6 +197,10 @@ export function RepzoWidgetProvider({
|
|
|
184
197
|
loadEarlier: labels.loadEarlier,
|
|
185
198
|
emptyConversation: labels.emptyConversation,
|
|
186
199
|
messagePlaceholder: labels.messagePlaceholder,
|
|
200
|
+
send: labels.sendMessage,
|
|
201
|
+
jumpToLatest: labels.jumpToLatest,
|
|
202
|
+
jumpToLatestNew: labels.jumpToLatestNew,
|
|
203
|
+
morePhotos: labels.morePhotos,
|
|
187
204
|
failedToSend: labels.failedToSend,
|
|
188
205
|
tapToRetry: labels.tapToRetry,
|
|
189
206
|
openAttachment: labels.openAttachment,
|
|
@@ -228,6 +245,7 @@ export function RepzoWidgetProvider({
|
|
|
228
245
|
close: labels.close,
|
|
229
246
|
loadFailed: labels.imageLoadFailed,
|
|
230
247
|
openExternally: labels.openImageExternally,
|
|
248
|
+
position: labels.imagePosition,
|
|
231
249
|
},
|
|
232
250
|
voice: {
|
|
233
251
|
play: labels.voicePlay,
|
|
@@ -247,6 +265,14 @@ export function RepzoWidgetProvider({
|
|
|
247
265
|
pickAttachments(source, current, theme.maxAttachmentBytes),
|
|
248
266
|
[theme.maxAttachmentBytes]
|
|
249
267
|
);
|
|
268
|
+
const handleUploadAttachment = useCallback(
|
|
269
|
+
(draft: DraftAttachment, signal?: AbortSignal) =>
|
|
270
|
+
store.uploadAttachment(
|
|
271
|
+
{ uri: draft.fileUri!, name: draft.name, contentType: draft.contentType, size: draft.size },
|
|
272
|
+
signal
|
|
273
|
+
),
|
|
274
|
+
[store]
|
|
275
|
+
);
|
|
250
276
|
|
|
251
277
|
const handleOpenNotification = useCallback(
|
|
252
278
|
(conversationId: string) => void store.openConversation(conversationId),
|
|
@@ -453,8 +479,10 @@ export function RepzoWidgetProvider({
|
|
|
453
479
|
labels={chatLabels}
|
|
454
480
|
attachmentSources={attachmentSources}
|
|
455
481
|
onPickAttachments={handlePickAttachments}
|
|
482
|
+
onUploadAttachment={handleUploadAttachment}
|
|
456
483
|
onSendMessage={handleSendMessage}
|
|
457
484
|
onRetryMessage={handleRetryMessage}
|
|
485
|
+
photoAlbums={store.showsPhotoAlbums()}
|
|
458
486
|
onIdentifyVisitor={handleIdentifyVisitor}
|
|
459
487
|
onRateConversation={handleRateConversation}
|
|
460
488
|
onLoadMore={handleLoadMore}
|
|
@@ -472,7 +500,7 @@ export function RepzoWidgetProvider({
|
|
|
472
500
|
color={theme.primaryColor}
|
|
473
501
|
visitorName={state.visitorProfile.name}
|
|
474
502
|
conversationCount={state.conversations.length}
|
|
475
|
-
|
|
503
|
+
recentConversation={recentConversation}
|
|
476
504
|
unreadByConversation={state.unreadByConversation}
|
|
477
505
|
teamAgents={teamAgents}
|
|
478
506
|
isOnline={theme.isOnline}
|
|
@@ -483,19 +511,17 @@ export function RepzoWidgetProvider({
|
|
|
483
511
|
showPreviousConversations={theme.showPreviousConversations}
|
|
484
512
|
labels={{
|
|
485
513
|
close: labels.close,
|
|
486
|
-
|
|
487
|
-
viewAll: formatLabel(labels.homeViewAllCount, { count: state.conversations.length }),
|
|
488
|
-
recentTitle: labels.homeRecentTitle,
|
|
514
|
+
recentTitle: labels.homeRecentMessageTitle,
|
|
489
515
|
noConversations: labels.homeNoConversations,
|
|
490
516
|
statusOnline: labels.homeStatusOnline,
|
|
491
517
|
statusAway: labels.homeStatusAway,
|
|
492
518
|
team: labels.team,
|
|
493
519
|
time: timeLabels,
|
|
494
520
|
unread: labels.unreadBadge,
|
|
521
|
+
lastMessage: lastMessageLabels,
|
|
495
522
|
}}
|
|
496
523
|
onClose={handleClose}
|
|
497
524
|
onStartConversation={handleStartConversation}
|
|
498
|
-
onViewConversations={() => handleTabChange('messages')}
|
|
499
525
|
onOpenConversation={(conversationId) => store.openConversation(conversationId)}
|
|
500
526
|
/>
|
|
501
527
|
);
|
|
@@ -521,6 +547,7 @@ export function RepzoWidgetProvider({
|
|
|
521
547
|
statusAway: labels.homeStatusAway,
|
|
522
548
|
time: timeLabels,
|
|
523
549
|
unread: labels.unreadBadge,
|
|
550
|
+
lastMessage: lastMessageLabels,
|
|
524
551
|
}}
|
|
525
552
|
onSelect={(conversationId) => store.openConversation(conversationId)}
|
|
526
553
|
onNewConversation={handleStartConversation}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Picked-but-unsent attachments above the composer: a thumbnail for images, a
|
|
3
3
|
* name + size card for everything else, each with its own remove button.
|
|
4
|
+
* A file that uploads ahead of the send shows a spinner until it's up, and
|
|
5
|
+
* turns into a retry button when the upload fails.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
import { Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
8
|
+
import { ActivityIndicator, Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
7
9
|
|
|
8
10
|
import {
|
|
9
11
|
formatBytes,
|
|
@@ -11,24 +13,29 @@ import {
|
|
|
11
13
|
type DraftAttachment,
|
|
12
14
|
} from '../../attachments/pickers';
|
|
13
15
|
import { formatLabel } from '../../i18n';
|
|
16
|
+
import { globalStyles } from '../styles/global';
|
|
14
17
|
import { fontSize, palette, radius, spacing, withAlpha } from '../styles/theme';
|
|
15
18
|
import { START_ALIGN } from '../utils/rtl';
|
|
16
|
-
import { CloseIcon } from './icons';
|
|
19
|
+
import { AlertIcon, CloseIcon } from './icons';
|
|
17
20
|
|
|
18
21
|
interface AttachmentChipsProps {
|
|
19
22
|
attachments: DraftAttachment[];
|
|
20
23
|
/** While a send is in flight the chips are dimmed and can't be removed. */
|
|
21
24
|
disabled?: boolean;
|
|
22
|
-
/**
|
|
23
|
-
|
|
25
|
+
/** `remove`, `uploading` and `uploadFailed` keep their `{name}` placeholder; filled per chip. */
|
|
26
|
+
labels: { remove: string; uploading: string; uploadFailed: string; retry: string };
|
|
27
|
+
/** Removing a chip mid-upload cancels the upload. */
|
|
24
28
|
onRemove: (id: string) => void;
|
|
29
|
+
/** Starts a failed upload again. */
|
|
30
|
+
onRetry: (id: string) => void;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export function AttachmentChips({
|
|
28
34
|
attachments,
|
|
29
35
|
disabled,
|
|
30
|
-
|
|
36
|
+
labels,
|
|
31
37
|
onRemove,
|
|
38
|
+
onRetry,
|
|
32
39
|
}: AttachmentChipsProps) {
|
|
33
40
|
return (
|
|
34
41
|
<ScrollView
|
|
@@ -37,38 +44,82 @@ export function AttachmentChips({
|
|
|
37
44
|
keyboardShouldPersistTaps="handled"
|
|
38
45
|
style={styles.scroller}
|
|
39
46
|
contentContainerStyle={styles.row}>
|
|
40
|
-
{attachments.map((attachment) =>
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
{attachments.map((attachment) => {
|
|
48
|
+
const uploading = attachment.uploadStatus === 'uploading';
|
|
49
|
+
const failed = attachment.uploadStatus === 'failed';
|
|
50
|
+
const preview = isImageAttachment(attachment.contentType) ? (
|
|
51
|
+
<View>
|
|
43
52
|
<Image
|
|
44
53
|
source={{ uri: attachment.previewUri }}
|
|
45
54
|
style={styles.thumbnail}
|
|
46
55
|
resizeMode="cover"
|
|
47
|
-
accessible
|
|
56
|
+
accessible={!uploading && !failed}
|
|
48
57
|
accessibilityLabel={attachment.name}
|
|
49
58
|
accessibilityIgnoresInvertColors
|
|
50
59
|
/>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
{uploading || failed ? (
|
|
61
|
+
<View style={[styles.scrim, failed && styles.scrimFailed]}>
|
|
62
|
+
{uploading ? (
|
|
63
|
+
<ActivityIndicator color={palette.white} />
|
|
64
|
+
) : (
|
|
65
|
+
<AlertIcon size={20} color={palette.white} />
|
|
66
|
+
)}
|
|
67
|
+
</View>
|
|
68
|
+
) : null}
|
|
69
|
+
</View>
|
|
70
|
+
) : (
|
|
71
|
+
<View style={styles.file}>
|
|
72
|
+
<Text style={styles.fileName} numberOfLines={1}>
|
|
73
|
+
{attachment.name}
|
|
74
|
+
</Text>
|
|
75
|
+
<View style={styles.fileStatus}>
|
|
76
|
+
{uploading ? (
|
|
77
|
+
<ActivityIndicator size="small" color={palette.textMuted} />
|
|
78
|
+
) : failed ? (
|
|
79
|
+
<AlertIcon size={12} color={palette.danger} />
|
|
80
|
+
) : null}
|
|
81
|
+
<Text style={[styles.fileSize, failed && styles.fileFailed]} numberOfLines={1}>
|
|
82
|
+
{failed ? labels.retry : formatBytes(attachment.size)}
|
|
55
83
|
</Text>
|
|
56
|
-
<Text style={styles.fileSize}>{formatBytes(attachment.size)}</Text>
|
|
57
84
|
</View>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<View key={attachment.id} style={[styles.chip, disabled && styles.chipDisabled]}>
|
|
90
|
+
{failed ? (
|
|
91
|
+
<Pressable
|
|
92
|
+
onPress={() => onRetry(attachment.id)}
|
|
93
|
+
disabled={disabled}
|
|
94
|
+
accessibilityRole="button"
|
|
95
|
+
accessibilityLabel={formatLabel(labels.uploadFailed, { name: attachment.name })}
|
|
96
|
+
style={({ pressed }) => pressed && globalStyles.pressed}>
|
|
97
|
+
{preview}
|
|
98
|
+
</Pressable>
|
|
99
|
+
) : uploading ? (
|
|
100
|
+
<View
|
|
101
|
+
accessible
|
|
102
|
+
accessibilityLabel={formatLabel(labels.uploading, { name: attachment.name })}
|
|
103
|
+
accessibilityState={{ busy: true }}>
|
|
104
|
+
{preview}
|
|
105
|
+
</View>
|
|
106
|
+
) : (
|
|
107
|
+
preview
|
|
108
|
+
)}
|
|
109
|
+
<Pressable
|
|
110
|
+
onPress={() => onRemove(attachment.id)}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
accessibilityRole="button"
|
|
113
|
+
accessibilityLabel={formatLabel(labels.remove, { name: attachment.name })}
|
|
114
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
115
|
+
// 22pt badge + 11pt on every side = a 44pt target.
|
|
116
|
+
hitSlop={11}
|
|
117
|
+
style={({ pressed }) => [styles.remove, pressed && styles.removePressed]}>
|
|
118
|
+
<CloseIcon size={10} color={palette.white} />
|
|
119
|
+
</Pressable>
|
|
120
|
+
</View>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
72
123
|
</ScrollView>
|
|
73
124
|
);
|
|
74
125
|
}
|
|
@@ -98,6 +149,16 @@ const styles = StyleSheet.create({
|
|
|
98
149
|
width: CHIP_HEIGHT,
|
|
99
150
|
height: CHIP_HEIGHT,
|
|
100
151
|
},
|
|
152
|
+
// Over the thumbnail while it uploads; darker once it failed, so the alert reads.
|
|
153
|
+
scrim: {
|
|
154
|
+
...StyleSheet.absoluteFillObject,
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'center',
|
|
157
|
+
backgroundColor: withAlpha(palette.ink, 0.4),
|
|
158
|
+
},
|
|
159
|
+
scrimFailed: {
|
|
160
|
+
backgroundColor: withAlpha(palette.ink, 0.6),
|
|
161
|
+
},
|
|
101
162
|
file: {
|
|
102
163
|
width: 156,
|
|
103
164
|
height: CHIP_HEIGHT,
|
|
@@ -112,11 +173,21 @@ const styles = StyleSheet.create({
|
|
|
112
173
|
fontWeight: '600',
|
|
113
174
|
color: palette.ink,
|
|
114
175
|
},
|
|
176
|
+
fileStatus: {
|
|
177
|
+
flexDirection: 'row',
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
gap: spacing.xs,
|
|
180
|
+
marginTop: 2,
|
|
181
|
+
},
|
|
115
182
|
fileSize: {
|
|
183
|
+
flexShrink: 1,
|
|
116
184
|
textAlign: START_ALIGN,
|
|
117
185
|
fontSize: fontSize.caption,
|
|
118
186
|
color: palette.textMuted,
|
|
119
|
-
|
|
187
|
+
},
|
|
188
|
+
fileFailed: {
|
|
189
|
+
color: palette.danger,
|
|
190
|
+
fontWeight: '600',
|
|
120
191
|
},
|
|
121
192
|
remove: {
|
|
122
193
|
position: 'absolute',
|
|
@@ -15,6 +15,15 @@ import { forwardDirection, START_ALIGN } from '../utils/rtl';
|
|
|
15
15
|
import { fontSize, palette, spacing } from '../styles/theme';
|
|
16
16
|
|
|
17
17
|
const AVATAR_SIZE = 40;
|
|
18
|
+
const CARD_AVATAR_SIZE = 32;
|
|
19
|
+
|
|
20
|
+
export interface LastMessageLabels {
|
|
21
|
+
/** Stand in for a newest message that had no words, only photos or other files. */
|
|
22
|
+
photo: string;
|
|
23
|
+
file: string;
|
|
24
|
+
/** Before the visitor's own newest message; keeps `{message}`. Empty: no prefix (`boot({ youPrefix: false })`). */
|
|
25
|
+
you: string;
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
interface ConversationRowProps {
|
|
20
29
|
conversation: Conversation;
|
|
@@ -26,8 +35,15 @@ interface ConversationRowProps {
|
|
|
26
35
|
unreadCount?: number;
|
|
27
36
|
/** Template with a `{count}` token, for screen readers. */
|
|
28
37
|
unreadLabel?: string;
|
|
38
|
+
lastMessageLabels?: LastMessageLabels;
|
|
29
39
|
/** Messages runs full-bleed; Home insets it inside a card. */
|
|
30
40
|
horizontalPadding?: number;
|
|
41
|
+
/**
|
|
42
|
+
* `card` is Home's "Recent message" card: a smaller avatar, a regular-weight
|
|
43
|
+
* name with the time right after it, and no chevron. The words, unread state
|
|
44
|
+
* and screen-reader label are the list's.
|
|
45
|
+
*/
|
|
46
|
+
variant?: 'list' | 'card';
|
|
31
47
|
onPress: () => void;
|
|
32
48
|
}
|
|
33
49
|
|
|
@@ -38,47 +54,70 @@ export function ConversationRow({
|
|
|
38
54
|
timeLabels,
|
|
39
55
|
unreadCount = 0,
|
|
40
56
|
unreadLabel,
|
|
57
|
+
lastMessageLabels,
|
|
41
58
|
horizontalPadding = spacing.xl,
|
|
59
|
+
variant = 'list',
|
|
42
60
|
onPress,
|
|
43
61
|
}: ConversationRowProps) {
|
|
62
|
+
const card = variant === 'card';
|
|
44
63
|
const displayName = conversation.agentName || fallbackName;
|
|
45
64
|
const hasUnread = unreadCount > 0;
|
|
46
65
|
const unreadSuffix =
|
|
47
66
|
hasUnread && unreadLabel ? `. ${formatLabel(unreadLabel, { count: unreadCount })}` : '';
|
|
67
|
+
const attachment = conversation.lastMessageAttachment;
|
|
68
|
+
// "Photo" or "File" over Workstation's "2 attachments".
|
|
69
|
+
const snippet = (attachment && lastMessageLabels?.[attachment]) || conversation.snippet || '';
|
|
70
|
+
// "You: " is drawn a shade stronger than the message, so it reads as a label.
|
|
71
|
+
const [youBefore, youAfter] =
|
|
72
|
+
snippet && conversation.lastMessageFromVisitor && lastMessageLabels?.you
|
|
73
|
+
? splitAround(lastMessageLabels.you, '{message}')
|
|
74
|
+
: ['', ''];
|
|
75
|
+
const snippetLabel = `${youBefore}${snippet}${youAfter}`;
|
|
48
76
|
|
|
49
77
|
return (
|
|
50
78
|
<Pressable
|
|
51
79
|
onPress={onPress}
|
|
52
80
|
accessibilityRole="button"
|
|
53
81
|
accessibilityLabel={`${
|
|
54
|
-
|
|
82
|
+
snippet ? `${displayName}. ${snippetLabel}` : displayName
|
|
55
83
|
}${unreadSuffix}`}
|
|
56
84
|
android_ripple={{ color: palette.borderSoft }}
|
|
57
85
|
style={({ pressed }) => [
|
|
58
86
|
styles.row,
|
|
87
|
+
card && cardStyles.row,
|
|
59
88
|
{ paddingHorizontal: horizontalPadding },
|
|
60
89
|
pressed && styles.rowPressed,
|
|
61
90
|
]}>
|
|
62
91
|
<Avatar
|
|
63
92
|
avatar={conversation.agentAvatar}
|
|
64
93
|
name={conversation.agentName}
|
|
65
|
-
size={AVATAR_SIZE}
|
|
94
|
+
size={card ? CARD_AVATAR_SIZE : AVATAR_SIZE}
|
|
66
95
|
color={color}
|
|
67
96
|
/>
|
|
68
97
|
<View style={styles.content}>
|
|
69
|
-
<View style={styles.topLine}>
|
|
70
|
-
<Text
|
|
98
|
+
<View style={[styles.topLine, card && cardStyles.topLine]}>
|
|
99
|
+
<Text
|
|
100
|
+
style={
|
|
101
|
+
card
|
|
102
|
+
? [cardStyles.name, hasUnread && cardStyles.nameUnread]
|
|
103
|
+
: [styles.name, hasUnread && styles.nameUnread]
|
|
104
|
+
}
|
|
105
|
+
numberOfLines={1}>
|
|
71
106
|
{displayName}
|
|
72
107
|
</Text>
|
|
73
|
-
<Text style={styles.time}>
|
|
108
|
+
<Text style={[styles.time, card && cardStyles.time]}>
|
|
74
109
|
{conversation.lastMessageAt
|
|
75
110
|
? formatRelativeTime(conversation.lastMessageAt, timeLabels)
|
|
76
111
|
: ''}
|
|
77
112
|
</Text>
|
|
78
113
|
</View>
|
|
79
|
-
{
|
|
80
|
-
<Text
|
|
81
|
-
{
|
|
114
|
+
{snippet ? (
|
|
115
|
+
<Text
|
|
116
|
+
style={[styles.snippet, card && cardStyles.snippet, hasUnread && styles.snippetUnread]}
|
|
117
|
+
numberOfLines={2}>
|
|
118
|
+
{youBefore ? <Text style={styles.snippetYou}>{youBefore}</Text> : null}
|
|
119
|
+
{snippet}
|
|
120
|
+
{youAfter}
|
|
82
121
|
</Text>
|
|
83
122
|
) : null}
|
|
84
123
|
</View>
|
|
@@ -86,13 +125,20 @@ export function ConversationRow({
|
|
|
86
125
|
<View style={[styles.badge, { backgroundColor: color }]}>
|
|
87
126
|
<Text style={styles.badgeLabel}>{unreadCount > 9 ? '9+' : unreadCount}</Text>
|
|
88
127
|
</View>
|
|
89
|
-
) : (
|
|
128
|
+
) : card ? null : (
|
|
90
129
|
<ChevronIcon size={16} direction={forwardDirection()} color={palette.textSubtle} />
|
|
91
130
|
)}
|
|
92
131
|
</Pressable>
|
|
93
132
|
);
|
|
94
133
|
}
|
|
95
134
|
|
|
135
|
+
/** `'You: {message}'` → `['You: ', '']`; a template without the token goes in front. */
|
|
136
|
+
function splitAround(template: string, token: string): [string, string] {
|
|
137
|
+
const index = template.indexOf(token);
|
|
138
|
+
if (index < 0) return [`${template} `, ''];
|
|
139
|
+
return [template.slice(0, index), template.slice(index + token.length)];
|
|
140
|
+
}
|
|
141
|
+
|
|
96
142
|
/** Where a divider should start so it sits under the text, not the avatar. */
|
|
97
143
|
export function rowDividerInset(horizontalPadding: number = spacing.xl): number {
|
|
98
144
|
return horizontalPadding + AVATAR_SIZE + spacing.md;
|
|
@@ -145,6 +191,10 @@ const styles = StyleSheet.create({
|
|
|
145
191
|
color: palette.text,
|
|
146
192
|
fontWeight: '500',
|
|
147
193
|
},
|
|
194
|
+
snippetYou: {
|
|
195
|
+
color: palette.text,
|
|
196
|
+
fontWeight: '600',
|
|
197
|
+
},
|
|
148
198
|
// Replaces the chevron rather than crowding beside it, so the row keeps
|
|
149
199
|
// the same width whether or not there is anything unread.
|
|
150
200
|
badge: {
|
|
@@ -161,3 +211,34 @@ const styles = StyleSheet.create({
|
|
|
161
211
|
fontWeight: '700',
|
|
162
212
|
},
|
|
163
213
|
});
|
|
214
|
+
|
|
215
|
+
// Overrides for the `card` variant. It sits under the card's title, so it
|
|
216
|
+
// drops the list's minimum height, and its name, time and last message share
|
|
217
|
+
// one size, the way the Home design draws them.
|
|
218
|
+
const cardStyles = StyleSheet.create({
|
|
219
|
+
row: {
|
|
220
|
+
minHeight: 0,
|
|
221
|
+
paddingVertical: spacing.sm,
|
|
222
|
+
},
|
|
223
|
+
// The time follows the name instead of being pushed to the far edge.
|
|
224
|
+
topLine: {
|
|
225
|
+
justifyContent: 'flex-start',
|
|
226
|
+
},
|
|
227
|
+
name: {
|
|
228
|
+
textAlign: START_ALIGN,
|
|
229
|
+
flexShrink: 1,
|
|
230
|
+
fontSize: fontSize.body,
|
|
231
|
+
color: palette.ink,
|
|
232
|
+
},
|
|
233
|
+
nameUnread: {
|
|
234
|
+
fontWeight: '600',
|
|
235
|
+
},
|
|
236
|
+
time: {
|
|
237
|
+
fontSize: fontSize.body,
|
|
238
|
+
},
|
|
239
|
+
snippet: {
|
|
240
|
+
fontSize: fontSize.body,
|
|
241
|
+
lineHeight: fontSize.body * 1.35,
|
|
242
|
+
color: palette.textSubtle,
|
|
243
|
+
},
|
|
244
|
+
});
|