movius-chats 1.3.0 → 1.3.1

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,283 +1,616 @@
1
- # React Native Modern Chats UI
2
-
3
- A highly customizable, feature-rich chats interface component for React Native applications. Built with performance and flexibility in mind, this component provides a complete solution for implementing chats functionality in your mobile applications.
4
-
5
- ## ⚠️ Important Implementation Notes
6
-
7
- - **Native Rebuild Required**: This package uses native modules that require rebuilding your application after installation.
8
- - **Expo Go Compatibility**: This package is **not compatible** with Expo Go due to its native dependencies. You must use a development build or eject from Expo Go to use this library.
9
- - **Development Build**: For Expo users, you'll need to create a [Development Build](https://docs.expo.dev/develop/development-builds/introduction/) to use this package.
10
-
11
- ## Features
12
-
13
- - 🚀 Full TypeScript support
14
- - 📱 Native performance optimizations
15
- - 🎨 Extensive theme customization
16
- - 🖼️ Multi-media message support (text, images, video, audio)
17
- - 👤 Avatar and username display options
18
- - ⌨️ Typing indicators
19
- - 📎 File attachments
20
- - 🎥 Camera integration
21
- - 🎤 Voice messages
22
- - 💬 Message status indicators (sent, delivered, read)
23
- - 🎯 Custom component injection
24
- - 🔧 Comprehensive styling API
25
- - 🔄 Lazy loading for media messages
26
- - 📡 Debounced typing indicators
27
- - 🖼️ Avatar image caching
1
+ # movius-chats
2
+
3
+ A highly customizable, feature-rich chat UI for **React Native**. Drop in a single `ChatScreen` component to get message bubbles, media (images, video, audio), typing indicators, attachment previews, and a full input bar—with deep theming and custom icon/component hooks.
4
+
5
+ **npm:** [`movius-chats`](https://www.npmjs.com/package/movius-chats)
6
+ **Repository:** [github.com/David-Atueyi/Movius-Chats](https://github.com/David-Atueyi/Movius-Chats)
7
+
8
+ ---
9
+
10
+ ## Table of contents
11
+
12
+ - [Requirements](#requirements)
13
+ - [Important: native modules & Expo](#important-native-modules--expo)
14
+ - [Installation](#installation)
15
+ - [Quick start](#quick-start)
16
+ - [Message data model](#message-data-model)
17
+ - [Message list ordering](#message-list-ordering)
18
+ - [ChatScreen API](#chatscreen-api)
19
+ - [Core props](#core-props)
20
+ - [Feature flags](#feature-flags)
21
+ - [Input & typing](#input--typing)
22
+ - [Attachment preview](#attachment-preview)
23
+ - [Theming](#theming)
24
+ - [Custom components & icons](#custom-components--icons)
25
+ - [Usage examples](#usage-examples)
26
+ - [Text messages](#text-messages)
27
+ - [Media messages](#media-messages)
28
+ - [Typing indicators](#typing-indicators)
29
+ - [Attachments & camera (parent-controlled)](#attachments--camera-parent-controlled)
30
+ - [Attachment preview before send](#attachment-preview-before-send)
31
+ - [Custom theme](#custom-theme)
32
+ - [Custom input bar](#custom-input-bar)
33
+ - [Long-press actions](#long-press-actions)
34
+ - [TypeScript](#typescript)
35
+ - [Architecture overview](#architecture-overview)
36
+ - [Troubleshooting](#troubleshooting)
37
+ - [Contributing](#contributing)
38
+ - [License](#license)
39
+
40
+ ---
41
+
42
+ ## Requirements
43
+
44
+ | Dependency | Role |
45
+ |------------|------|
46
+ | `react` ≥ 16.8 | Peer dependency |
47
+ | `react-native` | Peer dependency |
48
+ | `react-native-reanimated` | Audio scrubber animations (peer) |
49
+ | `react-native-image-zoom-viewer` | Full-screen image viewer |
50
+ | `react-native-parsed-text` | Clickable URLs in messages |
51
+ | `react-native-sound` | Voice message playback |
52
+ | `react-native-svg` | Built-in icons |
53
+ | `react-native-video` | Video bubbles & preview |
54
+ | `twrnc` | Tailwind-style utility classes |
55
+
56
+ ---
57
+
58
+ ## Important: native modules & Expo
59
+
60
+ - **Rebuild required** after install. This library uses native modules (`react-native-sound`, `react-native-video`, etc.).
61
+ - **Not compatible with Expo Go.** Use a [development build](https://docs.expo.dev/develop/development-builds/introduction/) or a bare React Native app.
62
+ - **iOS:** run `pod install` in the `ios` folder after adding dependencies.
63
+
64
+ ---
28
65
 
29
66
  ## Installation
30
67
 
68
+ ### 1. Install the package
69
+
31
70
  ```bash
32
71
  npm install movius-chats
33
72
  # or
34
73
  yarn add movius-chats
74
+ # or
75
+ bun install movius-chats
35
76
  ```
36
77
 
37
- ### Required Dependencies
78
+ ### 2. Install peer & native dependencies
38
79
 
39
- The following packages are required for movius-chats to function properly. Install them using npm or yarn:
80
+ These are required in **your app** (some are bundled as dependencies of `movius-chats`, but you must still link/native-build them in the host app):
40
81
 
41
82
  ```bash
42
- # Using npm
43
- npm install react-native-image-zoom-viewer react-native-reanimated react-native-sound react-native-svg react-native-video twrnc
44
-
45
- # Using yarn
46
- yarn add react-native-image-zoom-viewer react-native-reanimated react-native-sound react-native-svg react-native-video twrnc
83
+ npm install react-native-reanimated react-native-image-zoom-viewer react-native-sound react-native-svg react-native-video twrnc
84
+ # or
85
+ yarn add react-native-reanimated react-native-image-zoom-viewer react-native-sound react-native-svg react-native-video twrnc
86
+ #or
87
+ bun install react-native-reanimated react-native-image-zoom-viewer react-native-sound react-native-svg react-native-video twrnc
47
88
  ```
48
89
 
49
- ### Additional Setup
90
+ > `react-native-parsed-text` is pulled in transitively; no extra install step unless your bundler requires it.
91
+
92
+ ### 3. Configure Reanimated
50
93
 
51
- For react-native-reanimated, add this line to your `babel.config.js`:
94
+ Add the Reanimated Babel plugin **last** in `babel.config.js`:
52
95
 
53
96
  ```javascript
54
97
  module.exports = {
55
- plugins: ['react-native-reanimated/plugin'],
98
+ presets: ['module:metro-react-native-babel-preset'],
99
+ plugins: [
100
+ // ...other plugins
101
+ 'react-native-reanimated/plugin',
102
+ ],
56
103
  };
57
104
  ```
58
105
 
59
- ### Post-Installation Steps
106
+ ### 4. Configure react-native-sound (recommended)
60
107
 
61
- After installing this package and its dependencies:
108
+ **iOS** enable playback in silent mode (optional, in `AppDelegate`):
62
109
 
63
- 1. **For React Native CLI Projects**:
110
+ ```objc
111
+ #import <AVFoundation/AVFoundation.h>
64
112
 
65
- ```bash
66
- npx pod-install # For iOS
67
- npx react-native run-android # Rebuild for Android
68
- npx react-native run-ios # Rebuild for iOS
69
- ```
113
+ // inside didFinishLaunchingWithOptions:
114
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
115
+ ```
116
+
117
+ **Android** — ensure internet permission if loading remote audio URLs in `AndroidManifest.xml`:
70
118
 
71
- 2. **For Expo Projects**:
72
- ```bash
73
- npx expo prebuild # Generate native code
74
- npx expo run:android # Build and run on Android
75
- npx expo run:ios # Build and run on iOS
76
- ```
119
+ ```xml
120
+ <uses-permission android:name="android.permission.INTERNET" />
121
+ ```
77
122
 
78
- ## Basic Usage
123
+ ### 5. Rebuild the native app
79
124
 
80
- ```typescript
125
+ **React Native CLI:**
126
+
127
+ ```bash
128
+ cd ios && pod install && cd ..
129
+ npx react-native run-ios
130
+ npx react-native run-android
131
+ ```
132
+
133
+ **Expo (development build):**
134
+
135
+ ```bash
136
+ npx expo prebuild
137
+ npx expo run:ios
138
+ # or
139
+ npx expo run:android
140
+ ```
141
+
142
+ After native dependency updates:
143
+
144
+ ```bash
145
+ npx expo prebuild --clean
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Quick start
151
+
152
+ ```tsx
153
+ import React, { useState } from 'react';
154
+ import { SafeAreaView } from 'react-native';
81
155
  import ChatScreen from 'movius-chats';
82
- import { Message } from 'movius-chats/lib/typescript/types';
83
- import { useState } from 'react';
156
+ import type { Message } from 'movius-chats/lib/typescript/types';
84
157
 
85
- const App = () => {
158
+ export default function App() {
86
159
  const [messages, setMessages] = useState<Message[]>([]);
160
+ const currentUserId = 'user-1';
87
161
 
88
- const handleSendMessage = (message: Omit<Message, "id" | "time" | "status">) => {
89
- // Handle sending message
162
+ const handleSendMessage = (
163
+ payload: Omit<Message, 'id' | 'time' | 'status'>
164
+ ) => {
90
165
  const newMessage: Message = {
91
- ...message,
92
- id: Date.now().toString(),
93
- time: new Date().toLocaleTimeString(),
94
- status: 'sent'
166
+ ...payload,
167
+ id: String(Date.now()),
168
+ time: new Date().toLocaleTimeString([], {
169
+ hour: '2-digit',
170
+ minute: '2-digit',
171
+ }),
172
+ status: 'sent',
95
173
  };
96
- setMessages(prev => [newMessage, ...prev]);
174
+ // Newest first — see "Message list ordering"
175
+ setMessages((prev) => [newMessage, ...prev]);
97
176
  };
98
177
 
99
178
  return (
100
- <ChatScreen
101
- messages={messages}
102
- currentUserId="user123"
103
- onSendMessage={handleSendMessage}
104
- showAvatars
105
- />
179
+ <SafeAreaView style={{ flex: 1 }}>
180
+ <ChatScreen
181
+ messages={messages}
182
+ currentUserId={currentUserId}
183
+ onSendMessage={handleSendMessage}
184
+ placeholder="Type a message..."
185
+ showAvatars
186
+ showBubbleTail
187
+ showMessageStatus
188
+ showEmojiButton
189
+ showAttachmentsButton
190
+ showCameraButton
191
+ showVoiceRecordButton
192
+ />
193
+ </SafeAreaView>
106
194
  );
195
+ }
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Message data model
201
+
202
+ Each item in `messages` must match the `Message` interface:
203
+
204
+ | Field | Type | Required | Description |
205
+ |-------|------|----------|-------------|
206
+ | `id` | `string` | Yes | Unique message id |
207
+ | `senderId` | `string` | Yes | User id of the sender |
208
+ | `time` | `string` | Yes | Display time (e.g. `"2:30 PM"`) — you format this |
209
+ | `status` | `'sent' \| 'delivered' \| 'read'` | Yes | Delivery state (shown only for current user) |
210
+ | `text` | `string` | No | Plain text; URLs are auto-linked |
211
+ | `image` | `string` | No | Image URI |
212
+ | `video` | `string` | No | Video URI |
213
+ | `audio` | `string` | No | Audio file URI |
214
+ | `senderName` | `string` | No | Shown when `showUserNames` is true |
215
+ | `senderAvatar` | `string` | No | Avatar image URI; falls back to first letter of `senderName` |
216
+
217
+ A message can combine fields (e.g. text + image), but typically you use one primary content type per bubble.
218
+
219
+ ```typescript
220
+ import type { Message } from 'movius-chats/lib/typescript/types';
221
+
222
+ const example: Message = {
223
+ id: '1',
224
+ senderId: 'user-2',
225
+ senderName: 'Alex',
226
+ senderAvatar: 'https://example.com/avatar.jpg',
227
+ text: 'Check this out https://example.com',
228
+ time: '10:42 AM',
229
+ status: 'read',
107
230
  };
108
231
  ```
109
232
 
110
- ## Props
111
-
112
- ### Core Props
113
-
114
- | Prop | Type | Required | Description |
115
- | ------------------ | ------------------------------------------------------------ | -------- | ---------------------------------------------------------------------- |
116
- | messages | Message[] | Yes | Array of message objects to display |
117
- | currentUserId | string | Yes | ID of the current user |
118
- | onSendMessage | (message: Omit<Message, "id" \| "time" \| "status">) => void | Yes | Callback when a message is sent |
119
- | onMessageLongPress | (message: Message) => void | No | Callback for long-pressing a message |
120
- | onAttachmentPress | () => void | No | Callback for attachment button press |
121
- | onAudioRecordStart | () => void | No | Callback when audio recording starts |
122
- | onAudioRecordEnd | () => void | No | Callback when audio recording ends |
123
- | onCameraPress | () => void | No | Callback for camera button press |
124
- | onTypingStart | () => void | No | Callback when user starts typing |
125
- | onTypingEnd | () => void | No | Callback when user stops typing |
126
- | placeholder | string | No | Input placeholder text |
127
- | typingUsers | Array<{ id: string; avatar: string; name: string }> | No | List of users who are typing |
233
+ ---
234
+
235
+ ## Message list ordering
236
+
237
+ `ChatScreen` uses an **inverted** `FlatList`. Put the **newest message at index `0`** of the `messages` array:
238
+
239
+ ```typescript
240
+ setMessages((prev) => [newMessage, ...prev]); // correct
241
+ ```
242
+
243
+ Older messages sit at higher indices and appear higher on screen.
244
+
245
+ **Grouping:** consecutive messages from the same sender share bubble styling; avatars and bubble tails show on the first message of a sequence (`isFirstInSequence`).
246
+
247
+ ---
248
+
249
+ ## ChatScreen API
250
+
251
+ `ChatScreen` is the **default export** from `movius-chats`. It wraps your chat in `AudioProvider` + `ChatProvider` and renders the message list, typing indicator, input (or custom input), and full-screen media viewer.
252
+
253
+ ### Core props
254
+
255
+ | Prop | Type | Required | Description |
256
+ |------|------|----------|-------------|
257
+ | `messages` | `Message[]` | Yes | Messages to render (newest first) |
258
+ | `currentUserId` | `string` | Yes | Logged-in user id; used for bubble alignment & status |
259
+ | `onSendMessage` | `(msg: Omit<Message, 'id' \| 'time' \| 'status'>) => void` | Yes | Fired when user taps send with text and/or `previewData` |
260
+ | `onMessageLongPress` | `(message: Message) => void` | No | Long-press on a bubble (reply, delete, etc.) |
261
+ | `placeholder` | `string` | No | Input placeholder (default: `"Message"`) |
262
+
263
+ ### Feature flags
264
+
265
+ All flags below default to **falsy** (hidden) unless you pass `true`:
266
+
267
+ | Prop | Description |
268
+ |------|-------------|
269
+ | `showAvatars` | Avatar (or initial) on received messages & typing row |
270
+ | `showUserNames` | Sender name above received bubbles |
271
+ | `showBubbleTail` | WhatsApp-style tail on first bubble in a sequence |
272
+ | `showMessageStatus` | Timestamp + checkmarks for sent messages |
273
+ | `showEmojiButton` | Emoji button in input (UI only; wire your own picker) |
274
+ | `showAttachmentsButton` | Paperclip → calls `onAttachmentPress` |
275
+ | `showCameraButton` | Camera icon when input is empty → `onCameraPress` |
276
+ | `showVoiceRecordButton` | Mic when input empty; send when text present |
277
+
278
+ ### Input & typing
279
+
280
+ | Prop | Type | Description |
281
+ |------|------|-------------|
282
+ | `onTypingStart` | `() => void` | Called when input has non-empty text |
283
+ | `onTypingEnd` | `() => void` | Called when input is cleared |
284
+ | `onAttachmentPress` | `() => void` | User tapped attachment — open document picker, etc. |
285
+ | `onCameraPress` | `() => void` | User tapped camera — open camera / image picker |
286
+ | `onAudioRecordStart` | `() => void` | Mic press / long-press start |
287
+ | `onAudioRecordEnd` | `() => void` | Mic release — upload recorded audio and append to messages |
288
+ | `typingUsers` | `Array<{ id: string; avatar: string; name: string }>` | Users currently typing (excludes `currentUserId` in UI) |
289
+
290
+ **Note:** Built-in `onSendMessage` from the default input only includes `{ text, senderId }`. Recording, camera, and file picking are **intentionally delegated** to your app via the callbacks above.
291
+
292
+ ### Attachment preview
293
+
294
+ Show a file/image/video preview above the input before sending:
295
+
296
+ | Prop | Type | Description |
297
+ |------|------|-------------|
298
+ | `previewData` | `{ uri: string; type: string; name: string }` | MIME type in `type` (e.g. `image/jpeg`, `video/mp4`, `application/pdf`) |
299
+ | `closePreview` | `() => void` | Clear preview when user taps X |
300
+
301
+ When `previewData` is set, send is enabled even if text is empty. Your `onSendMessage` handler should read `previewData` from closure/state and attach `image`, `video`, or file metadata to the outgoing message.
128
302
 
129
303
  ### Theming
130
304
 
131
- The component supports extensive theming through the `theme` prop:
305
+ Pass a `theme` object to customize colors, typography, and styles. All keys are optional.
132
306
 
133
307
  ```typescript
134
- theme?: {
135
- colors?: {
136
- sentMessageTailColor?: string;
137
- receivedMessageTailColor?: string;
138
- timestamp?: string;
139
- inputsIconsColor?: string;
140
- sendIconsColor?: string;
141
- placeholderTextColor?: string;
142
- audioPlayIconColor?: string;
143
- audioPauseIconColor?: string;
144
- videoPlayIconColor?: string;
145
- };
146
- bubbleStyle?: {
147
- sent?: ViewStyle;
148
- received?: ViewStyle;
149
- avatarTextStyle?: TextStyle;
150
- userNameStyle?: TextStyle;
151
- avatarImageStyle?: ImageStyle;
152
- typingContainerStyle?: ViewStyle;
153
- additionalTypingUsersContainerStyle?: ViewStyle;
154
- additionalTypingUsersTextStyle?: TextStyle;
155
- };
156
- messageStyle?: {
157
- sentTextStyle?: TextStyle;
158
- receivedTextStyle?: TextStyle;
159
- audioPlayButtonStyle?: ViewStyle;
160
- audioKnobStyle?: ViewStyle;
161
- progressBarStyle?: ViewStyle;
162
- activeProgressBarStyle?: ViewStyle;
163
- audioDurationStyle?: TextStyle;
164
- };
165
- inputStyles?: {
166
- inputSectionContainerStyle?: ViewStyle;
167
- inputContainerStyle?: ViewStyle;
168
- sendButtonStyle?: ViewStyle;
169
- };
170
- filePreviewStyle?: {
171
- root?: ViewStyle;
172
- container?: ViewStyle;
173
- iconContainer?: ViewStyle;
174
- nameContainer?: ViewStyle;
175
- text?: TextStyle;
176
- };
308
+ theme?: {
309
+ fontFamily?: string;
310
+
311
+ colors?: {
312
+ sentMessageTailColor?: string;
313
+ receivedMessageTailColor?: string;
314
+ timestamp?: string;
315
+ inputsIconsColor?: string;
316
+ sendIconsColor?: string;
317
+ placeholderTextColor?: string;
318
+ inputTextColor?: string;
319
+ audioPlayIconColor?: string;
320
+ audioPauseIconColor?: string;
321
+ videoPlayIconColor?: string;
322
+ sentIconColor?: string;
323
+ deliveredIconColor?: string;
324
+ readIconColor?: string;
325
+ };
326
+
327
+ sizes?: {
328
+ inputIconSize?: string; // twrnc class, e.g. 'h-6 w-6'
329
+ };
330
+
331
+ bubbleStyle?: {
332
+ sent?: ViewStyle;
333
+ received?: ViewStyle;
334
+ avatarTextStyle?: TextStyle;
335
+ userNameStyle?: TextStyle;
336
+ avatarImageStyle?: ImageStyle;
337
+ typingContainerStyle?: ViewStyle;
338
+ additionalTypingUsersContainerStyle?: ViewStyle;
339
+ additionalTypingUsersTextStyle?: TextStyle;
340
+ };
341
+
342
+ messageStyle?: {
343
+ sentTextStyle?: TextStyle;
344
+ receivedTextStyle?: TextStyle;
345
+ audioPlayButtonStyle?: ViewStyle;
346
+ audioKnobStyle?: ViewStyle;
347
+ progressBarStyle?: ViewStyle;
348
+ activeProgressBarStyle?: ViewStyle;
349
+ audioDurationStyle?: TextStyle;
177
350
  };
351
+
352
+ inputStyles?: {
353
+ inputSectionContainerStyle?: ViewStyle;
354
+ inputContainerStyle?: ViewStyle;
355
+ sendButtonStyle?: ViewStyle;
356
+ };
357
+
358
+ filePreviewStyle?: {
359
+ root?: ViewStyle;
360
+ container?: ViewStyle;
361
+ iconContainer?: ViewStyle;
362
+ nameContainer?: ViewStyle;
363
+ text?: TextStyle;
364
+ };
365
+ }
178
366
  ```
179
367
 
180
- ### Custom Components
368
+ Default bubble colors (before `theme` overrides): sent ≈ green (`bg-green-500`), received ≈ white.
181
369
 
182
- | Prop | Type | Description |
183
- | ---------------------------- | ------------------------------------ | -------------------------- | ------------------------------- |
184
- | renderCustomInput | () => React.ReactNode | Custom input component |
185
- | renderCustomVideoBubbleError | () => React.ReactNode | Custom video error display |
186
- | renderCustomTyping | () => React.ReactNode | Custom typing indicator |
187
- | CustomEmojiIcon | () => React.ReactNode | Custom emoji picker icon |
188
- | CustomAttachmentIcon | () => React.ReactNode | Custom attachment icon |
189
- | CustomCameraIcon | () => React.ReactNode | Custom camera icon |
190
- | CustomSendIcon | () => React.ReactNode | Custom send button icon |
191
- | CustomMicrophoneIcon | () => React.ReactNode | Custom microphone icon |
192
- | CustomPlayIcon | () => React.ReactNode | Custom play icon |
193
- | CustomPauseIcon | () => React.ReactNode | Custom pause icon |
194
- | CustomFileIcon | React.ComponentType<{ style?: any }> | Custom file icon |
195
- | CustomImagePreview | React.ComponentType<{ uri: string }> | Custom image preview component. |
196
- | CustomVideoPreview | React.ComponentType<{ uri: string }> | Custom video preview component. |
370
+ ### Custom components & icons
197
371
 
372
+ | Prop | Type | Description |
373
+ |------|------|-------------|
374
+ | `renderCustomInput` | `() => React.ReactNode` | Replace entire input bar |
375
+ | `renderCustomTyping` | `() => React.ReactNode` | Replace typing bubble content |
376
+ | `renderCustomVideoBubbleError` | `() => React.ReactNode` | Replace inline video error UI |
377
+ | `CustomEmojiIcon` | `() => React.ReactNode` | Emoji button |
378
+ | `CustomAttachmentIcon` | `() => React.ReactNode` | Attachment button |
379
+ | `CustomCameraIcon` | `() => React.ReactNode` | Camera button |
380
+ | `CustomSendIcon` | `() => React.ReactNode` | Send button |
381
+ | `CustomMicrophoneIcon` | `() => React.ReactNode` | Microphone button |
382
+ | `CustomPlayIcon` | `() => React.ReactNode` | Play icon in video/audio bubbles |
383
+ | `CustomPauseIcon` | `() => React.ReactNode` | Pause icon in audio player |
384
+ | `CustomFileIcon` | `React.ComponentType<{ style?: any }>` | Generic file preview icon |
385
+ | `CustomImagePreview` | `React.ComponentType<{ uri: string }>` | Image attachment preview |
386
+ | `CustomVideoPreview` | `React.ComponentType<{ uri: string }>` | Video attachment preview |
198
387
 
199
- ## Advanced Usage
388
+ ---
200
389
 
201
- ### Custom Theme Example
390
+ ## Usage examples
391
+
392
+ ### Text messages
393
+
394
+ ```tsx
395
+ onSendMessage={({ text, senderId }) => {
396
+ addMessage({ text, senderId, status: 'sent' });
397
+ }}
398
+ ```
399
+
400
+ ### Media messages
401
+
402
+ Add URIs when building the `Message` object (usually after upload):
403
+
404
+ ```tsx
405
+ const imageMessage: Message = {
406
+ id: '2',
407
+ senderId: currentUserId,
408
+ image: 'https://cdn.example.com/photo.jpg',
409
+ time: '11:00 AM',
410
+ status: 'delivered',
411
+ };
412
+
413
+ const audioMessage: Message = {
414
+ id: '3',
415
+ senderId: 'user-2',
416
+ audio: 'file:///path/to/recording.m4a',
417
+ time: '11:05 AM',
418
+ status: 'read',
419
+ };
420
+ ```
421
+
422
+ Tap image/video bubbles to open the built-in **MediaViewer** (pinch-zoom for images, native controls for video).
423
+
424
+ ### Typing indicators
425
+
426
+ ```tsx
427
+ const [typingUsers, setTypingUsers] = useState<
428
+ { id: string; avatar: string; name: string }[]
429
+ >([]);
202
430
 
203
- ```typescript
204
431
  <ChatScreen
205
- messages={messages}
206
- currentUserId="user123"
207
- onSendMessage={handleSendMessage}
432
+ typingUsers={typingUsers}
433
+ onTypingStart={() => notifyServer('typing-start')}
434
+ onTypingEnd={() => notifyServer('typing-end')}
435
+ // ...
436
+ />
437
+ ```
438
+
439
+ When your socket receives “user X is typing”, push into `typingUsers`. The component shows up to two avatars plus a “+N” badge.
440
+
441
+ ### Attachments & camera (parent-controlled)
442
+
443
+ ```tsx
444
+ import { launchImageLibrary } from 'react-native-image-picker';
445
+
446
+ <ChatScreen
447
+ showAttachmentsButton
448
+ showCameraButton
449
+ onAttachmentPress={async () => {
450
+ const result = await launchImageLibrary({ mediaType: 'mixed' });
451
+ }}
452
+ onCameraPress={async () => {
453
+ // open camera, then add message with image/video URI
454
+ }}
455
+ onAudioRecordStart={() => {
456
+ // start native recorder
457
+ }}
458
+ onAudioRecordEnd={async () => {
459
+ // stop recorder, upload, then:
460
+ // addMessage({ audio: uploadedUrl, senderId: currentUserId, ... });
461
+ }}
462
+ />
463
+ ```
464
+
465
+ ### Attachment preview before send
466
+
467
+ ```tsx
468
+ const [previewData, setPreviewData] = useState<{
469
+ uri: string;
470
+ type: string;
471
+ name: string;
472
+ } | null>(null);
473
+
474
+ <ChatScreen
475
+ previewData={previewData ?? undefined}
476
+ closePreview={() => setPreviewData(null)}
477
+ onAttachmentPress={async () => {
478
+ const asset = await pickDocument();
479
+ setPreviewData({
480
+ uri: asset.uri,
481
+ type: asset.type ?? 'application/octet-stream',
482
+ name: asset.name ?? 'file',
483
+ });
484
+ }}
485
+ onSendMessage={({ text, senderId }) => {
486
+ const msg: Message = {
487
+ id: String(Date.now()),
488
+ senderId,
489
+ text: text || undefined,
490
+ image: previewData?.type.startsWith('image/')
491
+ ? previewData.uri
492
+ : undefined,
493
+ video: previewData?.type.startsWith('video/')
494
+ ? previewData.uri
495
+ : undefined,
496
+ time: formatTime(new Date()),
497
+ status: 'sent',
498
+ };
499
+ setMessages((prev) => [msg, ...prev]);
500
+ setPreviewData(null);
501
+ }}
502
+ />
503
+ ```
504
+
505
+ ### Custom theme
506
+
507
+ ```tsx
508
+ <ChatScreen
208
509
  theme={{
510
+ fontFamily: 'Inter-Regular',
209
511
  colors: {
210
512
  sentMessageTailColor: '#007AFF',
211
513
  receivedMessageTailColor: '#E9E9EB',
212
514
  timestamp: '#8E8E93',
515
+ readIconColor: '#34C759',
516
+ inputTextColor: '#000000',
213
517
  },
214
518
  bubbleStyle: {
215
- sent: {
216
- backgroundColor: '#007AFF',
217
- borderRadius: 20,
218
- },
219
- received: {
220
- backgroundColor: '#E9E9EB',
221
- borderRadius: 20,
222
- },
519
+ sent: { backgroundColor: '#007AFF' },
520
+ received: { backgroundColor: '#E9E9EB' },
521
+ },
522
+ messageStyle: {
523
+ sentTextStyle: { color: '#FFFFFF' },
524
+ receivedTextStyle: { color: '#000000' },
223
525
  },
224
- filePreviewStyle: {
225
- root: { top: 10, left: 10 },
226
- container: { backgroundColor: '#f0f0f0', borderRadius: 16 },
227
- iconContainer: { backgroundColor: '#333' },
228
- nameContainer: { backgroundColor: '#eee' },
229
- text: { color: 'red', fontWeight: 'bold' },
526
+ inputStyles: {
527
+ sendButtonStyle: { backgroundColor: '#007AFF' },
230
528
  },
231
529
  }}
530
+ // ...
531
+ />
532
+ ```
533
+
534
+ ### Custom input bar
535
+
536
+ ```tsx
537
+ <ChatScreen
538
+ renderCustomInput={() => <MyComposer />}
539
+ // Still use messages / currentUserId / onSendMessage via your own state
540
+ />
541
+ ```
542
+
543
+ When using `renderCustomInput`, you are responsible for calling your send logic; the default `ChatInput` is not mounted.
544
+
545
+ ### Long-press actions
546
+
547
+ ```tsx
548
+ <ChatScreen
549
+ onMessageLongPress={(message) => {
550
+ }}
232
551
  />
233
552
  ```
234
553
 
235
- ### Expo Usage
554
+ ---
555
+
556
+ ## TypeScript
557
+
558
+ The main export is the default `ChatScreen` component. Types live in the build output:
236
559
 
237
- If you're using Expo, follow these steps:
560
+ ```typescript
561
+ import ChatScreen from 'movius-chats';
562
+ import type {
563
+ Message,
564
+ ChatScreenProps,
565
+ } from 'movius-chats/lib/typescript/types';
566
+ ```
238
567
 
239
- 1. Create a development build of your app:
568
+ `ChatScreenProps` is the full props interface for `ChatScreen`.
240
569
 
241
- ```bash
242
- npx expo prebuild
243
- ```
570
+ ---
244
571
 
245
- 2. Run on your desired platform:
572
+ ## Architecture overview
246
573
 
247
- ```bash
248
- npx expo run:android
249
- # or
250
- npx expo run:ios
251
- ```
574
+ ```
575
+ ChatScreen
576
+ ├── AudioProvider # one audio message plays at a time
577
+ ├── ChatProvider # props, theme, media viewer state
578
+ ├── FlatList (inverted) # ChatBubble per message
579
+ │ └── ListHeaderComponent → TypingIndicator
580
+ ├── ChatInput (optional) # text, icons, file preview
581
+ └── MediaViewer (Modal) # full-screen image / video
582
+ ```
252
583
 
253
- 3. For subsequent updates to the native modules, you'll need to rebuild:
254
- ```bash
255
- npx expo prebuild --clean
256
- ```
584
+ Internal pieces (not exported from the package entry, but useful when reading source):
257
585
 
258
- ### Performance Considerations
586
+ - **ChatBubble** — layout, tail, avatar, `MessageContent`, `MessageStatus`
587
+ - **MessageContent** — image, video thumbnail, `AudioPlayer`, parsed text
588
+ - **AudioPlayer** — `react-native-sound` + Reanimated scrubber
589
+ - **FilePreview** — pre-send attachment chip above input
259
590
 
260
- - Messages are rendered using `FlatList` for optimal performance
261
- - Avatar images are cached automatically
262
- - Media messages use lazy loading
263
- - Typing indicators are debounced
591
+ ---
264
592
 
265
593
  ## Troubleshooting
266
594
 
267
- ### Common Issues
595
+ | Issue | What to try |
596
+ |-------|-------------|
597
+ | `Native module not found` | Rebuild iOS/Android after install; run `pod install` on iOS |
598
+ | Crashes in Expo Go | Use a development build; native modules are not in Expo Go |
599
+ | Audio silent on iOS | Set `AVAudioSession` category to playback (see installation) |
600
+ | Video/audio won’t load | Check URI scheme (`https://`, `file://`) and Android `INTERNET` permission |
601
+ | Reanimated worklet errors | Ensure `react-native-reanimated/plugin` is **last** in Babel config |
602
+ | Types not found | Import from `movius-chats/lib/typescript/types` |
603
+ | Messages appear in wrong order | Newest item must be `messages[0]` (inverted list) |
604
+ | Icons/buttons missing | Pass feature flags (`showEmojiButton`, etc.) — they default to off |
268
605
 
269
- - **"Native module not found" error**: Ensure you've rebuilt your app after installing the package.
270
- - **Crashes in Expo Go**: This package uses native modules that are not compatible with Expo Go. Use a development build instead.
271
- - **Audio/Video not working**: Check that you've installed all required dependencies and rebuilt the app.
606
+ ---
272
607
 
273
608
  ## Contributing
274
609
 
275
- We welcome contributions! Please see our contributing guide for details.
276
-
277
- ## License
610
+ Issues and pull requests are welcome on [GitHub](https://github.com/David-Atueyi/Movius-Chats/issues).
278
611
 
279
- MIT
612
+ ---
280
613
 
281
- ## Support
614
+ ## License
282
615
 
283
- For issues and feature requests, please file an issue on the GitHub repository.
616
+ ISC see [package.json](./package.json).