movius-chats 1.3.13 → 1.4.0

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.
Files changed (32) hide show
  1. package/lib/commonjs/index.js +5 -3
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/module/index.js +5 -3
  4. package/lib/module/index.js.map +1 -1
  5. package/lib/typescript/assets/Icons/ChevronUpIcon.d.ts +5 -0
  6. package/lib/typescript/assets/Icons/LockIcon.d.ts +5 -0
  7. package/lib/typescript/assets/Icons/TrashIcon.d.ts +5 -0
  8. package/lib/typescript/components/AudioPlayer/types.d.ts +1 -0
  9. package/lib/typescript/components/ChatInput/types.d.ts +2 -2
  10. package/lib/typescript/components/VoiceRecorder/LongPressRecording.d.ts +13 -0
  11. package/lib/typescript/components/VoiceRecorder/NormalRecording.d.ts +20 -0
  12. package/lib/typescript/components/VoiceRecorder/WaveformAnimation.d.ts +10 -0
  13. package/lib/typescript/hooks/useVoiceRecorder.d.ts +19 -0
  14. package/lib/typescript/types/index.d.ts +74 -1
  15. package/package.json +12 -2
  16. package/scripts/patchSound.js +48 -23
  17. package/src/assets/Icons/ChevronUpIcon.tsx +20 -0
  18. package/src/assets/Icons/LockIcon.tsx +28 -0
  19. package/src/assets/Icons/TrashIcon.tsx +26 -0
  20. package/src/components/AudioPlayer/AudioPlayer.tsx +147 -163
  21. package/src/components/AudioPlayer/types.ts +1 -0
  22. package/src/components/ChatBubble/MediaGrid.tsx +4 -1
  23. package/src/components/ChatBubble/MessageContent.tsx +1 -0
  24. package/src/components/ChatInput/ChatInput.tsx +296 -62
  25. package/src/components/ChatInput/FilePreview.tsx +3 -0
  26. package/src/components/ChatInput/types.ts +2 -2
  27. package/src/components/MediaViewer/MediaViewer.tsx +45 -10
  28. package/src/components/VoiceRecorder/LongPressRecording.tsx +195 -0
  29. package/src/components/VoiceRecorder/NormalRecording.tsx +156 -0
  30. package/src/components/VoiceRecorder/WaveformAnimation.tsx +56 -0
  31. package/src/hooks/useVoiceRecorder.ts +206 -0
  32. package/src/types/index.ts +80 -1
