MM-54117 - Calls: Support wiredHeadsets; generalize panel_item (#7545)

* generalize panel_item; support wiredHeadsets

* i18n
This commit is contained in:
Christopher Poile 2023-09-25 08:45:14 -04:00 committed by GitHub
parent f24642989b
commit 0ec2b09fd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 178 additions and 109 deletions

View file

@ -35,7 +35,7 @@ const CopyChannelLinkOption = ({channelName, teamName, showAsLabel, testID}: Pro
<SlideUpPanelItem
onPress={onCopyLink}
text={intl.formatMessage({id: 'channel_info.copy_link', defaultMessage: 'Copy Link'})}
icon='link-variant'
leftIcon='link-variant'
testID={testID}
/>
);

View file

@ -45,7 +45,7 @@ const InfoBox = ({channelId, containerStyle, showAsLabel = false, testID}: Props
if (showAsLabel) {
return (
<SlideUpPanelItem
icon='information-outline'
leftIcon='information-outline'
onPress={onViewInfo}
testID={testID}
text={intl.formatMessage({id: 'channel_header.info', defaultMessage: 'View info'})}

View file

@ -174,7 +174,7 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
return (
<SlideUpPanelItem
destructive={true}
icon={icon}
leftIcon={icon}
onPress={onLeave}
text={leaveText}
testID={testID}

View file

@ -167,7 +167,7 @@ const AtMention = ({
style={style.bottomSheet}
>
<SlideUpPanelItem
icon='content-copy'
leftIcon='content-copy'
onPress={() => {
dismissBottomSheet();
let username = mentionName;
@ -182,7 +182,7 @@ const AtMention = ({
/>
<SlideUpPanelItem
destructive={true}
icon='cancel'
leftIcon='cancel'
onPress={() => {
dismissBottomSheet();
}}

View file

@ -123,7 +123,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
style={style.bottomSheet}
>
<SlideUpPanelItem
icon='content-copy'
leftIcon='content-copy'
onPress={() => {
dismissBottomSheet();
Clipboard.setString(content);
@ -133,7 +133,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
/>
<SlideUpPanelItem
destructive={true}
icon='cancel'
leftIcon='cancel'
onPress={() => {
dismissBottomSheet();
}}

View file

@ -157,7 +157,7 @@ const MarkdownImage = ({
style={style.bottomSheet}
>
<SlideUpPanelItem
icon='content-copy'
leftIcon='content-copy'
onPress={() => {
dismissBottomSheet();
Clipboard.setString(linkDestination || source);
@ -167,7 +167,7 @@ const MarkdownImage = ({
/>
<SlideUpPanelItem
destructive={true}
icon='cancel'
leftIcon='cancel'
onPress={() => {
dismissBottomSheet();
}}

View file

@ -139,7 +139,7 @@ const LatexCodeBlock = ({content, theme}: Props) => {
style={styles.bottomSheet}
>
<SlideUpPanelItem
icon='content-copy'
leftIcon='content-copy'
onPress={() => {
dismissBottomSheet();
Clipboard.setString(content);
@ -149,7 +149,7 @@ const LatexCodeBlock = ({content, theme}: Props) => {
/>
<SlideUpPanelItem
destructive={true}
icon='cancel'
leftIcon='cancel'
onPress={dismissBottomSheet}
testID='at_mention.bottom_sheet.cancel'
text={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}

View file

@ -116,7 +116,7 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
style={style.bottomSheet}
>
<SlideUpPanelItem
icon='content-copy'
leftIcon='content-copy'
onPress={() => {
dismissBottomSheet();
Clipboard.setString(href);
@ -126,7 +126,7 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
/>
<SlideUpPanelItem
destructive={true}
icon='cancel'
leftIcon='cancel'
onPress={() => {
dismissBottomSheet();
}}

View file

@ -66,13 +66,13 @@ const CameraType = ({onPress}: Props) => {
/>
}
<SlideUpPanelItem
icon='camera-outline'
leftIcon='camera-outline'
onPress={onPhoto}
testID='camera_type.photo'
text={intl.formatMessage({id: 'camera_type.photo.option', defaultMessage: 'Capture Photo'})}
/>
<SlideUpPanelItem
icon='video-outline'
leftIcon='video-outline'
onPress={onVideo}
testID='camera_type.video'
text={intl.formatMessage({id: 'camera_type.video.option', defaultMessage: 'Record Video'})}

View file

@ -44,7 +44,7 @@ const Failed = ({post, theme}: FailedProps) => {
style={styles.bottomSheet}
>
<SlideUpPanelItem
icon='send-outline'
leftIcon='send-outline'
onPress={() => {
dismissBottomSheet();
retryFailedPost(serverUrl, post);
@ -54,7 +54,7 @@ const Failed = ({post, theme}: FailedProps) => {
/>
<SlideUpPanelItem
destructive={true}
icon='close-circle-outline'
leftIcon='close-circle-outline'
onPress={() => {
dismissBottomSheet();
removePost(serverUrl, post);

View file

@ -14,10 +14,12 @@ import {isValidUrl} from '@utils/url';
type SlideUpPanelProps = {
destructive?: boolean;
icon?: string | Source;
rightIcon?: boolean;
imageStyles?: StyleProp<ImageStyle>;
iconStyles?: StyleProp<TextStyle>;
leftIcon?: string | Source;
leftImageStyles?: StyleProp<ImageStyle>;
leftIconStyles?: StyleProp<TextStyle>;
rightIcon?: string | Source;
rightImageStyles?: StyleProp<ImageStyle>;
rightIconStyles?: StyleProp<TextStyle>;
onPress: () => void;
textStyles?: TextStyle;
testID?: string;
@ -65,13 +67,55 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});
const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text, textStyles, iconStyles, rightIcon = false}: SlideUpPanelProps) => {
const SlideUpPanelItem = ({
destructive = false,
leftIcon,
leftImageStyles,
leftIconStyles,
rightIcon,
rightImageStyles,
rightIconStyles,
onPress,
testID,
text,
textStyles,
}: SlideUpPanelProps) => {
const theme = useTheme();
const style = getStyleSheet(theme);
const {image: leftImage, iconStyle: leftIconStyle} = useImageAndStyle(leftIcon, leftImageStyles, leftIconStyles, destructive);
const {image: rightImage, iconStyle: rightIconStyle} = useImageAndStyle(rightIcon, rightImageStyles, rightIconStyles, destructive);
const handleOnPress = useCallback(preventDoubleTap(onPress, 500), []);
return (
<TouchableHighlight
onPress={handleOnPress}
style={style.container}
testID={testID}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
>
<View style={style.row}>
{Boolean(leftImage) &&
<View style={leftIconStyle}>{leftImage}</View>
}
<View style={style.textContainer}>
<Text style={[style.text, destructive && style.destructive, textStyles]}>{text}</Text>
</View>
{Boolean(rightImage) &&
<View style={rightIconStyle}>{rightImage}</View>
}
</View>
</TouchableHighlight>
);
};
const useImageAndStyle = (icon: string | Source | undefined, imageStyles: StyleProp<ImageStyle>, iconStyles: StyleProp<TextStyle>, destructive: boolean) => {
const theme = useTheme();
const style = getStyleSheet(theme);
let image;
let iconStyle: StyleProp<ViewStyle> = [style.iconContainer];
let iconStyle: Array<StyleProp<ViewStyle>> = [style.iconContainer];
if (icon) {
if (typeof icon === 'object') {
if (icon.uri && isValidUrl(icon.uri)) {
@ -101,26 +145,7 @@ const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text
}
}
return (
<TouchableHighlight
onPress={handleOnPress}
style={style.container}
testID={testID}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
>
<View style={style.row}>
{Boolean(image) && !rightIcon &&
<View style={iconStyle}>{image}</View>
}
<View style={style.textContainer}>
<Text style={[style.text, destructive && style.destructive, textStyles]}>{text}</Text>
</View>
{Boolean(image) && rightIcon &&
<View style={iconStyle}>{image}</View>
}
</View>
</TouchableHighlight>
);
return {image, iconStyle};
};
export default SlideUpPanelItem;

View file

@ -14,7 +14,7 @@ import {Device} from '@constants';
import {useTheme} from '@context/theme';
import {bottomSheet, dismissBottomSheet} from '@screens/navigation';
import {bottomSheetSnapPoint} from '@utils/helpers';
import {typography} from '@utils/typography';
import {makeStyleSheetFromTheme} from '@utils/theme';
type Props = {
pressableStyle: StyleProp<ViewStyle>;
@ -23,13 +23,16 @@ type Props = {
currentCall: CurrentCall;
}
const style = {
bold: typography('Body', 200, 'SemiBold'),
};
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => ({
checkIcon: {
color: theme.buttonBg,
},
}));
export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, currentCall}: Props) => {
const intl = useIntl();
const theme = useTheme();
const style = getStyleFromTheme(theme);
const {bottom} = useSafeAreaInsets();
const isTablet = Device.IS_TABLET; // not `useIsTablet` because even if we're in splitView, we're still using a tablet.
const color = theme.awayIndicator;
@ -38,10 +41,14 @@ export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, c
const tabletLabel = intl.formatMessage({id: 'mobile.calls_tablet', defaultMessage: 'Tablet'});
const speakerLabel = intl.formatMessage({id: 'mobile.calls_speaker', defaultMessage: 'SpeakerPhone'});
const bluetoothLabel = intl.formatMessage({id: 'mobile.calls_bluetooth', defaultMessage: 'Bluetooth'});
const headsetLabel = intl.formatMessage({id: 'mobile.calls_headset', defaultMessage: 'Headset'});
const deviceSelector = useCallback(async () => {
const currentDevice = audioDeviceInfo.selectedAudioDevice;
const available = audioDeviceInfo.availableAudioDeviceList;
let available = audioDeviceInfo.availableAudioDeviceList;
if (available.includes(AudioDevice.WiredHeadset)) {
available = available.filter((d) => d !== AudioDevice.Earpiece);
}
const selectDevice = (device: AudioDevice) => {
setPreferredAudioRoute(device);
dismissBottomSheet();
@ -52,34 +59,47 @@ export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, c
<View>
{available.includes(AudioDevice.Earpiece) && isTablet &&
<SlideUpPanelItem
icon={'tablet'}
leftIcon={'tablet'}
onPress={() => selectDevice(AudioDevice.Earpiece)}
text={tabletLabel}
textStyles={currentDevice === AudioDevice.Earpiece ? {...style.bold, color} : {}}
rightIcon={currentDevice === AudioDevice.Earpiece ? 'check' : undefined}
rightIconStyles={currentDevice === AudioDevice.Earpiece ? style.checkIcon : {}}
/>
}
{available.includes(AudioDevice.Earpiece) && !isTablet &&
<SlideUpPanelItem
icon={'cellphone'}
leftIcon={'cellphone'}
onPress={() => selectDevice(AudioDevice.Earpiece)}
text={phoneLabel}
textStyles={currentDevice === AudioDevice.Earpiece ? {...style.bold, color} : {}}
rightIcon={currentDevice === AudioDevice.Earpiece ? 'check' : undefined}
rightIconStyles={currentDevice === AudioDevice.Earpiece ? style.checkIcon : {}}
/>
}
{available.includes(AudioDevice.Speakerphone) &&
<SlideUpPanelItem
icon={'volume-high'}
leftIcon={'volume-high'}
onPress={() => selectDevice(AudioDevice.Speakerphone)}
text={speakerLabel}
textStyles={currentDevice === AudioDevice.Speakerphone ? {...style.bold, color} : {}}
rightIcon={currentDevice === AudioDevice.Speakerphone ? 'check' : undefined}
rightIconStyles={currentDevice === AudioDevice.Speakerphone ? style.checkIcon : {}}
/>
}
{available.includes(AudioDevice.Bluetooth) &&
<SlideUpPanelItem
icon={'bluetooth'}
leftIcon={'bluetooth'}
onPress={() => selectDevice(AudioDevice.Bluetooth)}
text={bluetoothLabel}
textStyles={currentDevice === AudioDevice.Bluetooth ? {...style.bold, color} : {}}
rightIcon={currentDevice === AudioDevice.Bluetooth ? 'check' : undefined}
rightIconStyles={currentDevice === AudioDevice.Bluetooth ? style.checkIcon : {}}
/>
}
{available.includes(AudioDevice.WiredHeadset) &&
<SlideUpPanelItem
leftIcon={'headphones'}
onPress={() => selectDevice(AudioDevice.WiredHeadset)}
text={headsetLabel}
rightIcon={currentDevice === AudioDevice.WiredHeadset ? 'check' : undefined}
rightIconStyles={currentDevice === AudioDevice.WiredHeadset ? style.checkIcon : {}}
/>
}
</View>
@ -89,7 +109,7 @@ export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, c
await bottomSheet({
closeButtonId: 'close-other-actions',
renderContent,
snapPoints: [1, bottomSheetSnapPoint(audioDeviceInfo.availableAudioDeviceList.length + 1, ITEM_HEIGHT, bottom)],
snapPoints: [1, bottomSheetSnapPoint(available.length + 1, ITEM_HEIGHT, bottom)],
title: intl.formatMessage({id: 'mobile.calls_audio_device', defaultMessage: 'Select audio device'}),
theme,
});
@ -106,6 +126,10 @@ export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, c
icon = 'bluetooth';
label = bluetoothLabel;
break;
case AudioDevice.WiredHeadset:
icon = 'headphones';
label = headsetLabel;
break;
}
return (

View file

@ -3,7 +3,7 @@
import {RTCMonitor, RTCPeer} from '@mattermost/calls/lib';
import {deflate} from 'pako';
import {DeviceEventEmitter, type EmitterSubscription, Platform} from 'react-native';
import {DeviceEventEmitter, type EmitterSubscription, NativeEventEmitter, NativeModules, Platform} from 'react-native';
import InCallManager from 'react-native-incall-manager';
import {mediaDevices, MediaStream, MediaStreamTrack, RTCPeerConnection} from 'react-native-webrtc';
@ -24,6 +24,8 @@ import type {EmojiData} from '@mattermost/calls/lib/types';
const peerConnectTimeout = 5000;
const rtcMonitorInterval = 4000;
const InCallManagerEmitter = new NativeEventEmitter(NativeModules.InCallManager);
export async function newConnection(
serverUrl: string,
channelID: string,
@ -40,6 +42,7 @@ export async function newConnection(
let isClosed = false;
let onCallEnd: EmitterSubscription | null = null;
let audioDeviceChanged: EmitterSubscription | null = null;
let wiredHeadsetEvent: EmitterSubscription | null = null;
const streams: MediaStream[] = [];
let rtcMonitor: RTCMonitor | null = null;
const logger = {
@ -106,6 +109,7 @@ export async function newConnection(
peer = null;
InCallManager.stop();
audioDeviceChanged?.remove();
wiredHeadsetEvent?.remove();
if (closeCb) {
closeCb();
@ -224,29 +228,50 @@ export async function newConnection(
let btInitialized = false;
let speakerInitialized = false;
audioDeviceChanged = DeviceEventEmitter.addListener('onAudioDeviceChanged', (data: AudioDeviceInfoRaw) => {
const info: AudioDeviceInfo = {
availableAudioDeviceList: JSON.parse(data.availableAudioDeviceList),
selectedAudioDevice: data.selectedAudioDevice,
};
setAudioDeviceInfo(info);
if (Platform.OS === 'android') {
audioDeviceChanged = DeviceEventEmitter.addListener('onAudioDeviceChanged', (data: AudioDeviceInfoRaw) => {
const info: AudioDeviceInfo = {
availableAudioDeviceList: JSON.parse(data.availableAudioDeviceList),
selectedAudioDevice: data.selectedAudioDevice,
};
setAudioDeviceInfo(info);
logDebug('AudioDeviceChanged. info:', info);
// Auto switch to bluetooth the first time we connect to bluetooth, but not after.
if (!btInitialized) {
if (info.availableAudioDeviceList.includes(AudioDevice.Bluetooth)) {
setPreferredAudioRoute(AudioDevice.Bluetooth);
btInitialized = true;
} else if (!speakerInitialized) {
// If we don't have bluetooth available, default to speakerphone on.
setPreferredAudioRoute(AudioDevice.Speakerphone);
speakerInitialized = true;
// Auto switch to bluetooth the first time we connect to bluetooth, but not after.
if (!btInitialized) {
if (info.availableAudioDeviceList.includes(AudioDevice.Bluetooth)) {
setPreferredAudioRoute(AudioDevice.Bluetooth);
btInitialized = true;
} else if (!speakerInitialized) {
// If we don't have bluetooth available, default to speakerphone on.
setPreferredAudioRoute(AudioDevice.Speakerphone);
speakerInitialized = true;
}
}
}
});
});
}
// We default to speakerphone (Android is handled above in the onAudioDeviceChanged handler above).
// We default to speakerphone, but not if the WiredHeadset is plugged in.
if (Platform.OS === 'ios') {
setSpeakerphoneOn(true);
wiredHeadsetEvent = InCallManagerEmitter.addListener('WiredHeadset', (data) => {
// Log for customer debugging. For the moment we're not changing output labels because of incall-manager iOS
// limitations with how it reports Bluetooth -- namely that it doesn't, so we don't know when Bluetooth is
// overriding the earpiece and/or headset.
logDebug('WiredHeadset plugged in. Data:', data);
// iOS switches to the headset when we connect it, so turn off speakerphone to keep UI in sync.
if (data.isPlugged) {
setSpeakerphoneOn(false);
}
});
// If headset is plugged in when the call starts, use it.
const report = await InCallManager.getIsWiredHeadsetPluggedIn();
if (report.isWiredHeadsetPluggedIn) {
setSpeakerphoneOn(false);
} else {
setSpeakerphoneOn(true);
}
}
peer = new RTCPeer({

View file

@ -477,7 +477,7 @@ const CallScreen = ({
{
showStartRecording &&
<SlideUpPanelItem
icon={'record-circle-outline'}
leftIcon={'record-circle-outline'}
onPress={startRecording}
text={recordOptionTitle}
/>
@ -485,14 +485,14 @@ const CallScreen = ({
{
showStopRecording &&
<SlideUpPanelItem
icon={'record-square-outline'}
leftIcon={'record-square-outline'}
onPress={stopRecording}
text={stopRecordingOptionTitle}
textStyles={style.denimDND}
/>
}
<SlideUpPanelItem
icon='message-text-outline'
leftIcon='message-text-outline'
onPress={switchToThread}
text={callThreadOptionTitle}
/>
@ -764,7 +764,6 @@ const CallScreen = ({
iconStyle={[
style.buttonIcon,
isLandscape && style.buttonIconLandscape,
style.speakerphoneIcon,
currentCall.speakerphoneOn && style.buttonOn,
]}
buttonTextStyle={style.buttonText}

View file

@ -42,11 +42,6 @@ export default function DropdownSlideup({
const style = getStyleFromTheme(theme);
const isTablet = useIsTablet();
const commonProps = {
rightIcon: true,
iconStyles: style.checkIcon,
};
const handlePublicPress = useCallback(() => {
dismissBottomSheet();
onPress(PUBLIC);
@ -73,16 +68,16 @@ export default function DropdownSlideup({
onPress={handlePublicPress}
testID='browse_channels.dropdown_slideup_item.public_channels'
text={intl.formatMessage({id: 'browse_channels.publicChannels', defaultMessage: 'Public Channels'})}
icon={selected === PUBLIC ? 'check' : undefined}
{...commonProps}
rightIcon={selected === PUBLIC ? 'check' : undefined}
rightIconStyles={style.checkIcon}
/>
{canShowArchivedChannels && (
<SlideUpPanelItem
onPress={handleArchivedPress}
testID='browse_channels.dropdown_slideup_item.archived_channels'
text={intl.formatMessage({id: 'browse_channels.archivedChannels', defaultMessage: 'Archived Channels'})}
icon={selected === ARCHIVED ? 'check' : undefined}
{...commonProps}
rightIcon={selected === ARCHIVED ? 'check' : undefined}
rightIconStyles={style.checkIcon}
/>
)}
{sharedChannelsEnabled && (
@ -90,8 +85,8 @@ export default function DropdownSlideup({
onPress={handleSharedPress}
testID='browse_channels.dropdown_slideup_item.shared_channels'
text={intl.formatMessage({id: 'browse_channels.sharedChannels', defaultMessage: 'Shared Channels'})}
icon={selected === SHARED ? 'check' : undefined}
{...commonProps}
rightIcon={selected === SHARED ? 'check' : undefined}
rightIconStyles={style.checkIcon}
/>
)}
</BottomSheetContent>

View file

@ -71,7 +71,7 @@ const PanelItem = ({pickerAction, pictureUtils, onRemoveProfileImage}: PanelItem
return (
<SlideUpPanelItem
icon={item.icon}
leftIcon={item.icon}
onPress={item.onPress}
testID={item.testID}
text={intl.formatMessage(item.text)}

View file

@ -78,8 +78,8 @@ const UserStatus = ({currentUser}: Props) => {
</View>
)}
<SlideUpPanelItem
icon='check-circle'
iconStyles={{color: theme.onlineIndicator}}
leftIcon='check-circle'
leftIconStyles={{color: theme.onlineIndicator}}
onPress={() => setUserStatus(ONLINE)}
testID='user_status.online.option'
text={intl.formatMessage({
@ -89,8 +89,8 @@ const UserStatus = ({currentUser}: Props) => {
textStyles={styles.label}
/>
<SlideUpPanelItem
icon='clock'
iconStyles={{color: theme.awayIndicator}}
leftIcon='clock'
leftIconStyles={{color: theme.awayIndicator}}
onPress={() => setUserStatus(AWAY)}
testID='user_status.away.option'
text={intl.formatMessage({
@ -100,8 +100,8 @@ const UserStatus = ({currentUser}: Props) => {
textStyles={styles.label}
/>
<SlideUpPanelItem
icon='minus-circle'
iconStyles={{color: theme.dndIndicator}}
leftIcon='minus-circle'
leftIconStyles={{color: theme.dndIndicator}}
onPress={() => setUserStatus(DND)}
testID='user_status.dnd.option'
text={intl.formatMessage({
@ -111,8 +111,8 @@ const UserStatus = ({currentUser}: Props) => {
textStyles={styles.label}
/>
<SlideUpPanelItem
icon='circle-outline'
iconStyles={{color: changeOpacity('#B8B8B8', 0.64)}}
leftIcon='circle-outline'
leftIconStyles={{color: changeOpacity('#B8B8B8', 0.64)}}
onPress={() => setUserStatus(OFFLINE)}
testID='user_status.offline.option'
text={intl.formatMessage({

View file

@ -435,6 +435,7 @@
"mobile.calls_ended_at": "Ended at",
"mobile.calls_error_message": "Error: {error}",
"mobile.calls_error_title": "Error",
"mobile.calls_headset": "Headset",
"mobile.calls_host": "host",
"mobile.calls_host_rec": "You are recording this meeting. Consider letting everyone know that this meeting is being recorded.",
"mobile.calls_host_rec_error": "Please try to record again. You can also contact your system admin for troubleshooting help.",

14
package-lock.json generated
View file

@ -18,7 +18,7 @@
"@formatjs/intl-relativetimeformat": "11.2.4",
"@gorhom/bottom-sheet": "4.4.7",
"@mattermost/calls": "github:mattermost/calls-common#v0.17.0",
"@mattermost/compass-icons": "0.1.37",
"@mattermost/compass-icons": "0.1.38",
"@mattermost/react-native-emm": "1.3.5",
"@mattermost/react-native-network-client": "1.4.1",
"@mattermost/react-native-paste-input": "0.6.4",
@ -3436,9 +3436,9 @@
}
},
"node_modules/@mattermost/compass-icons": {
"version": "0.1.37",
"resolved": "https://registry.npmjs.org/@mattermost/compass-icons/-/compass-icons-0.1.37.tgz",
"integrity": "sha512-4me1W0hj1nu8A1gpdQA6cij/hyb2P7uIYMJQ+xrNvn5ImTRfQ67LEdyNOa/LY+oT1NO2ui6kKOs8oLUehIaneg=="
"version": "0.1.38",
"resolved": "https://registry.npmjs.org/@mattermost/compass-icons/-/compass-icons-0.1.38.tgz",
"integrity": "sha512-JfLQJtvxD++7lw+jiLWs55+SOhF8wPQgfTCOEi/uyl9bRxfY736a9fPzRcRJwF80se7lPMxvAx8itwvTz0o7CQ=="
},
"node_modules/@mattermost/react-native-emm": {
"version": "1.3.5",
@ -25415,9 +25415,9 @@
}
},
"@mattermost/compass-icons": {
"version": "0.1.37",
"resolved": "https://registry.npmjs.org/@mattermost/compass-icons/-/compass-icons-0.1.37.tgz",
"integrity": "sha512-4me1W0hj1nu8A1gpdQA6cij/hyb2P7uIYMJQ+xrNvn5ImTRfQ67LEdyNOa/LY+oT1NO2ui6kKOs8oLUehIaneg=="
"version": "0.1.38",
"resolved": "https://registry.npmjs.org/@mattermost/compass-icons/-/compass-icons-0.1.38.tgz",
"integrity": "sha512-JfLQJtvxD++7lw+jiLWs55+SOhF8wPQgfTCOEi/uyl9bRxfY736a9fPzRcRJwF80se7lPMxvAx8itwvTz0o7CQ=="
},
"@mattermost/react-native-emm": {
"version": "1.3.5",

View file

@ -15,7 +15,7 @@
"@formatjs/intl-relativetimeformat": "11.2.4",
"@gorhom/bottom-sheet": "4.4.7",
"@mattermost/calls": "github:mattermost/calls-common#v0.17.0",
"@mattermost/compass-icons": "0.1.37",
"@mattermost/compass-icons": "0.1.38",
"@mattermost/react-native-emm": "1.3.5",
"@mattermost/react-native-network-client": "1.4.1",
"@mattermost/react-native-paste-input": "0.6.4",