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 +605 -197
- package/lib/commonjs/index.js +3 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +3 -3
- 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/components/ChatInput/TruncateFileName.d.ts +2 -2
- package/lib/typescript/hooks/useKeyboardInset.d.ts +5 -0
- package/lib/typescript/types/index.d.ts +13 -0
- package/lib/typescript/utils/theme.d.ts +6 -0
- package/package.json +1 -1
- package/src/assets/Icons/CheckAllIcon.tsx +6 -1
- package/src/assets/Icons/CheckIcon.tsx +6 -2
- package/src/components/AudioPlayer/AudioPlayer.tsx +8 -4
- package/src/components/ChatBubble/ChatBubble.tsx +15 -8
- package/src/components/ChatBubble/MessageContent.tsx +23 -10
- package/src/components/ChatBubble/MessageStatus.tsx +19 -12
- package/src/components/ChatInput/ChatInput.tsx +26 -22
- package/src/components/ChatInput/FilePreview.tsx +9 -1
- package/src/components/ChatInput/TruncateFileName.tsx +9 -3
- package/src/components/MediaViewer/MediaViewer.tsx +9 -1
- package/src/components/TypingComponent/TypingIndicator.tsx +18 -9
- package/src/hooks/useKeyboardInset.ts +42 -0
- package/src/index.tsx +35 -3
- package/src/types/index.ts +14 -0
- package/src/utils/theme.ts +37 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { ViewStyle } from "react-native";
|
|
2
|
-
|
|
2
|
+
interface IconProps {
|
|
3
3
|
style?: ViewStyle;
|
|
4
|
-
|
|
4
|
+
fill?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const CheckAllIcon: ({ style, fill }: IconProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { ViewStyle } from "react-native";
|
|
2
|
-
|
|
2
|
+
interface IconProps {
|
|
3
3
|
style?: ViewStyle;
|
|
4
|
-
|
|
4
|
+
fill?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const CheckIcon: ({ style, fill }: IconProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { StyleProp, TextStyle } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, TextStyle } from 'react-native';
|
|
3
3
|
declare const _default: React.MemoExoticComponent<({ fileName, style, }: {
|
|
4
4
|
fileName: string;
|
|
5
5
|
style?: StyleProp<TextStyle>;
|
|
@@ -20,6 +20,10 @@ export interface ChatScreenProps {
|
|
|
20
20
|
onAudioRecordEnd?: () => void;
|
|
21
21
|
onAudioRecordStart?: () => void;
|
|
22
22
|
onCameraPress?: () => void;
|
|
23
|
+
/** Extra space subtracted from keyboard height (header, tab bar, safe area). */
|
|
24
|
+
keyboardVerticalOffset?: number;
|
|
25
|
+
/** Set true if your screen already handles keyboard insets. */
|
|
26
|
+
disableKeyboardAvoiding?: boolean;
|
|
23
27
|
typingUsers?: Array<{
|
|
24
28
|
id: string;
|
|
25
29
|
avatar: string;
|
|
@@ -44,6 +48,7 @@ export interface ChatScreenProps {
|
|
|
44
48
|
uri: string;
|
|
45
49
|
}>;
|
|
46
50
|
theme?: {
|
|
51
|
+
fontFamily?: string;
|
|
47
52
|
colors?: {
|
|
48
53
|
sentMessageTailColor?: string;
|
|
49
54
|
receivedMessageTailColor?: string;
|
|
@@ -54,6 +59,14 @@ export interface ChatScreenProps {
|
|
|
54
59
|
audioPlayIconColor?: string;
|
|
55
60
|
audioPauseIconColor?: string;
|
|
56
61
|
videoPlayIconColor?: string;
|
|
62
|
+
inputTextColor?: string;
|
|
63
|
+
sentIconColor?: string;
|
|
64
|
+
deliveredIconColor?: string;
|
|
65
|
+
readIconColor?: string;
|
|
66
|
+
};
|
|
67
|
+
sizes?: {
|
|
68
|
+
/** Twrnc classes (e.g. `"h-8 w-8"`) or pixel size (e.g. `28`). */
|
|
69
|
+
inputIconSize?: string | number;
|
|
57
70
|
};
|
|
58
71
|
bubbleStyle?: {
|
|
59
72
|
sent?: ViewStyle;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StyleProp, TextStyle, ViewStyle } from 'react-native';
|
|
2
|
+
/** Resolves theme.sizes.inputIconSize (twrnc class string or pixel number). */
|
|
3
|
+
export declare function getInputIconStyle(size?: string | number, extraClass?: string): ViewStyle;
|
|
4
|
+
/** Applies theme.fontFamily to any Text / ParsedText style array. */
|
|
5
|
+
export declare function withFontFamily(style: StyleProp<TextStyle>, fontFamily?: string): StyleProp<TextStyle>;
|
|
6
|
+
export declare function getFontFamilyStyle(fontFamily?: string): TextStyle | undefined;
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
import { ViewStyle } from "react-native";
|
|
3
3
|
import Svg, { Path } from "react-native-svg";
|
|
4
|
+
interface IconProps {
|
|
5
|
+
style?: ViewStyle;
|
|
6
|
+
fill?: string;
|
|
7
|
+
}
|
|
4
8
|
|
|
5
|
-
export const CheckAllIcon = ({ style }:
|
|
9
|
+
export const CheckAllIcon = ({ style, fill }: IconProps) => {
|
|
6
10
|
return (
|
|
7
11
|
<Svg style={style} viewBox="0 0 24 24">
|
|
8
12
|
<Path
|
|
13
|
+
fill={fill}
|
|
9
14
|
d="M.41 13.41L6 19l1.41-1.42L1.83 12m20.41-6.42L11.66 16.17L7.5 12l-1.43 1.41L11.66 19l12-12M18 7l-1.41-1.42l-6.35 6.35l1.42 1.41z"
|
|
10
15
|
></Path>
|
|
11
16
|
</Svg>
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
import { ViewStyle } from "react-native";
|
|
3
3
|
import Svg, { Path } from "react-native-svg";
|
|
4
|
+
interface IconProps {
|
|
5
|
+
style?: ViewStyle;
|
|
6
|
+
fill?: string;
|
|
7
|
+
}
|
|
4
8
|
|
|
5
|
-
export const CheckIcon = ({ style }:
|
|
9
|
+
export const CheckIcon = ({ style, fill }: IconProps) => {
|
|
6
10
|
return (
|
|
7
11
|
<Svg style={style} viewBox="0 0 24 24">
|
|
8
|
-
<Path d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"></Path>
|
|
12
|
+
<Path d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z" fill={fill}></Path>
|
|
9
13
|
</Svg>
|
|
10
14
|
);
|
|
11
15
|
};
|
|
@@ -12,6 +12,7 @@ import { PlayIcon } from '../../assets/Icons/PlayIcon';
|
|
|
12
12
|
import { useAudio } from '../../context/AudioContext';
|
|
13
13
|
import { useChatContext } from '../../context/ChatContext';
|
|
14
14
|
import { formatDuration } from '../../utils/datefunc';
|
|
15
|
+
import { withFontFamily } from '../../utils/theme';
|
|
15
16
|
import { AudioPlayerProps } from './types';
|
|
16
17
|
|
|
17
18
|
const AudioPlayer: React.FC<AudioPlayerProps> = ({
|
|
@@ -244,10 +245,13 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({
|
|
|
244
245
|
</View>
|
|
245
246
|
<View style={tw`px-4 py-1`}>
|
|
246
247
|
<Text
|
|
247
|
-
style={
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
style={withFontFamily(
|
|
249
|
+
[
|
|
250
|
+
tw`text-xs text-gray-500`,
|
|
251
|
+
theme?.messageStyle?.audioDurationStyle,
|
|
252
|
+
],
|
|
253
|
+
theme?.fontFamily
|
|
254
|
+
)}
|
|
251
255
|
>
|
|
252
256
|
{!isNaN(currentTime) ? formatDuration(currentTime) : '0:00'}
|
|
253
257
|
</Text>
|
|
@@ -3,6 +3,7 @@ import { Image, Pressable, Text, View } from 'react-native';
|
|
|
3
3
|
import tw from 'twrnc';
|
|
4
4
|
import { ArrowBack2RoundedIcon } from '../../assets/Icons/ArrowBack2RoundedIcon';
|
|
5
5
|
import { useChatContext } from '../../context/ChatContext';
|
|
6
|
+
import { withFontFamily } from '../../utils/theme';
|
|
6
7
|
import MessageContent from './MessageContent';
|
|
7
8
|
import MessageStatus from './MessageStatus';
|
|
8
9
|
import { ChatBubbleProps } from './types';
|
|
@@ -71,10 +72,13 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
|
|
|
71
72
|
/>
|
|
72
73
|
) : (
|
|
73
74
|
<Text
|
|
74
|
-
style={
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
style={withFontFamily(
|
|
76
|
+
[
|
|
77
|
+
tw`text-sm text-black font-semibold capitalize rounded-full bg-zinc-300 w-full h-full text-center pt-0.5`,
|
|
78
|
+
theme?.bubbleStyle?.avatarTextStyle,
|
|
79
|
+
],
|
|
80
|
+
theme?.fontFamily
|
|
81
|
+
)}
|
|
78
82
|
>
|
|
79
83
|
{message.senderName?.charAt(0)}
|
|
80
84
|
</Text>
|
|
@@ -82,10 +86,13 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
|
|
|
82
86
|
</View>
|
|
83
87
|
{showUserNames && message.senderName && (
|
|
84
88
|
<Text
|
|
85
|
-
style={
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
style={withFontFamily(
|
|
90
|
+
[
|
|
91
|
+
tw`text-sm text-black font-semibold mt-1 capitalize`,
|
|
92
|
+
theme?.bubbleStyle?.userNameStyle,
|
|
93
|
+
],
|
|
94
|
+
theme?.fontFamily
|
|
95
|
+
)}
|
|
89
96
|
>
|
|
90
97
|
{message.senderName}
|
|
91
98
|
</Text>
|
|
@@ -6,6 +6,7 @@ import { LoadingIcon } from '../../assets/Icons/LoadingIcon';
|
|
|
6
6
|
import { PlayIcon } from '../../assets/Icons/PlayIcon';
|
|
7
7
|
import { useChatContext } from '../../context/ChatContext';
|
|
8
8
|
import { formatDuration } from '../../utils/datefunc';
|
|
9
|
+
import { getFontFamilyStyle, withFontFamily } from '../../utils/theme';
|
|
9
10
|
import AudioPlayer from '../AudioPlayer/AudioPlayer';
|
|
10
11
|
import { MessageContentProps } from './types';
|
|
11
12
|
import ParsedText from 'react-native-parsed-text';
|
|
@@ -86,7 +87,12 @@ const MessageContent: React.FC<MessageContentProps> = ({
|
|
|
86
87
|
<View
|
|
87
88
|
style={tw`absolute inset-0 flex items-center justify-center bg-red-500/60 p-2`}
|
|
88
89
|
>
|
|
89
|
-
<Text
|
|
90
|
+
<Text
|
|
91
|
+
style={withFontFamily(
|
|
92
|
+
tw`text-white font-bold`,
|
|
93
|
+
theme?.fontFamily
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
90
96
|
Failed to load video
|
|
91
97
|
</Text>
|
|
92
98
|
</View>
|
|
@@ -127,18 +133,25 @@ const MessageContent: React.FC<MessageContentProps> = ({
|
|
|
127
133
|
|
|
128
134
|
{message.text && (
|
|
129
135
|
<ParsedText
|
|
130
|
-
style={
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
style={withFontFamily(
|
|
137
|
+
[
|
|
138
|
+
tw`pt-1`,
|
|
139
|
+
showMessageStatus ? tw`pb-0` : tw`pb-2`,
|
|
140
|
+
{ wordBreak: 'break-word', overflowWrap: 'break-word' },
|
|
141
|
+
isCurrentUser
|
|
142
|
+
? theme?.messageStyle?.sentTextStyle
|
|
143
|
+
: theme?.messageStyle?.receivedTextStyle,
|
|
144
|
+
],
|
|
145
|
+
theme?.fontFamily
|
|
146
|
+
)}
|
|
138
147
|
parse={[
|
|
139
148
|
{
|
|
140
149
|
type: 'url',
|
|
141
|
-
style: {
|
|
150
|
+
style: {
|
|
151
|
+
color: 'blue',
|
|
152
|
+
textDecorationLine: 'underline',
|
|
153
|
+
...getFontFamilyStyle(theme?.fontFamily),
|
|
154
|
+
},
|
|
142
155
|
onPress: (url) =>
|
|
143
156
|
Linking.openURL(
|
|
144
157
|
url.startsWith('http') ? url : `https://${url}`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Text, View } from 'react-native';
|
|
3
3
|
import tw from 'twrnc';
|
|
4
|
+
import { withFontFamily } from '../../utils/theme';
|
|
4
5
|
import { CheckAllIcon } from '../../assets/Icons/CheckAllIcon';
|
|
5
6
|
import { CheckIcon } from '../../assets/Icons/CheckIcon';
|
|
6
7
|
import { useChatContext } from '../../context/ChatContext';
|
|
@@ -28,15 +29,18 @@ const MessageStatus: React.FC<MessageStatusProps> = ({
|
|
|
28
29
|
]}
|
|
29
30
|
>
|
|
30
31
|
<Text
|
|
31
|
-
style={
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
style={withFontFamily(
|
|
33
|
+
[
|
|
34
|
+
tw`text-xs`,
|
|
35
|
+
{
|
|
36
|
+
color:
|
|
37
|
+
hasText || hasAudio
|
|
38
|
+
? theme?.colors?.timestamp || 'rgba(107, 114, 128, 0.7)'
|
|
39
|
+
: 'white',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
theme?.fontFamily
|
|
43
|
+
)}
|
|
40
44
|
>
|
|
41
45
|
{time}
|
|
42
46
|
</Text>
|
|
@@ -44,17 +48,20 @@ const MessageStatus: React.FC<MessageStatusProps> = ({
|
|
|
44
48
|
<View style={tw`ml-1 flex-row items-center`}>
|
|
45
49
|
{status === 'sent' && (
|
|
46
50
|
<CheckIcon
|
|
47
|
-
style={tw.style('
|
|
51
|
+
style={tw.style('h-4 w-4', { opacity: 0.7 })}
|
|
52
|
+
fill={theme?.colors?.sentIconColor || '#6B7280'}
|
|
48
53
|
/>
|
|
49
54
|
)}
|
|
50
55
|
{status === 'delivered' && (
|
|
51
56
|
<CheckAllIcon
|
|
52
|
-
style={tw.style('
|
|
57
|
+
style={tw.style('h-4 w-4', { opacity: 0.7 })}
|
|
58
|
+
fill={theme?.colors?.deliveredIconColor || '#6B7280'}
|
|
53
59
|
/>
|
|
54
60
|
)}
|
|
55
61
|
{status === 'read' && (
|
|
56
62
|
<CheckAllIcon
|
|
57
|
-
style={tw.style('
|
|
63
|
+
style={tw.style('h-4 w-4', { opacity: 0.9 })}
|
|
64
|
+
fill={theme?.colors?.readIconColor || '#3B82F6'}
|
|
58
65
|
/>
|
|
59
66
|
)}
|
|
60
67
|
</View>
|
|
@@ -7,6 +7,7 @@ import { MicrophoneIcon } from '../../assets/Icons/MicrophoneIcon';
|
|
|
7
7
|
import { PaperClipIcon } from '../../assets/Icons/PaperClipIcon';
|
|
8
8
|
import { PaperPlaneIcon } from '../../assets/Icons/PaperPlaneIcon';
|
|
9
9
|
import { useChatContext } from '../../context/ChatContext';
|
|
10
|
+
import { getInputIconStyle, withFontFamily } from '../../utils/theme';
|
|
10
11
|
import FilePreview from './FilePreview';
|
|
11
12
|
import { ChatInputProps, InputHeightState } from './types';
|
|
12
13
|
|
|
@@ -47,6 +48,13 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
47
48
|
closePreview,
|
|
48
49
|
} = useChatContext();
|
|
49
50
|
|
|
51
|
+
const inputIconStyle = getInputIconStyle(
|
|
52
|
+
theme?.sizes?.inputIconSize,
|
|
53
|
+
inputHeight.isMultiline ? 'pb-14' : undefined
|
|
54
|
+
);
|
|
55
|
+
const inputIconStyleInline = getInputIconStyle(theme?.sizes?.inputIconSize);
|
|
56
|
+
const sendIconStyle = getInputIconStyle(theme?.sizes?.inputIconSize);
|
|
57
|
+
|
|
50
58
|
const handleContentSizeChange = useCallback(
|
|
51
59
|
(event: { nativeEvent: { contentSize: { height: number } } }) => {
|
|
52
60
|
const newHeight = Math.min(
|
|
@@ -87,7 +95,6 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
87
95
|
|
|
88
96
|
return (
|
|
89
97
|
<View style={tw`w-full px-2`}>
|
|
90
|
-
{/* File Preview above the input */}
|
|
91
98
|
{previewData && (
|
|
92
99
|
<FilePreview
|
|
93
100
|
previewData={previewData}
|
|
@@ -105,7 +112,6 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
105
112
|
theme?.inputStyles?.inputSectionContainerStyle,
|
|
106
113
|
]}
|
|
107
114
|
>
|
|
108
|
-
{/* LEFT: Input and icons */}
|
|
109
115
|
<View
|
|
110
116
|
style={[
|
|
111
117
|
tw`flex-1 px-3.5 bg-white gap-1 flex-row justify-between`,
|
|
@@ -121,10 +127,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
121
127
|
<CustomEmojiIcon />
|
|
122
128
|
) : (
|
|
123
129
|
<EmojiFunnySquareIcon
|
|
124
|
-
style={
|
|
125
|
-
`${Platform.OS === 'ios' ? 'h-6 w-6' : 'w-7 h-7'}`,
|
|
126
|
-
inputHeight.isMultiline ? 'pb-14' : 'pb-0'
|
|
127
|
-
)}
|
|
130
|
+
style={inputIconStyle}
|
|
128
131
|
color={theme?.colors?.inputsIconsColor || 'rgba(0,0,0,0.7)'}
|
|
129
132
|
/>
|
|
130
133
|
)}
|
|
@@ -135,11 +138,18 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
135
138
|
value={inputText}
|
|
136
139
|
onChangeText={setInputText}
|
|
137
140
|
placeholder={placeholder || 'Message'}
|
|
138
|
-
style={
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
style={withFontFamily(
|
|
142
|
+
[
|
|
143
|
+
tw`bg-transparent flex-1 pl-2 my-3`,
|
|
144
|
+
Platform.OS === 'ios' ? tw`text-[17px]` : tw`text-[16px]`,
|
|
145
|
+
{ minHeight: MIN_INPUT_HEIGHT, maxHeight: MAX_INPUT_HEIGHT },
|
|
146
|
+
{
|
|
147
|
+
color:
|
|
148
|
+
theme?.colors?.inputTextColor || 'rgba(0, 0, 0, 0.87)',
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
theme?.fontFamily
|
|
152
|
+
)}
|
|
143
153
|
placeholderTextColor={
|
|
144
154
|
theme?.colors?.placeholderTextColor || 'rgba(0, 0, 0, 0.4)'
|
|
145
155
|
}
|
|
@@ -160,9 +170,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
160
170
|
<CustomAttachmentIcon />
|
|
161
171
|
) : (
|
|
162
172
|
<PaperClipIcon
|
|
163
|
-
style={
|
|
164
|
-
Platform.OS === 'ios' ? 'h-6 w-6' : 'w-7 h-7'
|
|
165
|
-
)}
|
|
173
|
+
style={inputIconStyleInline}
|
|
166
174
|
color={theme?.colors?.inputsIconsColor || 'rgba(0,0,0,0.7)'}
|
|
167
175
|
/>
|
|
168
176
|
)}
|
|
@@ -174,9 +182,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
174
182
|
<CustomCameraIcon />
|
|
175
183
|
) : (
|
|
176
184
|
<CameraIcon
|
|
177
|
-
style={
|
|
178
|
-
Platform.OS === 'ios' ? 'h-6 w-6' : 'w-7 h-7'
|
|
179
|
-
)}
|
|
185
|
+
style={inputIconStyleInline}
|
|
180
186
|
color={theme?.colors?.inputsIconsColor || 'rgba(0,0,0,0.7)'}
|
|
181
187
|
/>
|
|
182
188
|
)}
|
|
@@ -185,7 +191,6 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
185
191
|
</View>
|
|
186
192
|
</View>
|
|
187
193
|
|
|
188
|
-
{/* RIGHT: Send / Mic Button always at bottom */}
|
|
189
194
|
<Pressable
|
|
190
195
|
style={[
|
|
191
196
|
tw`p-2 rounded-full bg-green-600 justify-center items-center`,
|
|
@@ -208,7 +213,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
208
213
|
<CustomSendIcon />
|
|
209
214
|
) : (
|
|
210
215
|
<PaperPlaneIcon
|
|
211
|
-
style={
|
|
216
|
+
style={sendIconStyle}
|
|
212
217
|
color={theme?.colors?.sendIconsColor || 'rgba(255,255,255,0.7)'}
|
|
213
218
|
/>
|
|
214
219
|
)
|
|
@@ -217,7 +222,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
217
222
|
<CustomMicrophoneIcon />
|
|
218
223
|
) : (
|
|
219
224
|
<MicrophoneIcon
|
|
220
|
-
style={
|
|
225
|
+
style={sendIconStyle}
|
|
221
226
|
color={theme?.colors?.sendIconsColor || 'rgba(255,255,255,0.7)'}
|
|
222
227
|
/>
|
|
223
228
|
)
|
|
@@ -225,7 +230,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
225
230
|
<CustomSendIcon />
|
|
226
231
|
) : (
|
|
227
232
|
<PaperPlaneIcon
|
|
228
|
-
style={
|
|
233
|
+
style={sendIconStyle}
|
|
229
234
|
color={theme?.colors?.sendIconsColor || 'rgba(255,255,255,0.7)'}
|
|
230
235
|
/>
|
|
231
236
|
)}
|
|
@@ -233,7 +238,6 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
233
238
|
</View>
|
|
234
239
|
</View>
|
|
235
240
|
);
|
|
236
|
-
|
|
237
241
|
};
|
|
238
242
|
|
|
239
243
|
export default React.memo(ChatInput);
|
|
@@ -3,6 +3,7 @@ import { Image, Pressable, Text, View } from 'react-native';
|
|
|
3
3
|
import Video from 'react-native-video';
|
|
4
4
|
import { FileIcon } from '../../assets/Icons/FileIcon';
|
|
5
5
|
import { useChatContext } from '../../context/ChatContext';
|
|
6
|
+
import { withFontFamily } from '../../utils/theme';
|
|
6
7
|
import TruncateFileName from './TruncateFileName';
|
|
7
8
|
import { ChatInputProps } from './types';
|
|
8
9
|
|
|
@@ -57,7 +58,14 @@ const FilePreview: React.FC<Omit<ChatInputProps, 'onSendMessage'>> = ({
|
|
|
57
58
|
alignItems: 'center',
|
|
58
59
|
}}
|
|
59
60
|
>
|
|
60
|
-
<Text
|
|
61
|
+
<Text
|
|
62
|
+
style={withFontFamily(
|
|
63
|
+
{ fontSize: 12, color: 'black' },
|
|
64
|
+
theme?.fontFamily
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
X
|
|
68
|
+
</Text>
|
|
61
69
|
</Pressable>
|
|
62
70
|
|
|
63
71
|
{isImage ? (
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { StyleProp, Text, TextStyle } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, Text, TextStyle } from 'react-native';
|
|
3
|
+
import { useChatContext } from '../../context/ChatContext';
|
|
4
|
+
import { withFontFamily } from '../../utils/theme';
|
|
3
5
|
|
|
4
6
|
const TruncateFileName = ({
|
|
5
7
|
fileName,
|
|
@@ -8,6 +10,7 @@ const TruncateFileName = ({
|
|
|
8
10
|
fileName: string;
|
|
9
11
|
style?: StyleProp<TextStyle>;
|
|
10
12
|
}) => {
|
|
13
|
+
const { theme } = useChatContext();
|
|
11
14
|
const getTruncatedName = (fullName: string) => {
|
|
12
15
|
const lastDot = fullName.lastIndexOf('.');
|
|
13
16
|
if (lastDot === -1) return fullName;
|
|
@@ -23,7 +26,10 @@ const TruncateFileName = ({
|
|
|
23
26
|
<Text
|
|
24
27
|
numberOfLines={2}
|
|
25
28
|
ellipsizeMode="tail"
|
|
26
|
-
style={
|
|
29
|
+
style={withFontFamily(
|
|
30
|
+
[{ fontSize: 14, fontWeight: '600', color: 'black' }, style],
|
|
31
|
+
theme?.fontFamily
|
|
32
|
+
)}
|
|
27
33
|
>
|
|
28
34
|
{getTruncatedName(fileName)}
|
|
29
35
|
</Text>
|
|
@@ -5,6 +5,8 @@ import Video, { VideoRef } from 'react-native-video';
|
|
|
5
5
|
import tw from 'twrnc';
|
|
6
6
|
import { LoadingIcon } from '../../assets/Icons/LoadingIcon';
|
|
7
7
|
import { XIcon } from '../../assets/Icons/XIcon';
|
|
8
|
+
import { useChatContext } from '../../context/ChatContext';
|
|
9
|
+
import { withFontFamily } from '../../utils/theme';
|
|
8
10
|
import { MediaViewerProps } from './types';
|
|
9
11
|
|
|
10
12
|
const MediaViewer: React.FC<MediaViewerProps> = ({
|
|
@@ -12,6 +14,7 @@ const MediaViewer: React.FC<MediaViewerProps> = ({
|
|
|
12
14
|
videoUrl,
|
|
13
15
|
onClose,
|
|
14
16
|
}) => {
|
|
17
|
+
const { theme } = useChatContext();
|
|
15
18
|
const videoRef = useRef<VideoRef>(null);
|
|
16
19
|
const [videoIsLoading, setVideoIsLoading] = useState(false);
|
|
17
20
|
const [videoHasError, setVideoHasError] = useState(false);
|
|
@@ -84,7 +87,12 @@ const MediaViewer: React.FC<MediaViewerProps> = ({
|
|
|
84
87
|
<View
|
|
85
88
|
style={tw`absolute inset-0 flex items-center justify-center bg-red-500/60 p-2`}
|
|
86
89
|
>
|
|
87
|
-
<Text
|
|
90
|
+
<Text
|
|
91
|
+
style={withFontFamily(
|
|
92
|
+
tw`text-white font-bold`,
|
|
93
|
+
theme?.fontFamily
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
88
96
|
Failed to load video
|
|
89
97
|
</Text>
|
|
90
98
|
</View>
|
|
@@ -2,6 +2,7 @@ import { Image, Text, View } from 'react-native';
|
|
|
2
2
|
import tw from 'twrnc';
|
|
3
3
|
import { ArrowBack2RoundedIcon } from '../../assets/Icons/ArrowBack2RoundedIcon';
|
|
4
4
|
import { useChatContext } from '../../context/ChatContext';
|
|
5
|
+
import { withFontFamily } from '../../utils/theme';
|
|
5
6
|
|
|
6
7
|
export interface TypingUser {
|
|
7
8
|
id: string;
|
|
@@ -55,10 +56,13 @@ export const TypingIndicator = ({
|
|
|
55
56
|
/>
|
|
56
57
|
) : (
|
|
57
58
|
<Text
|
|
58
|
-
style={
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
style={withFontFamily(
|
|
60
|
+
[
|
|
61
|
+
tw`text-sm text-black font-semibold capitalize rounded-full bg-zinc-300 w-full h-full text-center pt-0.5`,
|
|
62
|
+
theme?.bubbleStyle?.avatarTextStyle,
|
|
63
|
+
],
|
|
64
|
+
theme?.fontFamily
|
|
65
|
+
)}
|
|
62
66
|
>
|
|
63
67
|
{user.name?.charAt(0)}
|
|
64
68
|
</Text>
|
|
@@ -77,10 +81,13 @@ export const TypingIndicator = ({
|
|
|
77
81
|
]}
|
|
78
82
|
>
|
|
79
83
|
<Text
|
|
80
|
-
style={
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
style={withFontFamily(
|
|
85
|
+
[
|
|
86
|
+
tw`text-white text-xs font-semibold`,
|
|
87
|
+
theme?.bubbleStyle?.additionalTypingUsersTextStyle,
|
|
88
|
+
],
|
|
89
|
+
theme?.fontFamily
|
|
90
|
+
)}
|
|
84
91
|
>
|
|
85
92
|
+{additionalUsers}
|
|
86
93
|
</Text>
|
|
@@ -107,7 +114,9 @@ export const TypingIndicator = ({
|
|
|
107
114
|
renderCustomTyping()
|
|
108
115
|
) : (
|
|
109
116
|
<View style={tw`flex-row items-center py-3 px-2 justify-center`}>
|
|
110
|
-
<Text style={tw`text-gray-600
|
|
117
|
+
<Text style={withFontFamily(tw`text-gray-600`, theme?.fontFamily)}>
|
|
118
|
+
Typing...
|
|
119
|
+
</Text>
|
|
111
120
|
</View>
|
|
112
121
|
)}
|
|
113
122
|
</View>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Keyboard, KeyboardEvent, Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns bottom padding to apply when the software keyboard is open.
|
|
6
|
+
* More reliable than KeyboardAvoidingView alone on Android chat layouts.
|
|
7
|
+
*/
|
|
8
|
+
export function useKeyboardInset(
|
|
9
|
+
keyboardVerticalOffset = 0,
|
|
10
|
+
enabled = true
|
|
11
|
+
) {
|
|
12
|
+
const [inset, setInset] = useState(0);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!enabled) {
|
|
16
|
+
setInset(0);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const showEvent =
|
|
21
|
+
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
22
|
+
const hideEvent =
|
|
23
|
+
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
24
|
+
|
|
25
|
+
const onShow = (event: KeyboardEvent) => {
|
|
26
|
+
const height = event.endCoordinates.height;
|
|
27
|
+
setInset(Math.max(0, height - keyboardVerticalOffset));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const onHide = () => setInset(0);
|
|
31
|
+
|
|
32
|
+
const showSub = Keyboard.addListener(showEvent, onShow);
|
|
33
|
+
const hideSub = Keyboard.addListener(hideEvent, onHide);
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
showSub.remove();
|
|
37
|
+
hideSub.remove();
|
|
38
|
+
};
|
|
39
|
+
}, [keyboardVerticalOffset, enabled]);
|
|
40
|
+
|
|
41
|
+
return enabled ? inset : 0;
|
|
42
|
+
}
|