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 CHANGED
@@ -1,101 +1,1490 @@
1
1
  # movius-chats
2
2
 
3
- A customizable React Native chat UI library. One `ChatScreen` component: message bubbles, WhatsApp-style media grids, audio playback, voice recording, composer previews, typing indicators, and a full-screen media gallery — with per-side theming and replaceable UI pieces.
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
- This package is built for **plain React Native** (CLI / bare workflow). It does **not** depend on any Expo module.
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
- ## Table of contents
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
- 1. [What is included](#what-is-included)
15
- 2. [Package layout](#package-layout)
16
- 3. [Dependencies](#dependencies)
17
- 4. [Installation](#installation)
18
- 5. [Quick start](#quick-start)
19
- 6. [Message data model](#message-data-model)
20
- 7. [Message list order](#message-list-order)
21
- 8. [ChatScreen API](#chatscreen-api)
22
- 9. [Voice recording](#voice-recording)
23
- 10. [Audio message bubbles](#audio-message-bubbles)
24
- 11. [Media grids & gallery](#media-grids--gallery)
25
- 12. [Composer attachment preview](#composer-attachment-preview)
26
- 13. [Theme & styling](#theme--styling)
27
- 14. [Keyboard behavior](#keyboard-behavior)
28
- 15. [Custom components & icons](#custom-components--icons)
29
- 16. [TypeScript](#typescript)
30
- 17. [Troubleshooting](#troubleshooting)
31
- 18. [Publishing](#publishing)
32
- 19. [License](#license)
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
- ## What is included
244
+ ## Quick Start
37
245
 
38
- | Feature | Implementation |
39
- |---------|----------------|
40
- | Text messages | `react-native-parsed-text` (URLs tappable) |
41
- | Image / video albums | `MediaGrid` — 1 / 2 / 3 / 4+ layout, 320px height |
42
- | Full-screen viewer | `MediaViewer` — swipe, counter, selective video autoplay |
43
- | Audio messages | `react-native-video` (hidden player) + waveform UI |
44
- | Playback speed | 1x → 1.5x → 2x while playing |
45
- | Voice recording | `react-native-audio-record` (optional peer) |
46
- | File attachments | Tappable rows; default `Linking.openURL` |
47
- | Typing indicator | Up to 2 avatars + `+N` badge |
48
- | Input bar | Growing text field, emoji / clip / camera / send / mic |
49
- | Status icons | Sent / delivered / read checkmarks |
50
- | Theming | Separate `sent*` / `received*` colors for most bubble parts |
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
- ## Package layout
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
- src/
58
- ├── index.tsx # ChatScreen entry
59
- ├── types/index.ts # Message, ChatScreenProps, recorder types
60
- ├── context/
61
- │ ├── ChatContext.tsx # Props + gallery state
62
- │ └── AudioContext.tsx # One audio plays at a time
63
- ├── hooks/
64
- │ ├── useKeyboardInset.ts # Keyboard height → input margin
65
- │ └── useVoiceRecorder.ts # Mic capture (audio-record + fs)
66
- ├── utils/
67
- │ ├── bubbleTheme.ts # sent/received color helpers
68
- │ ├── messageMedia.ts # collectMediaItems()
69
- │ ├── theme.ts # fontFamily, input icon size
70
- │ └── datefunc.ts # formatDuration()
71
- ├── assets/Icons/ # SVG icons (play, mic, tail, etc.)
72
- └── components/
73
- ├── ChatBubble/ # Bubble, content, status, media grid
74
- ├── ChatInput/ # Input, FilePreview, voice gestures
75
- ├── AudioPlayer/ # WhatsApp-style audio UI
76
- ├── MediaViewer/ # Full-screen gallery modal
77
- ├── TypingComponent/
78
- └── VoiceRecorder/ # Normal + long-press recording UI
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
- Published build output: `lib/commonjs`, `lib/module`, `lib/typescript`.
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
- ### Bundled (installed with movius-chats)
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
- | Package | Use |
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
- ### Peer dependencies (your app must install)
1373
+ **Solutions:**
97
1374
 
98
- | Package | Required |
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 | Use |
107
- |---------|-----|
108
- | `react-native-audio-record` | Record microphone |
109
- | `react-native-fs` | Delete cancelled recording files |
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 | Type | Description |
237
- |-------|------|-------------|
238
- | `id` | `string` | Unique id |
239
- | `senderId` | `string` | Who sent it |
240
- | `time` | `string` | Display time (you format it) |
241
- | `status` | `'sent' \| 'delivered' \| 'read'` | Checkmarks on **your** messages only |
242
- | `text` | `string` | Body text |
243
- | `audio` | `string` | Audio file URI |
244
- | `image` | `string` | Single image (legacy; prefer `mediaItems`) |
245
- | `video` | `string` | Single video (legacy) |
246
- | `mediaItems` | `MessageMediaItem[]` | Album in one bubble |
247
- | `fileAttachments` | `MessageFileAttachment[]` | PDF, doc, etc. |
248
- | `senderName` | `string` | Group name + audio avatar initial |
249
- | `senderAvatar` | `string` | Image URI for audio bubble avatar |
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
- { uri: string; kind: 'image' | 'video' }
1643
+ {
1644
+ uri: string;
1645
+ kind: 'image' | 'video';
1646
+ }
255
1647
  ```
256
1648
 
257
1649
  ### `MessageFileAttachment`
258
1650
 
259
1651
  ```ts
260
- { uri: string; type: string; name: string }
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 | Type | Description |
296
- |------|------|-------------|
297
- | `messages` | `Message[]` | Newest first |
298
- | `currentUserId` | `string` | Sent vs received layout |
299
- | `onSendMessage` | `(Omit<Message, 'id' \| 'time' \| 'status'>) => void` | Send button |
300
- | `onMessageLongPress` | `(message: Message) => void` | Long-press bubble |
301
- | `placeholder` | `string` | Input placeholder (default `"Message"`) |
302
- | `keyboardVerticalOffset` | `number` | **iOS only** — passed to `KeyboardAvoidingView` |
303
- | `disableKeyboardAvoiding` | `boolean` | Turn off built-in keyboard lift |
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 | Description |
312
- |------|-------------|
313
- | `onTypingStart` / `onTypingEnd` | Input text empty ↔ non-empty |
314
- | `onAttachmentPress` | Paperclip — open your picker |
315
- | `onCameraPress` | Camera icon |
316
- | `onAudioRecordStart` | Recording began |
317
- | `onAudioRecordEnd` | `(RecordingResult?) => void` when done or cancelled |
318
- | `onFileAttachmentPress` | File chip in bubble (default: `Linking.openURL`) |
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 | Description |
323
- |------|-------------|
324
- | `previewItems` | Multiple attachments before send |
325
- | `previewData` | Single attachment (legacy) |
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` | Clears all if `onRemovePreviewItem` not set |
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 | Type |
334
- |------|------|
335
- | `CustomVoiceRecorder` | `(VoiceRecorderExposedState) => ReactNode` — replace entire recorder UI |
336
- | `theme.voiceRecorder.config` | `VoiceRecorderConfig` — `maxDuration`, lock, slide-to-cancel, etc. |
337
- | `theme.voiceRecorder.ui` | `RecordingUIProps` — colors/sizes for timer, lock pill, recorder play/pause |
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 | Type |
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 | Result |
355
- |--------|--------|
356
- | **Tap** mic | Normal bar: trash, timer, waveform, play/pause preview, send |
357
- | **Long-press** mic | Hold mode: “slide to cancel”, lock column above send |
358
- | Slide **left** | Cancel (file deleted via `react-native-fs`) |
359
- | Slide **up** to lock | Switches to normal bar (`lockSlideDistance` in `theme.voiceRecorder.config`) |
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 | Layout (left → right) |
404
- |------|------------------------|
405
- | **Sent** | Avatar or speed pill → play/pause → waveform |
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 | Avatar slot |
409
- |-------|-------------|
410
- | Idle / finished | `senderAvatar` or first letter of `senderName` |
411
- | Playing | Pill showing **1x**, **1.5x**, or **2x** (tap to cycle) |
412
- | Ended | Avatar again |
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 | Height |
439
- |-------|--------|--------|
440
- | 1 | Full width, cover | 320px |
441
- | 2 | Two columns | 320px |
442
- | 3 | One top, two bottom | 320px |
443
- | 4+ | 2×2, `+N` on last cell | 320px |
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 | UI |
478
- |--------------|-----|
479
- | 1 image/video | Single thumb + × |
480
- | 2–3 media | Fanned stack, × on each |
481
- | 4+ media | Fan of 3 + `+N`, × per visible card |
482
- | Documents | Chips; scrollable after 3 |
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 | Used for |
495
- |------|----------|
496
- | `sentTimestampColor` / `receivedTimestampColor` | Message & file timestamps |
497
- | `sentMessageTextColor` / `receivedMessageTextColor` | Bubble text |
498
- | `sentBubbleBackgroundColor` / `receivedBubbleBackgroundColor` | Bubble background |
499
- | `sentMessageTailColor` / `receivedMessageTailColor` | Corner tail (`ArrowBack2RoundedIcon`) |
500
- | `sentFileAttachmentBackground` / `receivedFileAttachmentBackground` | File chip |
501
- | `sentFileAttachmentTextColor` / `receivedFileAttachmentTextColor` | File name |
502
- | `sentFileAttachmentSubtitleColor` / `receivedFileAttachmentSubtitleColor` | MIME line |
503
- | `sentAudioWaveformColor` / `receivedAudioWaveformColor` | Inactive waveform bars |
504
- | `sentAudioWaveformActiveColor` / `receivedAudioWaveformActiveColor` | Active bars + scrubber |
505
- | `sentAudioTimestampColor` / `receivedAudioTimestampColor` | Duration under waveform |
506
- | `sentAudioPlayIconColor` / `receivedAudioPlayIconColor` | Play icon |
507
- | `sentAudioPauseIconColor` / `receivedAudioPauseIconColor` | Pause icon |
508
- | `sentAudioSpeedTextColor` / `receivedAudioSpeedTextColor` | **1x / 1.5x / 2x** pill text |
509
- | `sentMediaTimestampBackground` / `receivedMediaTimestampBackground` | Timestamp pill on file-only bubbles |
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 | Behavior |
559
- |----------|----------|
560
- | **Android** | `useKeyboardInset` sets `marginBottom` on the input row (= keyboard height) |
561
- | **iOS** | Same inset **plus** `KeyboardAvoidingView` with `behavior="padding"` and `keyboardVerticalOffset` |
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 | Replaces |
574
- |------|----------|
575
- | `renderCustomInput` | Entire input + recorder (you handle preview yourself) |
576
- | `renderCustomTyping` | “Typing…” content |
577
- | `renderCustomVideoBubbleError` | Inline video error in grid |
578
- | `CustomVoiceRecorder` | Built-in recorder bars |
579
- | `CustomEmojiIcon` | Emoji button |
580
- | `CustomAttachmentIcon` | Paperclip |
581
- | `CustomCameraIcon` | Camera |
582
- | `CustomSendIcon` | Send |
583
- | `CustomMicrophoneIcon` | Mic |
584
- | `CustomPlayIcon` / `CustomPauseIcon` | Audio (and related) playback |
585
- | `CustomFileIcon` | Document chip icon |
586
- | `CustomImagePreview` / `CustomVideoPreview` | Composer thumbnails |
587
-
588
- ### File attachments without Expo
589
-
590
- Default tap uses React Native `Linking.openURL`. For local files or share sheets, use your own module in the host app:
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 | What to do |
630
- |---------|------------|
631
- | `Cannot read property 'init' of null` | Install `react-native-audio-record`, run `pod install`, **rebuild** the app |
632
- | Recording never starts | Mic permission; iOS `NSMicrophoneUsageDescription`; Android `RECORD_AUDIO` |
633
- | No audio playback | Ensure `react-native-video` is linked; URI must be `file://` or `http(s)://` |
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 | `react-native-reanimated/plugin` must be **last** in Babel plugins |
636
- | Font not applied | Register font in the **host** app; pass exact `fontFamily` string |
637
- | `inputIconSize` ignored on send/mic | By design |
638
- | Keyboard covers input (Android) | `android:windowSoftInputMode="adjustResize"`; parent `flex: 1` |
639
- | × clears all previews | Implement `onRemovePreviewItem` |
640
- | Wrong audio avatar | Set `senderAvatar` and `senderName` on the `Message` |
641
- | Messages upside down | Newest at `messages[0]` |
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).