movius-chats 1.3.0 → 1.3.2

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,691 @@
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
+ ```
70
116
 
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
- ```
117
+ **Android** ensure internet permission if loading remote audio URLs in `AndroidManifest.xml`:
77
118
 
78
- ## Basic Usage
119
+ ```xml
120
+ <uses-permission android:name="android.permission.INTERNET" />
121
+ ```
79
122
 
80
- ```typescript
123
+ ### 5. Rebuild the native app
124
+
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
+ | `keyboardVerticalOffset` | `number` | No | Subtract from keyboard height (header + tab bar + safe area). Default: `0` |
263
+ | `disableKeyboardAvoiding` | `boolean` | No | Set `true` if your screen already handles the keyboard |
264
+
265
+ ### Feature flags
266
+
267
+ All flags below default to **falsy** (hidden) unless you pass `true`:
268
+
269
+ | Prop | Description |
270
+ |------|-------------|
271
+ | `showAvatars` | Avatar (or initial) on received messages & typing row |
272
+ | `showUserNames` | Sender name above received bubbles |
273
+ | `showBubbleTail` | WhatsApp-style tail on first bubble in a sequence |
274
+ | `showMessageStatus` | Timestamp + checkmarks for sent messages |
275
+ | `showEmojiButton` | Emoji button in input (UI only; wire your own picker) |
276
+ | `showAttachmentsButton` | Paperclip → calls `onAttachmentPress` |
277
+ | `showCameraButton` | Camera icon when input is empty → `onCameraPress` |
278
+ | `showVoiceRecordButton` | Mic when input empty; send when text present |
279
+
280
+ ### Input & typing
281
+
282
+ | Prop | Type | Description |
283
+ |------|------|-------------|
284
+ | `onTypingStart` | `() => void` | Called when input has non-empty text |
285
+ | `onTypingEnd` | `() => void` | Called when input is cleared |
286
+ | `onAttachmentPress` | `() => void` | User tapped attachment — open document picker, etc. |
287
+ | `onCameraPress` | `() => void` | User tapped camera — open camera / image picker |
288
+ | `onAudioRecordStart` | `() => void` | Mic press / long-press start |
289
+ | `onAudioRecordEnd` | `() => void` | Mic release — upload recorded audio and append to messages |
290
+ | `typingUsers` | `Array<{ id: string; avatar: string; name: string }>` | Users currently typing (excludes `currentUserId` in UI) |
291
+
292
+ **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.
293
+
294
+ ### Attachment preview
295
+
296
+ Show a file/image/video preview above the input before sending:
297
+
298
+ | Prop | Type | Description |
299
+ |------|------|-------------|
300
+ | `previewData` | `{ uri: string; type: string; name: string }` | MIME type in `type` (e.g. `image/jpeg`, `video/mp4`, `application/pdf`) |
301
+ | `closePreview` | `() => void` | Clear preview when user taps X |
302
+
303
+ 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
304
 
129
305
  ### Theming
130
306
 
131
- The component supports extensive theming through the `theme` prop:
307
+ Pass a `theme` object to customize colors, typography, and styles. All keys are optional.
132
308
 
133
309
  ```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;
