diff --git a/app/components/user_avatars_stack/index.tsx b/app/components/user_avatars_stack/index.tsx index 86734f059..7b7c47aba 100644 --- a/app/components/user_avatars_stack/index.tsx +++ b/app/components/user_avatars_stack/index.tsx @@ -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; noBorder?: boolean; + avatarStyle?: StyleProp; + overflowContainerStyle?: StyleProp; + overflowItemStyle?: StyleProp; + overflowTextStyle?: StyleProp; } 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) => ( ))} {Boolean(overflowUsersCount) && ( - - - + + + {'+' + overflowUsersCount.toString()} diff --git a/app/constants/view.ts b/app/constants/view.ts index 29cbfb71c..50ee021a0 100644 --- a/app/constants/view.ts +++ b/app/constants/view.ts @@ -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; diff --git a/app/products/calls/components/call_avatar.tsx b/app/products/calls/components/call_avatar.tsx index f0ad9f90f..8881dfcec 100644 --- a/app/products/calls/components/call_avatar.tsx +++ b/app/products/calls/components/call_avatar.tsx @@ -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 = ( @@ -190,7 +196,7 @@ const CallAvatar = ({userModel, volume = 0, serverUrl, sharingScreen, size, mute @@ -212,34 +218,28 @@ const CallAvatar = ({userModel, volume = 0, serverUrl, sharingScreen, size, mute /> ); - const view = ( - - {profile} - { - muted !== undefined && - - - - } - {topRightIcon} - - ); - - if (Platform.OS === 'android') { - return ( - - - {view} + return ( + + + + + {profile} + { + muted !== undefined && + + + + } + {topRightIcon} + - ); - } - - return view; + + ); }; export default CallAvatar; diff --git a/app/products/calls/components/call_notification/call_notification.tsx b/app/products/calls/components/call_notification/call_notification.tsx index 20d3c2249..40fa401ac 100644 --- a/app/products/calls/components/call_notification/call_notification.tsx +++ b/app/products/calls/components/call_notification/call_notification.tsx @@ -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 = ( - - ); return ( - + - - {message} - - - {joinLabel} - - - + + + + {message} + + + + + diff --git a/app/products/calls/components/current_call_bar/current_call_bar.tsx b/app/products/calls/components/current_call_bar/current_call_bar.tsx index d5698799f..2b705b605 100644 --- a/app/products/calls/components/current_call_bar/current_call_bar.tsx +++ b/app/products/calls/components/current_call_bar/current_call_bar.tsx @@ -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} > - + - + {talkingMessage} {`~${displayName}`} @@ -274,13 +281,13 @@ const CurrentCallBar = ({ {micPermissionsError && } {currentCall?.callQualityAlert && } diff --git a/app/products/calls/components/floating_call_container.tsx b/app/products/calls/components/floating_call_container.tsx index 40f28f76a..bc826b8d0 100644 --- a/app/products/calls/components/floating_call_container.tsx +++ b/app/products/calls/components/floating_call_container.tsx @@ -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 && } + {showIncomingCalls && + + } ); }; diff --git a/app/products/calls/components/incoming_calls_container.tsx b/app/products/calls/components/incoming_calls_container.tsx index b4301201b..8ca052423 100644 --- a/app/products/calls/components/incoming_calls_container.tsx +++ b/app/products/calls/components/incoming_calls_container.tsx @@ -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 ( - + {calls.map((ic) => ( of$(state?.id || '')), + ); + return { + callId, participants, channelCallStartTime, limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, channelId), diff --git a/app/products/calls/components/join_call_banner/join_call_banner.tsx b/app/products/calls/components/join_call_banner/join_call_banner.tsx index 7f24ecdf7..7934c1a26 100644 --- a/app/products/calls/components/join_call_banner/join_call_banner.tsx +++ b/app/products/calls/components/join_call_banner/join_call_banner.tsx @@ -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 ( - - - {isLimitRestricted ? ( + + + + - ) : ( - - )} + {isLimitRestricted ? ( + + ) : ( + + )} + + + + + + ); diff --git a/app/products/calls/components/join_call_banner/rounded_header_calls.tsx b/app/products/calls/components/join_call_banner/rounded_header_calls.tsx deleted file mode 100644 index 54671ce88..000000000 --- a/app/products/calls/components/join_call_banner/rounded_header_calls.tsx +++ /dev/null @@ -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 ( - - - - ); -}; diff --git a/app/products/calls/components/message_bar.tsx b/app/products/calls/components/message_bar.tsx index 9fa6ec2f8..a35a3735d 100644 --- a/app/products/calls/components/message_bar.tsx +++ b/app/products/calls/components/message_bar.tsx @@ -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 = ( ); break; case Calls.MessageBarType.CallQuality: @@ -111,38 +121,30 @@ const MessageBar = ({type, onPress}: Props) => { icon = ( ); break; } return ( - + - {icon} - {message} - [ - style.pressable, - style.iconContainer, - pressed && style.pressedIconContainer, - ]} - > - {({pressed}) => ( + + {icon} + + + {message} + + + - )} + diff --git a/app/products/calls/screens/call_screen/call_screen.tsx b/app/products/calls/screens/call_screen/call_screen.tsx index 103ced171..313889e7c 100644 --- a/app/products/calls/screens/call_screen/call_screen.tsx +++ b/app/products/calls/screens/call_screen/call_screen.tsx @@ -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} > - + + + 0 && } - {showIncomingCalls && - - {incomingCalls.incomingCalls.map((ic) => ( + + {showIncomingCalls && + incomingCalls.incomingCalls.map((ic) => ( - ))} - - } - {micPermissionsError && - - } - {currentCall.callQualityAlert && - - } + )) + } + {micPermissionsError && + + } + {currentCall.callQualityAlert && + + } + @@ -127,7 +125,6 @@ const Channel = ({ callsEnabledInChannel={isCallsEnabledInChannel} isTabletView={isTabletView} /> - {showJoinCallBanner && } {shouldRender && <> @@ -154,16 +151,10 @@ const Channel = ({ } - {showIncomingCalls && - - } ); diff --git a/app/screens/thread/thread.tsx b/app/screens/thread/thread.tsx index bc2f1d8fe..5e879326e 100644 --- a/app/screens/thread/thread.tsx +++ b/app/screens/thread/thread.tsx @@ -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 ( @@ -123,7 +121,6 @@ const Thread = ({ onLayout={onLayout} > - {showJoinCallBanner && } {Boolean(rootPost) && <> @@ -148,18 +145,11 @@ const Thread = ({ } - {showIncomingCalls && - - } ); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 985b58325..e1be78279 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -451,7 +451,6 @@ "mobile.calls_host_rec_title": "You are recording", "mobile.calls_incoming_dm": "{name} is inviting you to a call", "mobile.calls_incoming_gm": "{name} is inviting you to a call with {num, plural, one {# other} other {# others}}", - "mobile.calls_join_button": "Join", "mobile.calls_join_call": "Join call", "mobile.calls_lasted": "Lasted {duration}", "mobile.calls_leave": "Leave",