diff --git a/android/app/build.gradle b/android/app/build.gradle index 668e84164..9a47ac471 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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' diff --git a/app/components/post_list/more_messages/more_messages.tsx b/app/components/post_list/more_messages/more_messages.tsx index 18b0024dc..bb33275b4 100644 --- a/app/components/post_list/more_messages/more_messages.tsx +++ b/app/components/post_list/more_messages/more_messages.tsx @@ -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 ( - + diff --git a/app/components/post_list/post_list.tsx b/app/components/post_list/post_list.tsx index f2ad16fac..7bd89b9a8 100644 --- a/app/components/post_list/post_list.tsx +++ b/app/components/post_list/post_list.tsx @@ -52,7 +52,6 @@ type Props = { header?: ReactElement; testID: string; currentCallBarVisible?: boolean; - joinCallBannerVisible?: boolean; savedPostIds: Set; } @@ -111,8 +110,6 @@ const PostList = ({ showMoreMessages, showNewMessageLine = true, testID, - currentCallBarVisible, - joinCallBannerVisible, savedPostIds, }: Props) => { const listRef = useRef>(null); @@ -377,8 +374,6 @@ const PostList = ({ scrollToIndex={scrollToIndex} theme={theme} testID={`${testID}.more_messages_button`} - currentCallBarVisible={Boolean(currentCallBarVisible)} - joinCallBannerVisible={Boolean(joinCallBannerVisible)} /> } 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..0a88911f8 100644 --- a/app/products/calls/components/floating_call_container.tsx +++ b/app/products/calls/components/floating_call_container.tsx @@ -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 && } + {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/hooks.ts b/app/products/calls/hooks.ts index e92426eab..28d0fe35a 100644 --- a/app/products/calls/hooks.ts +++ b/app/products/calls/hooks.ts @@ -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; +}; 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,15 +125,12 @@ const Channel = ({ callsEnabledInChannel={isCallsEnabledInChannel} isTabletView={isTabletView} /> - {showJoinCallBanner && } {shouldRender && <> } - {showIncomingCalls && - - } ); diff --git a/app/screens/channel/channel_post_list/channel_post_list.tsx b/app/screens/channel/channel_post_list/channel_post_list.tsx index 9d43581df..986f49a2f 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.tsx +++ b/app/screens/channel/channel_post_list/channel_post_list.tsx @@ -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} /> ); 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/app/utils/notification/index.ts b/app/utils/notification/index.ts index b53c096c8..8460de9c9 100644 --- a/app/utils/notification/index.ts +++ b/app/utils/notification/index.ts @@ -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) { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 2431004e3..029bcdfa1 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", @@ -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.", diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index e9608699b..470487519 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -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; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 27de4b73f..a6d40ce7a 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -37,7 +37,7 @@ CFBundleVersion - 490 + 492 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 006552425..03f703396 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 2.10.0 CFBundleVersion - 490 + 492 UIAppFonts OpenSans-Bold.ttf diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index bbe279808..691c24829 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 2.10.0 CFBundleVersion - 490 + 492 NSExtension NSExtensionPointIdentifier