MM-54278 - Calls banners redesign (#7611)
* join call banner * same style of voice-on indicator; avatar & emoji sizing spacing polish * refactor all calls bars; new calls bars UI designs * i18n changes * fix for warning bar's close icon color * refactor banners to normalize UI; new 'noone is talking' design * fix for avatar in current call bar * change design for incoming call on calls screen * remove commented out code
This commit is contained in:
parent
a01fac4753
commit
a0b1367fd3
15 changed files with 437 additions and 480 deletions
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {type StyleProp, Text, TouchableOpacity, View, type ViewStyle} from 'react-native';
|
||||
import {type StyleProp, Text, type TextStyle, TouchableOpacity, View, type ViewStyle} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
|
|
@ -32,6 +32,10 @@ type Props = {
|
|||
breakAt?: number;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
noBorder?: boolean;
|
||||
avatarStyle?: StyleProp<ViewStyle>;
|
||||
overflowContainerStyle?: StyleProp<ViewStyle>;
|
||||
overflowItemStyle?: StyleProp<ViewStyle>;
|
||||
overflowTextStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
|
@ -42,7 +46,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
container: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
firstAvatar: {
|
||||
avatarCommon: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
|
|
@ -56,35 +60,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
borderWidth: 0,
|
||||
},
|
||||
notFirstAvatars: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderWidth: (size / 2) + 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: size / 2,
|
||||
marginLeft: imgOverlap,
|
||||
},
|
||||
overflowContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
marginLeft: imgOverlap,
|
||||
},
|
||||
overflowItem: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
overflowText: {
|
||||
|
|
@ -110,6 +95,10 @@ const UserAvatarsStack = ({
|
|||
style: baseContainerStyle,
|
||||
users,
|
||||
noBorder = false,
|
||||
avatarStyle,
|
||||
overflowContainerStyle,
|
||||
overflowItemStyle,
|
||||
overflowTextStyle,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
|
|
@ -165,14 +154,14 @@ const UserAvatarsStack = ({
|
|||
{displayUsers.map((user, index) => (
|
||||
<UserAvatar
|
||||
key={user.id}
|
||||
style={index === 0 ? [style.firstAvatar, noBorder && style.noBorder] : [style.notFirstAvatars, noBorder && style.noBorder]}
|
||||
style={index === 0 ? [style.avatarCommon, noBorder && style.noBorder, avatarStyle] : [style.avatarCommon, style.notFirstAvatars, noBorder && style.noBorder, avatarStyle]}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
{Boolean(overflowUsersCount) && (
|
||||
<View style={[style.overflowContainer, noBorder && style.noBorder]}>
|
||||
<View style={[style.overflowItem, noBorder && style.noBorder]}>
|
||||
<Text style={style.overflowText}>
|
||||
<View style={[style.avatarCommon, style.overflowContainer, noBorder && style.noBorder, overflowContainerStyle]}>
|
||||
<View style={[style.avatarCommon, style.overflowItem, noBorder && style.noBorder, overflowItemStyle]}>
|
||||
<Text style={[style.overflowText, overflowTextStyle]}>
|
||||
{'+' + overflowUsersCount.toString()}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ export const KEYBOARD_TRACKING_OFFSET = 72;
|
|||
export const SEARCH_INPUT_HEIGHT = Platform.select({android: 40, default: 36});
|
||||
export const SEARCH_INPUT_MARGIN = 5;
|
||||
|
||||
export const JOIN_CALL_BAR_HEIGHT = 38;
|
||||
export const CURRENT_CALL_BAR_HEIGHT = 68;
|
||||
export const CALL_ERROR_BAR_HEIGHT = 62;
|
||||
export const CALL_NOTIFICATION_BAR_HEIGHT = 60;
|
||||
export const JOIN_CALL_BAR_HEIGHT = 40;
|
||||
export const CURRENT_CALL_BAR_HEIGHT = 60;
|
||||
export const CALL_ERROR_BAR_HEIGHT = 52;
|
||||
export const CALL_NOTIFICATION_BAR_HEIGHT = 40;
|
||||
|
||||
export const ANNOUNCEMENT_BAR_HEIGHT = 40;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import type UserModel from '@typings/database/models/servers/user';
|
|||
|
||||
type Props = {
|
||||
userModel?: UserModel;
|
||||
volume?: number;
|
||||
speaking?: boolean;
|
||||
serverUrl: string;
|
||||
size: number;
|
||||
muted?: boolean;
|
||||
|
|
@ -27,49 +27,47 @@ type Props = {
|
|||
}
|
||||
|
||||
// Note: microSize is 32, smallSize is 72
|
||||
const mediumSize = 96;
|
||||
const mediumSize = 72;
|
||||
const mediumBorderWidth = 3;
|
||||
const largeBorderWidth = 6;
|
||||
|
||||
const getStyleSheet = ({
|
||||
theme,
|
||||
volume,
|
||||
size,
|
||||
}: { theme: CallsTheme; volume: number; size: number }) => {
|
||||
const getStyleSheet = ({theme, size}: { theme: CallsTheme; size: number }) => {
|
||||
// Note: we are using the same mute/reaction sizes for small and medium sizes
|
||||
const mediumIcon = size <= mediumSize;
|
||||
const muteWidthHeight = mediumIcon ? 28 : 36;
|
||||
const muteBorderRadius = mediumIcon ? 14 : 18;
|
||||
const reactWidthHeight = mediumIcon ? 32 : 40;
|
||||
const reactBorderRadius = mediumIcon ? 16 : 20;
|
||||
const borderWidth = size <= mediumSize ? mediumBorderWidth : largeBorderWidth;
|
||||
|
||||
return StyleSheet.create({
|
||||
pictureContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: size + (borderWidth * 4),
|
||||
width: size + (borderWidth * 4),
|
||||
},
|
||||
pictureHalo: {
|
||||
backgroundColor: changeOpacity(theme.onlineIndicator, 0.24 * volume),
|
||||
height: size + 16,
|
||||
width: size + 16,
|
||||
padding: 4,
|
||||
borderRadius: (size + 16) / 2,
|
||||
backgroundColor: changeOpacity(theme.onlineIndicator, 0.24),
|
||||
height: size + (borderWidth * 4),
|
||||
width: size + (borderWidth * 4),
|
||||
padding: borderWidth,
|
||||
borderRadius: (size + (borderWidth * 4)) / 2,
|
||||
},
|
||||
pictureHalo2: {
|
||||
backgroundColor: changeOpacity(theme.onlineIndicator, 0.32 * volume),
|
||||
height: size + 8,
|
||||
width: size + 8,
|
||||
padding: 3,
|
||||
borderRadius: (size + 8) / 2,
|
||||
backgroundColor: changeOpacity(theme.onlineIndicator, 0.32),
|
||||
height: size + (borderWidth * 2),
|
||||
width: size + (borderWidth * 2),
|
||||
padding: borderWidth,
|
||||
borderRadius: (size + (borderWidth * 4)) / 2,
|
||||
},
|
||||
picture: {
|
||||
borderRadius: size / 2,
|
||||
height: size,
|
||||
width: size,
|
||||
marginBottom: 5,
|
||||
},
|
||||
profileIcon: {
|
||||
color: changeOpacity(theme.buttonColor, 0.16),
|
||||
},
|
||||
voiceShadow: {
|
||||
shadowColor: 'rgb(61, 184, 135)',
|
||||
shadowOffset: {width: 0, height: 0},
|
||||
shadowOpacity: 1,
|
||||
shadowRadius: 10,
|
||||
color: changeOpacity(theme.buttonColor, 0.56),
|
||||
},
|
||||
muteIconContainer: {
|
||||
position: 'absolute',
|
||||
|
|
@ -143,23 +141,31 @@ const getStyleSheet = ({
|
|||
paddingTop: Platform.select({ios: 5}),
|
||||
},
|
||||
emoji: {
|
||||
paddingLeft: Platform.select({ios: 5, default: 5}),
|
||||
paddingTop: Platform.select({ios: 7, default: 3}),
|
||||
paddingLeft: Platform.select({ios: mediumIcon ? 4 : 6, default: 5}),
|
||||
paddingTop: Platform.select({ios: 5, default: 3}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const CallAvatar = ({userModel, volume = 0, serverUrl, sharingScreen, size, muted, raisedHand, reaction}: Props) => {
|
||||
const CallAvatar = ({
|
||||
userModel,
|
||||
speaking = false,
|
||||
serverUrl,
|
||||
sharingScreen,
|
||||
size,
|
||||
muted,
|
||||
raisedHand,
|
||||
reaction,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const callsTheme = useMemo(() => makeCallsTheme(theme), [theme]);
|
||||
const style = useMemo(() => getStyleSheet({theme: callsTheme, volume, size}), [callsTheme, volume, size]);
|
||||
const style = useMemo(() => getStyleSheet({theme: callsTheme, size}), [callsTheme, size]);
|
||||
|
||||
const iconSize = size <= mediumSize ? 18 : 24;
|
||||
const reactionSize = size <= mediumSize ? 22 : 26;
|
||||
const styleShadow = volume > 0 ? style.voiceShadow : undefined;
|
||||
const reactionSize = size <= mediumSize ? 22 : 28;
|
||||
|
||||
// Only show one or the other.
|
||||
let topRightIcon: JSX.Element | null = null;
|
||||
let topRightIcon: React.JSX.Element | null = null;
|
||||
if (sharingScreen) {
|
||||
topRightIcon = (
|
||||
<View style={style.reactionContainer}>
|
||||
|
|
@ -190,7 +196,7 @@ const CallAvatar = ({userModel, volume = 0, serverUrl, sharingScreen, size, mute
|
|||
<Emoji
|
||||
emojiName={reaction.name}
|
||||
literal={reaction.literal}
|
||||
size={reactionSize - Platform.select({ios: 6, default: 4})}
|
||||
size={reactionSize - Platform.select({ios: 3, default: 4})}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -212,34 +218,28 @@ const CallAvatar = ({userModel, volume = 0, serverUrl, sharingScreen, size, mute
|
|||
/>
|
||||
);
|
||||
|
||||
const view = (
|
||||
<View style={[style.picture, styleShadow]}>
|
||||
{profile}
|
||||
{
|
||||
muted !== undefined &&
|
||||
<View style={style.muteIconContainer}>
|
||||
<CompassIcon
|
||||
name={muted ? 'microphone-off' : 'microphone'}
|
||||
size={iconSize}
|
||||
style={[style.muteIcon, !muted && style.muteIconUnmuted]}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
{topRightIcon}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
<View style={style.pictureHalo}>
|
||||
<View style={style.pictureHalo2}>
|
||||
{view}
|
||||
return (
|
||||
<View style={style.pictureContainer}>
|
||||
<View style={[speaking && style.pictureHalo]}>
|
||||
<View style={[speaking && style.pictureHalo2]}>
|
||||
<View style={[style.picture]}>
|
||||
{profile}
|
||||
{
|
||||
muted !== undefined &&
|
||||
<View style={style.muteIconContainer}>
|
||||
<CompassIcon
|
||||
name={muted ? 'microphone-off' : 'microphone'}
|
||||
size={iconSize}
|
||||
style={[style.muteIcon, !muted && style.muteIconUnmuted]}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
{topRightIcon}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallAvatar;
|
||||
|
|
|
|||
|
|
@ -3,108 +3,94 @@
|
|||
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
||||
import {Pressable, Text, View} from 'react-native';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {fetchProfilesInChannel} from '@actions/remote/user';
|
||||
import {dismissIncomingCall} from '@calls/actions/calls';
|
||||
import {leaveAndJoinWithAlert} from '@calls/alerts';
|
||||
import {removeIncomingCall} from '@calls/state';
|
||||
import {ChannelType, type IncomingCallNotification} from '@calls/types/calls';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {Preferences} from '@constants';
|
||||
import {CALL_NOTIFICATION_BAR_HEIGHT} from '@constants/view';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import WebsocketManager from '@managers/websocket_manager';
|
||||
import ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
outerContainer: {
|
||||
backgroundColor: Preferences.THEMES.denim.onlineIndicator,
|
||||
borderRadius: 8,
|
||||
height: CALL_NOTIFICATION_BAR_HEIGHT,
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
},
|
||||
outerOnCallsScreen: {
|
||||
backgroundColor: changeOpacity(Preferences.THEMES.denim.onlineIndicator, 0.40),
|
||||
backgroundColor: theme.onlineIndicator,
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 6,
|
||||
},
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
borderRadius: 8,
|
||||
borderWidth: 2,
|
||||
borderStyle: 'solid',
|
||||
borderColor: changeOpacity(Preferences.THEMES.denim.buttonColor, 0.16),
|
||||
gap: 8,
|
||||
paddingTop: 4,
|
||||
paddingRight: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 8,
|
||||
alignItems: 'center',
|
||||
backgroundColor: changeOpacity('#000', 0.16),
|
||||
},
|
||||
innerOnCallsScreen: {
|
||||
borderColor: changeOpacity(Preferences.THEMES.denim.buttonColor, 0.16),
|
||||
paddingRight: 2,
|
||||
paddingLeft: 6,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 2,
|
||||
borderColor: changeOpacity(theme.buttonColor, 0.16),
|
||||
backgroundColor: changeOpacity('#000', 0.12),
|
||||
},
|
||||
text: {
|
||||
flex: 1,
|
||||
...typography('Body', 200),
|
||||
lineHeight: 20,
|
||||
color: Preferences.THEMES.denim.buttonColor,
|
||||
},
|
||||
boldText: {
|
||||
...typography('Body', 200, 'SemiBold'),
|
||||
lineHeight: 20,
|
||||
},
|
||||
join: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-end',
|
||||
height: 40,
|
||||
gap: 7,
|
||||
backgroundColor: Preferences.THEMES.denim.buttonColor,
|
||||
paddingTop: 10,
|
||||
paddingRight: 20,
|
||||
paddingBottom: 10,
|
||||
paddingLeft: 20,
|
||||
borderRadius: 30,
|
||||
},
|
||||
joinOnCallsScreen: {
|
||||
backgroundColor: changeOpacity(Preferences.THEMES.denim.buttonColor, 0.12),
|
||||
},
|
||||
joinLabel: {
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
},
|
||||
joinIconLabel: {
|
||||
color: Preferences.THEMES.denim.onlineIndicator,
|
||||
},
|
||||
joinIconLabelOnCallsScreen: {
|
||||
color: Preferences.THEMES.denim.buttonColor,
|
||||
},
|
||||
dismiss: {
|
||||
height: 40,
|
||||
width: 40,
|
||||
borderRadius: 20,
|
||||
padding: 0,
|
||||
backgroundColor: changeOpacity(Preferences.THEMES.denim.buttonColor, 0.08),
|
||||
profileContainer: {
|
||||
width: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
dismissOnCallsScreen: {
|
||||
backgroundColor: 'transparent',
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
color: theme.buttonColor,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
dismissIcon: {
|
||||
color: Preferences.THEMES.denim.buttonColor,
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 8,
|
||||
},
|
||||
});
|
||||
text: {
|
||||
...typography('Body', 100),
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
boldText: {
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
lineHeight: 20,
|
||||
},
|
||||
dismissContainer: {
|
||||
alignItems: 'center',
|
||||
width: 32,
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
closeIcon: {
|
||||
color: changeOpacity(theme.buttonColor, 0.56),
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
incomingCall: IncomingCallNotification;
|
||||
|
|
@ -123,6 +109,8 @@ export const CallNotification = ({
|
|||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
useEffect(() => {
|
||||
const channelMembers = members?.filter((m) => m.userId !== currentUserId);
|
||||
|
|
@ -131,10 +119,6 @@ export const CallNotification = ({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const onJoinPress = useCallback(() => {
|
||||
leaveAndJoinWithAlert(intl, incomingCall.serverUrl, incomingCall.channelID);
|
||||
}, [intl, incomingCall]);
|
||||
|
||||
const onContainerPress = useCallback(async () => {
|
||||
if (serverUrl !== incomingCall.serverUrl) {
|
||||
await DatabaseManager.setActiveServerDatabase(incomingCall.serverUrl);
|
||||
|
|
@ -179,47 +163,31 @@ export const CallNotification = ({
|
|||
/>
|
||||
);
|
||||
}
|
||||
const joinLabel = (
|
||||
<FormattedText
|
||||
id={'mobile.calls_join_button'}
|
||||
defaultMessage={'Join'}
|
||||
style={[style.joinIconLabel, style.joinLabel, onCallsScreen && style.joinIconLabelOnCallsScreen]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={[style.outerContainer, onCallsScreen && style.outerOnCallsScreen]}>
|
||||
<View style={[style.outerContainer]}>
|
||||
<Pressable
|
||||
style={[style.innerContainer, onCallsScreen && style.innerOnCallsScreen]}
|
||||
onPress={onContainerPress}
|
||||
>
|
||||
<ProfilePicture
|
||||
author={incomingCall.callerModel}
|
||||
url={incomingCall.serverUrl}
|
||||
size={32}
|
||||
showStatus={false}
|
||||
/>
|
||||
{message}
|
||||
<Pressable
|
||||
style={[style.join, onCallsScreen && style.joinOnCallsScreen]}
|
||||
onPress={onJoinPress}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={18}
|
||||
style={[style.joinIconLabel, onCallsScreen && style.joinIconLabelOnCallsScreen]}
|
||||
/>
|
||||
{joinLabel}
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[style.dismiss, onCallsScreen && style.dismissOnCallsScreen]}
|
||||
onPress={onDismissPress}
|
||||
>
|
||||
<CompassIcon
|
||||
name={'close'}
|
||||
<View style={style.profileContainer}>
|
||||
<ProfilePicture
|
||||
author={incomingCall.callerModel}
|
||||
url={incomingCall.serverUrl}
|
||||
size={24}
|
||||
style={style.dismissIcon}
|
||||
showStatus={false}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
{message}
|
||||
</View>
|
||||
<Pressable onPress={onDismissPress}>
|
||||
<View style={style.dismissContainer}>
|
||||
<CompassIcon
|
||||
name='close'
|
||||
style={[style.icon, style.closeIcon]}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ type Props = {
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => {
|
||||
return {
|
||||
wrapper: {
|
||||
margin: 8,
|
||||
marginRight: 8,
|
||||
marginLeft: 8,
|
||||
backgroundColor: theme.callsBg,
|
||||
borderRadius: 8,
|
||||
},
|
||||
|
|
@ -55,27 +56,33 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => {
|
|||
paddingTop: 8,
|
||||
paddingRight: 12,
|
||||
paddingBottom: 8,
|
||||
paddingLeft: 12,
|
||||
height: CURRENT_CALL_BAR_HEIGHT - 10,
|
||||
paddingLeft: 6,
|
||||
height: CURRENT_CALL_BAR_HEIGHT,
|
||||
},
|
||||
avatarOutline: {
|
||||
height: 40,
|
||||
width: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: changeOpacity(theme.buttonColor, 0.08),
|
||||
padding: 2,
|
||||
marginRight: 6,
|
||||
marginLeft: 6,
|
||||
},
|
||||
pressable: {
|
||||
zIndex: 10,
|
||||
},
|
||||
profilePic: {
|
||||
marginTop: 4,
|
||||
marginRight: Platform.select({android: -8}),
|
||||
marginLeft: Platform.select({android: -8}),
|
||||
},
|
||||
userInfo: {
|
||||
text: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
paddingLeft: 6,
|
||||
gap: 2,
|
||||
},
|
||||
speakingUser: {
|
||||
color: theme.buttonColor,
|
||||
...typography('Body', 200, 'SemiBold'),
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
},
|
||||
speakingPostfix: {
|
||||
...typography('Body', 200, 'Regular'),
|
||||
...typography('Body', 100, 'Regular'),
|
||||
},
|
||||
channelAndTime: {
|
||||
color: changeOpacity(theme.buttonColor, 0.56),
|
||||
|
|
@ -224,15 +231,15 @@ const CurrentCallBar = ({
|
|||
style={style.container}
|
||||
onPress={goToCallScreen}
|
||||
>
|
||||
<View style={style.profilePic}>
|
||||
<View style={[!speaker && style.avatarOutline]}>
|
||||
<CallAvatar
|
||||
userModel={userModelsDict[speaker || '']}
|
||||
volume={speaker ? 0.5 : 0}
|
||||
speaking={Boolean(speaker)}
|
||||
serverUrl={currentCall?.serverUrl || ''}
|
||||
size={32}
|
||||
size={speaker ? 40 : 24}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.userInfo}>
|
||||
<View style={style.text}>
|
||||
{talkingMessage}
|
||||
<Text style={style.channelAndTime}>
|
||||
{`~${displayName}`}
|
||||
|
|
@ -274,13 +281,13 @@ const CurrentCallBar = ({
|
|||
{micPermissionsError &&
|
||||
<MessageBar
|
||||
type={Calls.MessageBarType.Microphone}
|
||||
onPress={setMicPermissionsErrorDismissed}
|
||||
onDismiss={setMicPermissionsErrorDismissed}
|
||||
/>
|
||||
}
|
||||
{currentCall?.callQualityAlert &&
|
||||
<MessageBar
|
||||
type={Calls.MessageBarType.CallQuality}
|
||||
onPress={setCallQualityAlertDismissed}
|
||||
onDismiss={setCallQualityAlertDismissed}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View, Platform, StyleSheet} from 'react-native';
|
||||
import {View, StyleSheet} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CurrentCallBar from '@calls/components/current_call_bar';
|
||||
import {IncomingCallsContainer} from '@calls/components/incoming_calls_container';
|
||||
import JoinCallBanner from '@calls/components/join_call_banner';
|
||||
import {DEFAULT_HEADER_HEIGHT} from '@constants/view';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
|
||||
const topBarHeight = DEFAULT_HEADER_HEIGHT;
|
||||
|
||||
|
|
@ -16,29 +18,27 @@ const style = StyleSheet.create({
|
|||
wrapper: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 9,
|
||||
},
|
||||
ios: {
|
||||
zIndex: 9,
|
||||
},
|
||||
}),
|
||||
marginTop: 8,
|
||||
gap: 8,
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
showJoinCallBanner: boolean;
|
||||
showIncomingCalls: boolean;
|
||||
isInACall: boolean;
|
||||
threadScreen?: boolean;
|
||||
}
|
||||
|
||||
const FloatingCallContainer = ({channelId, showJoinCallBanner, isInACall, threadScreen}: Props) => {
|
||||
const FloatingCallContainer = ({channelId, showJoinCallBanner, showIncomingCalls, isInACall, threadScreen}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const topBarIsIncludedAlready = Boolean(isTablet || threadScreen);
|
||||
const wrapperTop = {
|
||||
top: insets.top + (threadScreen ? 0 : topBarHeight),
|
||||
top: insets.top + (topBarIsIncludedAlready ? 0 : topBarHeight),
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -50,6 +50,11 @@ const FloatingCallContainer = ({channelId, showJoinCallBanner, isInACall, thread
|
|||
/>
|
||||
}
|
||||
{isInACall && <CurrentCallBar/>}
|
||||
{showIncomingCalls &&
|
||||
<IncomingCallsContainer
|
||||
channelId={channelId}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,45 +3,25 @@
|
|||
|
||||
import React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CallNotification from '@calls/components/call_notification';
|
||||
import {useCurrentCall, useGlobalCallsState, useIncomingCalls} from '@calls/state';
|
||||
import {
|
||||
CALL_ERROR_BAR_HEIGHT,
|
||||
CURRENT_CALL_BAR_HEIGHT,
|
||||
DEFAULT_HEADER_HEIGHT,
|
||||
JOIN_CALL_BAR_HEIGHT,
|
||||
} from '@constants/view';
|
||||
|
||||
const topBarHeight = DEFAULT_HEADER_HEIGHT;
|
||||
import {useIncomingCalls} from '@calls/state';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
marginTop: 8,
|
||||
gap: 8,
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
showingJoinCallBanner: boolean;
|
||||
showingCurrentCallBanner: boolean;
|
||||
threadScreen?: boolean;
|
||||
}
|
||||
|
||||
export const IncomingCallsContainer = ({
|
||||
channelId,
|
||||
showingJoinCallBanner,
|
||||
showingCurrentCallBanner,
|
||||
threadScreen,
|
||||
}: Props) => {
|
||||
const incomingCalls = useIncomingCalls().incomingCalls;
|
||||
const insets = useSafeAreaInsets();
|
||||
const micPermissionsGranted = useGlobalCallsState().micPermissionsGranted;
|
||||
const currentCall = useCurrentCall();
|
||||
|
||||
// If we're in the channel for the incoming call, don't show the incoming call banner.
|
||||
const calls = incomingCalls.filter((ic) => ic.channelID !== channelId);
|
||||
|
|
@ -49,17 +29,8 @@ export const IncomingCallsContainer = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const micPermissionsError = !micPermissionsGranted && (currentCall ? !currentCall.micPermissionsErrorDismissed : false);
|
||||
const qualityAlert = showingCurrentCallBanner && (currentCall ? currentCall.callQualityAlert && currentCall.callQualityAlertDismissed === 0 : false);
|
||||
const top = insets.top + (threadScreen ? 0 : topBarHeight) +
|
||||
(showingJoinCallBanner ? JOIN_CALL_BAR_HEIGHT : 0) +
|
||||
(showingCurrentCallBanner ? CURRENT_CALL_BAR_HEIGHT - 2 : 0) +
|
||||
(micPermissionsError ? CALL_ERROR_BAR_HEIGHT + 8 : 0) +
|
||||
(qualityAlert ? CALL_ERROR_BAR_HEIGHT + 8 : 0);
|
||||
const wrapperTop = {top};
|
||||
|
||||
return (
|
||||
<View style={[style.wrapper, wrapperTop]}>
|
||||
<View style={style.wrapper}>
|
||||
{calls.map((ic) => (
|
||||
<CallNotification
|
||||
key={ic.callID}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ const enhanced = withObservables(['serverUrl', 'channelId'], ({
|
|||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const callId = callsState.pipe(
|
||||
switchMap((state) => of$(state?.id || '')),
|
||||
);
|
||||
|
||||
return {
|
||||
callId,
|
||||
participants,
|
||||
channelCallStartTime,
|
||||
limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, channelId),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import React from 'react';
|
|||
import {useIntl} from 'react-intl';
|
||||
import {View, Pressable} from 'react-native';
|
||||
|
||||
import {dismissIncomingCall} from '@calls/actions';
|
||||
import {leaveAndJoinWithAlert, showLimitRestrictedAlert} from '@calls/alerts';
|
||||
import {removeIncomingCall} from '@calls/state';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedRelativeTime from '@components/formatted_relative_time';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
|
|
@ -21,6 +23,7 @@ import type UserModel from '@typings/database/models/servers/user';
|
|||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
callId: string;
|
||||
serverUrl: string;
|
||||
participants: UserModel[];
|
||||
channelCallStartTime: number;
|
||||
|
|
@ -29,38 +32,57 @@ type Props = {
|
|||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
outerContainer: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
borderRadius: 8,
|
||||
height: JOIN_CALL_BAR_HEIGHT,
|
||||
marginRight: 8,
|
||||
marginLeft: 8,
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 6,
|
||||
},
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#339970', // intentionally not themed
|
||||
width: '100%',
|
||||
borderTopLeftRadius: 12,
|
||||
borderTopRightRadius: 12,
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
borderRadius: 8,
|
||||
paddingTop: 4,
|
||||
paddingRight: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 8,
|
||||
alignItems: 'center',
|
||||
height: JOIN_CALL_BAR_HEIGHT,
|
||||
backgroundColor: '#339970', // intentionally not themed
|
||||
},
|
||||
innerContainerRestricted: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.48),
|
||||
},
|
||||
joinCallIcon: {
|
||||
color: theme.buttonColor,
|
||||
marginRight: 7,
|
||||
iconContainer: {
|
||||
top: 1,
|
||||
width: 32,
|
||||
},
|
||||
joinCall: {
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
color: theme.buttonColor,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
textContainer: {
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
marginLeft: 8,
|
||||
},
|
||||
joinCallText: {
|
||||
color: theme.buttonColor,
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
},
|
||||
started: {
|
||||
startedText: {
|
||||
flex: 1,
|
||||
color: changeOpacity(theme.buttonColor, 0.84),
|
||||
color: changeOpacity(theme.buttonColor, 0.80),
|
||||
...typography(),
|
||||
marginLeft: 10,
|
||||
marginLeft: 6,
|
||||
},
|
||||
limitReached: {
|
||||
flex: 1,
|
||||
|
|
@ -70,18 +92,47 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
color: changeOpacity(theme.sidebarText, 0.84),
|
||||
fontWeight: '400',
|
||||
},
|
||||
headerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 0,
|
||||
top: 16,
|
||||
avatarStyle: {
|
||||
borderColor: '#339970',
|
||||
backgroundColor: '#339970',
|
||||
},
|
||||
overflowContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: (24 / 2),
|
||||
borderColor: '#339970',
|
||||
backgroundColor: '#339970',
|
||||
borderRadius: 24 / 2,
|
||||
marginTop: 1,
|
||||
},
|
||||
overflowItem: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 26 / 2,
|
||||
borderWidth: 1,
|
||||
borderColor: '#339970',
|
||||
backgroundColor: changeOpacity(theme.buttonColor, 0.24),
|
||||
},
|
||||
overflowText: {
|
||||
fontSize: 10,
|
||||
fontWeight: 'bold',
|
||||
color: changeOpacity(theme.buttonColor, 0.80),
|
||||
textAlign: 'center',
|
||||
},
|
||||
dismissContainer: {
|
||||
alignItems: 'center',
|
||||
width: 32,
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
closeIcon: {
|
||||
color: changeOpacity(theme.buttonColor, 0.56),
|
||||
},
|
||||
}));
|
||||
|
||||
const JoinCallBanner = ({
|
||||
channelId,
|
||||
callId,
|
||||
serverUrl,
|
||||
participants,
|
||||
channelCallStartTime,
|
||||
|
|
@ -100,42 +151,61 @@ const JoinCallBanner = ({
|
|||
leaveAndJoinWithAlert(intl, serverUrl, channelId);
|
||||
};
|
||||
|
||||
const onDismissPress = () => {
|
||||
removeIncomingCall(serverUrl, callId, channelId);
|
||||
dismissIncomingCall(serverUrl, channelId);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={style.outerContainer}>
|
||||
<Pressable
|
||||
style={[style.innerContainer, isLimitRestricted && style.innerContainerRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={18}
|
||||
style={style.joinCallIcon}
|
||||
/>
|
||||
<FormattedText
|
||||
id={'mobile.calls_join_call'}
|
||||
defaultMessage={'Join call'}
|
||||
style={style.joinCall}
|
||||
/>
|
||||
{isLimitRestricted ? (
|
||||
<View style={style.iconContainer}>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
<FormattedText
|
||||
id={'mobile.calls_limit_reached'}
|
||||
defaultMessage={'Participant limit reached'}
|
||||
style={style.limitReached}
|
||||
id={'mobile.calls_join_call'}
|
||||
defaultMessage={'Join call'}
|
||||
style={style.joinCallText}
|
||||
/>
|
||||
) : (
|
||||
<FormattedRelativeTime
|
||||
value={channelCallStartTime}
|
||||
updateIntervalInSeconds={1}
|
||||
style={style.started}
|
||||
/>
|
||||
)}
|
||||
{isLimitRestricted ? (
|
||||
<FormattedText
|
||||
id={'mobile.calls_limit_reached'}
|
||||
defaultMessage={'Participant limit reached'}
|
||||
style={style.limitReached}
|
||||
/>
|
||||
) : (
|
||||
<FormattedRelativeTime
|
||||
value={channelCallStartTime}
|
||||
updateIntervalInSeconds={1}
|
||||
style={style.startedText}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<UserAvatarsStack
|
||||
channelId={channelId}
|
||||
location={Screens.CHANNEL}
|
||||
users={participants}
|
||||
breakAt={1}
|
||||
noBorder={true}
|
||||
breakAt={3}
|
||||
avatarStyle={style.avatarStyle}
|
||||
overflowContainerStyle={style.overflowContainer}
|
||||
overflowItemStyle={style.overflowItem}
|
||||
overflowTextStyle={style.overflowText}
|
||||
/>
|
||||
<Pressable onPress={onDismissPress}>
|
||||
<View style={style.dismissContainer}>
|
||||
<CompassIcon
|
||||
name='close'
|
||||
style={[style.icon, style.closeIcon]}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {DEFAULT_HEADER_HEIGHT, JOIN_CALL_BAR_HEIGHT} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
threadScreen?: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
backgroundColor: '#339970', // intentionally not themed
|
||||
height: 40,
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
},
|
||||
content: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderTopLeftRadius: 12,
|
||||
borderTopRightRadius: 12,
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
export const RoundedHeaderCalls = ({threadScreen}: Props) => {
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
const containerTop = {
|
||||
top: insets.top + (threadScreen ? JOIN_CALL_BAR_HEIGHT : JOIN_CALL_BAR_HEIGHT + DEFAULT_HEADER_HEIGHT),
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, containerTop]}>
|
||||
<View style={styles.content}/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
@ -11,7 +11,7 @@ import CompassIcon from '@components/compass_icon';
|
|||
import {Calls} from '@constants';
|
||||
import {CALL_ERROR_BAR_HEIGHT} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type {CallsTheme} from '@calls/types/calls';
|
||||
|
|
@ -19,70 +19,80 @@ import type {MessageBarType} from '@constants/calls';
|
|||
|
||||
type Props = {
|
||||
type: MessageBarType;
|
||||
onPress: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => (
|
||||
{
|
||||
pressable: {
|
||||
zIndex: 10,
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
|
||||
outerContainer: {
|
||||
borderRadius: 8,
|
||||
height: CALL_ERROR_BAR_HEIGHT,
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 6,
|
||||
},
|
||||
errorWrapper: {
|
||||
padding: 8,
|
||||
paddingTop: 0,
|
||||
},
|
||||
errorBar: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: theme.dndIndicator,
|
||||
minHeight: CALL_ERROR_BAR_HEIGHT,
|
||||
width: '100%',
|
||||
borderRadius: 5,
|
||||
padding: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
warningBar: {
|
||||
backgroundColor: theme.awayIndicator,
|
||||
},
|
||||
errorText: {
|
||||
flex: 1,
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
warningText: {
|
||||
color: theme.callsBg,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
margin: 0,
|
||||
padding: 9,
|
||||
},
|
||||
pressedIconContainer: {
|
||||
backgroundColor: theme.buttonColor,
|
||||
},
|
||||
errorIcon: {
|
||||
color: theme.buttonColor,
|
||||
fontSize: 18,
|
||||
},
|
||||
warningIcon: {
|
||||
color: theme.callsBg,
|
||||
},
|
||||
pressedErrorIcon: {
|
||||
color: theme.dndIndicator,
|
||||
},
|
||||
pressedWarningIcon: {
|
||||
color: theme.awayIndicator,
|
||||
},
|
||||
paddingRight: {
|
||||
paddingRight: 9,
|
||||
},
|
||||
}
|
||||
));
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
},
|
||||
outerContainerWarning: {
|
||||
backgroundColor: theme.awayIndicator,
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: 'row',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
borderRadius: 8,
|
||||
paddingTop: 4,
|
||||
paddingRight: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 8,
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.dndIndicator,
|
||||
},
|
||||
innerContainerWarning: {
|
||||
backgroundColor: theme.awayIndicator,
|
||||
},
|
||||
iconContainer: {
|
||||
top: 1,
|
||||
width: 32,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
color: theme.buttonColor,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
warningIcon: {
|
||||
color: theme.callsBg,
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 8,
|
||||
},
|
||||
errorText: {
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
color: theme.buttonColor,
|
||||
},
|
||||
warningText: {
|
||||
color: theme.callsBg,
|
||||
},
|
||||
dismissContainer: {
|
||||
alignItems: 'center',
|
||||
width: 32,
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
closeIcon: {
|
||||
color: changeOpacity(theme.buttonColor, 0.56),
|
||||
},
|
||||
closeIconWarning: {
|
||||
color: changeOpacity(theme.callsBg, 0.56),
|
||||
},
|
||||
}));
|
||||
|
||||
const MessageBar = ({type, onPress}: Props) => {
|
||||
const MessageBar = ({type, onDismiss}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const callsTheme = useMemo(() => makeCallsTheme(theme), [theme]);
|
||||
|
|
@ -100,7 +110,7 @@ const MessageBar = ({type, onPress}: Props) => {
|
|||
icon = (
|
||||
<CompassIcon
|
||||
name='microphone-off'
|
||||
style={[style.errorIcon, style.paddingRight]}
|
||||
style={[style.icon]}
|
||||
/>);
|
||||
break;
|
||||
case Calls.MessageBarType.CallQuality:
|
||||
|
|
@ -111,38 +121,30 @@ const MessageBar = ({type, onPress}: Props) => {
|
|||
icon = (
|
||||
<CompassIcon
|
||||
name='alert-outline'
|
||||
style={[style.errorIcon, style.warningIcon, style.paddingRight]}
|
||||
style={[style.icon, style.warningIcon]}
|
||||
/>);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.errorWrapper}>
|
||||
<View style={[style.outerContainer, warning && style.outerContainerWarning]}>
|
||||
<Pressable
|
||||
onPress={Permissions.openSettings}
|
||||
style={[style.errorBar, warning && style.warningBar]}
|
||||
style={[style.innerContainer, warning && style.innerContainerWarning]}
|
||||
>
|
||||
{icon}
|
||||
<Text style={[style.errorText, warning && style.warningText]}>{message}</Text>
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
hitSlop={5}
|
||||
style={({pressed}) => [
|
||||
style.pressable,
|
||||
style.iconContainer,
|
||||
pressed && style.pressedIconContainer,
|
||||
]}
|
||||
>
|
||||
{({pressed}) => (
|
||||
<View style={style.iconContainer}>
|
||||
{icon}
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
<Text style={[style.errorText, warning && style.warningText]}>{message}</Text>
|
||||
</View>
|
||||
<Pressable onPress={onDismiss}>
|
||||
<View style={style.dismissContainer}>
|
||||
<CompassIcon
|
||||
name='close'
|
||||
style={[style.errorIcon,
|
||||
warning && style.warningIcon,
|
||||
pressed && style.pressedErrorIcon,
|
||||
pressed && warning && style.pressedWarningIcon,
|
||||
]}
|
||||
style={[style.icon, style.closeIcon, warning && style.closeIconWarning]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
|
|||
height: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
floatingBarsContainer: {
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
@ -172,8 +178,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
|
|||
marginTop: 5,
|
||||
marginBottom: 0,
|
||||
},
|
||||
profileScreenOn: {
|
||||
marginBottom: 2,
|
||||
},
|
||||
username: {
|
||||
marginTop: 10,
|
||||
width: usernameL,
|
||||
textAlign: 'center',
|
||||
color: theme.buttonColor,
|
||||
|
|
@ -183,11 +191,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
|
|||
marginTop: 0,
|
||||
width: usernameM,
|
||||
},
|
||||
incomingCallContainer: {
|
||||
width: '100%',
|
||||
marginBottom: 8,
|
||||
gap: 8,
|
||||
},
|
||||
buttonsContainer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
|
@ -604,16 +607,18 @@ const CallScreen = ({
|
|||
style={[style.user, screenShareOn && style.userScreenOn]}
|
||||
key={user.id}
|
||||
>
|
||||
<CallAvatar
|
||||
userModel={user.userModel}
|
||||
volume={currentCall.voiceOn[user.id] ? 1 : 0}
|
||||
muted={user.muted}
|
||||
sharingScreen={user.id === currentCall.screenOn}
|
||||
raisedHand={Boolean(user.raisedHand)}
|
||||
reaction={user.reaction?.emoji}
|
||||
size={avatarSize}
|
||||
serverUrl={currentCall.serverUrl}
|
||||
/>
|
||||
<View style={[screenShareOn && style.profileScreenOn]}>
|
||||
<CallAvatar
|
||||
userModel={user.userModel}
|
||||
speaking={currentCall.voiceOn[user.id]}
|
||||
muted={user.muted}
|
||||
sharingScreen={user.id === currentCall.screenOn}
|
||||
raisedHand={Boolean(user.raisedHand)}
|
||||
reaction={user.reaction?.emoji}
|
||||
size={avatarSize}
|
||||
serverUrl={currentCall.serverUrl}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={[style.username, smallerAvatar && style.usernameShort]}
|
||||
numberOfLines={1}
|
||||
|
|
@ -691,29 +696,29 @@ const CallScreen = ({
|
|||
{!isLandscape && currentCall.reactionStream.length > 0 &&
|
||||
<EmojiList reactionStream={currentCall.reactionStream}/>
|
||||
}
|
||||
{showIncomingCalls &&
|
||||
<View style={style.incomingCallContainer}>
|
||||
{incomingCalls.incomingCalls.map((ic) => (
|
||||
<View style={style.floatingBarsContainer}>
|
||||
{showIncomingCalls &&
|
||||
incomingCalls.incomingCalls.map((ic) => (
|
||||
<CallNotification
|
||||
key={ic.callID}
|
||||
incomingCall={ic}
|
||||
onCallsScreen={true}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
}
|
||||
{micPermissionsError &&
|
||||
<MessageBar
|
||||
type={Calls.MessageBarType.Microphone}
|
||||
onPress={setMicPermissionsErrorDismissed}
|
||||
/>
|
||||
}
|
||||
{currentCall.callQualityAlert &&
|
||||
<MessageBar
|
||||
type={Calls.MessageBarType.CallQuality}
|
||||
onPress={setCallQualityAlertDismissed}
|
||||
/>
|
||||
}
|
||||
))
|
||||
}
|
||||
{micPermissionsError &&
|
||||
<MessageBar
|
||||
type={Calls.MessageBarType.Microphone}
|
||||
onDismiss={setMicPermissionsErrorDismissed}
|
||||
/>
|
||||
}
|
||||
{currentCall.callQualityAlert &&
|
||||
<MessageBar
|
||||
type={Calls.MessageBarType.CallQuality}
|
||||
onDismiss={setCallQualityAlertDismissed}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
<View style={[style.buttonsContainer]}>
|
||||
<View
|
||||
style={[
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import {type Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area
|
|||
|
||||
import {storeLastViewedChannelIdAndServer, removeLastViewedChannelIdAndServer} from '@actions/app/global';
|
||||
import FloatingCallContainer from '@calls/components/floating_call_container';
|
||||
import {IncomingCallsContainer} from '@calls/components/incoming_calls_container';
|
||||
import {RoundedHeaderCalls} from '@calls/components/join_call_banner/rounded_header_calls';
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import PostDraft from '@components/post_draft';
|
||||
import {Screens} from '@constants';
|
||||
|
|
@ -110,7 +108,7 @@ const Channel = ({
|
|||
setContainerHeight(e.nativeEvent.layout.height);
|
||||
}, []);
|
||||
|
||||
const showFloatingCallContainer = showJoinCallBanner || isInACall;
|
||||
const showFloatingCallContainer = showJoinCallBanner || isInACall || showIncomingCalls;
|
||||
|
||||
return (
|
||||
<FreezeScreen>
|
||||
|
|
@ -127,7 +125,6 @@ const Channel = ({
|
|||
callsEnabledInChannel={isCallsEnabledInChannel}
|
||||
isTabletView={isTabletView}
|
||||
/>
|
||||
{showJoinCallBanner && <RoundedHeaderCalls/>}
|
||||
{shouldRender &&
|
||||
<>
|
||||
<View style={[styles.flex, {marginTop}]}>
|
||||
|
|
@ -154,16 +151,10 @@ const Channel = ({
|
|||
<FloatingCallContainer
|
||||
channelId={channelId}
|
||||
showJoinCallBanner={showJoinCallBanner}
|
||||
showIncomingCalls={showIncomingCalls}
|
||||
isInACall={isInACall}
|
||||
/>
|
||||
}
|
||||
{showIncomingCalls &&
|
||||
<IncomingCallsContainer
|
||||
channelId={channelId}
|
||||
showingJoinCallBanner={showJoinCallBanner}
|
||||
showingCurrentCallBanner={isInACall}
|
||||
/>
|
||||
}
|
||||
</SafeAreaView>
|
||||
</FreezeScreen>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import {type Edge, SafeAreaView} from 'react-native-safe-area-context';
|
|||
|
||||
import {storeLastViewedThreadIdAndServer, removeLastViewedThreadIdAndServer} from '@actions/app/global';
|
||||
import FloatingCallContainer from '@calls/components/floating_call_container';
|
||||
import {IncomingCallsContainer} from '@calls/components/incoming_calls_container';
|
||||
import {RoundedHeaderCalls} from '@calls/components/join_call_banner/rounded_header_calls';
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import PostDraft from '@components/post_draft';
|
||||
import RoundedHeaderContext from '@components/rounded_header_context';
|
||||
|
|
@ -111,7 +109,7 @@ const Thread = ({
|
|||
setContainerHeight(e.nativeEvent.layout.height);
|
||||
}, []);
|
||||
|
||||
const showFloatingCallContainer = showJoinCallBanner || isInACall;
|
||||
const showFloatingCallContainer = showJoinCallBanner || isInACall || showIncomingCalls;
|
||||
|
||||
return (
|
||||
<FreezeScreen>
|
||||
|
|
@ -123,7 +121,6 @@ const Thread = ({
|
|||
onLayout={onLayout}
|
||||
>
|
||||
<RoundedHeaderContext/>
|
||||
{showJoinCallBanner && <RoundedHeaderCalls threadScreen={true}/>}
|
||||
{Boolean(rootPost) &&
|
||||
<>
|
||||
<View style={styles.flex}>
|
||||
|
|
@ -148,18 +145,11 @@ const Thread = ({
|
|||
<FloatingCallContainer
|
||||
channelId={rootPost!.channelId}
|
||||
showJoinCallBanner={showJoinCallBanner}
|
||||
showIncomingCalls={showIncomingCalls}
|
||||
isInACall={isInACall}
|
||||
threadScreen={true}
|
||||
/>
|
||||
}
|
||||
{showIncomingCalls &&
|
||||
<IncomingCallsContainer
|
||||
channelId={rootPost!.channelId}
|
||||
showingJoinCallBanner={showJoinCallBanner}
|
||||
showingCurrentCallBanner={isInACall}
|
||||
threadScreen={true}
|
||||
/>
|
||||
}
|
||||
</SafeAreaView>
|
||||
</FreezeScreen>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -451,7 +451,6 @@
|
|||
"mobile.calls_host_rec_title": "You are recording",
|
||||
"mobile.calls_incoming_dm": "<b>{name}</b> is inviting you to a call",
|
||||
"mobile.calls_incoming_gm": "<b>{name}</b> is inviting you to a call with <b>{num, plural, one {# other} other {# others}}</b>",
|
||||
"mobile.calls_join_button": "Join",
|
||||
"mobile.calls_join_call": "Join call",
|
||||
"mobile.calls_lasted": "Lasted {duration}",
|
||||
"mobile.calls_leave": "Leave",
|
||||
|
|
|
|||
Loading…
Reference in a new issue