forked from Ivasoft/mattermost-mobile
Compare commits
9 Commits
build-458
...
voice-mess
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97ab2f86cb | ||
|
|
79ee4ebb30 | ||
|
|
0db1d556df | ||
|
|
441e710bc1 | ||
|
|
f9b7932920 | ||
|
|
97cd096934 | ||
|
|
081ac80fde | ||
|
|
f9f0e95ce1 | ||
|
|
9854683321 |
@@ -40,6 +40,7 @@
|
|||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
|
tools:replace="android:allowBackup"
|
||||||
>
|
>
|
||||||
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
|
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
|
||||||
<meta-data android:name="android.content.APP_RESTRICTIONS"
|
<meta-data android:name="android.content.APP_RESTRICTIONS"
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ export async function updateDraftFile(serverUrl: string, channelId: string, root
|
|||||||
return {error: 'file not found'};
|
return {error: 'file not found'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.is_voice_recording = draft.files[i].is_voice_recording;
|
||||||
|
|
||||||
// We create a new list to make sure we re-render the draft input.
|
// We create a new list to make sure we re-render the draft input.
|
||||||
const newFiles = [...draft.files];
|
const newFiles = [...draft.files];
|
||||||
newFiles[i] = file;
|
newFiles[i] = file;
|
||||||
|
|
||||||
draft.prepareUpdate((d) => {
|
draft.prepareUpdate((d) => {
|
||||||
d.files = newFiles;
|
d.files = newFiles;
|
||||||
});
|
});
|
||||||
|
|||||||
113
app/components/files/voice_recording_file/index.tsx
Normal file
113
app/components/files/voice_recording_file/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useCallback, useMemo} from 'react';
|
||||||
|
import {StyleSheet, Text, useWindowDimensions, View} from 'react-native';
|
||||||
|
|
||||||
|
import CompassIcon from '@components/compass_icon';
|
||||||
|
import PlayBack from '@components/files/voice_recording_file/playback';
|
||||||
|
import {MIC_SIZE, VOICE_MESSAGE_CARD_RATIO} from '@constants/view';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
import {typography} from '@utils/typography';
|
||||||
|
|
||||||
|
//i18n
|
||||||
|
const VOICE_MESSAGE = 'Voice message';
|
||||||
|
const UPLOADING_TEXT = 'Uploading..(0%)';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
file: FileInfo;
|
||||||
|
uploading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderColor: changeOpacity(theme.centerChannelColor, 0.56),
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 6,
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 3,
|
||||||
|
},
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
centerContainer: {
|
||||||
|
marginLeft: 12,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
color: theme.centerChannelColor,
|
||||||
|
...typography('Heading', 200),
|
||||||
|
},
|
||||||
|
uploading: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||||
|
...typography('Body', 75),
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
backgroundColor: changeOpacity(theme.centerChannelColor, 0.56),
|
||||||
|
},
|
||||||
|
mic: {
|
||||||
|
borderRadius: MIC_SIZE / 2,
|
||||||
|
backgroundColor: changeOpacity(theme.buttonBg, 0.12),
|
||||||
|
height: MIC_SIZE,
|
||||||
|
width: MIC_SIZE,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginLeft: 12,
|
||||||
|
},
|
||||||
|
playBackContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const VoiceRecordingFile = ({file, uploading}: Props) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyleSheet(theme);
|
||||||
|
const dimensions = useWindowDimensions();
|
||||||
|
const isVoiceMessage = file.is_voice_recording;
|
||||||
|
|
||||||
|
const voiceStyle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
width: dimensions.width * VOICE_MESSAGE_CARD_RATIO,
|
||||||
|
};
|
||||||
|
}, [dimensions.width]);
|
||||||
|
|
||||||
|
const getUploadingView = useCallback(() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View
|
||||||
|
style={styles.mic}
|
||||||
|
>
|
||||||
|
<CompassIcon
|
||||||
|
name='microphone'
|
||||||
|
size={24}
|
||||||
|
color={theme.buttonBg}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.centerContainer}>
|
||||||
|
<Text style={styles.title}>{VOICE_MESSAGE}</Text>
|
||||||
|
<Text style={styles.uploading}>{UPLOADING_TEXT}</Text>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [uploading]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.container,
|
||||||
|
isVoiceMessage && voiceStyle,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{uploading ? getUploadingView() : <PlayBack/>}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VoiceRecordingFile;
|
||||||
61
app/components/files/voice_recording_file/playback.tsx
Normal file
61
app/components/files/voice_recording_file/playback.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import {TouchableOpacity, View} from 'react-native';
|
||||||
|
|
||||||
|
import CompassIcon from '@components/compass_icon';
|
||||||
|
import SoundWave from '@components/post_draft/draft_input/voice_input/sound_wave';
|
||||||
|
import TimeElapsed from '@components/post_draft/draft_input/voice_input/time_elapsed';
|
||||||
|
import {MIC_SIZE} from '@constants/view';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
import {preventDoubleTap} from '@utils/tap';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||||
|
return {
|
||||||
|
mic: {
|
||||||
|
borderRadius: MIC_SIZE / 2,
|
||||||
|
backgroundColor: changeOpacity(theme.buttonBg, 0.12),
|
||||||
|
height: MIC_SIZE,
|
||||||
|
width: MIC_SIZE,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginLeft: 12,
|
||||||
|
},
|
||||||
|
playBackContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const PlayBack = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyleSheet(theme);
|
||||||
|
const [playing, setPlaying] = useState(false);
|
||||||
|
|
||||||
|
const play = preventDoubleTap(() => {
|
||||||
|
return setPlaying((p) => !p);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={styles.playBackContainer}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.mic}
|
||||||
|
onPress={play}
|
||||||
|
>
|
||||||
|
<CompassIcon
|
||||||
|
color={theme.buttonBg}
|
||||||
|
name='play'
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<SoundWave animating={playing}/>
|
||||||
|
<TimeElapsed/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayBack;
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useCallback, useRef} from 'react';
|
import React, {useCallback, useRef, useState} from 'react';
|
||||||
import {LayoutChangeEvent, Platform, ScrollView, View} from 'react-native';
|
import {LayoutChangeEvent, Platform, ScrollView, View} from 'react-native';
|
||||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import QuickActions from '@components/post_draft/quick_actions';
|
||||||
import PostPriorityLabel from '@components/post_priority/post_priority_label';
|
import PostPriorityLabel from '@components/post_priority/post_priority_label';
|
||||||
import {useTheme} from '@context/theme';
|
import {useTheme} from '@context/theme';
|
||||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
|
||||||
import PostInput from '../post_input';
|
import RecordAction from '../record_action';
|
||||||
import QuickActions from '../quick_actions';
|
|
||||||
import SendAction from '../send_action';
|
import SendAction from '../send_action';
|
||||||
import Typing from '../typing';
|
import Typing from '../typing';
|
||||||
import Uploads from '../uploads';
|
|
||||||
|
import MessageInput from './message_input';
|
||||||
|
import VoiceInput from './voice_input';
|
||||||
|
|
||||||
import type {PasteInputRef} from '@mattermost/react-native-paste-input';
|
import type {PasteInputRef} from '@mattermost/react-native-paste-input';
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ type Props = {
|
|||||||
channelId: string;
|
channelId: string;
|
||||||
rootId?: string;
|
rootId?: string;
|
||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
|
voiceMessageEnabled: boolean;
|
||||||
canShowPostPriority?: boolean;
|
canShowPostPriority?: boolean;
|
||||||
|
|
||||||
// Post Props
|
// Post Props
|
||||||
@@ -51,16 +54,6 @@ const SAFE_AREA_VIEW_EDGES: Edge[] = ['left', 'right'];
|
|||||||
|
|
||||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||||
return {
|
return {
|
||||||
actionsContainer: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingBottom: Platform.select({
|
|
||||||
ios: 1,
|
|
||||||
android: 2,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
inputContainer: {
|
inputContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -84,6 +77,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||||||
borderTopLeftRadius: 12,
|
borderTopLeftRadius: 12,
|
||||||
borderTopRightRadius: 12,
|
borderTopRightRadius: 12,
|
||||||
},
|
},
|
||||||
|
actionsContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: Platform.select({
|
||||||
|
ios: 1,
|
||||||
|
android: 2,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
sendVoiceMessage: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: -5,
|
||||||
|
top: 16,
|
||||||
|
},
|
||||||
postPriorityLabel: {
|
postPriorityLabel: {
|
||||||
marginLeft: 12,
|
marginLeft: 12,
|
||||||
marginTop: Platform.select({
|
marginTop: Platform.select({
|
||||||
@@ -95,42 +103,83 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default function DraftInput({
|
export default function DraftInput({
|
||||||
testID,
|
addFiles,
|
||||||
|
canSend,
|
||||||
channelId,
|
channelId,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
cursorPosition,
|
||||||
canShowPostPriority,
|
canShowPostPriority,
|
||||||
files,
|
files,
|
||||||
maxMessageLength,
|
maxMessageLength,
|
||||||
rootId = '',
|
rootId = '',
|
||||||
value,
|
|
||||||
uploadFileError,
|
|
||||||
sendMessage,
|
sendMessage,
|
||||||
canSend,
|
testID,
|
||||||
updateValue,
|
|
||||||
addFiles,
|
|
||||||
updateCursorPosition,
|
updateCursorPosition,
|
||||||
cursorPosition,
|
|
||||||
updatePostInputTop,
|
updatePostInputTop,
|
||||||
|
updateValue,
|
||||||
|
uploadFileError,
|
||||||
|
value,
|
||||||
|
voiceMessageEnabled,
|
||||||
postPriority,
|
postPriority,
|
||||||
updatePostPriority,
|
updatePostPriority,
|
||||||
setIsFocused,
|
setIsFocused,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const [recording, setRecording] = useState(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const style = getStyleSheet(theme);
|
||||||
|
|
||||||
const handleLayout = useCallback((e: LayoutChangeEvent) => {
|
const handleLayout = useCallback((e: LayoutChangeEvent) => {
|
||||||
updatePostInputTop(e.nativeEvent.layout.height);
|
updatePostInputTop(e.nativeEvent.layout.height);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onPresRecording = useCallback(() => {
|
||||||
|
setRecording(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onCloseRecording = useCallback(() => {
|
||||||
|
setRecording(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isHandlingVoice = files[0]?.is_voice_recording || recording;
|
||||||
const inputRef = useRef<PasteInputRef>();
|
const inputRef = useRef<PasteInputRef>();
|
||||||
const focus = useCallback(() => {
|
const focus = useCallback(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
const postInputTestID = `${testID}.post.input`;
|
|
||||||
const quickActionsTestID = `${testID}.quick_actions`;
|
const quickActionsTestID = `${testID}.quick_actions`;
|
||||||
const sendActionTestID = `${testID}.send_action`;
|
const sendActionTestID = `${testID}.send_action`;
|
||||||
const style = getStyleSheet(theme);
|
const recordActionTestID = `${testID}.record_action`;
|
||||||
|
|
||||||
|
const getActionButton = useCallback(() => {
|
||||||
|
if (value.length === 0 && files.length === 0 && voiceMessageEnabled) {
|
||||||
|
return (
|
||||||
|
<RecordAction
|
||||||
|
onPress={onPresRecording}
|
||||||
|
testID={recordActionTestID}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SendAction
|
||||||
|
disabled={!canSend}
|
||||||
|
sendMessage={sendMessage}
|
||||||
|
testID={sendActionTestID}
|
||||||
|
containerStyle={isHandlingVoice && style.sendVoiceMessage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
canSend,
|
||||||
|
files.length,
|
||||||
|
onCloseRecording,
|
||||||
|
onPresRecording,
|
||||||
|
sendMessage,
|
||||||
|
testID,
|
||||||
|
value.length,
|
||||||
|
voiceMessageEnabled,
|
||||||
|
isHandlingVoice,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -144,61 +193,63 @@ export default function DraftInput({
|
|||||||
style={style.inputWrapper}
|
style={style.inputWrapper}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
>
|
>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={style.inputContainer}
|
|
||||||
contentContainerStyle={style.inputContentContainer}
|
contentContainerStyle={style.inputContentContainer}
|
||||||
keyboardShouldPersistTaps={'always'}
|
|
||||||
scrollEnabled={false}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
pinchGestureEnabled={false}
|
|
||||||
overScrollMode={'never'}
|
|
||||||
disableScrollViewPanResponder={true}
|
disableScrollViewPanResponder={true}
|
||||||
|
keyboardShouldPersistTaps={'always'}
|
||||||
|
overScrollMode={'never'}
|
||||||
|
pinchGestureEnabled={false}
|
||||||
|
scrollEnabled={false}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
style={style.inputContainer}
|
||||||
>
|
>
|
||||||
{Boolean(postPriority?.priority) && (
|
{Boolean(postPriority?.priority) && (
|
||||||
<View style={style.postPriorityLabel}>
|
<View style={style.postPriorityLabel}>
|
||||||
<PostPriorityLabel label={postPriority!.priority}/>
|
<PostPriorityLabel label={postPriority!.priority}/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<PostInput
|
{recording && (
|
||||||
testID={postInputTestID}
|
<VoiceInput
|
||||||
channelId={channelId}
|
|
||||||
maxMessageLength={maxMessageLength}
|
|
||||||
rootId={rootId}
|
|
||||||
cursorPosition={cursorPosition}
|
|
||||||
updateCursorPosition={updateCursorPosition}
|
|
||||||
updateValue={updateValue}
|
|
||||||
value={value}
|
|
||||||
addFiles={addFiles}
|
|
||||||
sendMessage={sendMessage}
|
|
||||||
inputRef={inputRef}
|
|
||||||
setIsFocused={setIsFocused}
|
|
||||||
/>
|
|
||||||
<Uploads
|
|
||||||
currentUserId={currentUserId}
|
|
||||||
files={files}
|
|
||||||
uploadFileError={uploadFileError}
|
|
||||||
channelId={channelId}
|
|
||||||
rootId={rootId}
|
|
||||||
/>
|
|
||||||
<View style={style.actionsContainer}>
|
|
||||||
<QuickActions
|
|
||||||
testID={quickActionsTestID}
|
|
||||||
fileCount={files.length}
|
|
||||||
addFiles={addFiles}
|
addFiles={addFiles}
|
||||||
updateValue={updateValue}
|
onClose={onCloseRecording}
|
||||||
value={value}
|
setRecording={setRecording}
|
||||||
postPriority={postPriority}
|
|
||||||
updatePostPriority={updatePostPriority}
|
|
||||||
canShowPostPriority={canShowPostPriority}
|
|
||||||
focus={focus}
|
|
||||||
/>
|
/>
|
||||||
<SendAction
|
)}
|
||||||
testID={sendActionTestID}
|
{!recording &&
|
||||||
disabled={!canSend}
|
<MessageInput
|
||||||
|
addFiles={addFiles}
|
||||||
|
channelId={channelId}
|
||||||
|
currentUserId={currentUserId}
|
||||||
|
cursorPosition={cursorPosition}
|
||||||
|
files={files}
|
||||||
|
inputRef={inputRef}
|
||||||
|
maxMessageLength={maxMessageLength}
|
||||||
|
rootId={rootId}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
|
setIsFocused={setIsFocused}
|
||||||
|
testID={testID}
|
||||||
|
updateCursorPosition={updateCursorPosition}
|
||||||
|
updateValue={updateValue}
|
||||||
|
uploadFileError={uploadFileError}
|
||||||
|
value={value}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
<View style={style.actionsContainer}>
|
||||||
|
{!isHandlingVoice &&
|
||||||
|
<QuickActions
|
||||||
|
addFiles={addFiles}
|
||||||
|
canShowPostPriority={canShowPostPriority}
|
||||||
|
fileCount={files.length}
|
||||||
|
postPriority={postPriority}
|
||||||
|
testID={quickActionsTestID}
|
||||||
|
updatePostPriority={updatePostPriority}
|
||||||
|
updateValue={updateValue}
|
||||||
|
value={value}
|
||||||
|
focus={focus}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{!isHandlingVoice && getActionButton()}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
19
app/components/post_draft/draft_input/index.ts
Normal file
19
app/components/post_draft/draft_input/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||||
|
import withObservables from '@nozbe/with-observables';
|
||||||
|
|
||||||
|
import {observeVoiceMessagesEnabled} from '@queries/servers/system';
|
||||||
|
|
||||||
|
import DraftInput from './draft_input';
|
||||||
|
|
||||||
|
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||||
|
|
||||||
|
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||||
|
return {
|
||||||
|
voiceMessageEnabled: observeVoiceMessagesEnabled(database),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withDatabase(enhanced(DraftInput));
|
||||||
83
app/components/post_draft/draft_input/message_input.tsx
Normal file
83
app/components/post_draft/draft_input/message_input.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import PostInput from '../post_input';
|
||||||
|
import Uploads from '../uploads';
|
||||||
|
|
||||||
|
import type {PasteInputRef} from '@mattermost/react-native-paste-input';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
testID?: string;
|
||||||
|
channelId: string;
|
||||||
|
rootId?: string;
|
||||||
|
currentUserId: string;
|
||||||
|
|
||||||
|
// Cursor Position Handler
|
||||||
|
updateCursorPosition: (pos: number) => void;
|
||||||
|
cursorPosition: number;
|
||||||
|
|
||||||
|
// Send Handler
|
||||||
|
sendMessage: () => void;
|
||||||
|
maxMessageLength: number;
|
||||||
|
|
||||||
|
// Draft Handler
|
||||||
|
addFiles: (files: FileInfo[]) => void;
|
||||||
|
files: FileInfo[];
|
||||||
|
inputRef: React.MutableRefObject<PasteInputRef | undefined>;
|
||||||
|
setIsFocused: (isFocused: boolean) => void;
|
||||||
|
uploadFileError: React.ReactNode;
|
||||||
|
updateValue: (value: string) => void;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MessageInput({
|
||||||
|
addFiles,
|
||||||
|
channelId,
|
||||||
|
currentUserId,
|
||||||
|
cursorPosition,
|
||||||
|
files,
|
||||||
|
inputRef,
|
||||||
|
maxMessageLength,
|
||||||
|
rootId = '',
|
||||||
|
sendMessage,
|
||||||
|
setIsFocused,
|
||||||
|
testID,
|
||||||
|
updateCursorPosition,
|
||||||
|
updateValue,
|
||||||
|
uploadFileError,
|
||||||
|
value,
|
||||||
|
}: Props) {
|
||||||
|
// Render
|
||||||
|
const postInputTestID = `${testID}.post.input`;
|
||||||
|
const isHandlingVoice = files[0]?.is_voice_recording;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!isHandlingVoice && (
|
||||||
|
<PostInput
|
||||||
|
addFiles={addFiles}
|
||||||
|
channelId={channelId}
|
||||||
|
cursorPosition={cursorPosition}
|
||||||
|
inputRef={inputRef}
|
||||||
|
maxMessageLength={maxMessageLength}
|
||||||
|
rootId={rootId}
|
||||||
|
sendMessage={sendMessage}
|
||||||
|
setIsFocused={setIsFocused}
|
||||||
|
testID={postInputTestID}
|
||||||
|
updateCursorPosition={updateCursorPosition}
|
||||||
|
updateValue={updateValue}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Uploads
|
||||||
|
currentUserId={currentUserId}
|
||||||
|
files={files}
|
||||||
|
uploadFileError={uploadFileError}
|
||||||
|
channelId={channelId}
|
||||||
|
rootId={rootId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useEffect} from 'react';
|
||||||
|
import {View} from 'react-native';
|
||||||
|
import Animated, {interpolate, SharedValue, useAnimatedStyle, useSharedValue, withRepeat, withTiming} from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import CompassIcon from '@components/compass_icon';
|
||||||
|
import {MIC_SIZE} from '@constants/view';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
const iconCommon = {
|
||||||
|
height: MIC_SIZE,
|
||||||
|
width: MIC_SIZE,
|
||||||
|
alignItems: 'center' as const,
|
||||||
|
justifyContent: 'center' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const round = {
|
||||||
|
borderRadius: MIC_SIZE / 2,
|
||||||
|
backgroundColor: changeOpacity(theme.buttonBg, 0.12),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
mic: {
|
||||||
|
...iconCommon,
|
||||||
|
...round,
|
||||||
|
},
|
||||||
|
abs: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
concentric: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const useConcentricStyles = (circleId: number, sharedValue: SharedValue<number>) => {
|
||||||
|
const circles = [1.5, 2.5, 3.5];
|
||||||
|
return useAnimatedStyle(() => {
|
||||||
|
const scale = interpolate(sharedValue.value, [0, 1], [circles[circleId], 1]);
|
||||||
|
const opacity = interpolate(sharedValue.value, [0, 1], [1, 0]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
opacity,
|
||||||
|
transform: [{scale}],
|
||||||
|
borderRadius: MIC_SIZE / 2,
|
||||||
|
};
|
||||||
|
}, [sharedValue]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnimatedMicrophone = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyleSheet(theme);
|
||||||
|
|
||||||
|
const val = useSharedValue(0);
|
||||||
|
|
||||||
|
const firstCircleAnimx = useConcentricStyles(0, val);
|
||||||
|
const secondCircleAnimx = useConcentricStyles(1, val);
|
||||||
|
const thirdCircleAnimx = useConcentricStyles(2, val);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
val.value = withRepeat(
|
||||||
|
withTiming(1, {duration: 1000}),
|
||||||
|
800,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.mic]}>
|
||||||
|
<View style={styles.concentric} >
|
||||||
|
<Animated.View style={[styles.mic, styles.abs, firstCircleAnimx]}/>
|
||||||
|
<Animated.View style={[styles.mic, styles.abs, secondCircleAnimx]}/>
|
||||||
|
<Animated.View style={[styles.mic, styles.abs, thirdCircleAnimx]}/>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[styles.mic, styles.abs]}
|
||||||
|
>
|
||||||
|
<CompassIcon
|
||||||
|
name='microphone'
|
||||||
|
size={24}
|
||||||
|
color={theme.buttonBg}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnimatedMicrophone;
|
||||||
|
|
||||||
109
app/components/post_draft/draft_input/voice_input/index.tsx
Normal file
109
app/components/post_draft/draft_input/voice_input/index.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useEffect} from 'react';
|
||||||
|
import {TouchableOpacity, View} from 'react-native';
|
||||||
|
|
||||||
|
import CompassIcon from '@components/compass_icon';
|
||||||
|
import {MIC_SIZE} from '@constants/view';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
import {extractFileInfo} from '@utils/file';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
|
||||||
|
import AnimatedMicrophone from './animated_microphone';
|
||||||
|
import SoundWave from './sound_wave';
|
||||||
|
import TimeElapsed from './time_elapsed';
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
const iconCommon = {
|
||||||
|
height: MIC_SIZE,
|
||||||
|
width: MIC_SIZE,
|
||||||
|
alignItems: 'center' as const,
|
||||||
|
justifyContent: 'center' as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const round = {
|
||||||
|
borderRadius: MIC_SIZE / 2,
|
||||||
|
backgroundColor: changeOpacity(theme.buttonBg, 0.12),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
mainContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
height: 88,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
},
|
||||||
|
mic: {
|
||||||
|
...iconCommon,
|
||||||
|
...round,
|
||||||
|
},
|
||||||
|
check: {
|
||||||
|
...iconCommon,
|
||||||
|
...round,
|
||||||
|
backgroundColor: theme.buttonBg,
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
...iconCommon,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
type VoiceInputProps = {
|
||||||
|
setRecording: (v: boolean) => void;
|
||||||
|
addFiles: (f: FileInfo[]) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
const VoiceInput = ({onClose, addFiles, setRecording}: VoiceInputProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyleSheet(theme);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const record = async () => {
|
||||||
|
const url = ''; //await recorder.current?.stopRecorder()
|
||||||
|
const fi = await extractFileInfo([{uri: url}]);
|
||||||
|
fi[0].is_voice_recording = true;
|
||||||
|
addFiles(fi as FileInfo[]);
|
||||||
|
setRecording(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//todo: to start recording as soon as this screen shows up
|
||||||
|
// record();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.mainContainer}>
|
||||||
|
<AnimatedMicrophone/>
|
||||||
|
<SoundWave/>
|
||||||
|
<TimeElapsed/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.close}
|
||||||
|
onPress={onClose}
|
||||||
|
>
|
||||||
|
<CompassIcon
|
||||||
|
color={theme.buttonBg}
|
||||||
|
name='close'
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.check}
|
||||||
|
onPress={onClose} // to be fixed when wiring is completed
|
||||||
|
>
|
||||||
|
<CompassIcon
|
||||||
|
color={theme.buttonColor}
|
||||||
|
name='check'
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VoiceInput;
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {random} from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import {View} from 'react-native';
|
||||||
|
import Animated, {cancelAnimation, Extrapolation, interpolate, useAnimatedStyle, useSharedValue, withRepeat, withSpring} from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import {WAVEFORM_HEIGHT} from '@constants/view';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
import useDidUpdate from '@hooks/did_update';
|
||||||
|
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
height: WAVEFORM_HEIGHT,
|
||||||
|
width: 165,
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflow: 'hidden',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
singleBar: {
|
||||||
|
height: WAVEFORM_HEIGHT,
|
||||||
|
width: 2,
|
||||||
|
backgroundColor: theme.buttonBg,
|
||||||
|
marginRight: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
type SoundWaveProps = {
|
||||||
|
animating?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SoundWave = ({animating = true}: SoundWaveProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyleSheet(theme);
|
||||||
|
|
||||||
|
const animatedValue = useSharedValue(5);
|
||||||
|
|
||||||
|
const animatedStyles = useAnimatedStyle(() => {
|
||||||
|
const newHeight = interpolate(
|
||||||
|
animatedValue.value,
|
||||||
|
[5, 40],
|
||||||
|
[0, 40],
|
||||||
|
Extrapolation.EXTEND,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
height: newHeight,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useDidUpdate(() => {
|
||||||
|
if (animating) {
|
||||||
|
animatedValue.value = withRepeat(
|
||||||
|
withSpring(40, {
|
||||||
|
damping: 10,
|
||||||
|
mass: 0.6,
|
||||||
|
overshootClamping: true,
|
||||||
|
}),
|
||||||
|
800,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
cancelAnimation(animatedValue);
|
||||||
|
}
|
||||||
|
}, [animating]);
|
||||||
|
|
||||||
|
const getAudioBars = () => {
|
||||||
|
const bars = [];
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
let height;
|
||||||
|
if (random(i, 50) % 2 === 0) {
|
||||||
|
height = random(5, 30);
|
||||||
|
}
|
||||||
|
bars.push(
|
||||||
|
<Animated.View
|
||||||
|
key={i}
|
||||||
|
style={[
|
||||||
|
styles.singleBar,
|
||||||
|
{height},
|
||||||
|
!height && animatedStyles,
|
||||||
|
]}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return bars;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{getAudioBars()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SoundWave;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import {Text} from 'react-native';
|
||||||
|
|
||||||
|
//fixme: hook up the time elapsed progress from the lib in here
|
||||||
|
|
||||||
|
const TimeElapsed = () => {
|
||||||
|
const [timeElapsed] = useState('00:00');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{timeElapsed}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TimeElapsed;
|
||||||
48
app/components/post_draft/record_action/index.tsx
Normal file
48
app/components/post_draft/record_action/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import CompassIcon from '@components/compass_icon';
|
||||||
|
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onPress: () => void;
|
||||||
|
testID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
recordButtonContainer: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingRight: 8,
|
||||||
|
},
|
||||||
|
recordButton: {
|
||||||
|
borderRadius: 4,
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function RecordButton({onPress, testID}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableWithFeedback
|
||||||
|
onPress={onPress}
|
||||||
|
style={styles.recordButtonContainer}
|
||||||
|
testID={testID}
|
||||||
|
type={'opacity'}
|
||||||
|
>
|
||||||
|
<CompassIcon
|
||||||
|
color={theme.centerChannelColor}
|
||||||
|
name='microphone'
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</TouchableWithFeedback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecordButton;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useMemo} from 'react';
|
import React, {useMemo} from 'react';
|
||||||
import {View} from 'react-native';
|
import {StyleProp, View, ViewStyle} from 'react-native';
|
||||||
|
|
||||||
import CompassIcon from '@components/compass_icon';
|
import CompassIcon from '@components/compass_icon';
|
||||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||||
@@ -13,6 +13,7 @@ type Props = {
|
|||||||
testID: string;
|
testID: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
sendMessage: () => void;
|
sendMessage: () => void;
|
||||||
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||||
@@ -39,6 +40,7 @@ function SendButton({
|
|||||||
testID,
|
testID,
|
||||||
disabled,
|
disabled,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
containerStyle,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const sendButtonTestID = disabled ? `${testID}.send.button.disabled` : `${testID}.send.button`;
|
const sendButtonTestID = disabled ? `${testID}.send.button.disabled` : `${testID}.send.button`;
|
||||||
@@ -57,7 +59,7 @@ function SendButton({
|
|||||||
<TouchableWithFeedback
|
<TouchableWithFeedback
|
||||||
testID={sendButtonTestID}
|
testID={sendButtonTestID}
|
||||||
onPress={sendMessage}
|
onPress={sendMessage}
|
||||||
style={style.sendButtonContainer}
|
style={[style.sendButtonContainer, containerStyle]}
|
||||||
type={'opacity'}
|
type={'opacity'}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {handleReactionToLatestPost} from '@actions/remote/reactions';
|
|||||||
import {setStatus} from '@actions/remote/user';
|
import {setStatus} from '@actions/remote/user';
|
||||||
import {handleCallsSlashCommand} from '@calls/actions/calls';
|
import {handleCallsSlashCommand} from '@calls/actions/calls';
|
||||||
import {Events, Screens} from '@constants';
|
import {Events, Screens} from '@constants';
|
||||||
import {PostPriorityType} from '@constants/post';
|
import {PostPriorityType, PostTypes} from '@constants/post';
|
||||||
import {NOTIFY_ALL_MEMBERS} from '@constants/post_draft';
|
import {NOTIFY_ALL_MEMBERS} from '@constants/post_draft';
|
||||||
import {useServerUrl} from '@context/server';
|
import {useServerUrl} from '@context/server';
|
||||||
import DraftUploadManager from '@managers/draft_upload_manager';
|
import DraftUploadManager from '@managers/draft_upload_manager';
|
||||||
@@ -121,6 +121,7 @@ export default function SendHandler({
|
|||||||
channel_id: channelId,
|
channel_id: channelId,
|
||||||
root_id: rootId,
|
root_id: rootId,
|
||||||
message: value,
|
message: value,
|
||||||
|
type: (files[0]?.is_voice_recording ? PostTypes.VOICE_MESSAGE : '') as PostType,
|
||||||
} as Post;
|
} as Post;
|
||||||
|
|
||||||
if (Object.keys(postPriority).length) {
|
if (Object.keys(postPriority).length) {
|
||||||
|
|||||||
@@ -156,17 +156,14 @@ function Uploads({
|
|||||||
{buildFilePreviews()}
|
{buildFilePreviews()}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[style.errorContainer, errorAnimatedStyle]}
|
style={[style.errorContainer, errorAnimatedStyle]}
|
||||||
>
|
>
|
||||||
{Boolean(uploadFileError) &&
|
{Boolean(uploadFileError) &&
|
||||||
<View style={style.errorTextContainer}>
|
<View style={style.errorTextContainer}>
|
||||||
|
|
||||||
<Text style={style.warning}>
|
<Text style={style.warning}>
|
||||||
{uploadFileError}
|
{uploadFileError}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native';
|
import {StyleSheet, TouchableWithoutFeedback, useWindowDimensions, View} from 'react-native';
|
||||||
import Animated from 'react-native-reanimated';
|
import Animated from 'react-native-reanimated';
|
||||||
|
|
||||||
import {updateDraftFile} from '@actions/local/draft';
|
import {updateDraftFile} from '@actions/local/draft';
|
||||||
import FileIcon from '@components/files/file_icon';
|
import FileIcon from '@components/files/file_icon';
|
||||||
import ImageFile from '@components/files/image_file';
|
import ImageFile from '@components/files/image_file';
|
||||||
|
import VoiceRecordingFile from '@components/files/voice_recording_file';
|
||||||
import ProgressBar from '@components/progress_bar';
|
import ProgressBar from '@components/progress_bar';
|
||||||
|
import {VOICE_MESSAGE_CARD_RATIO} from '@constants/view';
|
||||||
import {useServerUrl} from '@context/server';
|
import {useServerUrl} from '@context/server';
|
||||||
import {useTheme} from '@context/theme';
|
import {useTheme} from '@context/theme';
|
||||||
import useDidUpdate from '@hooks/did_update';
|
import useDidUpdate from '@hooks/did_update';
|
||||||
@@ -63,8 +65,10 @@ export default function UploadItem({
|
|||||||
const serverUrl = useServerUrl();
|
const serverUrl = useServerUrl();
|
||||||
const removeCallback = useRef<(() => void)|null>(null);
|
const removeCallback = useRef<(() => void)|null>(null);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
const dimensions = useWindowDimensions();
|
||||||
|
|
||||||
const loading = DraftUploadManager.isUploading(file.clientId!);
|
const loading = DraftUploadManager.isUploading(file.clientId!);
|
||||||
|
const isVoiceMessage = file.is_voice_recording;
|
||||||
|
|
||||||
const handlePress = useCallback(() => {
|
const handlePress = useCallback(() => {
|
||||||
openGallery(file);
|
openGallery(file);
|
||||||
@@ -115,6 +119,16 @@ export default function UploadItem({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isVoiceMessage) {
|
||||||
|
return (
|
||||||
|
<VoiceRecordingFile
|
||||||
|
file={file}
|
||||||
|
uploading={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileIcon
|
<FileIcon
|
||||||
backgroundColor={changeOpacity(theme.centerChannelColor, 0.08)}
|
backgroundColor={changeOpacity(theme.centerChannelColor, 0.08)}
|
||||||
@@ -124,14 +138,35 @@ export default function UploadItem({
|
|||||||
);
|
);
|
||||||
}, [file]);
|
}, [file]);
|
||||||
|
|
||||||
|
const voiceStyle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
width: dimensions.width * VOICE_MESSAGE_CARD_RATIO,
|
||||||
|
};
|
||||||
|
}, [dimensions.width]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={file.clientId}
|
key={file.clientId}
|
||||||
style={style.preview}
|
style={[
|
||||||
|
style.preview,
|
||||||
|
isVoiceMessage && voiceStyle,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<View style={style.previewContainer}>
|
<View
|
||||||
<TouchableWithoutFeedback onPress={onGestureEvent}>
|
style={[
|
||||||
<Animated.View style={[styles, style.filePreview]}>
|
style.previewContainer,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPress={onGestureEvent}
|
||||||
|
disabled={file.is_voice_recording}
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles,
|
||||||
|
style.filePreview,
|
||||||
|
]}
|
||||||
|
>
|
||||||
{filePreviewComponent}
|
{filePreviewComponent}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {View, Platform} from 'react-native';
|
import {View, Platform, StyleProp, ViewStyle} from 'react-native';
|
||||||
|
|
||||||
import {removeDraftFile} from '@actions/local/draft';
|
import {removeDraftFile} from '@actions/local/draft';
|
||||||
import CompassIcon from '@components/compass_icon';
|
import CompassIcon from '@components/compass_icon';
|
||||||
@@ -14,8 +14,9 @@ import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
rootId: string;
|
|
||||||
clientId: string;
|
clientId: string;
|
||||||
|
rootId: string;
|
||||||
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||||
@@ -30,7 +31,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||||||
},
|
},
|
||||||
removeButton: {
|
removeButton: {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
alignSelf: 'center',
|
|
||||||
|
// alignSelf: 'center',
|
||||||
marginTop: Platform.select({
|
marginTop: Platform.select({
|
||||||
ios: 5.4,
|
ios: 5.4,
|
||||||
android: 4.75,
|
android: 4.75,
|
||||||
@@ -44,8 +46,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||||||
|
|
||||||
export default function UploadRemove({
|
export default function UploadRemove({
|
||||||
channelId,
|
channelId,
|
||||||
rootId,
|
containerStyle,
|
||||||
clientId,
|
clientId,
|
||||||
|
rootId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const style = getStyleSheet(theme);
|
const style = getStyleSheet(theme);
|
||||||
@@ -58,7 +61,7 @@ export default function UploadRemove({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableWithFeedback
|
<TouchableWithFeedback
|
||||||
style={style.tappableContainer}
|
style={[style.tappableContainer, containerStyle]}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
type={'opacity'}
|
type={'opacity'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useCallback, useState} from 'react';
|
import React, {useCallback, useState} from 'react';
|
||||||
import {LayoutChangeEvent, StyleProp, View, ViewStyle} from 'react-native';
|
import {LayoutChangeEvent, StyleProp, Text, View, ViewStyle} from 'react-native';
|
||||||
|
|
||||||
|
import {PostTypes} from '@app/constants/post';
|
||||||
import Files from '@components/files';
|
import Files from '@components/files';
|
||||||
import FormattedText from '@components/formatted_text';
|
import FormattedText from '@components/formatted_text';
|
||||||
import JumboEmoji from '@components/jumbo_emoji';
|
import JumboEmoji from '@components/jumbo_emoji';
|
||||||
@@ -38,6 +39,7 @@ type BodyProps = {
|
|||||||
post: PostModel;
|
post: PostModel;
|
||||||
searchPatterns?: SearchPattern[];
|
searchPatterns?: SearchPattern[];
|
||||||
showAddReaction?: boolean;
|
showAddReaction?: boolean;
|
||||||
|
voiceMessageEnabled?: boolean;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,7 +79,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||||||
const Body = ({
|
const Body = ({
|
||||||
appsEnabled, hasFiles, hasReactions, highlight, highlightReplyBar,
|
appsEnabled, hasFiles, hasReactions, highlight, highlightReplyBar,
|
||||||
isCRTEnabled, isEphemeral, isFirstReply, isJumboEmoji, isLastReply, isPendingOrFailed, isPostAddChannelMember,
|
isCRTEnabled, isEphemeral, isFirstReply, isJumboEmoji, isLastReply, isPendingOrFailed, isPostAddChannelMember,
|
||||||
location, post, searchPatterns, showAddReaction, theme,
|
location, post, searchPatterns, showAddReaction, voiceMessageEnabled, theme,
|
||||||
}: BodyProps) => {
|
}: BodyProps) => {
|
||||||
const style = getStyleSheet(theme);
|
const style = getStyleSheet(theme);
|
||||||
const isEdited = postEdited(post);
|
const isEdited = postEdited(post);
|
||||||
@@ -159,36 +161,52 @@ const Body = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hasBeenDeleted) {
|
if (!hasBeenDeleted) {
|
||||||
body = (
|
if (voiceMessageEnabled && post.type === PostTypes.VOICE_MESSAGE) {
|
||||||
<View style={style.messageBody}>
|
body = (
|
||||||
{message}
|
<View style={style.messageBody}>
|
||||||
{hasContent &&
|
<Text>{'I am a recording'}</Text>
|
||||||
<Content
|
{/* <VoiceMessagePost /> */}
|
||||||
isReplyPost={isReplyPost}
|
{hasReactions && showAddReaction &&
|
||||||
layoutWidth={layoutWidth}
|
<Reactions
|
||||||
location={location}
|
location={location}
|
||||||
post={post}
|
post={post}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{hasFiles &&
|
</View>
|
||||||
<Files
|
);
|
||||||
failed={isFailed}
|
} else {
|
||||||
layoutWidth={layoutWidth}
|
body = (
|
||||||
location={location}
|
<View style={style.messageBody}>
|
||||||
post={post}
|
{message}
|
||||||
isReplyPost={isReplyPost}
|
{hasContent &&
|
||||||
/>
|
<Content
|
||||||
}
|
isReplyPost={isReplyPost}
|
||||||
{hasReactions && showAddReaction &&
|
layoutWidth={layoutWidth}
|
||||||
<Reactions
|
location={location}
|
||||||
location={location}
|
post={post}
|
||||||
post={post}
|
theme={theme}
|
||||||
theme={theme}
|
/>
|
||||||
/>
|
}
|
||||||
}
|
{hasFiles &&
|
||||||
</View>
|
<Files
|
||||||
);
|
failed={isFailed}
|
||||||
|
layoutWidth={layoutWidth}
|
||||||
|
location={location}
|
||||||
|
post={post}
|
||||||
|
isReplyPost={isReplyPost}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{hasReactions && showAddReaction &&
|
||||||
|
<Reactions
|
||||||
|
location={location}
|
||||||
|
post={post}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
19
app/components/post_list/post/body/index.ts
Normal file
19
app/components/post_list/post/body/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||||
|
import withObservables from '@nozbe/with-observables';
|
||||||
|
|
||||||
|
import {observeVoiceMessagesEnabled} from '@queries/servers/system';
|
||||||
|
|
||||||
|
import Body from './body';
|
||||||
|
|
||||||
|
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||||
|
|
||||||
|
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||||
|
return {
|
||||||
|
voiceMessageEnabled: observeVoiceMessagesEnabled(database),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withDatabase(enhanced(Body));
|
||||||
@@ -35,6 +35,8 @@ export const PostTypes: Record<string, string> = {
|
|||||||
SYSTEM_AUTO_RESPONDER: 'system_auto_responder',
|
SYSTEM_AUTO_RESPONDER: 'system_auto_responder',
|
||||||
CUSTOM_CALLS: 'custom_calls',
|
CUSTOM_CALLS: 'custom_calls',
|
||||||
CUSTOM_CALLS_RECORDING: 'custom_calls_recording',
|
CUSTOM_CALLS_RECORDING: 'custom_calls_recording',
|
||||||
|
|
||||||
|
VOICE_MESSAGE: 'voice',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PostPriorityColors = {
|
export const PostPriorityColors = {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export const JOIN_CALL_BAR_HEIGHT = 38;
|
|||||||
export const CURRENT_CALL_BAR_HEIGHT = 74;
|
export const CURRENT_CALL_BAR_HEIGHT = 74;
|
||||||
export const CALL_ERROR_BAR_HEIGHT = 62;
|
export const CALL_ERROR_BAR_HEIGHT = 62;
|
||||||
|
|
||||||
|
export const VOICE_MESSAGE_CARD_RATIO = 0.72;
|
||||||
|
export const MIC_SIZE = 40;
|
||||||
|
export const WAVEFORM_HEIGHT = 40;
|
||||||
|
|
||||||
export const ANNOUNCEMENT_BAR_HEIGHT = 40;
|
export const ANNOUNCEMENT_BAR_HEIGHT = 40;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -44,5 +48,8 @@ export default {
|
|||||||
LARGE_HEADER_TITLE_HEIGHT,
|
LARGE_HEADER_TITLE_HEIGHT,
|
||||||
SUBTITLE_HEIGHT,
|
SUBTITLE_HEIGHT,
|
||||||
KEYBOARD_TRACKING_OFFSET,
|
KEYBOARD_TRACKING_OFFSET,
|
||||||
|
VOICE_MESSAGE_CARD_RATIO,
|
||||||
|
MIC_SIZE,
|
||||||
|
WAVEFORM_HEIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -495,6 +495,12 @@ export const observeAllowedThemesKeys = (database: Database) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const observeVoiceMessagesEnabled = (database: Database) => {
|
||||||
|
return observeConfig(database).pipe(
|
||||||
|
switchMap((c) => of$(c?.ExperimentalEnableVoiceMessage === 'true' && c?.FeatureFlagEnableVoiceMessages === 'true')),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getExpiredSession = async (database: Database) => {
|
export const getExpiredSession = async (database: Database) => {
|
||||||
try {
|
try {
|
||||||
const session = await database.get<SystemModel>(SYSTEM).find(SYSTEM_IDENTIFIERS.SESSION_EXPIRATION);
|
const session = await database.get<SystemModel>(SYSTEM).find(SYSTEM_IDENTIFIERS.SESSION_EXPIRATION);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|||||||
import {CopyPermalinkOption, FollowThreadOption, ReplyOption, SaveOption} from '@components/common_post_options';
|
import {CopyPermalinkOption, FollowThreadOption, ReplyOption, SaveOption} from '@components/common_post_options';
|
||||||
import {ITEM_HEIGHT} from '@components/option_item';
|
import {ITEM_HEIGHT} from '@components/option_item';
|
||||||
import {Screens} from '@constants';
|
import {Screens} from '@constants';
|
||||||
|
import {PostTypes} from '@constants/post';
|
||||||
import {REACTION_PICKER_HEIGHT, REACTION_PICKER_MARGIN} from '@constants/reaction_picker';
|
import {REACTION_PICKER_HEIGHT, REACTION_PICKER_MARGIN} from '@constants/reaction_picker';
|
||||||
import {useIsTablet} from '@hooks/device';
|
import {useIsTablet} from '@hooks/device';
|
||||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||||
@@ -91,7 +92,7 @@ const PostOptions = ({
|
|||||||
return items;
|
return items;
|
||||||
}, [
|
}, [
|
||||||
canAddReaction, canCopyPermalink, canCopyText,
|
canAddReaction, canCopyPermalink, canCopyText,
|
||||||
canDelete, canEdit, shouldRenderFollow, shouldShowBindings,
|
canDelete, canEdit && post.type !== PostTypes.VOICE_MESSAGE, shouldRenderFollow, shouldShowBindings,
|
||||||
canMarkAsUnread, canPin, canReply, isSystemPost, bottom,
|
canMarkAsUnread, canPin, canReply, isSystemPost, bottom,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ const PostOptions = ({
|
|||||||
postId={post.id}
|
postId={post.id}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{canEdit &&
|
{canEdit && post.type !== PostTypes.VOICE_MESSAGE &&
|
||||||
<EditOption
|
<EditOption
|
||||||
bottomSheetId={Screens.POST_OPTIONS}
|
bottomSheetId={Screens.POST_OPTIONS}
|
||||||
post={post}
|
post={post}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ PODS:
|
|||||||
- HMSegmentedControl (1.5.6)
|
- HMSegmentedControl (1.5.6)
|
||||||
- jail-monkey (2.8.0):
|
- jail-monkey (2.8.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- lame (1.0.0)
|
||||||
- libevent (2.1.12)
|
- libevent (2.1.12)
|
||||||
- libwebp (1.2.4):
|
- libwebp (1.2.4):
|
||||||
- libwebp/demux (= 1.2.4)
|
- libwebp/demux (= 1.2.4)
|
||||||
@@ -478,6 +479,9 @@ PODS:
|
|||||||
- React-Core
|
- React-Core
|
||||||
- React-RCTImage
|
- React-RCTImage
|
||||||
- React-RCTText
|
- React-RCTText
|
||||||
|
- RNAudioRecorderPlayer (3.5.1):
|
||||||
|
- lame
|
||||||
|
- React-Core
|
||||||
- RNCClipboard (1.11.1):
|
- RNCClipboard (1.11.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
- RNDateTimePicker (6.7.3):
|
- RNDateTimePicker (6.7.3):
|
||||||
@@ -653,6 +657,7 @@ DEPENDENCIES:
|
|||||||
- ReactNativeIncallManager (from `../node_modules/react-native-incall-manager`)
|
- ReactNativeIncallManager (from `../node_modules/react-native-incall-manager`)
|
||||||
- ReactNativeKeyboardTrackingView (from `../node_modules/react-native-keyboard-tracking-view`)
|
- ReactNativeKeyboardTrackingView (from `../node_modules/react-native-keyboard-tracking-view`)
|
||||||
- ReactNativeNavigation (from `../node_modules/react-native-navigation`)
|
- ReactNativeNavigation (from `../node_modules/react-native-navigation`)
|
||||||
|
- RNAudioRecorderPlayer (from `../node_modules/react-native-audio-recorder-player`)
|
||||||
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
|
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
|
||||||
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
||||||
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
|
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
|
||||||
@@ -692,6 +697,7 @@ SPEC REPOS:
|
|||||||
- FlipperKit
|
- FlipperKit
|
||||||
- fmt
|
- fmt
|
||||||
- HMSegmentedControl
|
- HMSegmentedControl
|
||||||
|
- lame
|
||||||
- libevent
|
- libevent
|
||||||
- libwebp
|
- libwebp
|
||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
@@ -831,6 +837,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native-keyboard-tracking-view"
|
:path: "../node_modules/react-native-keyboard-tracking-view"
|
||||||
ReactNativeNavigation:
|
ReactNativeNavigation:
|
||||||
:path: "../node_modules/react-native-navigation"
|
:path: "../node_modules/react-native-navigation"
|
||||||
|
RNAudioRecorderPlayer:
|
||||||
|
:path: "../node_modules/react-native-audio-recorder-player"
|
||||||
RNCClipboard:
|
RNCClipboard:
|
||||||
:path: "../node_modules/@react-native-clipboard/clipboard"
|
:path: "../node_modules/@react-native-clipboard/clipboard"
|
||||||
RNDateTimePicker:
|
RNDateTimePicker:
|
||||||
@@ -905,6 +913,7 @@ SPEC CHECKSUMS:
|
|||||||
hermes-engine: 922ccd744f50d9bfde09e9677bf0f3b562ea5fb9
|
hermes-engine: 922ccd744f50d9bfde09e9677bf0f3b562ea5fb9
|
||||||
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
||||||
jail-monkey: a71b35d482a70ecba844a90f002994012cf12a5d
|
jail-monkey: a71b35d482a70ecba844a90f002994012cf12a5d
|
||||||
|
lame: 1edb6e24a4e2056b9d1fd90c9fddc10c9aff875d
|
||||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||||
mattermost-react-native-turbo-log: 3c56c4e6e1d706b70d652e5509d1254eef914153
|
mattermost-react-native-turbo-log: 3c56c4e6e1d706b70d652e5509d1254eef914153
|
||||||
@@ -963,6 +972,7 @@ SPEC CHECKSUMS:
|
|||||||
ReactNativeIncallManager: b169b57d3064d8f62478f8fc3c485da6c75045d1
|
ReactNativeIncallManager: b169b57d3064d8f62478f8fc3c485da6c75045d1
|
||||||
ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
|
ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
|
||||||
ReactNativeNavigation: c3e1e813b46d4c7219949cfbb67feb90b8315058
|
ReactNativeNavigation: c3e1e813b46d4c7219949cfbb67feb90b8315058
|
||||||
|
RNAudioRecorderPlayer: 142484e03a1e6eadfea54026dca3ffe3eb53ee1b
|
||||||
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
||||||
RNDateTimePicker: 00247f26c34683c80be94207f488f6f13448586e
|
RNDateTimePicker: 00247f26c34683c80be94207f488f6f13448586e
|
||||||
RNDeviceInfo: 4701f0bf2a06b34654745053db0ce4cb0c53ada7
|
RNDeviceInfo: 4701f0bf2a06b34654745053db0ce4cb0c53ada7
|
||||||
|
|||||||
46
package-lock.json
generated
46
package-lock.json
generated
@@ -55,6 +55,7 @@
|
|||||||
"react-native": "0.71.1",
|
"react-native": "0.71.1",
|
||||||
"react-native-android-open-settings": "1.3.0",
|
"react-native-android-open-settings": "1.3.0",
|
||||||
"react-native-animated-numbers": "0.4.1",
|
"react-native-animated-numbers": "0.4.1",
|
||||||
|
"react-native-audio-recorder-player": "github:enahum/react-native-audio-recorder-player",
|
||||||
"react-native-background-timer": "2.4.1",
|
"react-native-background-timer": "2.4.1",
|
||||||
"react-native-button": "3.0.1",
|
"react-native-button": "3.0.1",
|
||||||
"react-native-calendars": "1.1293.0",
|
"react-native-calendars": "1.1293.0",
|
||||||
@@ -9244,6 +9245,15 @@
|
|||||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dooboolab-welcome": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dooboolab-welcome/-/dooboolab-welcome-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-2NbMaIIURElxEf/UAoVUFlXrO+7n/FRhLCiQlk4fkbGRh9cJ3/f8VEMPveR9m4Ug2l2Zey+UCXjd6EcBqHJ5bw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"dooboolab-welcome": "bin/hello.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.0.3",
|
"version": "16.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||||
@@ -18184,6 +18194,18 @@
|
|||||||
"react-native-reanimated": ">=2"
|
"react-native-reanimated": ">=2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-audio-recorder-player": {
|
||||||
|
"version": "3.5.1",
|
||||||
|
"resolved": "git+ssh://git@github.com/enahum/react-native-audio-recorder-player.git#3282ce950f859c6ccc3402986309f725708a1d5c",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dooboolab-welcome": "^1.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-background-timer": {
|
"node_modules/react-native-background-timer": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz",
|
||||||
@@ -21111,9 +21133,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ua-parser-js": {
|
"node_modules/ua-parser-js": {
|
||||||
"version": "0.7.31",
|
"version": "0.7.33",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
|
||||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
|
"integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -28715,6 +28737,11 @@
|
|||||||
"domhandler": "^4.2.0"
|
"domhandler": "^4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dooboolab-welcome": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dooboolab-welcome/-/dooboolab-welcome-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-2NbMaIIURElxEf/UAoVUFlXrO+7n/FRhLCiQlk4fkbGRh9cJ3/f8VEMPveR9m4Ug2l2Zey+UCXjd6EcBqHJ5bw=="
|
||||||
|
},
|
||||||
"dotenv": {
|
"dotenv": {
|
||||||
"version": "16.0.3",
|
"version": "16.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||||
@@ -35587,6 +35614,13 @@
|
|||||||
"react-native-reanimated": "^2.2.4"
|
"react-native-reanimated": "^2.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-audio-recorder-player": {
|
||||||
|
"version": "git+ssh://git@github.com/enahum/react-native-audio-recorder-player.git#3282ce950f859c6ccc3402986309f725708a1d5c",
|
||||||
|
"from": "react-native-audio-recorder-player@github:enahum/react-native-audio-recorder-player",
|
||||||
|
"requires": {
|
||||||
|
"dooboolab-welcome": "^1.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-background-timer": {
|
"react-native-background-timer": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz",
|
||||||
@@ -37727,9 +37761,9 @@
|
|||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.31",
|
"version": "0.7.33",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
|
||||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
|
"integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw=="
|
||||||
},
|
},
|
||||||
"uglify-es": {
|
"uglify-es": {
|
||||||
"version": "3.3.9",
|
"version": "3.3.9",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"react-native": "0.71.1",
|
"react-native": "0.71.1",
|
||||||
"react-native-android-open-settings": "1.3.0",
|
"react-native-android-open-settings": "1.3.0",
|
||||||
"react-native-animated-numbers": "0.4.1",
|
"react-native-animated-numbers": "0.4.1",
|
||||||
|
"react-native-audio-recorder-player": "github:enahum/react-native-audio-recorder-player",
|
||||||
"react-native-background-timer": "2.4.1",
|
"react-native-background-timer": "2.4.1",
|
||||||
"react-native-button": "3.0.1",
|
"react-native-button": "3.0.1",
|
||||||
"react-native-calendars": "1.1293.0",
|
"react-native-calendars": "1.1293.0",
|
||||||
|
|||||||
2
types/api/config.d.ts
vendored
2
types/api/config.d.ts
vendored
@@ -107,6 +107,7 @@ interface ClientConfig {
|
|||||||
ExperimentalEnableClickToReply: string;
|
ExperimentalEnableClickToReply: string;
|
||||||
ExperimentalEnableDefaultChannelLeaveJoinMessages: string;
|
ExperimentalEnableDefaultChannelLeaveJoinMessages: string;
|
||||||
ExperimentalEnablePostMetadata: string;
|
ExperimentalEnablePostMetadata: string;
|
||||||
|
ExperimentalEnableVoiceMessage: string;
|
||||||
ExperimentalGroupUnreadChannels: string;
|
ExperimentalGroupUnreadChannels: string;
|
||||||
ExperimentalHideTownSquareinLHS: string;
|
ExperimentalHideTownSquareinLHS: string;
|
||||||
ExperimentalNormalizeMarkdownLinks: string;
|
ExperimentalNormalizeMarkdownLinks: string;
|
||||||
@@ -120,6 +121,7 @@ interface ClientConfig {
|
|||||||
FeatureFlagCollapsedThreads?: string;
|
FeatureFlagCollapsedThreads?: string;
|
||||||
FeatureFlagGraphQL?: string;
|
FeatureFlagGraphQL?: string;
|
||||||
FeatureFlagPostPriority?: string;
|
FeatureFlagPostPriority?: string;
|
||||||
|
FeatureFlagEnableVoiceMessages?: string;
|
||||||
GfycatApiKey: string;
|
GfycatApiKey: string;
|
||||||
GfycatApiSecret: string;
|
GfycatApiSecret: string;
|
||||||
GoogleDeveloperKey: string;
|
GoogleDeveloperKey: string;
|
||||||
|
|||||||
1
types/api/files.d.ts
vendored
1
types/api/files.d.ts
vendored
@@ -22,6 +22,7 @@ type FileInfo = {
|
|||||||
uri?: string;
|
uri?: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
width: number;
|
width: number;
|
||||||
|
is_voice_recording?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FilesState = {
|
type FilesState = {
|
||||||
|
|||||||
Reference in New Issue
Block a user