movius-chats 1.5.13 → 1.5.15
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/README.md +1603 -208
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChatBubble/ChatBubble.tsx +7 -12
- package/src/components/MessageActions/LongPressOverlay.tsx +2 -8
- package/src/components/MessageActions/clipboard.ts +2 -15
package/README.md
CHANGED
|
@@ -1,101 +1,1490 @@
|
|
|
1
1
|
# movius-chats
|
|
2
2
|
|
|
3
|
-
A customizable
|
|
3
|
+
> A highly customizable, feature-rich chat interface component library for React Native applications.
|
|
4
|
+
|
|
5
|
+
A production-ready React Native chat UI library providing message bubbles, WhatsApp-style media grids, audio playback, voice recording, typing indicators, message replies, full-screen media gallery, and comprehensive theming—all wrapped in a single, powerful `ChatScreen` component. Works with both bare React Native and Expo.
|
|
4
6
|
|
|
5
7
|
**npm:** [`movius-chats`](https://www.npmjs.com/package/movius-chats)
|
|
6
|
-
**Repo:** [github.com/David-Atueyi/Movius-Chats](https://github.com/David-Atueyi/Movius-Chats)
|
|
8
|
+
**Repo:** [github.com/David-Atueyi/Movius-Chats](https://github.com/David-Atueyi/Movius-Chats)
|
|
9
|
+
**Version:** 1.5.14 | **License:** ISC
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 📋 Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Features](#features)
|
|
16
|
+
2. [What is included](#what-is-included)
|
|
17
|
+
3. [Installation](#installation)
|
|
18
|
+
4. [Quick start](#quick-start)
|
|
19
|
+
5. [Core concepts](#core-concepts)
|
|
20
|
+
- [Message data model](#message-data-model)
|
|
21
|
+
- [Message list order](#message-list-order)
|
|
22
|
+
- [Context system](#context-system)
|
|
23
|
+
6. [API reference](#api-reference)
|
|
24
|
+
- [ChatScreen props](#chatscreen-props)
|
|
25
|
+
- [Message interface](#message-interface)
|
|
26
|
+
- [Theme configuration](#theme-configuration)
|
|
27
|
+
7. [Features guide](#features-guide)
|
|
28
|
+
- [Voice recording](#voice-recording)
|
|
29
|
+
- [Audio message playback](#audio-message-playback)
|
|
30
|
+
- [Media grids & gallery](#media-grids--gallery)
|
|
31
|
+
- [Message replies](#message-replies)
|
|
32
|
+
- [Typing indicators](#typing-indicators)
|
|
33
|
+
- [File attachments](#file-attachments)
|
|
34
|
+
- [Message selection & actions](#message-selection--actions)
|
|
35
|
+
8. [Customization](#customization)
|
|
36
|
+
- [Theming & styling](#theming--styling)
|
|
37
|
+
- [Custom components & icons](#custom-components--icons)
|
|
38
|
+
- [Component overrides](#component-overrides)
|
|
39
|
+
9. [Project structure](#project-structure)
|
|
40
|
+
10. [Dependencies](#dependencies)
|
|
41
|
+
11. [Keyboard behavior](#keyboard-behavior)
|
|
42
|
+
12. [TypeScript support](#typescript-support)
|
|
43
|
+
13. [Advanced usage](#advanced-usage)
|
|
44
|
+
14. [Troubleshooting](#troubleshooting)
|
|
45
|
+
15. [Contributing](#contributing)
|
|
46
|
+
16. [License](#license)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ✨ Features
|
|
51
|
+
|
|
52
|
+
### Core Messaging
|
|
53
|
+
|
|
54
|
+
- **Text messages** with automatic link detection and tappable URLs
|
|
55
|
+
- **Sent, delivered, and read status** indicators with animated checkmarks
|
|
56
|
+
- **Avatar display** with customizable avatars and fallback initials
|
|
57
|
+
- **Sender names** (optional per message or bubble)
|
|
58
|
+
- **Typing indicators** with avatar stacking (up to 2 avatars + "+N" badge)
|
|
59
|
+
- **Timestamp display** with customizable date formatting
|
|
60
|
+
|
|
61
|
+
### Media Handling
|
|
62
|
+
|
|
63
|
+
- **Image & video albums** with WhatsApp-style smart grid layouts:
|
|
64
|
+
- 1×1 (single), 2×2 (two), 1+2 (three), 2×2 (four or more)
|
|
65
|
+
- Automatic height fitting (320px default)
|
|
66
|
+
- **Full-screen media gallery** with swipe navigation and image counter
|
|
67
|
+
- **Video autoplay control** (disabled in gallery by default)
|
|
68
|
+
- **Video thumbnails** with play icon overlay
|
|
69
|
+
- **Video playback** within the gallery with native controls
|
|
70
|
+
|
|
71
|
+
### Audio & Voice
|
|
72
|
+
|
|
73
|
+
- **Voice recording** with multiple capture modes:
|
|
74
|
+
- Tap-to-record (simple single-press)
|
|
75
|
+
- Long-press recording (hold microphone for continuous capture)
|
|
76
|
+
- Slide-to-cancel gesture (swipe left to cancel)
|
|
77
|
+
- Lock recording (tap lock to continue hands-free)
|
|
78
|
+
- Visual waveform display during recording
|
|
79
|
+
- Recording timer and duration display
|
|
80
|
+
- **Audio message playback** with WhatsApp-style UI:
|
|
81
|
+
- Animated waveform bars showing playback progress
|
|
82
|
+
- Playback speed control (1.0x → 1.5x → 2.0x cycling)
|
|
83
|
+
- Play/pause controls with status indicators
|
|
84
|
+
- Sender avatar or current playback speed in the UI
|
|
85
|
+
- **Audio context** ensuring only one audio plays at a time
|
|
86
|
+
- **Microphone permission handling** (Android + iOS)
|
|
87
|
+
|
|
88
|
+
### Chat Input
|
|
89
|
+
|
|
90
|
+
- **Growing text field** that auto-expands (min 32-50px, max 118px)
|
|
91
|
+
- **Rich input bar** with customizable icon buttons:
|
|
92
|
+
- Emoji picker button
|
|
93
|
+
- File attachment button
|
|
94
|
+
- Camera button
|
|
95
|
+
- Microphone (voice record) button
|
|
96
|
+
- Send button
|
|
97
|
+
- **File preview** with filename truncation
|
|
98
|
+
- **Keyboard avoiding** with per-platform tuning (iOS vs Android)
|
|
99
|
+
- **Custom input rendering** option for complete input override
|
|
100
|
+
|
|
101
|
+
### Message Actions
|
|
102
|
+
|
|
103
|
+
- **Long-press actions** with context menu:
|
|
104
|
+
- Copy message text
|
|
105
|
+
- Forward message
|
|
106
|
+
- Reply to message
|
|
107
|
+
- Edit message (with visual indicator)
|
|
108
|
+
- Delete message
|
|
109
|
+
- Customizable action order and labels
|
|
110
|
+
- **Customizable action sheet UI** (popover on tablet/large screens, bottom sheet on mobile)
|
|
111
|
+
- **Icon and label customization** per action
|
|
112
|
+
|
|
113
|
+
### Message Replies
|
|
114
|
+
|
|
115
|
+
- **Swipeable reply gesture** (right-swipe to reply on mobile)
|
|
116
|
+
- **Reply preview** in input with quoted context
|
|
117
|
+
- **Inline reply display** in message bubbles
|
|
118
|
+
- **Reply to any media type** (image, video, audio, file, text)
|
|
119
|
+
- **Customizable reply UI** colors, backgrounds, and icon sizes
|
|
120
|
+
- **Edit message indication** in reply context
|
|
121
|
+
|
|
122
|
+
### Selection & Batch Actions
|
|
123
|
+
|
|
124
|
+
- **Multi-select mode** for bulk operations
|
|
125
|
+
- **Selection UI** with checkbox indicators
|
|
126
|
+
- **Selection animations** and state management
|
|
127
|
+
- **Per-message selection state**
|
|
128
|
+
|
|
129
|
+
### Theming & Appearance
|
|
130
|
+
|
|
131
|
+
- **Dual-side coloring** with separate `sent*` and `received*` theme colors
|
|
132
|
+
- **Per-component theme overrides** (bubble backgrounds, text colors, borders, etc.)
|
|
133
|
+
- **Custom fonts** via `fontFamily` configuration
|
|
134
|
+
- **Icon sizing** customization for input bar and actions
|
|
135
|
+
- **Border radius** control for bubbles and media grids
|
|
136
|
+
- **Custom message styles** (shadows, padding, margin)
|
|
7
137
|
|
|
8
|
-
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 📦 What is Included
|
|
141
|
+
|
|
142
|
+
| Feature | Implementation |
|
|
143
|
+
| ---------------------------- | -------------------------------------------------------------------- |
|
|
144
|
+
| **Text rendering** | `react-native-parsed-text` (URLs, emails, phone numbers tappable) |
|
|
145
|
+
| **Image / video albums** | `MediaGrid` — 1/2/3/4+ smart layout, 320px height |
|
|
146
|
+
| **Full-screen media viewer** | `MediaViewer` — swipe navigation, counter, selective video autoplay |
|
|
147
|
+
| **Audio playback** | `react-native-video` (hidden player) + custom waveform visualization |
|
|
148
|
+
| **Variable playback speed** | 1.0x → 1.5x → 2.0x cycling during playback |
|
|
149
|
+
| **Voice recording** | `react-native-audio-record` (optional peer) with gesture controls |
|
|
150
|
+
| **File attachments** | Tappable rows with custom handler + default URL opener |
|
|
151
|
+
| **Typing indicators** | Avatar stacking with "+N" badge for multiple typers |
|
|
152
|
+
| **Input bar** | Growing TextField, emoji/clip/camera/send/mic buttons |
|
|
153
|
+
| **Status indicators** | Sent/delivered/read animated checkmarks |
|
|
154
|
+
| **Message replies** | Swipe-to-reply, inline display, edit tracking |
|
|
155
|
+
| **Message actions** | Long-press menus with copy, forward, delete, edit |
|
|
156
|
+
| **Selection mode** | Multi-select with batch operations |
|
|
157
|
+
| **Theming** | Comprehensive dual-sided (sent/received) color system |
|
|
9
158
|
|
|
10
159
|
---
|
|
11
160
|
|
|
12
|
-
##
|
|
161
|
+
## Installation
|
|
162
|
+
|
|
163
|
+
### Prerequisites
|
|
164
|
+
|
|
165
|
+
- **React Native 0.60+**
|
|
166
|
+
- **Node 14+**
|
|
167
|
+
|
|
168
|
+
### Step 1: Install the package
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install movius-chats
|
|
172
|
+
# or
|
|
173
|
+
yarn add movius-chats
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Step 2: Install peer dependencies
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install react react-native react-native-gesture-handler react-native-reanimated
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Step 3: Install optional dependencies (feature-specific)
|
|
183
|
+
|
|
184
|
+
For **voice recording:**
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npm install react-native-audio-record react-native-fs
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
For **clipboard support (copy message):**
|
|
13
191
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
192
|
+
```bash
|
|
193
|
+
npm install @react-native-clipboard/clipboard
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
For **file attachments** (already in React Native):
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# No additional packages needed; uses Linking API
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Step 4: Link native modules
|
|
203
|
+
|
|
204
|
+
**Bare React Native:**
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
cd ios && pod install && cd ..
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Expo:**
|
|
211
|
+
No manual linking required — Expo handles native module resolution automatically.
|
|
212
|
+
|
|
213
|
+
For Android, ensure your project supports AndroidX. Update `android/gradle.properties`:
|
|
214
|
+
|
|
215
|
+
```properties
|
|
216
|
+
android.useAndroidX=true
|
|
217
|
+
android.enableJetifier=true
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Step 5: Configure permissions
|
|
221
|
+
|
|
222
|
+
**Android** (`AndroidManifest.xml`):
|
|
223
|
+
|
|
224
|
+
```xml
|
|
225
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
226
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
227
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
228
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**iOS** (`Info.plist`):
|
|
232
|
+
|
|
233
|
+
```xml
|
|
234
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
235
|
+
<string>This app needs microphone access to record voice messages.</string>
|
|
236
|
+
<key>NSCameraUsageDescription</key>
|
|
237
|
+
<string>This app needs camera access to take photos.</string>
|
|
238
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
239
|
+
<string>This app needs photo library access.</string>
|
|
240
|
+
```
|
|
33
241
|
|
|
34
242
|
---
|
|
35
243
|
|
|
36
|
-
##
|
|
244
|
+
## Quick Start
|
|
37
245
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
246
|
+
### Basic Usage
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import React, { useState } from 'react';
|
|
250
|
+
import { ChatScreen } from 'movius-chats';
|
|
251
|
+
import type { Message } from 'movius-chats';
|
|
252
|
+
|
|
253
|
+
export const MyChatApp = () => {
|
|
254
|
+
const [messages, setMessages] = useState<Message[]>([
|
|
255
|
+
{
|
|
256
|
+
id: '1',
|
|
257
|
+
text: 'Hey! How are you?',
|
|
258
|
+
senderId: 'user-2',
|
|
259
|
+
time: '10:30 AM',
|
|
260
|
+
status: 'read',
|
|
261
|
+
senderName: 'Alice',
|
|
262
|
+
senderAvatar: 'https://example.com/avatar-alice.jpg',
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: '2',
|
|
266
|
+
text: "I'm doing great! How about you?",
|
|
267
|
+
senderId: 'user-1',
|
|
268
|
+
time: '10:31 AM',
|
|
269
|
+
status: 'delivered',
|
|
270
|
+
},
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
const currentUserId = 'user-1';
|
|
274
|
+
|
|
275
|
+
const handleSendMessage = (messageText: string) => {
|
|
276
|
+
const newMessage: Message = {
|
|
277
|
+
id: String(Date.now()),
|
|
278
|
+
text: messageText,
|
|
279
|
+
senderId: currentUserId,
|
|
280
|
+
time: new Date().toLocaleTimeString(),
|
|
281
|
+
status: 'sent',
|
|
282
|
+
};
|
|
283
|
+
setMessages((prev) => [...prev, newMessage]);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<ChatScreen
|
|
288
|
+
messages={messages}
|
|
289
|
+
currentUserId={currentUserId}
|
|
290
|
+
onSendMessage={handleSendMessage}
|
|
291
|
+
/>
|
|
292
|
+
);
|
|
293
|
+
};
|
|
294
|
+
```
|
|
51
295
|
|
|
52
296
|
---
|
|
53
297
|
|
|
54
|
-
##
|
|
298
|
+
## Core Concepts
|
|
299
|
+
|
|
300
|
+
### Message Data Model
|
|
301
|
+
|
|
302
|
+
Every message in movius-chats follows this **unified interface**:
|
|
55
303
|
|
|
304
|
+
```tsx
|
|
305
|
+
export interface Message {
|
|
306
|
+
id: string; // Unique message identifier
|
|
307
|
+
text?: string; // Text content
|
|
308
|
+
image?: string; // Single image URI (legacy)
|
|
309
|
+
video?: string; // Single video URI (legacy)
|
|
310
|
+
audio?: string; // Audio file URI
|
|
311
|
+
senderId: string; // ID of message sender
|
|
312
|
+
time: string; // Timestamp (e.g., "10:30 AM")
|
|
313
|
+
status: 'read' | 'delivered' | 'sent'; // Message status
|
|
314
|
+
senderName?: string; // Sender display name
|
|
315
|
+
senderAvatar?: string; // Sender avatar URI
|
|
316
|
+
mediaItems?: MessageMediaItem[]; // Array of images/videos
|
|
317
|
+
fileAttachments?: MessageFileAttachment[]; // Array of file attachments
|
|
318
|
+
replyTo?: MessageReply; // Reply context
|
|
319
|
+
edited?: boolean; // Whether message was edited
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Media item in album
|
|
323
|
+
export interface MessageMediaItem {
|
|
324
|
+
uri: string;
|
|
325
|
+
kind: 'image' | 'video';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// File attachment
|
|
329
|
+
export interface MessageFileAttachment {
|
|
330
|
+
uri: string;
|
|
331
|
+
type: string; // MIME type
|
|
332
|
+
name: string; // Display name
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Reply reference
|
|
336
|
+
export interface MessageReply {
|
|
337
|
+
messageId: string;
|
|
338
|
+
senderName?: string;
|
|
339
|
+
preview?: string; // Text preview (truncated)
|
|
340
|
+
mediaKind?: 'image' | 'video' | 'audio' | 'file';
|
|
341
|
+
thumbnailUri?: string;
|
|
342
|
+
}
|
|
56
343
|
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
344
|
+
|
|
345
|
+
### Message List Order
|
|
346
|
+
|
|
347
|
+
By default, messages are displayed **newest-first** (reverse chronological). The component expects:
|
|
348
|
+
|
|
349
|
+
- **First message in array** = **Newest/most recent**
|
|
350
|
+
- **Last message in array** = **Oldest**
|
|
351
|
+
|
|
352
|
+
If your data is ordered oldest-first, reverse it before passing:
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
const reversedMessages = [...messages].reverse();
|
|
356
|
+
<ChatScreen messages={reversedMessages} ... />
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Context System
|
|
360
|
+
|
|
361
|
+
movius-chats uses **React Context** to share state across components:
|
|
362
|
+
|
|
363
|
+
1. **ChatContext** — Main configuration and state:
|
|
364
|
+
|
|
365
|
+
- Message list, current user ID
|
|
366
|
+
- Media gallery state (which media is being viewed)
|
|
367
|
+
- Reply target (what message is being replied to)
|
|
368
|
+
- Long-press action state
|
|
369
|
+
- Selection mode and selected messages
|
|
370
|
+
- Edit draft state
|
|
371
|
+
- Callbacks for user actions
|
|
372
|
+
|
|
373
|
+
2. **AudioContext** — Ensures single audio playback:
|
|
374
|
+
- Tracks currently playing audio
|
|
375
|
+
- Pauses other audios when new one starts
|
|
376
|
+
- Coordinates playback speed cycling
|
|
377
|
+
|
|
378
|
+
Both contexts are automatically initialized by `ChatScreen`.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## API Reference
|
|
383
|
+
|
|
384
|
+
### ChatScreen Props
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
interface ChatScreenProps {
|
|
388
|
+
// Data
|
|
389
|
+
messages: Message[]; // Message array (newest first)
|
|
390
|
+
currentUserId: string; // Current user's ID
|
|
391
|
+
typingUsers?: Array<{ id: string; name: string }>; // Who's typing
|
|
392
|
+
|
|
393
|
+
// Callbacks
|
|
394
|
+
onSendMessage: (text: string) => void;
|
|
395
|
+
onAttachmentPress?: () => void;
|
|
396
|
+
onCameraPress?: () => void;
|
|
397
|
+
onAudioRecordEnd?: (result: RecordingResult) => void;
|
|
398
|
+
onAudioRecordStart?: () => void;
|
|
399
|
+
onMessageLongPress?: (message: Message) => void;
|
|
400
|
+
onTypingStart?: () => void;
|
|
401
|
+
onTypingEnd?: () => void;
|
|
402
|
+
|
|
403
|
+
// UI Options
|
|
404
|
+
showAvatars?: boolean; // Default: true
|
|
405
|
+
showUserNames?: boolean; // Default: true
|
|
406
|
+
showBubbleTail?: boolean; // Default: true
|
|
407
|
+
renderCustomInput?: (props: InputProps) => React.ReactNode;
|
|
408
|
+
|
|
409
|
+
// Custom Icons (optional)
|
|
410
|
+
CustomEmojiIcon?: React.ComponentType;
|
|
411
|
+
CustomAttachmentIcon?: React.ComponentType;
|
|
412
|
+
CustomCameraIcon?: React.ComponentType;
|
|
413
|
+
CustomMicrophoneIcon?: React.ComponentType;
|
|
414
|
+
CustomSendIcon?: React.ComponentType;
|
|
415
|
+
CustomFileIcon?: React.ComponentType;
|
|
416
|
+
CustomImagePreview?: React.ComponentType;
|
|
417
|
+
CustomVideoPreview?: React.ComponentType;
|
|
418
|
+
|
|
419
|
+
// Feature Configs
|
|
420
|
+
messageActionsConfig?: MessageActionsConfig;
|
|
421
|
+
replyProps?: ReplyConfig;
|
|
422
|
+
voiceRecorderConfig?: VoiceRecorderConfig;
|
|
423
|
+
recordingUIProps?: RecordingUIProps;
|
|
424
|
+
|
|
425
|
+
// Theming
|
|
426
|
+
theme?: ChatScreenTheme;
|
|
427
|
+
|
|
428
|
+
// Selection
|
|
429
|
+
selectionUI?: SelectionUIProps;
|
|
430
|
+
}
|
|
79
431
|
```
|
|
80
432
|
|
|
81
|
-
|
|
433
|
+
### Message Interface
|
|
434
|
+
|
|
435
|
+
See [Message Data Model](#message-data-model) section above for full details.
|
|
436
|
+
|
|
437
|
+
### Theme Configuration
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
interface ChatScreenTheme {
|
|
441
|
+
colors?: {
|
|
442
|
+
// Sent messages
|
|
443
|
+
sentBubbleBackground?: string;
|
|
444
|
+
sentTextColor?: string;
|
|
445
|
+
sentTimestampColor?: string;
|
|
446
|
+
sentStatusIconColor?: string;
|
|
447
|
+
sentAudioWaveformColor?: string;
|
|
448
|
+
sentAudioWaveformActiveColor?: string;
|
|
449
|
+
sentFileAttachmentBackground?: string;
|
|
450
|
+
sentFileAttachmentTextColor?: string;
|
|
451
|
+
sentMediaTimestampBackground?: string;
|
|
452
|
+
|
|
453
|
+
// Received messages
|
|
454
|
+
receivedBubbleBackground?: string;
|
|
455
|
+
receivedTextColor?: string;
|
|
456
|
+
receivedTimestampColor?: string;
|
|
457
|
+
receivedAudioWaveformColor?: string;
|
|
458
|
+
receivedAudioWaveformActiveColor?: string;
|
|
459
|
+
receivedFileAttachmentBackground?: string;
|
|
460
|
+
receivedFileAttachmentTextColor?: string;
|
|
461
|
+
receivedMediaTimestampBackground?: string;
|
|
462
|
+
|
|
463
|
+
// UI elements
|
|
464
|
+
inputBarBackground?: string;
|
|
465
|
+
inputPlaceholderTextColor?: string;
|
|
466
|
+
inputTextColor?: string;
|
|
467
|
+
typingIndicatorDotColor?: string;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// Fonts & Sizes
|
|
471
|
+
fontFamily?: string; // Default: system font
|
|
472
|
+
fontSize?: {
|
|
473
|
+
message?: number; // Default: 16
|
|
474
|
+
timestamp?: number; // Default: 12
|
|
475
|
+
typing?: number; // Default: 14
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// Styling overrides
|
|
479
|
+
messageStyle?: {
|
|
480
|
+
sentBubbleStyle?: ViewStyle;
|
|
481
|
+
receivedBubbleStyle?: ViewStyle;
|
|
482
|
+
sentMediaTimestampContainerStyle?: ViewStyle;
|
|
483
|
+
receivedMediaTimestampContainerStyle?: ViewStyle;
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// Media grid
|
|
487
|
+
mediaGrid?: {
|
|
488
|
+
height?: number; // Default: 320
|
|
489
|
+
borderRadius?: number; // Default: 12
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// Input bar
|
|
493
|
+
inputBar?: {
|
|
494
|
+
maxHeight?: number; // Default: 118
|
|
495
|
+
minHeight?: number; // Default: 32-50
|
|
496
|
+
iconSize?: number; // Default: computed
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Features Guide
|
|
504
|
+
|
|
505
|
+
### Voice Recording
|
|
506
|
+
|
|
507
|
+
Voice messages combine **gesture-based capture** with **multiple recording modes**:
|
|
508
|
+
|
|
509
|
+
#### Recording Modes
|
|
510
|
+
|
|
511
|
+
1. **Tap-to-Record** — Single press starts/stops
|
|
512
|
+
2. **Long-Press Record** — Hold microphone for continuous capture
|
|
513
|
+
3. **Slide-to-Cancel** — Swipe left while recording to cancel
|
|
514
|
+
4. **Lock Recording** — Tap lock icon to record hands-free
|
|
515
|
+
|
|
516
|
+
#### Configuration
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
<ChatScreen
|
|
520
|
+
voiceRecorderConfig={{
|
|
521
|
+
maxDuration: 300, // 5 minutes max (seconds)
|
|
522
|
+
enableSlideToCancel: true, // Show slide-to-cancel hint
|
|
523
|
+
enableLockRecording: true, // Show lock option
|
|
524
|
+
enableWaveform: true, // Show waveform during recording
|
|
525
|
+
}}
|
|
526
|
+
recordingUIProps={{
|
|
527
|
+
timerColor: '#FFFFFF',
|
|
528
|
+
waveformColor: '#E9EDEF',
|
|
529
|
+
recordingBackground: '#0B141A',
|
|
530
|
+
lockPillBackground: '#1F2937',
|
|
531
|
+
lockIconColor: '#FFFFFF',
|
|
532
|
+
chevronIconColor: '#8696A0',
|
|
533
|
+
cancelTextColor: '#F15C6D',
|
|
534
|
+
waveformBarCount: 32,
|
|
535
|
+
}}
|
|
536
|
+
onAudioRecordEnd={(result) => {
|
|
537
|
+
console.log('Recording saved to:', result.uri);
|
|
538
|
+
console.log('Duration:', result.duration, 'ms');
|
|
539
|
+
// Upload to server, add to message, etc.
|
|
540
|
+
}}
|
|
541
|
+
/>
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Recording Result
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
interface RecordingResult {
|
|
548
|
+
uri: string; // File system path
|
|
549
|
+
duration: number; // Milliseconds
|
|
550
|
+
size?: number; // Bytes
|
|
551
|
+
mimeType?: string; // e.g., 'audio/m4a'
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Audio Message Playback
|
|
556
|
+
|
|
557
|
+
Play audio messages with professional waveform visualization and playback speed control:
|
|
558
|
+
|
|
559
|
+
```tsx
|
|
560
|
+
// Add audio to a message
|
|
561
|
+
const audioMessage: Message = {
|
|
562
|
+
id: '123',
|
|
563
|
+
audio: 'file:///path/to/audio.m4a',
|
|
564
|
+
senderId: 'user-1',
|
|
565
|
+
time: '10:45 AM',
|
|
566
|
+
status: 'sent',
|
|
567
|
+
senderAvatar: 'https://...',
|
|
568
|
+
};
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Features:**
|
|
572
|
+
|
|
573
|
+
- **Animated waveform** that fills as playback progresses
|
|
574
|
+
- **Playback speed cycling:** 1.0x → 1.5x → 2.0x → 1.0x
|
|
575
|
+
- **Sender avatar** displayed or current speed overlay
|
|
576
|
+
- **Pause/resume** during playback
|
|
577
|
+
- **Automatic pause** when another audio starts
|
|
578
|
+
- **Duration display** in bottom-right of bubble
|
|
579
|
+
|
|
580
|
+
### Media Grids & Gallery
|
|
581
|
+
|
|
582
|
+
Display image and video albums with smart layout:
|
|
583
|
+
|
|
584
|
+
```tsx
|
|
585
|
+
const albumMessage: Message = {
|
|
586
|
+
id: 'msg-123',
|
|
587
|
+
mediaItems: [
|
|
588
|
+
{ uri: 'https://example.com/photo1.jpg', kind: 'image' },
|
|
589
|
+
{ uri: 'https://example.com/photo2.jpg', kind: 'image' },
|
|
590
|
+
{ uri: 'file:///video.mp4', kind: 'video' },
|
|
591
|
+
],
|
|
592
|
+
senderId: 'user-1',
|
|
593
|
+
time: '2:30 PM',
|
|
594
|
+
status: 'delivered',
|
|
595
|
+
};
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Smart Layouts:**
|
|
599
|
+
| Count | Layout |
|
|
600
|
+
|-------|--------|
|
|
601
|
+
| 1 | Full-width square |
|
|
602
|
+
| 2 | 50/50 side-by-side |
|
|
603
|
+
| 3 | Large left + 2 stacked right |
|
|
604
|
+
| 4+ | 2×2 grid |
|
|
605
|
+
|
|
606
|
+
**Gallery Features:**
|
|
607
|
+
|
|
608
|
+
- Swipe left/right to navigate
|
|
609
|
+
- Image counter (e.g., "3/5")
|
|
610
|
+
- Video thumbnails with play icon
|
|
611
|
+
- Optional video autoplay control
|
|
612
|
+
- Close button (X icon)
|
|
613
|
+
- Full-screen immersive viewing
|
|
614
|
+
|
|
615
|
+
### Message Replies
|
|
616
|
+
|
|
617
|
+
Enable conversations within conversations:
|
|
618
|
+
|
|
619
|
+
```tsx
|
|
620
|
+
// Reply configuration
|
|
621
|
+
<ChatScreen
|
|
622
|
+
replyProps={{
|
|
623
|
+
enableReply: true,
|
|
624
|
+
swipeThreshold: 60, // Pixels to swipe for reply
|
|
625
|
+
previewMaxLines: 2, // Truncate preview at N lines
|
|
626
|
+
swipe: {
|
|
627
|
+
iconColor: '#FFFFFF',
|
|
628
|
+
iconBackground: 'rgba(0,0,0,0.3)',
|
|
629
|
+
iconSize: 24,
|
|
630
|
+
},
|
|
631
|
+
}}
|
|
632
|
+
// ... other props
|
|
633
|
+
/>;
|
|
634
|
+
|
|
635
|
+
// Message with reply
|
|
636
|
+
const repliedMessage: Message = {
|
|
637
|
+
id: '456',
|
|
638
|
+
text: 'Sounds good!',
|
|
639
|
+
replyTo: {
|
|
640
|
+
messageId: '123',
|
|
641
|
+
senderName: 'Alice',
|
|
642
|
+
preview: 'Want to grab coffee?',
|
|
643
|
+
mediaKind: 'image',
|
|
644
|
+
thumbnailUri: 'https://...',
|
|
645
|
+
},
|
|
646
|
+
senderId: 'user-1',
|
|
647
|
+
time: '3:15 PM',
|
|
648
|
+
status: 'read',
|
|
649
|
+
};
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Reply Features:**
|
|
653
|
+
|
|
654
|
+
- Swipe-right gesture to reply on mobile
|
|
655
|
+
- Reply preview in input bar before sending
|
|
656
|
+
- Inline reply display in bubbles (visual indent + border)
|
|
657
|
+
- Reply to any media type (image, video, audio, file)
|
|
658
|
+
- Edit indication ("Edited" chip in reply context)
|
|
659
|
+
- Customizable colors and backgrounds
|
|
660
|
+
|
|
661
|
+
### Typing Indicators
|
|
662
|
+
|
|
663
|
+
Show who's currently typing:
|
|
664
|
+
|
|
665
|
+
```tsx
|
|
666
|
+
<ChatScreen
|
|
667
|
+
typingUsers={[
|
|
668
|
+
{ id: 'alice', name: 'Alice' },
|
|
669
|
+
{ id: 'bob', name: 'Bob' },
|
|
670
|
+
// If 3+ users, shows "Alice, Bob, +2 more"
|
|
671
|
+
]}
|
|
672
|
+
// ... other props
|
|
673
|
+
/>
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
**Features:**
|
|
677
|
+
|
|
678
|
+
- Animated dots bouncing
|
|
679
|
+
- Up to 2 avatars displayed inline
|
|
680
|
+
- "+N more" badge if 3+ users
|
|
681
|
+
- Customizable dot color
|
|
682
|
+
- Auto-dismisses when onTypingEnd called
|
|
683
|
+
|
|
684
|
+
### File Attachments
|
|
685
|
+
|
|
686
|
+
Handle any file type with custom handlers:
|
|
687
|
+
|
|
688
|
+
```tsx
|
|
689
|
+
const fileMessage: Message = {
|
|
690
|
+
id: '789',
|
|
691
|
+
fileAttachments: [
|
|
692
|
+
{
|
|
693
|
+
uri: 'file:///Documents/presentation.pdf',
|
|
694
|
+
type: 'application/pdf',
|
|
695
|
+
name: 'Q4_Presentation.pdf',
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
senderId: 'user-1',
|
|
699
|
+
time: '4:00 PM',
|
|
700
|
+
status: 'delivered',
|
|
701
|
+
};
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**Default Behavior:**
|
|
705
|
+
|
|
706
|
+
- Tappable row with file icon and name
|
|
707
|
+
- Uses React Native's `Linking.openURL` to open
|
|
708
|
+
- Fallback for unsupported file types
|
|
709
|
+
|
|
710
|
+
**Customization:**
|
|
711
|
+
|
|
712
|
+
```tsx
|
|
713
|
+
<ChatScreen
|
|
714
|
+
onAttachmentPress={() => {
|
|
715
|
+
// Custom file picker/handler
|
|
716
|
+
}}
|
|
717
|
+
// ... other props
|
|
718
|
+
/>
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Message Selection & Actions
|
|
722
|
+
|
|
723
|
+
Enable multi-select and bulk operations:
|
|
724
|
+
|
|
725
|
+
```tsx
|
|
726
|
+
<ChatScreen
|
|
727
|
+
onMessageLongPress={(message) => {
|
|
728
|
+
// Long-press opens action menu with options:
|
|
729
|
+
// - Copy
|
|
730
|
+
// - Forward
|
|
731
|
+
// - Reply
|
|
732
|
+
// - Edit
|
|
733
|
+
// - Delete
|
|
734
|
+
// Customizable via messageActionsConfig
|
|
735
|
+
}}
|
|
736
|
+
messageActionsConfig={{
|
|
737
|
+
actions: [
|
|
738
|
+
'copy', // Copy text to clipboard
|
|
739
|
+
'forward', // Forward message
|
|
740
|
+
'reply', // Reply to message
|
|
741
|
+
'edit', // Edit message content
|
|
742
|
+
'delete', // Delete message
|
|
743
|
+
],
|
|
744
|
+
// Custom labels
|
|
745
|
+
labels: {
|
|
746
|
+
copy: 'Copy Text',
|
|
747
|
+
forward: 'Share',
|
|
748
|
+
delete: 'Remove',
|
|
749
|
+
},
|
|
750
|
+
// Custom icons (SVG/images)
|
|
751
|
+
icons: {
|
|
752
|
+
copy: <CustomCopyIcon />,
|
|
753
|
+
},
|
|
754
|
+
}}
|
|
755
|
+
selectionUI={{
|
|
756
|
+
checkboxSize: 24,
|
|
757
|
+
checkboxColor: '#22c55e',
|
|
758
|
+
selectedBackground: 'rgba(34, 197, 94, 0.1)',
|
|
759
|
+
}}
|
|
760
|
+
/>
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## Customization
|
|
766
|
+
|
|
767
|
+
### Theming & Styling
|
|
768
|
+
|
|
769
|
+
#### Colors
|
|
770
|
+
|
|
771
|
+
```tsx
|
|
772
|
+
<ChatScreen
|
|
773
|
+
theme={{
|
|
774
|
+
colors: {
|
|
775
|
+
// Sent messages (right-aligned, typically green)
|
|
776
|
+
sentBubbleBackground: '#22c55e',
|
|
777
|
+
sentTextColor: '#FFFFFF',
|
|
778
|
+
sentTimestampColor: 'rgba(255, 255, 255, 0.7)',
|
|
779
|
+
sentStatusIconColor: '#FFFFFF',
|
|
780
|
+
sentAudioWaveformColor: 'rgba(255, 255, 255, 0.3)',
|
|
781
|
+
sentAudioWaveformActiveColor: 'rgba(255, 255, 255, 0.95)',
|
|
782
|
+
sentFileAttachmentBackground: 'rgba(255, 255, 255, 0.15)',
|
|
783
|
+
sentFileAttachmentTextColor: '#FFFFFF',
|
|
784
|
+
|
|
785
|
+
// Received messages (left-aligned, typically gray)
|
|
786
|
+
receivedBubbleBackground: '#E5E7EB',
|
|
787
|
+
receivedTextColor: '#1F2937',
|
|
788
|
+
receivedTimestampColor: 'rgba(107, 114, 128, 0.85)',
|
|
789
|
+
receivedAudioWaveformColor: 'rgba(0, 0, 0, 0.2)',
|
|
790
|
+
receivedAudioWaveformActiveColor: 'rgba(0, 0, 0, 0.6)',
|
|
791
|
+
receivedFileAttachmentBackground: 'rgba(0, 0, 0, 0.08)',
|
|
792
|
+
receivedFileAttachmentTextColor: '#1F2937',
|
|
793
|
+
|
|
794
|
+
// Input & UI
|
|
795
|
+
inputBarBackground: '#FFFFFF',
|
|
796
|
+
inputTextColor: '#1F2937',
|
|
797
|
+
inputPlaceholderTextColor: '#9CA3AF',
|
|
798
|
+
typingIndicatorDotColor: '#9CA3AF',
|
|
799
|
+
},
|
|
800
|
+
}}
|
|
801
|
+
/>
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
#### Fonts
|
|
805
|
+
|
|
806
|
+
```tsx
|
|
807
|
+
<ChatScreen
|
|
808
|
+
theme={{
|
|
809
|
+
fontFamily: 'Segoe UI', // Custom font
|
|
810
|
+
fontSize: {
|
|
811
|
+
message: 16,
|
|
812
|
+
timestamp: 12,
|
|
813
|
+
typing: 14,
|
|
814
|
+
},
|
|
815
|
+
}}
|
|
816
|
+
/>
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
#### Custom Styles
|
|
820
|
+
|
|
821
|
+
```tsx
|
|
822
|
+
<ChatScreen
|
|
823
|
+
theme={{
|
|
824
|
+
messageStyle: {
|
|
825
|
+
sentBubbleStyle: {
|
|
826
|
+
borderRadius: 16,
|
|
827
|
+
shadowColor: '#000',
|
|
828
|
+
shadowOffset: { width: 0, height: 2 },
|
|
829
|
+
shadowOpacity: 0.1,
|
|
830
|
+
shadowRadius: 4,
|
|
831
|
+
elevation: 3,
|
|
832
|
+
paddingHorizontal: 12,
|
|
833
|
+
paddingVertical: 8,
|
|
834
|
+
},
|
|
835
|
+
receivedBubbleStyle: {
|
|
836
|
+
borderRadius: 16,
|
|
837
|
+
paddingHorizontal: 12,
|
|
838
|
+
paddingVertical: 8,
|
|
839
|
+
},
|
|
840
|
+
},
|
|
841
|
+
mediaGrid: {
|
|
842
|
+
height: 320,
|
|
843
|
+
borderRadius: 12,
|
|
844
|
+
},
|
|
845
|
+
}}
|
|
846
|
+
/>
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### Custom Components & Icons
|
|
850
|
+
|
|
851
|
+
Override built-in UI icons:
|
|
852
|
+
|
|
853
|
+
```tsx
|
|
854
|
+
import { CustomIcon } from './CustomIcon';
|
|
855
|
+
|
|
856
|
+
<ChatScreen
|
|
857
|
+
// Input bar icons
|
|
858
|
+
CustomEmojiIcon={EmojiIcon}
|
|
859
|
+
CustomAttachmentIcon={AttachmentIcon}
|
|
860
|
+
CustomCameraIcon={CameraIcon}
|
|
861
|
+
CustomMicrophoneIcon={MicrophoneIcon}
|
|
862
|
+
CustomSendIcon={SendIcon}
|
|
863
|
+
CustomFileIcon={FileIcon}
|
|
864
|
+
// Media previews
|
|
865
|
+
CustomImagePreview={CustomImagePreview}
|
|
866
|
+
CustomVideoPreview={CustomVideoPreview}
|
|
867
|
+
// Custom input
|
|
868
|
+
renderCustomInput={(props) => <YourCustomInput {...props} />}
|
|
869
|
+
/>;
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### Component Overrides
|
|
873
|
+
|
|
874
|
+
The library exposes individual components for advanced customization:
|
|
875
|
+
|
|
876
|
+
```tsx
|
|
877
|
+
import {
|
|
878
|
+
ChatBubble,
|
|
879
|
+
ChatInput,
|
|
880
|
+
MediaViewer,
|
|
881
|
+
AudioPlayer,
|
|
882
|
+
TypingIndicator,
|
|
883
|
+
VoiceRecorder,
|
|
884
|
+
} from 'movius-chats';
|
|
885
|
+
|
|
886
|
+
// Use individually or extend
|
|
887
|
+
const CustomChatBubble = (props) => <ChatBubble {...props} staticMode={true} />;
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
## Project Structure
|
|
893
|
+
|
|
894
|
+
```
|
|
895
|
+
movius-chats/
|
|
896
|
+
├── src/
|
|
897
|
+
│ ├── index.tsx # Main ChatScreen export
|
|
898
|
+
│ ├── types/
|
|
899
|
+
│ │ └── index.ts # TypeScript interfaces
|
|
900
|
+
│ ├── context/
|
|
901
|
+
│ │ ├── ChatContext.tsx # Main chat state & props
|
|
902
|
+
│ │ └── AudioContext.tsx # Audio playback coordination
|
|
903
|
+
│ ├── hooks/
|
|
904
|
+
│ │ ├── useKeyboardInset.ts # iOS/Android keyboard height
|
|
905
|
+
│ │ └── useVoiceRecorder.ts # Voice recording hook
|
|
906
|
+
│ ├── components/
|
|
907
|
+
│ │ ├── ChatBubble/
|
|
908
|
+
│ │ │ ├── ChatBubble.tsx # Main bubble component
|
|
909
|
+
│ │ │ ├── MessageContent.tsx # Message text/media content
|
|
910
|
+
│ │ │ ├── MessageStatus.tsx # Status checkmarks
|
|
911
|
+
│ │ │ ├── MediaGrid.tsx # 1/2/3/4+ grid layouts
|
|
912
|
+
│ │ │ └── types.ts
|
|
913
|
+
│ │ ├── ChatInput/
|
|
914
|
+
│ │ │ ├── ChatInput.tsx # Input bar with buttons
|
|
915
|
+
│ │ │ ├── FilePreview.tsx # Selected file preview
|
|
916
|
+
│ │ │ └── TruncateFileName.ts
|
|
917
|
+
│ │ ├── AudioPlayer/
|
|
918
|
+
│ │ │ ├── AudioPlayer.tsx # Audio playback UI
|
|
919
|
+
│ │ │ └── types.ts
|
|
920
|
+
│ │ ├── MediaViewer/
|
|
921
|
+
│ │ │ └── MediaViewer.tsx # Full-screen gallery
|
|
922
|
+
│ │ ├── MessageActions/
|
|
923
|
+
│ │ │ ├── index.ts
|
|
924
|
+
│ │ │ ├── LongPressOverlay.tsx # Action menu overlay
|
|
925
|
+
│ │ │ ├── MessageActionsPopover.tsx # Tablet UI
|
|
926
|
+
│ │ │ └── MessageActionsSheet.tsx # Mobile bottom sheet
|
|
927
|
+
│ │ ├── Reply/
|
|
928
|
+
│ │ │ ├── index.ts
|
|
929
|
+
│ │ │ ├── SwipeableMessage.tsx # Swipe gesture handler
|
|
930
|
+
│ │ │ ├── ReplyPreview.tsx # Input bar reply preview
|
|
931
|
+
│ │ │ └── InlineReply.tsx # Bubble reply display
|
|
932
|
+
│ │ ├── TypingComponent/
|
|
933
|
+
│ │ │ └── TypingIndicator.tsx # Animated typing dots
|
|
934
|
+
│ │ └── VoiceRecorder/
|
|
935
|
+
│ │ ├── VoiceRecorder.tsx # Base recorder UI
|
|
936
|
+
│ │ ├── VoiceRecordingGesture.tsx # Gesture handlers
|
|
937
|
+
│ │ └── VoiceRecorderFlow/ # Recording modes & UI
|
|
938
|
+
│ ├── utils/
|
|
939
|
+
│ │ ├── bubbleTheme.ts # Color helpers for sent/received
|
|
940
|
+
│ │ ├── messageMedia.ts # Media collection utilities
|
|
941
|
+
│ │ ├── messageActions.ts # Action merging & defaults
|
|
942
|
+
│ │ ├── replyTheme.ts # Reply styling helpers
|
|
943
|
+
│ │ ├── theme.ts # Font & icon size helpers
|
|
944
|
+
│ │ └── datefunc.ts # Duration formatting
|
|
945
|
+
│ └── assets/
|
|
946
|
+
│ └── Icons/
|
|
947
|
+
│ ├── ArrowBack2RoundedIcon.tsx
|
|
948
|
+
│ ├── CameraIcon.tsx
|
|
949
|
+
│ ├── CheckAllIcon.tsx
|
|
950
|
+
│ ├── CheckIcon.tsx
|
|
951
|
+
│ ├── ChevronUpIcon.tsx
|
|
952
|
+
│ ├── ClosePreviewIcon.tsx
|
|
953
|
+
│ ├── CopyIcon.tsx
|
|
954
|
+
│ ├── EditIcon.tsx
|
|
955
|
+
│ ├── EmojiFunnySquareIcon.tsx
|
|
956
|
+
│ ├── FileIcon.tsx
|
|
957
|
+
│ ├── ForwardIcon.tsx
|
|
958
|
+
│ ├── LoadingIcon.tsx
|
|
959
|
+
│ ├── LockIcon.tsx
|
|
960
|
+
│ ├── MicrophoneIcon.tsx
|
|
961
|
+
│ ├── PaperClipIcon.tsx
|
|
962
|
+
│ ├── PaperPlaneIcon.tsx
|
|
963
|
+
│ ├── PauseIcon.tsx
|
|
964
|
+
│ ├── PlayIcon.tsx
|
|
965
|
+
│ ├── ReplyIcon.tsx
|
|
966
|
+
│ ├── SelectIcon.tsx
|
|
967
|
+
│ ├── TrashIcon.tsx
|
|
968
|
+
│ └── XIcon.tsx
|
|
969
|
+
├── lib/
|
|
970
|
+
│ ├── commonjs/ # CommonJS build output
|
|
971
|
+
│ ├── module/ # ES Module build output
|
|
972
|
+
│ └── typescript/ # TypeScript declarations
|
|
973
|
+
├── scripts/
|
|
974
|
+
│ └── patchSound.js # Audio module patching
|
|
975
|
+
├── babel.config.js # Babel configuration
|
|
976
|
+
├── rollup.config.mjs # Rollup bundler config
|
|
977
|
+
├── tsconfig.json # TypeScript config
|
|
978
|
+
├── tsconfig.types.json # TypeScript types config
|
|
979
|
+
├── tsconfig.build.json # TypeScript build config
|
|
980
|
+
├── package.json
|
|
981
|
+
└── README.md
|
|
982
|
+
```
|
|
82
983
|
|
|
83
984
|
---
|
|
84
985
|
|
|
85
986
|
## Dependencies
|
|
86
987
|
|
|
87
|
-
###
|
|
988
|
+
### Runtime Dependencies (Bundled)
|
|
989
|
+
|
|
990
|
+
| Package | Version | Purpose |
|
|
991
|
+
| -------------------------- | ------- | -------------------------------- |
|
|
992
|
+
| `react-native-video` | ^6.9.1 | Video/audio playback, thumbnails |
|
|
993
|
+
| `react-native-svg` | 15.2.0 | Built-in icon rendering |
|
|
994
|
+
| `react-native-parsed-text` | ^0.0.22 | Link/email detection in text |
|
|
995
|
+
| `twrnc` | ^4.6.1 | Tailwind-like utility styles |
|
|
996
|
+
|
|
997
|
+
### Peer Dependencies (Install in your app)
|
|
998
|
+
|
|
999
|
+
| Package | Version | Required | Purpose |
|
|
1000
|
+
| ----------------------------------- | ------- | -------- | ------------------------------ |
|
|
1001
|
+
| `react` | ≥16.8 | Yes | React hooks, context |
|
|
1002
|
+
| `react-native` | \* | Yes | Core framework |
|
|
1003
|
+
| `react-native-gesture-handler` | ≥2.0 | Yes | Swipe & long-press gestures |
|
|
1004
|
+
| `react-native-reanimated` | \* | Yes | Gesture animations |
|
|
1005
|
+
| `react-native-audio-record` | \* | Optional | Voice recording capture |
|
|
1006
|
+
| `react-native-fs` | \* | Optional | File system access (recording) |
|
|
1007
|
+
| `@react-native-clipboard/clipboard` | \* | Optional | Copy to clipboard |
|
|
1008
|
+
|
|
1009
|
+
### Dev Dependencies
|
|
1010
|
+
|
|
1011
|
+
- **Build:** Rollup, Babel, TypeScript
|
|
1012
|
+
- **Linting:** ESLint, Prettier
|
|
1013
|
+
- **Testing:** Jest
|
|
1014
|
+
- **Type Checking:** TypeScript
|
|
1015
|
+
|
|
1016
|
+
---
|
|
1017
|
+
|
|
1018
|
+
## Keyboard Behavior
|
|
1019
|
+
|
|
1020
|
+
The library handles platform-specific keyboard behavior automatically:
|
|
1021
|
+
|
|
1022
|
+
### iOS
|
|
1023
|
+
|
|
1024
|
+
- Soft keyboard slides up from bottom
|
|
1025
|
+
- Input bar adjusts position via `KeyboardAvoidingView`
|
|
1026
|
+
- Default offset: based on notch/safe area
|
|
1027
|
+
|
|
1028
|
+
### Android
|
|
1029
|
+
|
|
1030
|
+
- Keyboard dismisses text field focus automatically
|
|
1031
|
+
- Input bar uses `KeyboardAvoidingView`
|
|
1032
|
+
- Respects `android:windowSoftInputMode` setting
|
|
1033
|
+
|
|
1034
|
+
### Customization
|
|
1035
|
+
|
|
1036
|
+
```tsx
|
|
1037
|
+
// useKeyboardInset hook provides current inset
|
|
1038
|
+
const { keyboardHeight } = useKeyboardInset();
|
|
1039
|
+
|
|
1040
|
+
// Manual control via ChatInput props
|
|
1041
|
+
<ChatScreen
|
|
1042
|
+
renderCustomInput={(props) => (
|
|
1043
|
+
<YourInput keyboardOffset={customOffset} {...props} />
|
|
1044
|
+
)}
|
|
1045
|
+
/>;
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1050
|
+
## TypeScript Support
|
|
1051
|
+
|
|
1052
|
+
Full TypeScript support with exported types:
|
|
1053
|
+
|
|
1054
|
+
```tsx
|
|
1055
|
+
import {
|
|
1056
|
+
ChatScreen,
|
|
1057
|
+
Message,
|
|
1058
|
+
MessageMediaItem,
|
|
1059
|
+
MessageFileAttachment,
|
|
1060
|
+
MessageReply,
|
|
1061
|
+
RecordingResult,
|
|
1062
|
+
ChatScreenProps,
|
|
1063
|
+
ChatScreenTheme,
|
|
1064
|
+
VoiceRecorderConfig,
|
|
1065
|
+
MessageActionsConfig,
|
|
1066
|
+
ReplyConfig,
|
|
1067
|
+
} from 'movius-chats';
|
|
1068
|
+
|
|
1069
|
+
// Use types in your code
|
|
1070
|
+
const message: Message = {
|
|
1071
|
+
id: '1',
|
|
1072
|
+
text: 'Hello',
|
|
1073
|
+
senderId: 'user-1',
|
|
1074
|
+
time: '10:30 AM',
|
|
1075
|
+
status: 'sent',
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
type Props = ChatScreenProps;
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
### Type Definitions
|
|
1082
|
+
|
|
1083
|
+
All types are exported from `lib/typescript/index.d.ts` and available in:
|
|
1084
|
+
|
|
1085
|
+
- `lib/commonjs/index.d.ts` (CommonJS version)
|
|
1086
|
+
- `lib/module/index.d.ts` (ES Module version)
|
|
1087
|
+
|
|
1088
|
+
---
|
|
1089
|
+
|
|
1090
|
+
## Advanced Usage
|
|
1091
|
+
|
|
1092
|
+
### Custom Message Actions
|
|
1093
|
+
|
|
1094
|
+
```tsx
|
|
1095
|
+
import {
|
|
1096
|
+
mergeMessageActionLabels,
|
|
1097
|
+
mergeMessageActionIcons,
|
|
1098
|
+
} from 'movius-chats';
|
|
1099
|
+
|
|
1100
|
+
<ChatScreen
|
|
1101
|
+
messageActionsConfig={{
|
|
1102
|
+
actions: ['copy', 'reply', 'customAction', 'delete'],
|
|
1103
|
+
labels: mergeMessageActionLabels({
|
|
1104
|
+
customAction: 'Pin Message',
|
|
1105
|
+
delete: 'Remove',
|
|
1106
|
+
}),
|
|
1107
|
+
icons: mergeMessageActionIcons({
|
|
1108
|
+
customAction: <PinIcon />,
|
|
1109
|
+
}),
|
|
1110
|
+
}}
|
|
1111
|
+
onMessageActionTap={(action, message) => {
|
|
1112
|
+
if (action === 'customAction') {
|
|
1113
|
+
// Handle custom action
|
|
1114
|
+
}
|
|
1115
|
+
}}
|
|
1116
|
+
/>;
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
### Static Message Display (Read-Only)
|
|
1120
|
+
|
|
1121
|
+
```tsx
|
|
1122
|
+
// Disable all interactivity
|
|
1123
|
+
import { ChatBubble } from 'movius-chats';
|
|
1124
|
+
|
|
1125
|
+
<ChatBubble
|
|
1126
|
+
message={message}
|
|
1127
|
+
isCurrentUser={false}
|
|
1128
|
+
staticMode={true}
|
|
1129
|
+
onLongPress={() => {}}
|
|
1130
|
+
/>;
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
### Multi-User Typing
|
|
1134
|
+
|
|
1135
|
+
```tsx
|
|
1136
|
+
<ChatScreen
|
|
1137
|
+
typingUsers={[
|
|
1138
|
+
{ id: 'alice', name: 'Alice' },
|
|
1139
|
+
{ id: 'bob', name: 'Bob' },
|
|
1140
|
+
{ id: 'charlie', name: 'Charlie' },
|
|
1141
|
+
]}
|
|
1142
|
+
onTypingStart={() => console.log('Typing started')}
|
|
1143
|
+
onTypingEnd={() => console.log('Typing ended')}
|
|
1144
|
+
/>
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
### Custom Theme Colors (Dark Mode)
|
|
1148
|
+
|
|
1149
|
+
```tsx
|
|
1150
|
+
const darkTheme: ChatScreenTheme = {
|
|
1151
|
+
colors: {
|
|
1152
|
+
sentBubbleBackground: '#128C7E', // WhatsApp green
|
|
1153
|
+
sentTextColor: '#FFFFFF',
|
|
1154
|
+
receivedBubbleBackground: '#1F2937', // Dark gray
|
|
1155
|
+
receivedTextColor: '#F3F4F6',
|
|
1156
|
+
inputBarBackground: '#0F172A',
|
|
1157
|
+
inputTextColor: '#F3F4F6',
|
|
1158
|
+
inputPlaceholderTextColor: '#6B7280',
|
|
1159
|
+
},
|
|
1160
|
+
fontFamily: 'Roboto',
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
<ChatScreen theme={darkTheme} ... />
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
### Handling Large Message Lists
|
|
1167
|
+
|
|
1168
|
+
For performance with 1000+ messages:
|
|
1169
|
+
|
|
1170
|
+
```tsx
|
|
1171
|
+
// 1. Use virtualization (FlatList is already virtualized)
|
|
1172
|
+
// 2. Minimize re-renders with useMemo
|
|
1173
|
+
// 3. Lazy-load older messages
|
|
1174
|
+
|
|
1175
|
+
const [messages, setMessages] = useState<Message[]>([
|
|
1176
|
+
// Latest 50 messages
|
|
1177
|
+
]);
|
|
1178
|
+
|
|
1179
|
+
const handleScroll = (offset: number) => {
|
|
1180
|
+
if (offset > 500) {
|
|
1181
|
+
// Load older messages from backend
|
|
1182
|
+
loadMoreMessages();
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
---
|
|
1188
|
+
|
|
1189
|
+
## Troubleshooting
|
|
1190
|
+
|
|
1191
|
+
### Issue: Voice recording not working
|
|
1192
|
+
|
|
1193
|
+
**Symptoms:** Recording button doesn't respond or crashes
|
|
1194
|
+
|
|
1195
|
+
**Solutions:**
|
|
1196
|
+
|
|
1197
|
+
1. **Check permissions:**
|
|
1198
|
+
|
|
1199
|
+
```tsx
|
|
1200
|
+
import { PermissionsAndroid } from 'react-native';
|
|
1201
|
+
const granted = await PermissionsAndroid.request(
|
|
1202
|
+
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO
|
|
1203
|
+
);
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
2. **Install audio dependencies:**
|
|
1207
|
+
|
|
1208
|
+
```bash
|
|
1209
|
+
npm install react-native-audio-record react-native-fs
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
3. **Verify native linking (Android):**
|
|
1213
|
+
```bash
|
|
1214
|
+
cd android && ./gradlew clean && cd ..
|
|
1215
|
+
npx react-native run-android
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
### Issue: Media gallery doesn't display
|
|
1219
|
+
|
|
1220
|
+
**Symptoms:** Images/videos not showing in gallery
|
|
1221
|
+
|
|
1222
|
+
**Solutions:**
|
|
1223
|
+
|
|
1224
|
+
1. **Verify URIs are valid:**
|
|
1225
|
+
|
|
1226
|
+
```tsx
|
|
1227
|
+
const mediaItems = [
|
|
1228
|
+
{ uri: 'file:///path/to/image.jpg', kind: 'image' },
|
|
1229
|
+
// Not 'file:///path/to/image.jpg/' (no trailing slash)
|
|
1230
|
+
];
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
2. **Check MediaGrid height:**
|
|
1234
|
+
|
|
1235
|
+
```tsx
|
|
1236
|
+
theme={{
|
|
1237
|
+
mediaGrid: { height: 320 }
|
|
1238
|
+
}}
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
3. **Grant file permissions:**
|
|
1242
|
+
- **Android:** Add `READ_EXTERNAL_STORAGE` permission
|
|
1243
|
+
- **iOS:** Add `NSPhotoLibraryUsageDescription` to Info.plist
|
|
1244
|
+
|
|
1245
|
+
### Issue: Audio playback cutting off
|
|
1246
|
+
|
|
1247
|
+
**Symptoms:** Audio stops playing prematurely or doesn't play at all
|
|
1248
|
+
|
|
1249
|
+
**Solutions:**
|
|
1250
|
+
|
|
1251
|
+
1. **Check audio format:** Use supported formats (MP3, M4A, AAC)
|
|
1252
|
+
2. **Verify URI is accessible:**
|
|
1253
|
+
|
|
1254
|
+
```tsx
|
|
1255
|
+
// Correct
|
|
1256
|
+
{
|
|
1257
|
+
audio: 'file:///documents/voice.m4a';
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Wrong
|
|
1261
|
+
{
|
|
1262
|
+
audio: 'file://documents/voice.m4a';
|
|
1263
|
+
} // Missing /
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
3. **Clear audio cache:**
|
|
1267
|
+
```tsx
|
|
1268
|
+
const { clearAudioCache } = useAudio();
|
|
1269
|
+
clearAudioCache();
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
### Issue: Keyboard overlaps input
|
|
1273
|
+
|
|
1274
|
+
**Symptoms:** Chat input hidden when keyboard appears
|
|
1275
|
+
|
|
1276
|
+
**Solutions:**
|
|
1277
|
+
|
|
1278
|
+
1. **Verify KeyboardAvoidingView:**
|
|
1279
|
+
|
|
1280
|
+
```tsx
|
|
1281
|
+
// Already built-in, check if overridden
|
|
1282
|
+
<KeyboardAvoidingView
|
|
1283
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
1284
|
+
>
|
|
1285
|
+
<ChatScreen ... />
|
|
1286
|
+
</KeyboardAvoidingView>
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
2. **Adjust offset:**
|
|
1290
|
+
|
|
1291
|
+
```tsx
|
|
1292
|
+
<KeyboardAvoidingView keyboardVerticalOffset={100}>...</KeyboardAvoidingView>
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
3. **Check AndroidManifest.xml:**
|
|
1296
|
+
```xml
|
|
1297
|
+
<activity
|
|
1298
|
+
android:windowSoftInputMode="adjustResize"
|
|
1299
|
+
/>
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
### Issue: Memory leaks
|
|
1303
|
+
|
|
1304
|
+
**Symptoms:** App crashes with large message lists, performance degrades
|
|
1305
|
+
|
|
1306
|
+
**Solutions:**
|
|
1307
|
+
|
|
1308
|
+
1. **Cleanup contexts:**
|
|
1309
|
+
|
|
1310
|
+
```tsx
|
|
1311
|
+
useEffect(() => {
|
|
1312
|
+
return () => {
|
|
1313
|
+
clearMediaViewerGallery?.();
|
|
1314
|
+
// Cleanup
|
|
1315
|
+
};
|
|
1316
|
+
}, []);
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
2. **Use FlatList optimizations:**
|
|
1320
|
+
|
|
1321
|
+
```tsx
|
|
1322
|
+
<FlatList
|
|
1323
|
+
removeClippedSubviews={true}
|
|
1324
|
+
maxToRenderPerBatch={10}
|
|
1325
|
+
windowSize={21}
|
|
1326
|
+
updateCellsBatchingPeriod={50}
|
|
1327
|
+
/>
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
3. **Avoid inline function callbacks:**
|
|
1331
|
+
|
|
1332
|
+
```tsx
|
|
1333
|
+
// Bad
|
|
1334
|
+
onSendMessage={(text) => { ... }}
|
|
1335
|
+
|
|
1336
|
+
// Good
|
|
1337
|
+
const handleSend = useCallback((text) => { ... }, []);
|
|
1338
|
+
<ChatScreen onSendMessage={handleSend} />
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
### Issue: TypeScript errors
|
|
1342
|
+
|
|
1343
|
+
**Symptoms:** Type checking fails, missing types
|
|
1344
|
+
|
|
1345
|
+
**Solutions:**
|
|
1346
|
+
|
|
1347
|
+
1. **Regenerate types:**
|
|
1348
|
+
|
|
1349
|
+
```bash
|
|
1350
|
+
npm run build:types
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
2. **Check tsconfig.json:**
|
|
1354
|
+
|
|
1355
|
+
```json
|
|
1356
|
+
{
|
|
1357
|
+
"compilerOptions": {
|
|
1358
|
+
"strict": true,
|
|
1359
|
+
"skipLibCheck": true
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
```
|
|
1363
|
+
|
|
1364
|
+
3. **Update type definitions:**
|
|
1365
|
+
```bash
|
|
1366
|
+
npm install --save-dev @types/react-native@latest
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
### Issue: Android build fails
|
|
88
1370
|
|
|
89
|
-
|
|
90
|
-
|---------|-----|
|
|
91
|
-
| `react-native-video` | Video thumbnails, gallery video, **audio playback** |
|
|
92
|
-
| `react-native-svg` | Built-in icons |
|
|
93
|
-
| `react-native-parsed-text` | Link detection in text |
|
|
94
|
-
| `twrnc` | Internal styles |
|
|
1371
|
+
**Symptoms:** Gradle/NDK errors during build
|
|
95
1372
|
|
|
96
|
-
|
|
1373
|
+
**Solutions:**
|
|
97
1374
|
|
|
98
|
-
|
|
1375
|
+
1. **Enable AndroidX:**
|
|
1376
|
+
|
|
1377
|
+
```properties
|
|
1378
|
+
# android/gradle.properties
|
|
1379
|
+
android.useAndroidX=true
|
|
1380
|
+
android.enableJetifier=true
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
2. **Update Gradle:**
|
|
1384
|
+
|
|
1385
|
+
```properties
|
|
1386
|
+
# android/gradle/wrapper/gradle-wrapper.properties
|
|
1387
|
+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
|
1388
|
+
```
|
|
1389
|
+
|
|
1390
|
+
3. **Clean build:**
|
|
1391
|
+
```bash
|
|
1392
|
+
cd android
|
|
1393
|
+
./gradlew clean
|
|
1394
|
+
cd ..
|
|
1395
|
+
npx react-native run-android
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
### Issue: Reply gestures not working
|
|
1399
|
+
|
|
1400
|
+
**Symptoms:** Swipe-to-reply doesn't trigger
|
|
1401
|
+
|
|
1402
|
+
**Solutions:**
|
|
1403
|
+
|
|
1404
|
+
1. **Enable reply config:**
|
|
1405
|
+
|
|
1406
|
+
```tsx
|
|
1407
|
+
replyProps={{
|
|
1408
|
+
enableReply: true,
|
|
1409
|
+
swipeThreshold: 60, // Pixels to swipe
|
|
1410
|
+
}}
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
2. **Check gesture handler:**
|
|
1414
|
+
|
|
1415
|
+
```bash
|
|
1416
|
+
npm install react-native-gesture-handler@latest
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
3. **Verify RectButton nesting** (GestureHandler requirement)
|
|
1420
|
+
|
|
1421
|
+
---
|
|
1422
|
+
|
|
1423
|
+
## Contributing
|
|
1424
|
+
|
|
1425
|
+
Contributions welcome! Please:
|
|
1426
|
+
|
|
1427
|
+
1. Fork the repository
|
|
1428
|
+
2. Create a feature branch: `git checkout -b feature/my-feature`
|
|
1429
|
+
3. Commit changes: `git commit -m 'Add my feature'`
|
|
1430
|
+
4. Push to branch: `git push origin feature/my-feature`
|
|
1431
|
+
5. Open a Pull Request
|
|
1432
|
+
|
|
1433
|
+
### Development Setup
|
|
1434
|
+
|
|
1435
|
+
```bash
|
|
1436
|
+
# Clone repo
|
|
1437
|
+
git clone https://github.com/David-Atueyi/Movius-Chats.git
|
|
1438
|
+
cd Movius-Chats
|
|
1439
|
+
|
|
1440
|
+
# Install dependencies
|
|
1441
|
+
npm install
|
|
1442
|
+
|
|
1443
|
+
# Run tests
|
|
1444
|
+
npm run test
|
|
1445
|
+
|
|
1446
|
+
# Type check
|
|
1447
|
+
npm run typescript
|
|
1448
|
+
|
|
1449
|
+
# Lint
|
|
1450
|
+
npm run lint
|
|
1451
|
+
|
|
1452
|
+
# Build
|
|
1453
|
+
npm run build
|
|
1454
|
+
|
|
1455
|
+
# View types
|
|
1456
|
+
npm run build:types
|
|
1457
|
+
```
|
|
1458
|
+
|
|
1459
|
+
### Build Output
|
|
1460
|
+
|
|
1461
|
+
```bash
|
|
1462
|
+
npm run build
|
|
1463
|
+
# Generates:
|
|
1464
|
+
# - lib/commonjs/index.js (CommonJS)
|
|
1465
|
+
# - lib/module/index.js (ES Module)
|
|
1466
|
+
# - lib/typescript/index.d.ts (TypeScript declarations)
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
---
|
|
1470
|
+
|
|
1471
|
+
## License
|
|
1472
|
+
|
|
1473
|
+
ISC License — see [LICENSE](LICENSE) file for details.
|
|
1474
|
+
|
|
1475
|
+
**Copyright © 2024 David Atueyi**
|
|
1476
|
+
|
|
1477
|
+
---
|
|
1478
|
+
|
|
1479
|
+
## Support
|
|
1480
|
+
|
|
1481
|
+
- **Issue Tracker:** [GitHub Issues](https://github.com/David-Atueyi/Movius-Chats/issues)
|
|
1482
|
+
- **Discussions:** [GitHub Discussions](https://github.com/David-Atueyi/Movius-Chats/discussions)
|
|
1483
|
+
- **NPM Package:** [movius-chats on npm](https://www.npmjs.com/package/movius-chats)
|
|
1484
|
+
|
|
1485
|
+
---
|
|
1486
|
+
|
|
1487
|
+
**Built with ❤️ for React Native developers.**
|
|
99
1488
|
|---------|----------|
|
|
100
1489
|
| `react` ≥ 16.8 | Yes |
|
|
101
1490
|
| `react-native` | Yes |
|
|
@@ -103,10 +1492,10 @@ Published build output: `lib/commonjs`, `lib/module`, `lib/typescript`.
|
|
|
103
1492
|
|
|
104
1493
|
### Optional peers (voice recording only)
|
|
105
1494
|
|
|
106
|
-
| Package
|
|
107
|
-
|
|
108
|
-
| `react-native-audio-record` | Record microphone
|
|
109
|
-
| `react-native-fs`
|
|
1495
|
+
| Package | Use |
|
|
1496
|
+
| --------------------------- | -------------------------------- |
|
|
1497
|
+
| `react-native-audio-record` | Record microphone |
|
|
1498
|
+
| `react-native-fs` | Delete cancelled recording files |
|
|
110
1499
|
|
|
111
1500
|
If these are missing, the UI still renders; starting a recording logs an install hint.
|
|
112
1501
|
|
|
@@ -233,31 +1622,38 @@ Wrap the screen in `flex: 1`. Load custom fonts in **your** app before passing `
|
|
|
233
1622
|
|
|
234
1623
|
### `Message`
|
|
235
1624
|
|
|
236
|
-
| Field
|
|
237
|
-
|
|
238
|
-
| `id`
|
|
239
|
-
| `senderId`
|
|
240
|
-
| `time`
|
|
241
|
-
| `status`
|
|
242
|
-
| `text`
|
|
243
|
-
| `audio`
|
|
244
|
-
| `image`
|
|
245
|
-
| `video`
|
|
246
|
-
| `mediaItems`
|
|
247
|
-
| `fileAttachments` | `MessageFileAttachment[]`
|
|
248
|
-
| `senderName`
|
|
249
|
-
| `senderAvatar`
|
|
1625
|
+
| Field | Type | Description |
|
|
1626
|
+
| ----------------- | --------------------------------- | ------------------------------------------ |
|
|
1627
|
+
| `id` | `string` | Unique id |
|
|
1628
|
+
| `senderId` | `string` | Who sent it |
|
|
1629
|
+
| `time` | `string` | Display time (you format it) |
|
|
1630
|
+
| `status` | `'sent' \| 'delivered' \| 'read'` | Checkmarks on **your** messages only |
|
|
1631
|
+
| `text` | `string` | Body text |
|
|
1632
|
+
| `audio` | `string` | Audio file URI |
|
|
1633
|
+
| `image` | `string` | Single image (legacy; prefer `mediaItems`) |
|
|
1634
|
+
| `video` | `string` | Single video (legacy) |
|
|
1635
|
+
| `mediaItems` | `MessageMediaItem[]` | Album in one bubble |
|
|
1636
|
+
| `fileAttachments` | `MessageFileAttachment[]` | PDF, doc, etc. |
|
|
1637
|
+
| `senderName` | `string` | Group name + audio avatar initial |
|
|
1638
|
+
| `senderAvatar` | `string` | Image URI for audio bubble avatar |
|
|
250
1639
|
|
|
251
1640
|
### `MessageMediaItem`
|
|
252
1641
|
|
|
253
1642
|
```ts
|
|
254
|
-
{
|
|
1643
|
+
{
|
|
1644
|
+
uri: string;
|
|
1645
|
+
kind: 'image' | 'video';
|
|
1646
|
+
}
|
|
255
1647
|
```
|
|
256
1648
|
|
|
257
1649
|
### `MessageFileAttachment`
|
|
258
1650
|
|
|
259
1651
|
```ts
|
|
260
|
-
{
|
|
1652
|
+
{
|
|
1653
|
+
uri: string;
|
|
1654
|
+
type: string;
|
|
1655
|
+
name: string;
|
|
1656
|
+
}
|
|
261
1657
|
```
|
|
262
1658
|
|
|
263
1659
|
### `PreviewAttachment` (composer)
|
|
@@ -292,15 +1688,15 @@ Default export: `ChatScreen`. All props are optional except `messages`, `current
|
|
|
292
1688
|
|
|
293
1689
|
### Core
|
|
294
1690
|
|
|
295
|
-
| Prop
|
|
296
|
-
|
|
297
|
-
| `messages`
|
|
298
|
-
| `currentUserId`
|
|
299
|
-
| `onSendMessage`
|
|
300
|
-
| `onMessageLongPress`
|
|
301
|
-
| `placeholder`
|
|
302
|
-
| `keyboardVerticalOffset`
|
|
303
|
-
| `disableKeyboardAvoiding` | `boolean`
|
|
1691
|
+
| Prop | Type | Description |
|
|
1692
|
+
| ------------------------- | ----------------------------------------------------- | ----------------------------------------------- |
|
|
1693
|
+
| `messages` | `Message[]` | Newest first |
|
|
1694
|
+
| `currentUserId` | `string` | Sent vs received layout |
|
|
1695
|
+
| `onSendMessage` | `(Omit<Message, 'id' \| 'time' \| 'status'>) => void` | Send button |
|
|
1696
|
+
| `onMessageLongPress` | `(message: Message) => void` | Long-press bubble |
|
|
1697
|
+
| `placeholder` | `string` | Input placeholder (default `"Message"`) |
|
|
1698
|
+
| `keyboardVerticalOffset` | `number` | **iOS only** — passed to `KeyboardAvoidingView` |
|
|
1699
|
+
| `disableKeyboardAvoiding` | `boolean` | Turn off built-in keyboard lift |
|
|
304
1700
|
|
|
305
1701
|
### Feature flags (default `false`)
|
|
306
1702
|
|
|
@@ -308,39 +1704,39 @@ Default export: `ChatScreen`. All props are optional except `messages`, `current
|
|
|
308
1704
|
|
|
309
1705
|
### Callbacks
|
|
310
1706
|
|
|
311
|
-
| Prop
|
|
312
|
-
|
|
313
|
-
| `onTypingStart` / `onTypingEnd` | Input text empty ↔ non-empty
|
|
314
|
-
| `onAttachmentPress`
|
|
315
|
-
| `onCameraPress`
|
|
316
|
-
| `onAudioRecordStart`
|
|
317
|
-
| `onAudioRecordEnd`
|
|
318
|
-
| `onFileAttachmentPress`
|
|
1707
|
+
| Prop | Description |
|
|
1708
|
+
| ------------------------------- | --------------------------------------------------- |
|
|
1709
|
+
| `onTypingStart` / `onTypingEnd` | Input text empty ↔ non-empty |
|
|
1710
|
+
| `onAttachmentPress` | Paperclip — open your picker |
|
|
1711
|
+
| `onCameraPress` | Camera icon |
|
|
1712
|
+
| `onAudioRecordStart` | Recording began |
|
|
1713
|
+
| `onAudioRecordEnd` | `(RecordingResult?) => void` when done or cancelled |
|
|
1714
|
+
| `onFileAttachmentPress` | File chip in bubble (default: `Linking.openURL`) |
|
|
319
1715
|
|
|
320
1716
|
### Composer preview
|
|
321
1717
|
|
|
322
|
-
| Prop
|
|
323
|
-
|
|
324
|
-
| `previewItems`
|
|
325
|
-
| `previewData`
|
|
1718
|
+
| Prop | Description |
|
|
1719
|
+
| --------------------- | -------------------------------------------- |
|
|
1720
|
+
| `previewItems` | Multiple attachments before send |
|
|
1721
|
+
| `previewData` | Single attachment (legacy) |
|
|
326
1722
|
| `onRemovePreviewItem` | `(uri) => void` — remove **one** card by URI |
|
|
327
|
-
| `closePreview`
|
|
1723
|
+
| `closePreview` | Clears all if `onRemovePreviewItem` not set |
|
|
328
1724
|
|
|
329
1725
|
When preview or text exists, the **send** icon shows instead of the mic.
|
|
330
1726
|
|
|
331
1727
|
### Voice recorder customization
|
|
332
1728
|
|
|
333
|
-
| Prop
|
|
334
|
-
|
|
335
|
-
| `CustomVoiceRecorder`
|
|
336
|
-
| `theme.voiceRecorder.config` | `VoiceRecorderConfig` — `maxDuration`, lock, slide-to-cancel, etc.
|
|
337
|
-
| `theme.voiceRecorder.ui`
|
|
338
|
-
| `theme.voiceRecorder.styles` | `VoiceRecorderStyleOverrides`
|
|
1729
|
+
| Prop | Type |
|
|
1730
|
+
| ---------------------------- | --------------------------------------------------------------------------- |
|
|
1731
|
+
| `CustomVoiceRecorder` | `(VoiceRecorderExposedState) => ReactNode` — replace entire recorder UI |
|
|
1732
|
+
| `theme.voiceRecorder.config` | `VoiceRecorderConfig` — `maxDuration`, lock, slide-to-cancel, etc. |
|
|
1733
|
+
| `theme.voiceRecorder.ui` | `RecordingUIProps` — colors/sizes for timer, lock pill, recorder play/pause |
|
|
1734
|
+
| `theme.voiceRecorder.styles` | `VoiceRecorderStyleOverrides` |
|
|
339
1735
|
|
|
340
1736
|
### Typing
|
|
341
1737
|
|
|
342
|
-
| Prop
|
|
343
|
-
|
|
1738
|
+
| Prop | Type |
|
|
1739
|
+
| ------------- | ------------------------ |
|
|
344
1740
|
| `typingUsers` | `{ id, avatar, name }[]` |
|
|
345
1741
|
|
|
346
1742
|
---
|
|
@@ -351,13 +1747,13 @@ Requires **`react-native-audio-record`** and **`react-native-fs`** in the host a
|
|
|
351
1747
|
|
|
352
1748
|
### Gestures
|
|
353
1749
|
|
|
354
|
-
| Action
|
|
355
|
-
|
|
356
|
-
| **Tap** mic
|
|
357
|
-
| **Long-press** mic
|
|
358
|
-
| Slide **left**
|
|
359
|
-
| Slide **up** to lock
|
|
360
|
-
| **Release** without slide | Auto-send (`onAudioRecordEnd`)
|
|
1750
|
+
| Action | Result |
|
|
1751
|
+
| ------------------------- | ---------------------------------------------------------------------------- |
|
|
1752
|
+
| **Tap** mic | Normal bar: trash, timer, waveform, play/pause preview, send |
|
|
1753
|
+
| **Long-press** mic | Hold mode: “slide to cancel”, lock column above send |
|
|
1754
|
+
| Slide **left** | Cancel (file deleted via `react-native-fs`) |
|
|
1755
|
+
| Slide **up** to lock | Switches to normal bar (`lockSlideDistance` in `theme.voiceRecorder.config`) |
|
|
1756
|
+
| **Release** without slide | Auto-send (`onAudioRecordEnd`) |
|
|
361
1757
|
|
|
362
1758
|
### Wiring
|
|
363
1759
|
|
|
@@ -400,22 +1796,22 @@ CustomVoiceRecorder={(state) => (
|
|
|
400
1796
|
|
|
401
1797
|
WhatsApp-style row inside the bubble:
|
|
402
1798
|
|
|
403
|
-
| Side
|
|
404
|
-
|
|
405
|
-
| **Sent**
|
|
1799
|
+
| Side | Layout (left → right) |
|
|
1800
|
+
| ------------ | -------------------------------------------- |
|
|
1801
|
+
| **Sent** | Avatar or speed pill → play/pause → waveform |
|
|
406
1802
|
| **Received** | play/pause → waveform → avatar or speed pill |
|
|
407
1803
|
|
|
408
|
-
| State
|
|
409
|
-
|
|
410
|
-
| Idle / finished | `senderAvatar` or first letter of `senderName`
|
|
411
|
-
| Playing
|
|
412
|
-
| Ended
|
|
1804
|
+
| State | Avatar slot |
|
|
1805
|
+
| --------------- | ------------------------------------------------------- |
|
|
1806
|
+
| Idle / finished | `senderAvatar` or first letter of `senderName` |
|
|
1807
|
+
| Playing | Pill showing **1x**, **1.5x**, or **2x** (tap to cycle) |
|
|
1808
|
+
| Ended | Avatar again |
|
|
413
1809
|
|
|
414
|
-
- Waveform bars with scrubber dot; tap or drag to seek
|
|
415
|
-
- Duration under the waveform
|
|
416
|
-
- Play/pause is icon-only (no filled circle)
|
|
417
|
-
- Only one audio plays at a time (`AudioContext`)
|
|
418
|
-
- Video in the gallery pauses other audio
|
|
1810
|
+
- Waveform bars with scrubber dot; tap or drag to seek
|
|
1811
|
+
- Duration under the waveform
|
|
1812
|
+
- Play/pause is icon-only (no filled circle)
|
|
1813
|
+
- Only one audio plays at a time (`AudioContext`)
|
|
1814
|
+
- Video in the gallery pauses other audio
|
|
419
1815
|
|
|
420
1816
|
```tsx
|
|
421
1817
|
{
|
|
@@ -435,21 +1831,21 @@ WhatsApp-style row inside the bubble:
|
|
|
435
1831
|
|
|
436
1832
|
### Grid (`mediaItems`)
|
|
437
1833
|
|
|
438
|
-
| Count | Layout
|
|
439
|
-
|
|
440
|
-
| 1
|
|
441
|
-
| 2
|
|
442
|
-
| 3
|
|
443
|
-
| 4+
|
|
1834
|
+
| Count | Layout | Height |
|
|
1835
|
+
| ----- | ---------------------- | ------ |
|
|
1836
|
+
| 1 | Full width, cover | 320px |
|
|
1837
|
+
| 2 | Two columns | 320px |
|
|
1838
|
+
| 3 | One top, two bottom | 320px |
|
|
1839
|
+
| 4+ | 2×2, `+N` on last cell | 320px |
|
|
444
1840
|
|
|
445
1841
|
Tap opens `MediaViewer`. Thumbnail `Video` uses `pointerEvents="none"` so presses reach the parent.
|
|
446
1842
|
|
|
447
1843
|
### Gallery behavior
|
|
448
1844
|
|
|
449
|
-
- Horizontal `FlatList`, `n / total` header
|
|
450
|
-
- **Videos** play only if that video was the tapped item and the page is active
|
|
451
|
-
- Tapping an **image** in a mixed album does not start other videos
|
|
452
|
-
- Composer video previews **do** autoplay in the small preview card
|
|
1845
|
+
- Horizontal `FlatList`, `n / total` header
|
|
1846
|
+
- **Videos** play only if that video was the tapped item and the page is active
|
|
1847
|
+
- Tapping an **image** in a mixed album does not start other videos
|
|
1848
|
+
- Composer video previews **do** autoplay in the small preview card
|
|
453
1849
|
|
|
454
1850
|
### Legacy single fields
|
|
455
1851
|
|
|
@@ -471,15 +1867,15 @@ const [previews, setPreviews] = useState<PreviewAttachment[]>([]);
|
|
|
471
1867
|
}
|
|
472
1868
|
onAttachmentPress={openYourDocumentPicker}
|
|
473
1869
|
onSendMessage={handleSend}
|
|
474
|
-
|
|
1870
|
+
/>;
|
|
475
1871
|
```
|
|
476
1872
|
|
|
477
|
-
| Preview type
|
|
478
|
-
|
|
479
|
-
| 1 image/video | Single thumb + ×
|
|
480
|
-
| 2–3 media
|
|
481
|
-
| 4+ media
|
|
482
|
-
| Documents
|
|
1873
|
+
| Preview type | UI |
|
|
1874
|
+
| ------------- | ----------------------------------- |
|
|
1875
|
+
| 1 image/video | Single thumb + × |
|
|
1876
|
+
| 2–3 media | Fanned stack, × on each |
|
|
1877
|
+
| 4+ media | Fan of 3 + `+N`, × per visible card |
|
|
1878
|
+
| Documents | Chips; scrollable after 3 |
|
|
483
1879
|
|
|
484
1880
|
Use any picker you want (`react-native-document-picker`, `react-native-image-picker`, etc.) — movius-chats only displays `previewItems`.
|
|
485
1881
|
|
|
@@ -491,22 +1887,22 @@ Pass `theme` to `ChatScreen`. `theme.fontFamily` applies to **all** `Text` in th
|
|
|
491
1887
|
|
|
492
1888
|
### `theme.colors` — per side (`sent*` / `received*`)
|
|
493
1889
|
|
|
494
|
-
| Keys
|
|
495
|
-
|
|
496
|
-
| `sentTimestampColor` / `receivedTimestampColor`
|
|
497
|
-
| `sentMessageTextColor` / `receivedMessageTextColor`
|
|
498
|
-
| `sentBubbleBackgroundColor` / `receivedBubbleBackgroundColor`
|
|
499
|
-
| `sentMessageTailColor` / `receivedMessageTailColor`
|
|
500
|
-
| `sentFileAttachmentBackground` / `receivedFileAttachmentBackground`
|
|
501
|
-
| `sentFileAttachmentTextColor` / `receivedFileAttachmentTextColor`
|
|
502
|
-
| `sentFileAttachmentSubtitleColor` / `receivedFileAttachmentSubtitleColor` | MIME line
|
|
503
|
-
| `sentAudioWaveformColor` / `receivedAudioWaveformColor`
|
|
504
|
-
| `sentAudioWaveformActiveColor` / `receivedAudioWaveformActiveColor`
|
|
505
|
-
| `sentAudioTimestampColor` / `receivedAudioTimestampColor`
|
|
506
|
-
| `sentAudioPlayIconColor` / `receivedAudioPlayIconColor`
|
|
507
|
-
| `sentAudioPauseIconColor` / `receivedAudioPauseIconColor`
|
|
508
|
-
| `sentAudioSpeedTextColor` / `receivedAudioSpeedTextColor`
|
|
509
|
-
| `sentMediaTimestampBackground` / `receivedMediaTimestampBackground`
|
|
1890
|
+
| Keys | Used for |
|
|
1891
|
+
| ------------------------------------------------------------------------- | ------------------------------------- |
|
|
1892
|
+
| `sentTimestampColor` / `receivedTimestampColor` | Message & file timestamps |
|
|
1893
|
+
| `sentMessageTextColor` / `receivedMessageTextColor` | Bubble text |
|
|
1894
|
+
| `sentBubbleBackgroundColor` / `receivedBubbleBackgroundColor` | Bubble background |
|
|
1895
|
+
| `sentMessageTailColor` / `receivedMessageTailColor` | Corner tail (`ArrowBack2RoundedIcon`) |
|
|
1896
|
+
| `sentFileAttachmentBackground` / `receivedFileAttachmentBackground` | File chip |
|
|
1897
|
+
| `sentFileAttachmentTextColor` / `receivedFileAttachmentTextColor` | File name |
|
|
1898
|
+
| `sentFileAttachmentSubtitleColor` / `receivedFileAttachmentSubtitleColor` | MIME line |
|
|
1899
|
+
| `sentAudioWaveformColor` / `receivedAudioWaveformColor` | Inactive waveform bars |
|
|
1900
|
+
| `sentAudioWaveformActiveColor` / `receivedAudioWaveformActiveColor` | Active bars + scrubber |
|
|
1901
|
+
| `sentAudioTimestampColor` / `receivedAudioTimestampColor` | Duration under waveform |
|
|
1902
|
+
| `sentAudioPlayIconColor` / `receivedAudioPlayIconColor` | Play icon |
|
|
1903
|
+
| `sentAudioPauseIconColor` / `receivedAudioPauseIconColor` | Pause icon |
|
|
1904
|
+
| `sentAudioSpeedTextColor` / `receivedAudioSpeedTextColor` | **1x / 1.5x / 2x** pill text |
|
|
1905
|
+
| `sentMediaTimestampBackground` / `receivedMediaTimestampBackground` | Timestamp pill on file-only bubbles |
|
|
510
1906
|
|
|
511
1907
|
**Not themeable:** image/video-only messages (no text, no audio) always use **white** (`#ffffff`) for the timestamp text.
|
|
512
1908
|
|
|
@@ -555,10 +1951,10 @@ Input row, send button, preview strip.
|
|
|
555
1951
|
|
|
556
1952
|
Built into `ChatScreen`:
|
|
557
1953
|
|
|
558
|
-
| Platform
|
|
559
|
-
|
|
560
|
-
| **Android** | `useKeyboardInset` sets `marginBottom` on the input row (= keyboard height)
|
|
561
|
-
| **iOS**
|
|
1954
|
+
| Platform | Behavior |
|
|
1955
|
+
| ----------- | ------------------------------------------------------------------------------------------------- |
|
|
1956
|
+
| **Android** | `useKeyboardInset` sets `marginBottom` on the input row (= keyboard height) |
|
|
1957
|
+
| **iOS** | Same inset **plus** `KeyboardAvoidingView` with `behavior="padding"` and `keyboardVerticalOffset` |
|
|
562
1958
|
|
|
563
1959
|
If your navigator already avoids the keyboard:
|
|
564
1960
|
|
|
@@ -570,24 +1966,24 @@ If your navigator already avoids the keyboard:
|
|
|
570
1966
|
|
|
571
1967
|
## Custom components & icons
|
|
572
1968
|
|
|
573
|
-
| Prop
|
|
574
|
-
|
|
575
|
-
| `renderCustomInput`
|
|
576
|
-
| `renderCustomTyping`
|
|
577
|
-
| `renderCustomVideoBubbleError`
|
|
578
|
-
| `CustomVoiceRecorder`
|
|
579
|
-
| `CustomEmojiIcon`
|
|
580
|
-
| `CustomAttachmentIcon`
|
|
581
|
-
| `CustomCameraIcon`
|
|
582
|
-
| `CustomSendIcon`
|
|
583
|
-
| `CustomMicrophoneIcon`
|
|
584
|
-
| `CustomPlayIcon` / `CustomPauseIcon`
|
|
585
|
-
| `CustomFileIcon`
|
|
586
|
-
| `CustomImagePreview` / `CustomVideoPreview` | Composer thumbnails
|
|
587
|
-
|
|
588
|
-
### File attachments
|
|
589
|
-
|
|
590
|
-
Default tap uses React Native `Linking.openURL`. For local files or share sheets,
|
|
1969
|
+
| Prop | Replaces |
|
|
1970
|
+
| ------------------------------------------- | ----------------------------------------------------- |
|
|
1971
|
+
| `renderCustomInput` | Entire input + recorder (you handle preview yourself) |
|
|
1972
|
+
| `renderCustomTyping` | “Typing…” content |
|
|
1973
|
+
| `renderCustomVideoBubbleError` | Inline video error in grid |
|
|
1974
|
+
| `CustomVoiceRecorder` | Built-in recorder bars |
|
|
1975
|
+
| `CustomEmojiIcon` | Emoji button |
|
|
1976
|
+
| `CustomAttachmentIcon` | Paperclip |
|
|
1977
|
+
| `CustomCameraIcon` | Camera |
|
|
1978
|
+
| `CustomSendIcon` | Send |
|
|
1979
|
+
| `CustomMicrophoneIcon` | Mic |
|
|
1980
|
+
| `CustomPlayIcon` / `CustomPauseIcon` | Audio (and related) playback |
|
|
1981
|
+
| `CustomFileIcon` | Document chip icon |
|
|
1982
|
+
| `CustomImagePreview` / `CustomVideoPreview` | Composer thumbnails |
|
|
1983
|
+
|
|
1984
|
+
### File attachments - Custom handlers
|
|
1985
|
+
|
|
1986
|
+
Default tap uses React Native `Linking.openURL`. For local files or share sheets, you can implement custom handlers in your app:
|
|
591
1987
|
|
|
592
1988
|
```tsx
|
|
593
1989
|
import { Linking } from 'react-native';
|
|
@@ -626,23 +2022,22 @@ Source types while developing against the repo: `movius-chats/src/types` (field
|
|
|
626
2022
|
|
|
627
2023
|
## Troubleshooting
|
|
628
2024
|
|
|
629
|
-
| Problem
|
|
630
|
-
|
|
631
|
-
| `Cannot read property 'init' of null`
|
|
632
|
-
| Recording never starts
|
|
633
|
-
| No audio playback
|
|
2025
|
+
| Problem | What to do |
|
|
2026
|
+
| -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
2027
|
+
| `Cannot read property 'init' of null` | Install `react-native-audio-record`, run `pod install`, **rebuild** the app |
|
|
2028
|
+
| Recording never starts | Mic permission; iOS `NSMicrophoneUsageDescription`; Android `RECORD_AUDIO` |
|
|
2029
|
+
| No audio playback | Ensure `react-native-video` is linked; URI must be `file://` or `http(s)://` |
|
|
634
2030
|
| `NoSuchMethodError` `DefaultLoadControl` (Android) | Force `androidx.media3` to **1.3.1** in the app `android/build.gradle` `resolutionStrategy` |
|
|
635
|
-
| Reanimated error
|
|
636
|
-
| Font not applied
|
|
637
|
-
| `inputIconSize` ignored on send/mic
|
|
638
|
-
| Keyboard covers input (Android)
|
|
639
|
-
| × clears all previews
|
|
640
|
-
| Wrong audio avatar
|
|
641
|
-
| Messages upside down
|
|
2031
|
+
| Reanimated error | `react-native-reanimated/plugin` must be **last** in Babel plugins |
|
|
2032
|
+
| Font not applied | Register font in the **host** app; pass exact `fontFamily` string |
|
|
2033
|
+
| `inputIconSize` ignored on send/mic | By design |
|
|
2034
|
+
| Keyboard covers input (Android) | `android:windowSoftInputMode="adjustResize"`; parent `flex: 1` |
|
|
2035
|
+
| × clears all previews | Implement `onRemovePreviewItem` |
|
|
2036
|
+
| Wrong audio avatar | Set `senderAvatar` and `senderName` on the `Message` |
|
|
2037
|
+
| Messages upside down | Newest at `messages[0]` |
|
|
642
2038
|
|
|
643
2039
|
---
|
|
644
2040
|
|
|
645
|
-
|
|
646
2041
|
## License
|
|
647
2042
|
|
|
648
2043
|
ISC — see [package.json](./package.json).
|