@@ -0,0 +1,5 @@
1
+ import { ViewStyle } from 'react-native';
2
+ export declare const ChevronUpIcon: ({ style, color, }: {
3
+ style?: ViewStyle;
4
+ color?: string;
5
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { ViewStyle } from 'react-native';
2
+ export declare const LockIcon: ({ style, color, }: {
3
+ style?: ViewStyle;
4
+ color?: string;
5
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { ViewStyle } from 'react-native';
2
+ export declare const TrashIcon: ({ style, color, }: {
3
+ style?: ViewStyle;
4
+ color?: string;
5
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -2,4 +2,5 @@ export interface AudioPlayerProps {
2
2
  audioUrl: string;
3
3
  audioId: string;
4
4
  isVideoPlaying: boolean;
5
+ isCurrentUser: boolean;
5
6
  }
@@ -1,4 +1,4 @@
1
- import { Message } from '../../types';
1
+ import { Message, RecordingResult } from '../../types';
2
2
  export interface ChatInputProps {
3
3
  onSendMessage: (message: Omit<Message, 'id' | 'time' | 'status'>) => void;
4
4
  onTypingStart?: () => void;
@@ -6,7 +6,7 @@ export interface ChatInputProps {
6
6
  onAttachmentPress?: () => void;
7
7
  onCameraPress?: () => void;
8
8
  onAudioRecordStart?: () => void;
9
- onAudioRecordEnd?: () => void;
9
+ onAudioRecordEnd?: (audio?: RecordingResult) => void;
10
10
  placeholder?: string;
11
11
  previewData?: {
12
12
  uri: string;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { RecordingUIProps, VoiceRecorderStyleOverrides } from '../../types';
3
+ interface LongPressRecordingProps {
4
+ duration: number;
5
+ /** Current horizontal drag offset (negative = sliding left to cancel) */
6
+ slideX: number;
7
+ containerHeight?: number;
8
+ fontFamily?: string;
9
+ voiceRecorderStyles?: VoiceRecorderStyleOverrides;
10
+ recordingUIProps?: RecordingUIProps;
11
+ }
12
+ export declare const LongPressRecording: React.FC<LongPressRecordingProps>;
13
+ export {};
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { RecordingUIProps, VoiceRecorderStyleOverrides } from '../../types';
3
+ interface NormalRecordingProps {
4
+ isRecording: boolean;
5
+ isPaused: boolean;
6
+ duration: number;
7
+ onCancel: () => void;
8
+ onSend: () => void;
9
+ onPause: () => void;
10
+ onResume: () => void;
11
+ containerHeight?: number;
12
+ fontFamily?: string;
13
+ sendButtonColor?: string;
14
+ sendIconColor?: string;
15
+ enablePauseResume?: boolean;
16
+ voiceRecorderStyles?: VoiceRecorderStyleOverrides;
17
+ recordingUIProps?: RecordingUIProps;
18
+ }
19
+ export declare const NormalRecording: React.FC<NormalRecordingProps>;
20
+ export {};
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ interface WaveformAnimationProps {
4
+ isActive: boolean;
5
+ color?: string;
6
+ height?: number;
7
+ style?: StyleProp<ViewStyle>;
8
+ }
9
+ export declare const WaveformAnimation: React.FC<WaveformAnimationProps>;
10
+ export {};
@@ -0,0 +1,19 @@
1
+ import { RecordingResult } from '../types';
2
+ export type RecordingStatus = 'idle' | 'recording' | 'paused';
3
+ interface UseVoiceRecorderOptions {
4
+ maxDuration?: number;
5
+ onRecordStart?: () => void;
6
+ onRecordEnd?: (result: RecordingResult) => void;
7
+ }
8
+ export declare function useVoiceRecorder({ maxDuration, onRecordStart, onRecordEnd, }?: UseVoiceRecorderOptions): {
9
+ status: RecordingStatus;
10
+ duration: number;
11
+ isRecording: boolean;
12
+ isPaused: boolean;
13
+ startRecording: () => Promise<void>;
14
+ pauseRecording: () => Promise<void>;
15
+ resumeRecording: () => Promise<void>;
16
+ stopRecording: () => Promise<RecordingResult | null>;
17
+ cancelRecording: () => Promise<void>;
18
+ };
19
+ export {};
@@ -1,4 +1,61 @@
1
1
  import { ImageStyle, TextStyle, ViewStyle } from 'react-native';
2
+ /** Returned by the recorder when a recording successfully completes. */
3
+ export interface RecordingResult {
4
+ uri: string;
5
+ duration: number;
6
+ size?: number;
7
+ mimeType?: string;
8
+ }
9
+ /** Passed to `renderVoiceRecorder` so a custom UI has full control. */
10
+ export interface VoiceRecorderExposedState {
11
+ isRecording: boolean;
12
+ isPaused: boolean;
13
+ duration: number;
14
+ isLocked: boolean;
15
+ slideOffset: {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ waveformData: number[];
20
+ startRecording: () => void;
21
+ stopRecording: () => Promise<RecordingResult | null>;
22
+ pauseRecording: () => void;
23
+ resumeRecording: () => void;
24
+ cancelRecording: () => void;
25
+ }
26
+ /** Feature flags / limits for the built-in recorder. */
27
+ export interface VoiceRecorderConfig {
28
+ maxDuration?: number;
29
+ enableSlideToCancel?: boolean;
30
+ enableLockRecording?: boolean;
31
+ enableWaveform?: boolean;
32
+ autoSendOnRelease?: boolean;
33
+ enablePauseResume?: boolean;
34
+ recordingFormat?: string;
35
+ recordingQuality?: string;
36
+ animationDuration?: number;
37
+ }
38
+ /** Style overrides for each section of the recording UI. */
39
+ export interface VoiceRecorderStyleOverrides {
40
+ container?: ViewStyle;
41
+ timer?: TextStyle;
42
+ waveform?: ViewStyle;
43
+ micButton?: ViewStyle;
44
+ slideText?: TextStyle;
45
+ lockContainer?: ViewStyle;
46
+ trashButton?: ViewStyle;
47
+ }
48
+ /** Color / size tweaks for the default recording UI. */
49
+ export interface RecordingUIProps {
50
+ iconSize?: number;
51
+ recordingIconSize?: number;
52
+ sendIconSize?: number;
53
+ timerTextStyle?: TextStyle;
54
+ waveformColor?: string;
55
+ recordingBackground?: string;
56
+ cancelTextColor?: string;
57
+ micPulseColor?: string;
58
+ }
2
59
  /** Single image or video inside a message bubble (use `mediaItems` for albums). */
3
60
  export interface MessageMediaItem {
4
61
  uri: string;
@@ -35,10 +92,18 @@ export interface ChatScreenProps {
35
92
  onSendMessage: (message: Omit<Message, 'id' | 'time' | 'status'>) => void;
36
93
  onMessageLongPress?: (message: Message) => void;
37
94
  onAttachmentPress?: () => void;
38
- onAudioRecordEnd?: () => void;
95
+ onAudioRecordEnd?: (audio?: RecordingResult) => void;
39
96
  onAudioRecordStart?: () => void;
40
97
  onCameraPress?: () => void;
41
98
  onFileAttachmentPress?: (file: MessageFileAttachment) => void;
99
+ /** Replace the default recording UI with a fully custom component. */
100
+ renderVoiceRecorder?: (state: VoiceRecorderExposedState) => React.ReactNode;
101
+ /** Feature flags / limits for the built-in recorder. */
102
+ voiceRecorderProps?: VoiceRecorderConfig;
103
+ /** Style overrides for the built-in recording UI sections. */
104
+ voiceRecorderStyles?: VoiceRecorderStyleOverrides;
105
+ /** Color and size tweaks for the built-in recording UI. */
106
+ recordingUIProps?: RecordingUIProps;
42
107
  keyboardVerticalOffset?: number;
43
108
  disableKeyboardAvoiding?: boolean;
44
109
  typingUsers?: Array<{
@@ -74,6 +139,14 @@ export interface ChatScreenProps {
74
139
  audioPlayIconColor?: string;
75
140
  audioPauseIconColor?: string;
76
141
  videoPlayIconColor?: string;
142
+ /** Inactive (unplayed) waveform bar color */
143
+ audioWaveformColor?: string;
144
+ /** Active (played) waveform bar color */
145
+ audioWaveformActiveColor?: string;
146
+ /** Playback-time text color for sent audio messages */
147
+ sentAudioTimestampColor?: string;
148
+ /** Playback-time text color for received audio messages */
149
+ receivedAudioTimestampColor?: string;
77
150
  inputTextColor?: string;
78
151
  sentIconColor?: string;
79
152
  deliveredIconColor?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "movius-chats",
3
- "version": "1.3.13",
3
+ "version": "1.4.0",
4
4
  "description": "A highly customizable, feature-rich chat interface component for React Native applications",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -48,7 +48,17 @@
48
48
  "peerDependencies": {
49
49
  "react": ">=16.8.0",
50
50
  "react-native": "*",
51
- "react-native-reanimated": "*"
51
+ "react-native-reanimated": "*",
52
+ "expo-av": "*",
53
+ "expo-file-system": "*"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "expo-av": {
57
+ "optional": true
58
+ },
59
+ "expo-file-system": {
60
+ "optional": true
61
+ }
52
62
  },
53
63
  "dependencies": {
54
64
  "react-native-parsed-text": "^0.0.22",
@@ -1,66 +1,91 @@
1
-
2
-
3
1
  'use strict';
4
2
 
3
+ /**
4
+ * postinstall patch — react-native-sound + New Architecture compatibility
5
+ *
6
+ * react-native-sound imports resolveAssetSource the old internal way:
7
+ * require('react-native/Libraries/Image/resolveAssetSource')
8
+ * In React Native New Architecture (Fabric / TurboModules) that path no
9
+ * longer exports a bare function; it exports an object whose .default IS the
10
+ * function. This causes:
11
+ * TypeError: resolveAssetSource is not a function (it is Object)
12
+ *
13
+ * This script is run automatically via the `postinstall` hook so every
14
+ * consumer of movius-chats gets the fix without any manual steps.
15
+ */
16
+
5
17
  const fs = require('fs');
6
18
  const path = require('path');
7
19
 
20
+ // Possible locations for react-native-sound's main file.
21
+ // Note: the filename is lowercase "sound.js" in all published versions.
8
22
  const candidates = [
9
- // standard flat node_modules layout
23
+ // 1. Nested inside movius-chats own node_modules (npm didn't hoist it)
24
+ path.resolve(__dirname, '..', 'node_modules', 'react-native-sound', 'sound.js'),
25
+ // 2. Hoisted to the project root node_modules (flat layout)
26
+ path.resolve(__dirname, '..', '..', 'react-native-sound', 'sound.js'),
27
+ // 3. One level up further (yarn workspaces / pnpm monorepo)
28
+ path.resolve(__dirname, '..', '..', '..', 'react-native-sound', 'sound.js'),
29
+ // 4. Some older publish had an uppercase S — kept as a fallback
30
+ path.resolve(__dirname, '..', 'node_modules', 'react-native-sound', 'Sound.js'),
10
31
  path.resolve(__dirname, '..', '..', 'react-native-sound', 'Sound.js'),
11
- // hoisted monorepo layout (yarn workspaces / pnpm)
12
- path.resolve(__dirname, '..', '..', '..', 'react-native-sound', 'Sound.js'),
13
32
  ];
14
33
 
15
34
  const OLD_IMPORT =
35
+ 'var resolveAssetSource = require("react-native/Libraries/Image/resolveAssetSource");';
36
+
37
+ // Some versions use single quotes
38
+ const OLD_IMPORT_SQ =
16
39
  "var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');";
17
40
 
18
41
  const NEW_IMPORT = [
19
42
  "var _ras = require('react-native/Libraries/Image/resolveAssetSource');",
20
43
  '// movius-chats patch: support New Architecture (resolveAssetSource moved to Image)',
21
44
  "var resolveAssetSource = typeof _ras === 'function'",
22
- " ? _ras",
45
+ ' ? _ras',
23
46
  " : (_ras && typeof _ras.default === 'function'",
24
- " ? _ras.default",
47
+ ' ? _ras.default',
25
48
  " : require('react-native').Image.resolveAssetSource.bind(require('react-native').Image));",
26
49
  ].join('\n');
27
50
 
28
51
  const PATCH_MARKER = '// movius-chats patch:';
29
52
 
30
- let patched = false;
53
+ let attempted = false;
31
54
 
32
55
  for (const soundPath of candidates) {
33
56
  if (!fs.existsSync(soundPath)) continue;
34
57
 
58
+ attempted = true;
35
59
  let src = fs.readFileSync(soundPath, 'utf8');
36
60
 
37
61
  if (src.includes(PATCH_MARKER)) {
38
62
  console.log('[movius-chats] react-native-sound already patched — skipping.');
39
- patched = true;
40
63
  break;
41
64
  }
42
65
 
43
- if (!src.includes(OLD_IMPORT)) {
44
- // Different version of react-native-sound; the import line changed.
66
+ const hasDQ = src.includes(OLD_IMPORT);
67
+ const hasSQ = src.includes(OLD_IMPORT_SQ);
68
+
69
+ if (!hasDQ && !hasSQ) {
45
70
  console.warn(
46
- '[movius-chats] Could not locate the resolveAssetSource import in ' +
47
- soundPath +
48
- '.\n' +
49
- 'If you see a "resolveAssetSource is not a function" error, apply the\n' +
50
- 'patch manually — see the movius-chats README Troubleshooting section.'
71
+ '[movius-chats] react-native-sound found at ' + soundPath + ' but the\n' +
72
+ ' resolveAssetSource import line was not recognised (different version?).\n' +
73
+ ' If you see "resolveAssetSource is not a function" errors, apply the\n' +
74
+ ' patch manually see the movius-chats README Troubleshooting section.'
51
75
  );
52
- patched = true; // don't repeat the warning for every candidate
53
76
  break;
54
77
  }
55
78
 
56
- src = src.replace(OLD_IMPORT, NEW_IMPORT);
79
+ if (hasDQ) src = src.replace(OLD_IMPORT, NEW_IMPORT);
80
+ if (hasSQ) src = src.replace(OLD_IMPORT_SQ, NEW_IMPORT);
81
+
57
82
  fs.writeFileSync(soundPath, src, 'utf8');
58
- console.log('[movius-chats] ✅ react-native-sound patched for New Architecture compatibility.');
59
- patched = true;
83
+ console.log('[movius-chats] ✅ react-native-sound patched at ' + soundPath);
60
84
  break;
61
85
  }
62
86
 
63
- if (!patched) {
64
- // react-native-sound is not installed yet (peer dep, optional dep, etc.)
65
- // Silently skip — the patch will run again if the user installs it later.
87
+ // Silently do nothing if react-native-sound isn't installed yet
88
+ // (e.g. during the movius-chats dev build itself).
89
+ if (!attempted) {
90
+ // No-op — react-native-sound may be installed later as a peer dependency.
66
91
  }
@@ -0,0 +1,20 @@
1
+ import { ViewStyle } from 'react-native';
2
+ import Svg, { Path } from 'react-native-svg';
3
+
4
+ export const ChevronUpIcon = ({
5
+ style,
6
+ color = '#6b7280',
7
+ }: {
8
+ style?: ViewStyle;
9
+ color?: string;
10
+ }) => (
11
+ <Svg style={style} viewBox="0 0 24 24" fill="none">
12
+ <Path
13
+ d="M6 15l6-6 6 6"
14
+ stroke={color}
15
+ strokeWidth="2"
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ />
19
+ </Svg>
20
+ );
@@ -0,0 +1,28 @@
1
+ import { ViewStyle } from 'react-native';
2
+ import Svg, { Path, Rect } from 'react-native-svg';
3
+
4
+ export const LockIcon = ({
5
+ style,
6
+ color = '#6b7280',
7
+ }: {
8
+ style?: ViewStyle;
9
+ color?: string;
10
+ }) => (
11
+ <Svg style={style} viewBox="0 0 24 24" fill="none">
12
+ <Rect
13
+ x="5"
14
+ y="11"
15
+ width="14"
16
+ height="10"
17
+ rx="2"
18
+ stroke={color}
19
+ strokeWidth="1.5"
20
+ />
21
+ <Path
22
+ d="M8 11V7a4 4 0 0 1 8 0v4"
23
+ stroke={color}
24
+ strokeWidth="1.5"
25
+ strokeLinecap="round"
26
+ />
27
+ </Svg>
28
+ );
@@ -0,0 +1,26 @@
1
+ import { ViewStyle } from 'react-native';
2
+ import Svg, { Path } from 'react-native-svg';
3
+
4
+ export const TrashIcon = ({
5
+ style,
6
+ color = '#ef4444',
7
+ }: {
8
+ style?: ViewStyle;
9
+ color?: string;
10
+ }) => (
11
+ <Svg style={style} viewBox="0 0 24 24" fill="none">
12
+ <Path
13
+ d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"
14
+ stroke={color}
15
+ strokeWidth="1.5"
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ />
19
+ <Path
20
+ d="M10 11v6M14 11v6"
21
+ stroke={color}
22
+ strokeWidth="1.5"
23
+ strokeLinecap="round"
24
+ />
25
+ </Svg>
26
+ );