310
+ theme?: {
311
+ fontFamily?: string;
312
+
313
+ colors?: {
314
+ sentMessageTailColor?: string;
315
+ receivedMessageTailColor?: string;
316
+ timestamp?: string;
317
+ inputsIconsColor?: string;
318
+ sendIconsColor?: string;
319
+ placeholderTextColor?: string;
320
+ inputTextColor?: string;
321
+ audioPlayIconColor?: string;
322
+ audioPauseIconColor?: string;
323
+ videoPlayIconColor?: string;
324
+ sentIconColor?: string;
325
+ deliveredIconColor?: string;
326
+ readIconColor?: string;
327
+ };
328
+
329
+ sizes?: {
330
+ /** Twrnc classes (`"h-8 w-8"`) or pixels (`28`) for input-bar icons */
331
+ inputIconSize?: string | number;
176
332
  };
333
+
334
+ bubbleStyle?: {
335
+ sent?: ViewStyle;
336
+ received?: ViewStyle;
337
+ avatarTextStyle?: TextStyle;
338
+ userNameStyle?: TextStyle;
339
+ avatarImageStyle?: ImageStyle;
340
+ typingContainerStyle?: ViewStyle;
341
+ additionalTypingUsersContainerStyle?: ViewStyle;
342
+ additionalTypingUsersTextStyle?: TextStyle;
177
343
  };
344
+
345
+ messageStyle?: {
346
+ sentTextStyle?: TextStyle;
347
+ receivedTextStyle?: TextStyle;
348
+ audioPlayButtonStyle?: ViewStyle;
349
+ audioKnobStyle?: ViewStyle;
350
+ progressBarStyle?: ViewStyle;
351
+ activeProgressBarStyle?: ViewStyle;
352
+ audioDurationStyle?: TextStyle;
353
+ };
354
+
355
+ inputStyles?: {
356
+ inputSectionContainerStyle?: ViewStyle;
357
+ inputContainerStyle?: ViewStyle;
358
+ sendButtonStyle?: ViewStyle;
359
+ };
360
+
361
+ filePreviewStyle?: {
362
+ root?: ViewStyle;
363
+ container?: ViewStyle;
364
+ iconContainer?: ViewStyle;
365
+ nameContainer?: ViewStyle;
366
+ text?: TextStyle;
367
+ };
368
+ }
178
369
  ```
179
370
 
180
- ### Custom Components
371
+ Default bubble colors (before `theme` overrides): sent ≈ green (`bg-green-500`), received ≈ white.
181
372
 
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. |
373
+ #### Custom font (`theme.fontFamily`)
197
374
 
375
+ `fontFamily` applies to **all text** in the package (messages, timestamps, typing label, input, file names, errors).
198
376
 
199
- ## Advanced Usage
377
+ **You must load the font in your app first** — the library only sets the `fontFamily` style name.
200
378
 
201
- ### Custom Theme Example
379
+ **Expo:**
202
380
 
203
- ```typescript
381
+ ```tsx
382
+ import { useFonts } from 'expo-font';
383
+
384
+ export default function App() {
385
+ const [loaded] = useFonts({
386
+ InterRegular: require('./assets/fonts/Inter-Regular.ttf'),
387
+ });
388
+
389
+ if (!loaded) return null;
390
+
391
+ return (
392
+ <ChatScreen
393
+ theme={{ fontFamily: 'InterRegular' }}
394
+ // ...
395
+ />
396
+ );
397
+ }
398
+ ```
399
+
400
+ **React Native CLI:** follow [react-native custom fonts](https://reactnative.dev/docs/custom-fonts) and use the exact font family name registered on each platform.
401
+
402
+ #### Input icon size (`theme.sizes.inputIconSize`)
403
+
404
+ Use either **pixels** (recommended) or **twrnc classes**:
405
+
406
+ ```tsx
204
407
  <ChatScreen
205
- messages={messages}
206
- currentUserId="user123"
207
- onSendMessage={handleSendMessage}
208
408
  theme={{
409
+ sizes: {
410
+ inputIconSize: 28, // 28×28 px — attachment, camera, emoji, send, mic
411
+ // inputIconSize: 'h-8 w-8', // alternative: tailwind classes via twrnc
412
+ },
413
+ }}
414
+ />
415
+ ```
416
+
417
+ #### Keyboard avoiding
418
+
419
+ The package lifts the chat when the keyboard opens (keyboard listeners + `KeyboardAvoidingView` on iOS).
420
+
421
+ 1. Wrap `ChatScreen` in a parent with `flex: 1` (e.g. `SafeAreaView style={{ flex: 1 }}`).
422
+ 2. Set `keyboardVerticalOffset` to your header + top inset (try `60`–`100` on iOS with a stack header):
423
+
424
+ ```tsx
425
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
426
+
427
+ const insets = useSafeAreaInsets();
428
+
429
+ <ChatScreen
430
+ keyboardVerticalOffset={insets.top + 44}
431
+ // ...
432
+ />
433
+ ```
434
+
435
+ 3. **Android (Expo):** in `app.json`:
436
+
437
+ ```json
438
+ "android": {
439
+ "softwareKeyboardLayoutMode": "resize"
440
+ }
441
+ ```
442
+
443
+ 4. If your navigator already uses `KeyboardAvoidingView`, pass `disableKeyboardAvoiding` to avoid double offset.
444
+
445
+ ### Custom components & icons
446
+
447
+ | Prop | Type | Description |
448
+ |------|------|-------------|
449
+ | `renderCustomInput` | `() => React.ReactNode` | Replace entire input bar |
450
+ | `renderCustomTyping` | `() => React.ReactNode` | Replace typing bubble content |
451
+ | `renderCustomVideoBubbleError` | `() => React.ReactNode` | Replace inline video error UI |
452
+ | `CustomEmojiIcon` | `() => React.ReactNode` | Emoji button |
453
+ | `CustomAttachmentIcon` | `() => React.ReactNode` | Attachment button |
454
+ | `CustomCameraIcon` | `() => React.ReactNode` | Camera button |
455
+ | `CustomSendIcon` | `() => React.ReactNode` | Send button |
456
+ | `CustomMicrophoneIcon` | `() => React.ReactNode` | Microphone button |
457
+ | `CustomPlayIcon` | `() => React.ReactNode` | Play icon in video/audio bubbles |
458
+ | `CustomPauseIcon` | `() => React.ReactNode` | Pause icon in audio player |
459
+ | `CustomFileIcon` | `React.ComponentType<{ style?: any }>` | Generic file preview icon |
460
+ | `CustomImagePreview` | `React.ComponentType<{ uri: string }>` | Image attachment preview |
461
+ | `CustomVideoPreview` | `React.ComponentType<{ uri: string }>` | Video attachment preview |
462
+
463
+ ---
464
+
465
+ ## Usage examples
466
+
467
+ ### Text messages
468
+
469
+ ```tsx
470
+ onSendMessage={({ text, senderId }) => {
471
+ addMessage({ text, senderId, status: 'sent' });
472
+ }}
473
+ ```
474
+
475
+ ### Media messages
476
+
477
+ Add URIs when building the `Message` object (usually after upload):
478
+
479
+ ```tsx
480
+ const imageMessage: Message = {
481
+ id: '2',
482
+ senderId: currentUserId,
483
+ image: 'https://cdn.example.com/photo.jpg',
484
+ time: '11:00 AM',
485
+ status: 'delivered',
486
+ };
487
+
488
+ const audioMessage: Message = {
489
+ id: '3',
490
+ senderId: 'user-2',
491
+ audio: 'file:///path/to/recording.m4a',
492
+ time: '11:05 AM',
493
+ status: 'read',
494
+ };
495
+ ```
496
+
497
+ Tap image/video bubbles to open the built-in **MediaViewer** (pinch-zoom for images, native controls for video).
498
+
499
+ ### Typing indicators
500
+
501
+ ```tsx
502
+ const [typingUsers, setTypingUsers] = useState<
503
+ { id: string; avatar: string; name: string }[]
504
+ >([]);
505
+
506
+ <ChatScreen
507
+ typingUsers={typingUsers}
508
+ onTypingStart={() => notifyServer('typing-start')}
509
+ onTypingEnd={() => notifyServer('typing-end')}
510
+ // ...
511
+ />
512
+ ```
513
+
514
+ When your socket receives “user X is typing”, push into `typingUsers`. The component shows up to two avatars plus a “+N” badge.
515
+
516
+ ### Attachments & camera (parent-controlled)
517
+
518
+ ```tsx
519
+ import { launchImageLibrary } from 'react-native-image-picker';
520
+
521
+ <ChatScreen
522
+ showAttachmentsButton
523
+ showCameraButton
524
+ onAttachmentPress={async () => {
525
+ const result = await launchImageLibrary({ mediaType: 'mixed' });
526
+ }}
527
+ onCameraPress={async () => {
528
+ // open camera, then add message with image/video URI
529
+ }}
530
+ onAudioRecordStart={() => {
531
+ // start native recorder
532
+ }}
533
+ onAudioRecordEnd={async () => {
534
+ // stop recorder, upload, then:
535
+ // addMessage({ audio: uploadedUrl, senderId: currentUserId, ... });
536
+ }}
537
+ />
538
+ ```
539
+
540
+ ### Attachment preview before send
541
+
542
+ ```tsx
543
+ const [previewData, setPreviewData] = useState<{
544
+ uri: string;
545
+ type: string;
546
+ name: string;
547
+ } | null>(null);
548
+
549
+ <ChatScreen
550
+ previewData={previewData ?? undefined}
551
+ closePreview={() => setPreviewData(null)}
552
+ onAttachmentPress={async () => {
553
+ const asset = await pickDocument();
554
+ setPreviewData({
555
+ uri: asset.uri,
556
+ type: asset.type ?? 'application/octet-stream',
557
+ name: asset.name ?? 'file',
558
+ });
559
+ }}
560
+ onSendMessage={({ text, senderId }) => {
561
+ const msg: Message = {
562
+ id: String(Date.now()),
563
+ senderId,
564
+ text: text || undefined,
565
+ image: previewData?.type.startsWith('image/')
566
+ ? previewData.uri
567
+ : undefined,
568
+ video: previewData?.type.startsWith('video/')
569
+ ? previewData.uri
570
+ : undefined,
571
+ time: formatTime(new Date()),
572
+ status: 'sent',
573
+ };
574
+ setMessages((prev) => [msg, ...prev]);
575
+ setPreviewData(null);
576
+ }}
577
+ />
578
+ ```
579
+
580
+ ### Custom theme
581
+
582
+ ```tsx
583
+ <ChatScreen
584
+ theme={{
585
+ fontFamily: 'Inter-Regular',
209
586
  colors: {
210
587
  sentMessageTailColor: '#007AFF',
211
588
  receivedMessageTailColor: '#E9E9EB',
212
589
  timestamp: '#8E8E93',
590
+ readIconColor: '#34C759',
591
+ inputTextColor: '#000000',
213
592
  },
214
593
  bubbleStyle: {
215
- sent: {
216
- backgroundColor: '#007AFF',
217
- borderRadius: 20,
218
- },
219
- received: {
220
- backgroundColor: '#E9E9EB',
221
- borderRadius: 20,
222
- },
594
+ sent: { backgroundColor: '#007AFF' },
595
+ received: { backgroundColor: '#E9E9EB' },
596
+ },
597
+ messageStyle: {
598
+ sentTextStyle: { color: '#FFFFFF' },
599
+ receivedTextStyle: { color: '#000000' },
223
600
  },
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' },
601
+ inputStyles: {
602
+ sendButtonStyle: { backgroundColor: '#007AFF' },
230
603
  },
231
604
  }}
605
+ // ...
232
606
  />
233
607
  ```
234
608
 
235
- ### Expo Usage
609
+ ### Custom input bar
236
610
 
237
- If you're using Expo, follow these steps:
611
+ ```tsx
612
+ <ChatScreen
613
+ renderCustomInput={() => <MyComposer />}
614
+ // Still use messages / currentUserId / onSendMessage via your own state
615
+ />
616
+ ```
617
+
618
+ When using `renderCustomInput`, you are responsible for calling your send logic; the default `ChatInput` is not mounted.
619
+
620
+ ### Long-press actions
621
+
622
+ ```tsx
623
+ <ChatScreen
624
+ onMessageLongPress={(message) => {
625
+ }}
626
+ />
627
+ ```
628
+
629
+ ---
630
+
631
+ ## TypeScript
238
632
 
239
- 1. Create a development build of your app:
633
+ The main export is the default `ChatScreen` component. Types live in the build output:
240
634
 
241
- ```bash
242
- npx expo prebuild
243
- ```
635
+ ```typescript
636
+ import ChatScreen from 'movius-chats';
637
+ import type {
638
+ Message,
639
+ ChatScreenProps,
640
+ } from 'movius-chats/lib/typescript/types';
641
+ ```
642
+
643
+ `ChatScreenProps` is the full props interface for `ChatScreen`.
644
+
645
+ ---
244
646
 
245
- 2. Run on your desired platform:
647
+ ## Architecture overview
246
648
 
247
- ```bash
248
- npx expo run:android
249
- # or
250
- npx expo run:ios
251
- ```
649
+ ```
650
+ ChatScreen
651
+ ├── AudioProvider # one audio message plays at a time
652
+ ├── ChatProvider # props, theme, media viewer state
653
+ ├── FlatList (inverted) # ChatBubble per message
654
+ │ └── ListHeaderComponent → TypingIndicator
655
+ ├── ChatInput (optional) # text, icons, file preview
656
+ └── MediaViewer (Modal) # full-screen image / video
657
+ ```
252
658
 
253
- 3. For subsequent updates to the native modules, you'll need to rebuild:
254
- ```bash
255
- npx expo prebuild --clean
256
- ```
659
+ Internal pieces (not exported from the package entry, but useful when reading source):
257
660
 
258
- ### Performance Considerations
661
+ - **ChatBubble** — layout, tail, avatar, `MessageContent`, `MessageStatus`
662
+ - **MessageContent** — image, video thumbnail, `AudioPlayer`, parsed text
663
+ - **AudioPlayer** — `react-native-sound` + Reanimated scrubber
664
+ - **FilePreview** — pre-send attachment chip above input
259
665
 
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
666
+ ---
264
667
 
265
668
  ## Troubleshooting
266
669
 
267
- ### Common Issues
670
+ | Issue | What to try |
671
+ |-------|-------------|
672
+ | `Native module not found` | Rebuild iOS/Android after install; run `pod install` on iOS |
673
+ | Crashes in Expo Go | Use a development build; native modules are not in Expo Go |
674
+ | Audio silent on iOS | Set `AVAudioSession` category to playback (see installation) |
675
+ | Video/audio won’t load | Check URI scheme (`https://`, `file://`) and Android `INTERNET` permission |
676
+ | Reanimated worklet errors | Ensure `react-native-reanimated/plugin` is **last** in Babel config |
677
+ | Types not found | Import from `movius-chats/lib/typescript/types` |
678
+ | Messages appear in wrong order | Newest item must be `messages[0]` (inverted list) |
679
+ | Icons/buttons missing | Pass feature flags (`showEmojiButton`, etc.) — they default to off |
268
680
 
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.
681
+ ---
272
682
 
273
683
  ## Contributing
274
684
 
275
- We welcome contributions! Please see our contributing guide for details.
685
+ Issues and pull requests are welcome on [GitHub](https://github.com/David-Atueyi/Movius-Chats/issues).
276
686
 
277
- ## License
687
+ ---
278
688
 
279
- MIT
280
-
281
- ## Support
689
+ ## License
282
690
 
283
- For issues and feature requests, please file an issue on the GitHub repository.
691
+ ISC see [package.json](./package.json).