movius-chats 1.2.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 +530 -176
- package/lib/commonjs/index.js +14 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +14 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/assets/Icons/CheckAllIcon.d.ts +5 -2
- package/lib/typescript/assets/Icons/CheckIcon.d.ts +5 -2
- package/lib/typescript/assets/Icons/FileIcon.d.ts +5 -0
- package/lib/typescript/assets/Icons/LoadingIcon.d.ts +6 -3
- package/lib/typescript/assets/Icons/XIcon.d.ts +1 -1
- package/lib/typescript/components/ChatInput/ChatInput.d.ts +2 -2
- package/lib/typescript/components/ChatInput/FilePreview.d.ts +4 -0
- package/lib/typescript/components/ChatInput/TruncateFileName.d.ts +7 -0
- package/lib/typescript/components/ChatInput/types.d.ts +18 -2
- package/lib/typescript/context/ChatContext.d.ts +2 -2
- package/lib/typescript/types/index.d.ts +32 -2
- package/package.json +8 -7
- package/src/assets/Icons/CheckAllIcon.tsx +6 -1
- package/src/assets/Icons/CheckIcon.tsx +6 -2
- package/src/assets/Icons/FileIcon.tsx +22 -0
- package/src/assets/Icons/LoadingIcon.tsx +38 -10
- package/src/assets/Icons/XIcon.tsx +7 -7
- package/src/components/ChatBubble/ChatBubble.tsx +2 -2
- package/src/components/ChatBubble/MessageContent.tsx +18 -7
- package/src/components/ChatBubble/MessageStatus.tsx +6 -3
- package/src/components/ChatInput/ChatInput.tsx +160 -120
- package/src/components/ChatInput/FilePreview.tsx +161 -0
- package/src/components/ChatInput/TruncateFileName.tsx +33 -0
- package/src/components/ChatInput/types.ts +8 -2
- package/src/components/MediaViewer/MediaViewer.tsx +4 -6
- package/src/components/TypingComponent/TypingIndicator.tsx +3 -6
- package/src/context/ChatContext.tsx +4 -4
- package/src/index.tsx +62 -50
- package/src/types/index.ts +22 -2
- package/lib/commonjs/assets/Icons/ArrowBack2RoundedIcon.js +0 -2
- package/lib/commonjs/assets/Icons/ArrowBack2RoundedIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/CameraIcon.js +0 -2
- package/lib/commonjs/assets/Icons/CameraIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/CheckAllIcon.js +0 -2
- package/lib/commonjs/assets/Icons/CheckAllIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/CheckIcon.js +0 -2
- package/lib/commonjs/assets/Icons/CheckIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/EmojiFunnySquareIcon.js +0 -2
- package/lib/commonjs/assets/Icons/EmojiFunnySquareIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/LoadingIcon.js +0 -2
- package/lib/commonjs/assets/Icons/LoadingIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/MicrophoneIcon.js +0 -2
- package/lib/commonjs/assets/Icons/MicrophoneIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/PaperClipIcon.js +0 -2
- package/lib/commonjs/assets/Icons/PaperClipIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/PaperPlaneIcon.js +0 -2
- package/lib/commonjs/assets/Icons/PaperPlaneIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/PauseIcon.js +0 -2
- package/lib/commonjs/assets/Icons/PauseIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/PlayIcon.js +0 -2
- package/lib/commonjs/assets/Icons/PlayIcon.js.map +0 -1
- package/lib/commonjs/assets/Icons/XIcon.js +0 -2
- package/lib/commonjs/assets/Icons/XIcon.js.map +0 -1
- package/lib/commonjs/components/AudioPlayer/AudioPlayer.js +0 -2
- package/lib/commonjs/components/AudioPlayer/AudioPlayer.js.map +0 -1
- package/lib/commonjs/components/ChatBubble/ChatBubble.js +0 -2
- package/lib/commonjs/components/ChatBubble/ChatBubble.js.map +0 -1
- package/lib/commonjs/components/ChatBubble/MessageContent.js +0 -2
- package/lib/commonjs/components/ChatBubble/MessageContent.js.map +0 -1
- package/lib/commonjs/components/ChatBubble/MessageStatus.js +0 -2
- package/lib/commonjs/components/ChatBubble/MessageStatus.js.map +0 -1
- package/lib/commonjs/components/ChatInput/ChatInput.js +0 -2
- package/lib/commonjs/components/ChatInput/ChatInput.js.map +0 -1
- package/lib/commonjs/components/MediaViewer/MediaViewer.js +0 -2
- package/lib/commonjs/components/MediaViewer/MediaViewer.js.map +0 -1
- package/lib/commonjs/components/TypingComponent/TypingIndicator.js +0 -2
- package/lib/commonjs/components/TypingComponent/TypingIndicator.js.map +0 -1
- package/lib/commonjs/context/AudioContext.js +0 -2
- package/lib/commonjs/context/AudioContext.js.map +0 -1
- package/lib/commonjs/context/ChatContext.js +0 -2
- package/lib/commonjs/context/ChatContext.js.map +0 -1
- package/lib/commonjs/utils/datefunc.js +0 -2
- package/lib/commonjs/utils/datefunc.js.map +0 -1
- package/lib/module/assets/Icons/ArrowBack2RoundedIcon.js +0 -2
- package/lib/module/assets/Icons/ArrowBack2RoundedIcon.js.map +0 -1
- package/lib/module/assets/Icons/CameraIcon.js +0 -2
- package/lib/module/assets/Icons/CameraIcon.js.map +0 -1
- package/lib/module/assets/Icons/CheckAllIcon.js +0 -2
- package/lib/module/assets/Icons/CheckAllIcon.js.map +0 -1
- package/lib/module/assets/Icons/CheckIcon.js +0 -2
- package/lib/module/assets/Icons/CheckIcon.js.map +0 -1
- package/lib/module/assets/Icons/EmojiFunnySquareIcon.js +0 -2
- package/lib/module/assets/Icons/EmojiFunnySquareIcon.js.map +0 -1
- package/lib/module/assets/Icons/LoadingIcon.js +0 -2
- package/lib/module/assets/Icons/LoadingIcon.js.map +0 -1
- package/lib/module/assets/Icons/MicrophoneIcon.js +0 -2
- package/lib/module/assets/Icons/MicrophoneIcon.js.map +0 -1
- package/lib/module/assets/Icons/PaperClipIcon.js +0 -2
- package/lib/module/assets/Icons/PaperClipIcon.js.map +0 -1
- package/lib/module/assets/Icons/PaperPlaneIcon.js +0 -2
- package/lib/module/assets/Icons/PaperPlaneIcon.js.map +0 -1
- package/lib/module/assets/Icons/PauseIcon.js +0 -2
- package/lib/module/assets/Icons/PauseIcon.js.map +0 -1
- package/lib/module/assets/Icons/PlayIcon.js +0 -2
- package/lib/module/assets/Icons/PlayIcon.js.map +0 -1
- package/lib/module/assets/Icons/XIcon.js +0 -2
- package/lib/module/assets/Icons/XIcon.js.map +0 -1
- package/lib/module/components/AudioPlayer/AudioPlayer.js +0 -2
- package/lib/module/components/AudioPlayer/AudioPlayer.js.map +0 -1
- package/lib/module/components/ChatBubble/ChatBubble.js +0 -2
- package/lib/module/components/ChatBubble/ChatBubble.js.map +0 -1
- package/lib/module/components/ChatBubble/MessageContent.js +0 -2
- package/lib/module/components/ChatBubble/MessageContent.js.map +0 -1
- package/lib/module/components/ChatBubble/MessageStatus.js +0 -2
- package/lib/module/components/ChatBubble/MessageStatus.js.map +0 -1
- package/lib/module/components/ChatInput/ChatInput.js +0 -2
- package/lib/module/components/ChatInput/ChatInput.js.map +0 -1
- package/lib/module/components/MediaViewer/MediaViewer.js +0 -2
- package/lib/module/components/MediaViewer/MediaViewer.js.map +0 -1
- package/lib/module/components/TypingComponent/TypingIndicator.js +0 -2
- package/lib/module/components/TypingComponent/TypingIndicator.js.map +0 -1
- package/lib/module/context/AudioContext.js +0 -2
- package/lib/module/context/AudioContext.js.map +0 -1
- package/lib/module/context/ChatContext.js +0 -2
- package/lib/module/context/ChatContext.js.map +0 -1
- package/lib/module/utils/datefunc.js +0 -2
- package/lib/module/utils/datefunc.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,262 +1,616 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
A highly customizable, feature-rich
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
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
|
-
###
|
|
78
|
+
### 2. Install peer & native dependencies
|
|
38
79
|
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
Add the Reanimated Babel plugin **last** in `babel.config.js`:
|
|
52
95
|
|
|
53
96
|
```javascript
|
|
54
97
|
module.exports = {
|
|
55
|
-
|
|
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
|
-
###
|
|
106
|
+
### 4. Configure react-native-sound (recommended)
|
|
60
107
|
|
|
61
|
-
|
|
108
|
+
**iOS** — enable playback in silent mode (optional, in `AppDelegate`):
|
|
62
109
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
npx pod-install # For iOS
|
|
66
|
-
npx react-native run-android # Rebuild for Android
|
|
67
|
-
npx react-native run-ios # Rebuild for iOS
|
|
68
|
-
```
|
|
110
|
+
```objc
|
|
111
|
+
#import <AVFoundation/AVFoundation.h>
|
|
69
112
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
npx expo run:android # Build and run on Android
|
|
74
|
-
npx expo run:ios # Build and run on iOS
|
|
75
|
-
```
|
|
113
|
+
// inside didFinishLaunchingWithOptions:
|
|
114
|
+
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
|
|
115
|
+
```
|
|
76
116
|
|
|
77
|
-
|
|
117
|
+
**Android** — ensure internet permission if loading remote audio URLs in `AndroidManifest.xml`:
|
|
78
118
|
|
|
79
|
-
```
|
|
119
|
+
```xml
|
|
120
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
121
|
+
```
|
|
122
|
+
|
|
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';
|
|
80
155
|
import ChatScreen from 'movius-chats';
|
|
81
|
-
import { Message } from 'movius-chats/lib/typescript/types';
|
|
82
|
-
import { useState } from 'react';
|
|
156
|
+
import type { Message } from 'movius-chats/lib/typescript/types';
|
|
83
157
|
|
|
84
|
-
|
|
158
|
+
export default function App() {
|
|
85
159
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
160
|
+
const currentUserId = 'user-1';
|
|
86
161
|
|
|
87
|
-
const handleSendMessage = (
|
|
88
|
-
|
|
162
|
+
const handleSendMessage = (
|
|
163
|
+
payload: Omit<Message, 'id' | 'time' | 'status'>
|
|
164
|
+
) => {
|
|
89
165
|
const newMessage: Message = {
|
|
90
|
-
...
|
|
91
|
-
id: Date.now()
|
|
92
|
-
time: new Date().toLocaleTimeString(
|
|
93
|
-
|
|
166
|
+
...payload,
|
|
167
|
+
id: String(Date.now()),
|
|
168
|
+
time: new Date().toLocaleTimeString([], {
|
|
169
|
+
hour: '2-digit',
|
|
170
|
+
minute: '2-digit',
|
|
171
|
+
}),
|
|
172
|
+
status: 'sent',
|
|
94
173
|
};
|
|
95
|
-
|
|
174
|
+
// Newest first — see "Message list ordering"
|
|
175
|
+
setMessages((prev) => [newMessage, ...prev]);
|
|
96
176
|
};
|
|
97
177
|
|
|
98
178
|
return (
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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>
|
|
105
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',
|
|
106
230
|
};
|
|
107
231
|
```
|
|
108
232
|
|
|
109
|
-
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Message list ordering
|
|
110
236
|
|
|
111
|
-
|
|
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
|
|
112
254
|
|
|
113
255
|
| Prop | Type | Required | Description |
|
|
114
256
|
|------|------|----------|-------------|
|
|
115
|
-
| messages | Message[] | Yes |
|
|
116
|
-
| currentUserId | string | Yes |
|
|
117
|
-
| onSendMessage | (
|
|
118
|
-
| onMessageLongPress | (message: Message) => void | No |
|
|
119
|
-
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
|
126
|
-
|
|
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.
|
|
127
302
|
|
|
128
303
|
### Theming
|
|
129
304
|
|
|
130
|
-
|
|
305
|
+
Pass a `theme` object to customize colors, typography, and styles. All keys are optional.
|
|
131
306
|
|
|
132
307
|
```typescript
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
169
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
|
+
}
|
|
170
366
|
```
|
|
171
367
|
|
|
172
|
-
|
|
368
|
+
Default bubble colors (before `theme` overrides): sent ≈ green (`bg-green-500`), received ≈ white.
|
|
369
|
+
|
|
370
|
+
### Custom components & icons
|
|
173
371
|
|
|
174
372
|
| Prop | Type | Description |
|
|
175
373
|
|------|------|-------------|
|
|
176
|
-
| renderCustomInput | () => React.ReactNode |
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
| CustomEmojiIcon | () => React.ReactNode |
|
|
180
|
-
| CustomAttachmentIcon | () => React.ReactNode |
|
|
181
|
-
| CustomCameraIcon | () => React.ReactNode |
|
|
182
|
-
| CustomSendIcon | () => React.ReactNode |
|
|
183
|
-
| CustomMicrophoneIcon | () => React.ReactNode |
|
|
184
|
-
| CustomPlayIcon | () => React.ReactNode |
|
|
185
|
-
| CustomPauseIcon | () => React.ReactNode |
|
|
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 |
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Usage examples
|
|
391
|
+
|
|
392
|
+
### Text messages
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
onSendMessage={({ text, senderId }) => {
|
|
396
|
+
addMessage({ text, senderId, status: 'sent' });
|
|
397
|
+
}}
|
|
398
|
+
```
|
|
186
399
|
|
|
187
|
-
|
|
400
|
+
### Media messages
|
|
188
401
|
|
|
189
|
-
|
|
402
|
+
Add URIs when building the `Message` object (usually after upload):
|
|
190
403
|
|
|
191
|
-
```
|
|
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
|
+
>([]);
|
|
430
|
+
|
|
431
|
+
<ChatScreen
|
|
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
|
|
192
508
|
<ChatScreen
|
|
193
|
-
messages={messages}
|
|
194
|
-
currentUserId="user123"
|
|
195
|
-
onSendMessage={handleSendMessage}
|
|
196
509
|
theme={{
|
|
510
|
+
fontFamily: 'Inter-Regular',
|
|
197
511
|
colors: {
|
|
198
512
|
sentMessageTailColor: '#007AFF',
|
|
199
513
|
receivedMessageTailColor: '#E9E9EB',
|
|
200
514
|
timestamp: '#8E8E93',
|
|
515
|
+
readIconColor: '#34C759',
|
|
516
|
+
inputTextColor: '#000000',
|
|
201
517
|
},
|
|
202
518
|
bubbleStyle: {
|
|
203
|
-
sent: {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
519
|
+
sent: { backgroundColor: '#007AFF' },
|
|
520
|
+
received: { backgroundColor: '#E9E9EB' },
|
|
521
|
+
},
|
|
522
|
+
messageStyle: {
|
|
523
|
+
sentTextStyle: { color: '#FFFFFF' },
|
|
524
|
+
receivedTextStyle: { color: '#000000' },
|
|
525
|
+
},
|
|
526
|
+
inputStyles: {
|
|
527
|
+
sendButtonStyle: { backgroundColor: '#007AFF' },
|
|
211
528
|
},
|
|
212
529
|
}}
|
|
530
|
+
// ...
|
|
213
531
|
/>
|
|
214
532
|
```
|
|
215
533
|
|
|
216
|
-
###
|
|
534
|
+
### Custom input bar
|
|
217
535
|
|
|
218
|
-
|
|
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.
|
|
219
544
|
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
npx expo prebuild
|
|
223
|
-
```
|
|
545
|
+
### Long-press actions
|
|
224
546
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
547
|
+
```tsx
|
|
548
|
+
<ChatScreen
|
|
549
|
+
onMessageLongPress={(message) => {
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
```
|
|
231
553
|
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
npx expo prebuild --clean
|
|
235
|
-
```
|
|
554
|
+
---
|
|
236
555
|
|
|
237
|
-
|
|
556
|
+
## TypeScript
|
|
238
557
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
558
|
+
The main export is the default `ChatScreen` component. Types live in the build output:
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import ChatScreen from 'movius-chats';
|
|
562
|
+
import type {
|
|
563
|
+
Message,
|
|
564
|
+
ChatScreenProps,
|
|
565
|
+
} from 'movius-chats/lib/typescript/types';
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
`ChatScreenProps` is the full props interface for `ChatScreen`.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Architecture overview
|
|
573
|
+
|
|
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
|
+
```
|
|
583
|
+
|
|
584
|
+
Internal pieces (not exported from the package entry, but useful when reading source):
|
|
585
|
+
|
|
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
|
|
590
|
+
|
|
591
|
+
---
|
|
243
592
|
|
|
244
593
|
## Troubleshooting
|
|
245
594
|
|
|
246
|
-
|
|
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 |
|
|
247
605
|
|
|
248
|
-
|
|
249
|
-
- **Crashes in Expo Go**: This package uses native modules that are not compatible with Expo Go. Use a development build instead.
|
|
250
|
-
- **Audio/Video not working**: Check that you've installed all required dependencies and rebuilt the app.
|
|
606
|
+
---
|
|
251
607
|
|
|
252
608
|
## Contributing
|
|
253
609
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
## License
|
|
610
|
+
Issues and pull requests are welcome on [GitHub](https://github.com/David-Atueyi/Movius-Chats/issues).
|
|
257
611
|
|
|
258
|
-
|
|
612
|
+
---
|
|
259
613
|
|
|
260
|
-
##
|
|
614
|
+
## License
|
|
261
615
|
|
|
262
|
-
|
|
616
|
+
ISC — see [package.json](./package.json).
|