Merge branch 'main' into gm_to_channel
This commit is contained in:
commit
aff310471a
25 changed files with 524 additions and 537 deletions
|
|
@ -110,7 +110,7 @@ android {
|
|||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 490
|
||||
versionCode 492
|
||||
versionName "2.10.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
|
|||
|
|
@ -7,16 +7,15 @@ import Animated, {interpolate, useAnimatedStyle, useSharedValue, withSpring} fro
|
|||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {resetMessageCount} from '@actions/local/channel';
|
||||
import {useCallsAdjustment} from '@app/products/calls/hooks';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {Events} from '@constants';
|
||||
import {CURRENT_CALL_BAR_HEIGHT, JOIN_CALL_BAR_HEIGHT} from '@constants/view';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {makeStyleSheetFromTheme, hexToHue} from '@utils/theme';
|
||||
import {makeStyleSheetFromTheme, hexToHue, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type {PostList} from '@typings/components/post_list';
|
||||
|
|
@ -34,8 +33,6 @@ type Props = {
|
|||
unreadCount: number;
|
||||
theme: Theme;
|
||||
testID: string;
|
||||
currentCallBarVisible: boolean;
|
||||
joinCallBannerVisible: boolean;
|
||||
}
|
||||
|
||||
const HIDDEN_TOP = -60;
|
||||
|
|
@ -60,9 +57,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
flexDirection: 'row',
|
||||
justifyContent: 'space-evenly',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 12,
|
||||
width: '100%',
|
||||
height: 42,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
paddingTop: 4,
|
||||
paddingRight: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 8,
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
|
|
@ -70,29 +71,30 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
},
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
},
|
||||
roundBorder: {
|
||||
borderRadius: 8,
|
||||
iconContainer: {
|
||||
top: 1,
|
||||
width: 32,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
color: theme.buttonColor,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
iconContainer: {
|
||||
top: 2,
|
||||
width: 22,
|
||||
closeIcon: {
|
||||
color: changeOpacity(theme.buttonColor, 0.56),
|
||||
},
|
||||
pressContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
textContainer: {
|
||||
paddingLeft: 4,
|
||||
marginLeft: 8,
|
||||
},
|
||||
text: {
|
||||
color: theme.buttonColor,
|
||||
...typography('Body', 200, 'SemiBold'),
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -110,11 +112,8 @@ const MoreMessages = ({
|
|||
unreadCount,
|
||||
testID,
|
||||
theme,
|
||||
currentCallBarVisible,
|
||||
joinCallBannerVisible,
|
||||
}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const isTablet = useIsTablet();
|
||||
const insets = useSafeAreaInsets();
|
||||
const pressed = useRef(false);
|
||||
const resetting = useRef(false);
|
||||
|
|
@ -122,12 +121,14 @@ const MoreMessages = ({
|
|||
const [loading, setLoading] = useState(EphemeralStore.isLoadingMessagesForChannel(serverUrl, channelId));
|
||||
const [remaining, setRemaining] = useState(0);
|
||||
const underlayColor = useMemo(() => `hsl(${hexToHue(theme.buttonBg)}, 50%, 38%)`, [theme]);
|
||||
const top = useSharedValue(0);
|
||||
const adjustedShownTop = SHOWN_TOP + (currentCallBarVisible ? CURRENT_CALL_BAR_HEIGHT : 0) + (joinCallBannerVisible ? JOIN_CALL_BAR_HEIGHT : 0);
|
||||
const adjustTop = isTablet || (isCRTEnabled && rootId);
|
||||
const shownTop = adjustTop ? SHOWN_TOP : adjustedShownTop;
|
||||
const BARS_FACTOR = Math.abs((1) / (HIDDEN_TOP - SHOWN_TOP));
|
||||
const styles = getStyleSheet(theme);
|
||||
const top = useSharedValue(0);
|
||||
const callsAdjustment = useCallsAdjustment(serverUrl, channelId);
|
||||
|
||||
// The final top:
|
||||
const adjustedTop = insets.top + callsAdjustment;
|
||||
|
||||
const BARS_FACTOR = Math.abs((1) / (HIDDEN_TOP - SHOWN_TOP));
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{
|
||||
|
|
@ -142,13 +143,13 @@ const MoreMessages = ({
|
|||
[
|
||||
HIDDEN_TOP,
|
||||
HIDDEN_TOP,
|
||||
shownTop + (adjustTop ? 0 : insets.top),
|
||||
shownTop + (adjustTop ? 0 : insets.top),
|
||||
adjustedTop,
|
||||
adjustedTop,
|
||||
],
|
||||
'clamp',
|
||||
), {damping: 15}),
|
||||
}],
|
||||
}), [shownTop, insets.top, adjustTop]);
|
||||
}), [adjustedTop]);
|
||||
|
||||
// Due to the implementation differences "unreadCount" gets updated for a channel on reset but not for a thread.
|
||||
// So we maintain a localUnreadCount to hide the indicator when the count is reset.
|
||||
|
|
@ -254,7 +255,7 @@ const MoreMessages = ({
|
|||
|
||||
return (
|
||||
<Animated.View style={[styles.animatedContainer, animatedStyle]}>
|
||||
<View style={[styles.container, styles.roundBorder]}>
|
||||
<View style={[styles.container]}>
|
||||
<TouchableWithFeedback
|
||||
type={'opacity'}
|
||||
onPress={onPress}
|
||||
|
|
@ -295,7 +296,7 @@ const MoreMessages = ({
|
|||
<View style={styles.cancelContainer}>
|
||||
<CompassIcon
|
||||
name='close'
|
||||
style={styles.icon}
|
||||
style={[styles.icon, styles.closeIcon]}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ type Props = {
|
|||
header?: ReactElement;
|
||||
testID: string;
|
||||
currentCallBarVisible?: boolean;
|
||||
joinCallBannerVisible?: boolean;
|
||||
savedPostIds: Set<string>;
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +110,6 @@ const PostList = ({
|
|||
showMoreMessages,
|
||||
showNewMessageLine = true,
|
||||
testID,
|
||||
currentCallBarVisible,
|
||||
joinCallBannerVisible,
|
||||
savedPostIds,
|
||||
}: Props) => {
|
||||
const listRef = useRef<FlatList<string | PostModel>>(null);
|
||||
|
|
@ -377,8 +374,6 @@ const PostList = ({
|
|||
scrollToIndex={scrollToIndex}
|
||||
theme={theme}
|
||||
testID={`${testID}.more_messages_button`}
|
||||
currentCallBarVisible={Boolean(currentCallBarVisible)}
|
||||
joinCallBannerVisible={Boolean(joinCallBannerVisible)}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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,43 +2,42 @@
|
|||
// 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 {DEFAULT_HEADER_HEIGHT, TABLET_HEADER_HEIGHT} from '@constants/view';
|
||||
import {useServerUrl} from '@context/server';
|
||||
|
||||
const topBarHeight = DEFAULT_HEADER_HEIGHT;
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
|
||||
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 topBarForTablet = (isTablet && !threadScreen) ? TABLET_HEADER_HEIGHT : 0;
|
||||
const topBarChannel = (!isTablet && !threadScreen) ? DEFAULT_HEADER_HEIGHT : 0;
|
||||
const wrapperTop = {
|
||||
top: insets.top + (threadScreen ? 0 : topBarHeight),
|
||||
top: insets.top + topBarForTablet + topBarChannel,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -50,6 +49,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>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import {useIntl} from 'react-intl';
|
|||
import {Alert, Platform} from 'react-native';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import {CALL_ERROR_BAR_HEIGHT, CALL_NOTIFICATION_BAR_HEIGHT, CURRENT_CALL_BAR_HEIGHT, JOIN_CALL_BAR_HEIGHT} from '@app/constants/view';
|
||||
import {initializeVoiceTrack} from '@calls/actions/calls';
|
||||
import {setMicPermissionsGranted} from '@calls/state';
|
||||
import {setMicPermissionsGranted, useCallsState, useChannelsWithCalls, useCurrentCall, useGlobalCallsState, useIncomingCalls} from '@calls/state';
|
||||
import {errorAlert} from '@calls/utils';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useAppState} from '@hooks/device';
|
||||
|
|
@ -108,3 +109,27 @@ export const usePermissionsChecker = (micPermissionsGranted: boolean) => {
|
|||
}
|
||||
}, [appState]);
|
||||
};
|
||||
|
||||
export const useCallsAdjustment = (serverUrl: string, channelId: string) => {
|
||||
const incomingCalls = useIncomingCalls().incomingCalls;
|
||||
const channelsWithCalls = useChannelsWithCalls(serverUrl);
|
||||
const callsState = useCallsState(serverUrl);
|
||||
const globalCallsState = useGlobalCallsState();
|
||||
const currentCall = useCurrentCall();
|
||||
const dismissed = Boolean(callsState.calls[channelId]?.dismissed[callsState.myUserId]);
|
||||
const inCurrentCall = currentCall?.id === channelId;
|
||||
const joinCallBannerVisible = Boolean(channelsWithCalls[channelId]) && !dismissed && !inCurrentCall;
|
||||
|
||||
// Do we have calls banners?
|
||||
const currentCallBarVisible = Boolean(currentCall);
|
||||
const micPermissionsError = !globalCallsState.micPermissionsGranted && (currentCall && !currentCall.micPermissionsErrorDismissed);
|
||||
const callQualityAlert = Boolean(currentCall?.callQualityAlert);
|
||||
const incomingCallsShowing = incomingCalls.filter((ic) => ic.channelID !== channelId);
|
||||
const callsIncomingAdjustment = (incomingCallsShowing.length * CALL_NOTIFICATION_BAR_HEIGHT) + (incomingCallsShowing.length * 8);
|
||||
const callsAdjustment = (currentCallBarVisible ? CURRENT_CALL_BAR_HEIGHT + 8 : 0) +
|
||||
(micPermissionsError ? CALL_ERROR_BAR_HEIGHT + 8 : 0) +
|
||||
(callQualityAlert ? CALL_ERROR_BAR_HEIGHT + 8 : 0) +
|
||||
(joinCallBannerVisible ? JOIN_CALL_BAR_HEIGHT + 8 : 0) +
|
||||
callsIncomingAdjustment;
|
||||
return callsAdjustment;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,15 +125,12 @@ const Channel = ({
|
|||
callsEnabledInChannel={isCallsEnabledInChannel}
|
||||
isTabletView={isTabletView}
|
||||
/>
|
||||
{showJoinCallBanner && <RoundedHeaderCalls/>}
|
||||
{shouldRender &&
|
||||
<>
|
||||
<View style={[styles.flex, {marginTop}]}>
|
||||
<ChannelPostList
|
||||
channelId={channelId}
|
||||
nativeID={channelId}
|
||||
currentCallBarVisible={isInACall}
|
||||
joinCallBannerVisible={showJoinCallBanner}
|
||||
/>
|
||||
</View>
|
||||
<PostDraft
|
||||
|
|
@ -154,16 +149,10 @@ const Channel = ({
|
|||
<FloatingCallContainer
|
||||
channelId={channelId}
|
||||
showJoinCallBanner={showJoinCallBanner}
|
||||
showIncomingCalls={showIncomingCalls}
|
||||
isInACall={isInACall}
|
||||
/>
|
||||
}
|
||||
{showIncomingCalls &&
|
||||
<IncomingCallsContainer
|
||||
channelId={channelId}
|
||||
showingJoinCallBanner={showJoinCallBanner}
|
||||
showingCurrentCallBanner={isInACall}
|
||||
/>
|
||||
}
|
||||
</SafeAreaView>
|
||||
</FreezeScreen>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ type Props = {
|
|||
nativeID: string;
|
||||
posts: PostModel[];
|
||||
shouldShowJoinLeaveMessages: boolean;
|
||||
currentCallBarVisible: boolean;
|
||||
joinCallBannerVisible: boolean;
|
||||
}
|
||||
|
||||
const edges: Edge[] = ['bottom'];
|
||||
|
|
@ -42,7 +40,6 @@ const styles = StyleSheet.create({
|
|||
const ChannelPostList = ({
|
||||
channelId, contentContainerStyle, isCRTEnabled,
|
||||
lastViewedAt, nativeID, posts, shouldShowJoinLeaveMessages,
|
||||
currentCallBarVisible, joinCallBannerVisible,
|
||||
}: Props) => {
|
||||
const appState = useAppState();
|
||||
const isTablet = useIsTablet();
|
||||
|
|
@ -110,8 +107,6 @@ const ChannelPostList = ({
|
|||
shouldShowJoinLeaveMessages={shouldShowJoinLeaveMessages}
|
||||
showMoreMessages={true}
|
||||
testID='channel.post_list'
|
||||
currentCallBarVisible={currentCallBarVisible}
|
||||
joinCallBannerVisible={joinCallBannerVisible}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -90,12 +90,27 @@ export const emitNotificationError = (type: 'Team' | 'Channel' | 'Post' | 'Conne
|
|||
|
||||
export const scheduleExpiredNotification = (serverUrl: string, session: Session, serverName: string, locale = DEFAULT_LOCALE) => {
|
||||
const expiresAt = session?.expires_at || 0;
|
||||
const expiresInDays = Math.ceil(Math.abs(moment.duration(moment().diff(moment(expiresAt))).asDays()));
|
||||
const expiresInHours = Math.ceil(Math.abs(moment.duration(moment().diff(moment(expiresAt))).asHours()));
|
||||
const expiresInDays = Math.floor(expiresInHours / 24); // Calculate expiresInDays
|
||||
const remainingHours = expiresInHours % 24; // Calculate remaining hours
|
||||
const intl = createIntl({locale, messages: getTranslations(locale)});
|
||||
const body = intl.formatMessage({
|
||||
id: 'mobile.session_expired',
|
||||
defaultMessage: 'Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}}.',
|
||||
}, {siteName: serverName, daysCount: expiresInDays});
|
||||
let body = '';
|
||||
if (expiresInDays === 0) {
|
||||
body = intl.formatMessage({
|
||||
id: 'mobile.session_expired_hrs',
|
||||
defaultMessage: 'Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {hoursCount, number} {hoursCount, plural, one {hour} other {hours}}.',
|
||||
}, {siteName: serverName, hoursCount: remainingHours});
|
||||
} else if (expiresInHours === 0) {
|
||||
body = intl.formatMessage({
|
||||
id: 'mobile.session_expired_days',
|
||||
defaultMessage: 'Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}}.',
|
||||
}, {siteName: serverName, daysCount: expiresInDays});
|
||||
} else {
|
||||
body = intl.formatMessage({
|
||||
id: 'mobile.session_expired_days_hrs',
|
||||
defaultMessage: 'Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}} and {hoursCount, number} {hoursCount, plural, one {hour} other {hours}}.',
|
||||
}, {siteName: serverName, daysCount: expiresInDays, hoursCount: remainingHours});
|
||||
}
|
||||
const title = intl.formatMessage({id: 'mobile.session_expired.title', defaultMessage: 'Session Expired'});
|
||||
|
||||
if (expiresAt) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -710,7 +709,9 @@
|
|||
"mobile.server_url.deeplink.emm.denied": "This app is controlled by an EMM and the DeepLink server url does not match the EMM allowed server",
|
||||
"mobile.server_url.empty": "Please enter a valid server URL",
|
||||
"mobile.server_url.invalid_format": "URL must start with http:// or https://",
|
||||
"mobile.session_expired": "Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}}.",
|
||||
"mobile.session_expired_days": "Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}}.",
|
||||
"mobile.session_expired_days_hrs": "Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {daysCount, number} {daysCount, plural, one {day} other {days}} and {hoursCount, number} {hoursCount, plural, one {hour} other {hours}}.",
|
||||
"mobile.session_expired_hrs": "Please log in to continue receiving notifications. Sessions for {siteName} are configured to expire every {hoursCount, number} {hoursCount, plural, one {hour} other {hours}}.",
|
||||
"mobile.session_expired.title": "Session Expired",
|
||||
"mobile.set_status.dnd": "Do Not Disturb",
|
||||
"mobile.storage_permission_denied_description": "Upload files to your server. Open Settings to grant {applicationName} Read and Write access to files on this device.",
|
||||
|
|
|
|||
|
|
@ -1929,7 +1929,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 490;
|
||||
CURRENT_PROJECT_VERSION = 492;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
|
|
@ -1973,7 +1973,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 490;
|
||||
CURRENT_PROJECT_VERSION = 492;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
|
|
@ -2116,7 +2116,7 @@
|
|||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 490;
|
||||
CURRENT_PROJECT_VERSION = 492;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
@ -2165,7 +2165,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 490;
|
||||
CURRENT_PROJECT_VERSION = 492;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>490</string>
|
||||
<string>492</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.10.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>490</string>
|
||||
<string>492</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.10.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>490</string>
|
||||
<string>492</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
Loading…
Reference in a new issue