Compare commits

...

12 commits

Author SHA1 Message Date
unified-ci-app[bot]
954188b3e9
Bump app build number to 507 (#7809)
Some checks failed
github-release / test (push) Has been cancelled
github-release / build-ios-unsigned (push) Has been cancelled
github-release / build-android-unsigned (push) Has been cancelled
github-release / release (push) Has been cancelled
Co-authored-by: runner <runner@Mac-1706874884733.local>
2024-02-02 13:17:12 +01:00
Mattermost Build
acc34b3121
Fix multi-server icon mention badge (#7795) (#7804)
(cherry picked from commit 0897f54bf0)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2024-02-02 12:43:24 +01:00
Mattermost Build
ebba06a916
Update iOS permissions texts (#7805) (#7807)
* Update iOS permissions texts

* Add missing changes

(cherry picked from commit 41113ce857)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2024-02-02 10:23:48 +01:00
Mattermost Build
cf345808a5
revert change to allow arbitrary loads (#7801) (#7803) 2024-02-01 21:07:53 +08:00
Mattermost Build
1c3c925a53
Bump app build number to 504 (#7788) (#7797)
Co-authored-by: runner <runner@Mac-1706259209788.local>
(cherry picked from commit 5e3e66281a)

Co-authored-by: unified-ci-app[bot] <121569378+unified-ci-app[bot]@users.noreply.github.com>
2024-01-30 12:59:21 +01:00
Mattermost Build
340759896e
Remove motion reducer from critical animations (#7786) (#7794)
Co-authored-by: Mattermost Build <build@mattermost.com>
(cherry picked from commit 780f623274)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2024-01-30 12:41:46 +01:00
unified-ci-app[bot]
305e0e4fde
Bump app build and version number (#7781)
* Bump app build number to 503

* Bump app version number to 2.13.0

---------

Co-authored-by: runner <runner@Mac-1705936004149.local>
2024-01-22 16:13:56 +01:00
Mattermost Build
2f35a79322
MM-56542 fix custom theme selection (#7762) (#7766) 2024-01-18 06:08:22 +08:00
Mattermost Build
fd7bada58e
Fix joining channels from the find channels screen (#7732) (#7757)
* Fix joining channels from the find channels screen

* Fix lint

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
(cherry picked from commit 8bd9cac9f5)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2024-01-16 10:29:24 +01:00
Mattermost Build
4599f41c7a
Fix MM55258 (#7729) (#7758)
* Fix MM55258

* Address feedback

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
(cherry picked from commit 9ea38cfb3f)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
2024-01-16 10:28:44 +01:00
Tom De Moor
62dd814a6d
Cherry-picking translations (#7760)
* Translated using Weblate (Russian)

Currently translated at 100.0% (1111 of 1111 strings)

Translation: Mattermost/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1111 of 1111 strings)

Translation: Mattermost/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/zh_Hans/

* Translated using Weblate (Polish)

Currently translated at 100.0% (1111 of 1111 strings)

Translation: Mattermost/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/pl/

* Translated using Weblate (German)

Currently translated at 100.0% (1111 of 1111 strings)

Translation: Mattermost/mattermost-mobile-v2
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile-v2/de/

---------

Co-authored-by: Konstantin <eleferen@gmail.com>
Co-authored-by: ThrRip <coding@thrrip.space>
Co-authored-by: master7 <marcin.karkosz@rajska.info>
Co-authored-by: jprusch <rs@schaeferbarthold.de>
2024-01-15 18:07:15 +01:00
Mattermost Build
35c064ff9a
[MM-46339] Handle user leaving channel with active call (#7754) (#7759)
* Handle user leaving channel with active call

* Remove debug log

(cherry picked from commit 84858e4450)

Co-authored-by: Claudio Costa <cstcld91@gmail.com>
2024-01-15 09:16:50 -06:00
29 changed files with 328 additions and 56 deletions

View file

@ -111,8 +111,8 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 501
versionName "2.12.0"
versionCode 507
versionName "2.13.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

View file

@ -11,7 +11,9 @@ import {fetchMissingDirectChannelsInfo, fetchMyChannel, fetchChannelStats, fetch
import {fetchPostsForChannel} from '@actions/remote/post';
import {fetchRolesIfNeeded} from '@actions/remote/role';
import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user';
import {loadCallForChannel} from '@calls/actions/calls';
import {loadCallForChannel, leaveCall} from '@calls/actions/calls';
import {userLeftChannelErr, userRemovedFromChannelErr} from '@calls/errors';
import {getCurrentCall} from '@calls/state';
import {Events, General} from '@constants';
import DatabaseManager from '@database/manager';
import {deleteChannelMembership, getChannelById, prepareMyChannelsForTeam, getCurrentChannel} from '@queries/servers/channel';
@ -360,6 +362,9 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
const channelId = msg.data.channel_id || msg.broadcast.channel_id;
if (EphemeralStore.isLeavingChannel(channelId)) {
if (getCurrentCall()?.channelId === channelId) {
leaveCall(userLeftChannelErr);
}
return;
}
@ -382,7 +387,12 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
if (currentChannelId && currentChannelId === channelId) {
await handleKickFromChannel(serverUrl, currentChannelId);
}
await removeCurrentUserFromChannel(serverUrl, channelId);
if (getCurrentCall()?.channelId === channelId) {
leaveCall(userRemovedFromChannelErr);
}
} else {
const {models: deleteMemberModels} = await deleteChannelMembership(operator, userId, channelId, true);
if (deleteMemberModels) {

View file

@ -29,6 +29,12 @@ const styles = StyleSheet.create({
},
});
function getMaximumLineLength(code: string) {
return code.split('\n').reduce((prev, v) => Math.max(prev, v.length), 0);
}
const MAXIMUM_CODE_LINE_LENGTH = 300;
const Highlighter = ({code, language, textStyle, selectable = false}: SyntaxHiglightProps) => {
const theme = useTheme();
const style = codeTheme[theme.codeTheme] || github;
@ -38,6 +44,8 @@ const Highlighter = ({code, language, textStyle, selectable = false}: SyntaxHigl
{backgroundColor: style.hljs.background || theme.centerChannelBg},
],
[theme, selectable, style]);
const maximumLineLength = useMemo(() => getMaximumLineLength(code), [code]);
const languageToUse = maximumLineLength > MAXIMUM_CODE_LINE_LENGTH ? 'text' : language;
const nativeRenderer = useCallback(({rows, stylesheet}: rendererProps) => {
const digits = rows.length.toString().length;
@ -65,7 +73,7 @@ const Highlighter = ({code, language, textStyle, selectable = false}: SyntaxHigl
return (
<SyntaxHighlighter
style={style}
language={language}
language={languageToUse}
horizontal={true}
showLineNumbers={true}
renderer={nativeRenderer}

View file

@ -18,7 +18,7 @@ export const iosPermissions = defineMessages({
},
NSCameraUsageDescription: {
id: 'mobile.ios.plist.NSCameraUsageDescription',
defaultMessage: 'Enabling access to your device cameras means you can take photos or videos and upload them to {applicationName}.',
defaultMessage: 'Allowing access to your camera enables you to take photos or videos and attach them to messages.',
},
NSFaceIDUsageDescription: {
id: 'mobile.ios.plist.NSFaceIDUsageDescription',
@ -42,7 +42,7 @@ export const iosPermissions = defineMessages({
},
NSPhotoLibraryUsageDescription: {
id: 'mobile.ios.plist.NSPhotoLibraryUsageDescription',
defaultMessage: 'Enabling read access to your photo library means you can upload photos and videos from your device to {applicationName}.',
defaultMessage: 'Allowing access to your photo library enables you to select photos or videos and attach them to messages.',
},
NSSpeechRecognitionUsageDescription: {
id: 'mobile.ios.plist.NSSpeechRecognitionUsageDescription',

View file

@ -4,12 +4,14 @@
import assert from 'assert';
import {act, renderHook} from '@testing-library/react-hooks';
import {createIntl} from 'react-intl';
import InCallManager from 'react-native-incall-manager';
import * as CallsActions from '@calls/actions';
import {getConnectionForTesting} from '@calls/actions/calls';
import * as Permissions from '@calls/actions/permissions';
import {needsRecordingWillBePostedAlert, needsRecordingErrorAlert} from '@calls/alerts';
import {userLeftChannelErr, userRemovedFromChannelErr} from '@calls/errors';
import * as State from '@calls/state';
import {
myselfLeftCall,
@ -32,6 +34,7 @@ import {
DefaultCallsConfig,
DefaultCallsState,
} from '@calls/types/calls';
import {errorAlert} from '@calls/utils';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
@ -71,8 +74,8 @@ const mockClient = {
};
jest.mock('@calls/connection/connection', () => ({
newConnection: jest.fn(() => Promise.resolve({
disconnect: jest.fn(),
newConnection: jest.fn((serverURL, channelId, onClose) => Promise.resolve({
disconnect: jest.fn((err?: Error) => onClose(err)),
mute: jest.fn(),
unmute: jest.fn(),
waitForPeerConnection: jest.fn(() => Promise.resolve()),
@ -89,7 +92,17 @@ jest.mock('@queries/servers/thread', () => ({
})),
}));
jest.mock('@calls/alerts');
jest.mock('@calls/alerts', () => {
const alerts = jest.requireActual('../alerts');
return {
needsRecordingErrorAlert: jest.fn(),
needsRecordingWillBePostedAlert: jest.fn(),
showErrorAlertOnClose: alerts.showErrorAlertOnClose,
};
});
jest.mock('@calls/utils');
jest.mock('react-native-navigation', () => ({
Navigation: {
pop: jest.fn(() => Promise.resolve({
@ -174,7 +187,10 @@ describe('Actions.Calls', () => {
let response: { data?: string };
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true);
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true, createIntl({
locale: 'en',
messages: {},
}));
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
@ -201,7 +217,10 @@ describe('Actions.Calls', () => {
let response: { data?: string };
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true);
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true, createIntl({
locale: 'en',
messages: {},
}));
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
@ -235,7 +254,10 @@ describe('Actions.Calls', () => {
let response: { data?: string };
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true);
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true, createIntl({
locale: 'en',
messages: {},
}));
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
@ -265,7 +287,10 @@ describe('Actions.Calls', () => {
let response: { data?: string };
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'mysUserId', true);
response = await CallsActions.joinCall('server1', 'channel-id', 'mysUserId', true, createIntl({
locale: 'en',
messages: {},
}));
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
@ -396,4 +421,125 @@ describe('Actions.Calls', () => {
expect(mockClient.dismissCall).toBeCalledWith('channel-id');
});
it('userLeftChannelErr', async () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
});
addFakeCall('server1', 'channel-id');
let response: { data?: string };
const intl = createIntl({
locale: 'en',
messages: {},
});
intl.formatMessage = jest.fn();
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true, intl);
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
});
assert.equal(response!.data, 'channel-id');
assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id');
expect(newConnection).toBeCalled();
expect(newConnection.mock.calls[0][1]).toBe('channel-id');
expect(updateThreadFollowing).toBeCalled();
await act(async () => {
CallsActions.leaveCall(userLeftChannelErr);
});
expect(intl.formatMessage).toBeCalledWith({
id: 'mobile.calls_user_left_channel_error_title',
defaultMessage: 'You left the channel',
});
expect(intl.formatMessage).toBeCalledWith({
id: 'mobile.calls_user_left_channel_error_message',
defaultMessage: 'You have left the channel, and have been disconnected from the call.',
});
});
it('userRemovedFromChannelErr', async () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
});
addFakeCall('server1', 'channel-id');
let response: { data?: string };
const intl = createIntl({
locale: 'en',
messages: {},
});
intl.formatMessage = jest.fn();
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true, intl);
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
});
assert.equal(response!.data, 'channel-id');
assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id');
expect(newConnection).toBeCalled();
expect(newConnection.mock.calls[0][1]).toBe('channel-id');
expect(updateThreadFollowing).toBeCalled();
await act(async () => {
CallsActions.leaveCall(userRemovedFromChannelErr);
});
expect(intl.formatMessage).toBeCalledWith({
id: 'mobile.calls_user_removed_from_channel_error_title',
defaultMessage: 'You were removed from channel',
});
expect(intl.formatMessage).toBeCalledWith({
id: 'mobile.calls_user_removed_from_channel_error_message',
defaultMessage: 'You have been removed from the channel, and have been disconnected from the call.',
});
});
it('generic error on close', async () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
});
addFakeCall('server1', 'channel-id');
let response: { data?: string };
const intl = createIntl({
locale: 'en',
messages: {},
});
intl.formatMessage = jest.fn();
await act(async () => {
response = await CallsActions.joinCall('server1', 'channel-id', 'myUserId', true, intl);
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
});
assert.equal(response!.data, 'channel-id');
assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id');
expect(newConnection).toBeCalled();
expect(newConnection.mock.calls[0][1]).toBe('channel-id');
expect(updateThreadFollowing).toBeCalled();
await act(async () => {
CallsActions.leaveCall(new Error('generic error'));
});
expect(errorAlert).toBeCalled();
});
});

View file

@ -7,7 +7,12 @@ import InCallManager from 'react-native-incall-manager';
import {forceLogoutIfNecessary} from '@actions/remote/session';
import {updateThreadFollowing} from '@actions/remote/thread';
import {fetchUsersByIds} from '@actions/remote/user';
import {leaveAndJoinWithAlert, needsRecordingErrorAlert, needsRecordingWillBePostedAlert} from '@calls/alerts';
import {
leaveAndJoinWithAlert,
needsRecordingErrorAlert,
needsRecordingWillBePostedAlert,
showErrorAlertOnClose,
} from '@calls/alerts';
import {
getCallsConfig,
getCallsState,
@ -230,6 +235,7 @@ export const joinCall = async (
channelId: string,
userId: string,
hasMicPermission: boolean,
intl: IntlShape,
title?: string,
rootId?: string,
): Promise<{ error?: unknown; data?: string }> => {
@ -248,8 +254,12 @@ export const joinCall = async (
newCurrentCall(serverUrl, channelId, userId);
try {
connection = await newConnection(serverUrl, channelId, () => {
connection = await newConnection(serverUrl, channelId, (err?: Error) => {
myselfLeftCall();
if (err) {
logDebug('calls: error on close', getFullErrorMessage(err));
showErrorAlertOnClose(err, intl);
}
}, setScreenShareURL, hasMicPermission, title, rootId);
} catch (error) {
await forceLogoutIfNecessary(serverUrl, error);
@ -285,9 +295,9 @@ export const joinCall = async (
}
};
export const leaveCall = () => {
export const leaveCall = (err?: Error) => {
if (connection) {
connection.disconnect();
connection.disconnect(err);
connection = null;
}
};

View file

@ -6,6 +6,7 @@ import {Alert} from 'react-native';
import {hasMicrophonePermission, joinCall, leaveCall, unmuteMyself} from '@calls/actions';
import {dismissIncomingCall} from '@calls/actions/calls';
import {hasBluetoothPermission} from '@calls/actions/permissions';
import {userLeftChannelErr, userRemovedFromChannelErr} from '@calls/errors';
import {
getCallsConfig,
getCallsState,
@ -19,6 +20,7 @@ import DatabaseManager from '@database/manager';
import {getChannelById} from '@queries/servers/channel';
import {getCurrentUser} from '@queries/servers/user';
import {isDMorGM} from '@utils/channel';
import {getFullErrorMessage} from '@utils/errors';
import {logError} from '@utils/log';
import {isSystemAdmin} from '@utils/user';
@ -208,7 +210,7 @@ const doJoinCall = async (
removeIncomingCall(serverUrl, callId, channelId);
}
const res = await joinCall(serverUrl, channelId, user.id, hasPermission, title, rootId);
const res = await joinCall(serverUrl, channelId, user.id, hasPermission, intl, title, rootId);
if (res.error) {
const seeLogs = formatMessage({id: 'mobile.calls_see_logs', defaultMessage: 'See server logs'});
errorAlert(res.error?.toString() || seeLogs, intl);
@ -384,3 +386,35 @@ export const recordingErrorAlert = (intl: IntlShape) => {
}],
);
};
export const showErrorAlertOnClose = (err: Error, intl: IntlShape) => {
switch (err) {
case userLeftChannelErr:
Alert.alert(
intl.formatMessage({
id: 'mobile.calls_user_left_channel_error_title',
defaultMessage: 'You left the channel',
}),
intl.formatMessage({
id: 'mobile.calls_user_left_channel_error_message',
defaultMessage: 'You have left the channel, and have been disconnected from the call.',
}),
);
break;
case userRemovedFromChannelErr:
Alert.alert(
intl.formatMessage({
id: 'mobile.calls_user_removed_from_channel_error_title',
defaultMessage: 'You were removed from channel',
}),
intl.formatMessage({
id: 'mobile.calls_user_removed_from_channel_error_message',
defaultMessage: 'You have been removed from the channel, and have been disconnected from the call.',
}),
);
break;
default:
// Fallback with generic error
errorAlert(getFullErrorMessage(err, intl), intl);
}
};

View file

@ -39,7 +39,7 @@ if (Platform.OS === 'android') {
export async function newConnection(
serverUrl: string,
channelID: string,
closeCb: () => void,
closeCb: (err?: Error) => void,
setScreenShareURL: (url: string) => void,
hasMicPermission: boolean,
title?: string,
@ -93,7 +93,7 @@ export async function newConnection(
initializeVoiceTrack();
}
const disconnect = () => {
const disconnect = (err?: Error) => {
if (isClosed) {
return;
}
@ -126,7 +126,7 @@ export async function newConnection(
}
if (closeCb) {
closeCb();
closeCb(err);
}
};

View file

@ -0,0 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const userRemovedFromChannelErr = new Error('user was removed from channel');
export const userLeftChannelErr = new Error('user has left channel');

View file

@ -124,7 +124,7 @@ export type CallSession = {
export type ChannelsWithCalls = Dictionary<boolean>;
export type CallsConnection = {
disconnect: () => void;
disconnect: (err?: Error) => void;
mute: () => void;
unmute: () => void;
waitForPeerConnection: () => Promise<void>;

View file

@ -219,7 +219,8 @@ const FilteredList = ({
/>
);
}
if ('teamId' in item || 'team_id' in item) {
if ('teamId' in item) {
return (
<ChannelItem
channel={item}
@ -230,7 +231,9 @@ const FilteredList = ({
testID='find_channels.filtered_list.channel_item'
/>
);
} else if ('username' in item) {
}
if ('username' in item) {
return (
<UserItem
onUserPress={onOpenDirectMessage}
@ -244,6 +247,7 @@ const FilteredList = ({
return (
<ChannelItem
channel={item}
isOnCenterBg={true}
onPress={onJoinChannel}
showTeamName={showTeamName}
shouldHighlightState={true}

View file

@ -166,6 +166,9 @@ const Servers = React.forwardRef<ServersRef>((_, ref) => {
onPress={onPress}
style={styles.icon}
testID={'channel_list.servers.server_icon'}
badgeBorderColor={theme.sidebarBg}
badgeBackgroundColor={theme.mentionBg}
badgeColor={theme.mentionColor}
/>
);
});

View file

@ -5,7 +5,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {Platform, useWindowDimensions, View, type LayoutChangeEvent} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {Navigation} from 'react-native-navigation';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Animated, {ReduceMotion, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
import FormattedText from '@components/formatted_text';
@ -134,7 +134,7 @@ const LoginOptions = ({
const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350;
return {
transform: [{translateX: withTiming(translateX.value, {duration})}],
transform: [{translateX: withTiming(translateX.value, {duration, reduceMotion: ReduceMotion.Never})}],
};
}, []);

View file

@ -14,7 +14,7 @@ import {
BackHandler,
} from 'react-native';
import {Navigation} from 'react-native-navigation';
import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';
import Animated, {ReduceMotion, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';
import {storeOnboardingViewedValue} from '@actions/app/global';
import {Screens} from '@constants';
@ -108,7 +108,7 @@ const Onboarding = ({
const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350;
return {
transform: [{translateX: withTiming(translateX.value, {duration})}],
transform: [{translateX: withTiming(translateX.value, {duration, reduceMotion: ReduceMotion.Never})}],
};
}, []);

View file

@ -7,7 +7,7 @@ import {useIntl} from 'react-intl';
import {Alert, BackHandler, Platform, useWindowDimensions, View} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {Navigation} from 'react-native-navigation';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Animated, {ReduceMotion, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
import {doPing} from '@actions/remote/general';
@ -350,7 +350,7 @@ const Server = ({
const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350;
return {
transform: [{translateX: withTiming(translateX.value, {duration})}],
transform: [{translateX: withTiming(translateX.value, {duration, reduceMotion: ReduceMotion.Never})}],
};
}, []);

View file

@ -42,7 +42,6 @@ const DisplayTheme = ({allowedThemeKeys, componentId, currentTeamId, currentUser
const setThemePreference = useCallback(() => {
const allowedTheme = allowedThemeKeys.find((tk) => tk === newTheme);
const themeJson = Preferences.THEMES[allowedTheme as ThemeKey] || initialTheme;
const pref: PreferenceType = {
@ -70,7 +69,7 @@ const DisplayTheme = ({allowedThemeKeys, componentId, currentTeamId, currentUser
/>
{initialTheme.type === 'custom' && (
<CustomTheme
setTheme={setThemePreference}
setTheme={setNewTheme}
displayTheme={initialTheme.type}
/>
)}

View file

@ -782,7 +782,7 @@
"notification_settings.mentions": "Erwähnungen",
"notification_settings.mentions..keywordsDescription": "Andere Wörter, die eine Erwähnung auslösen",
"notification_settings.mentions.channelWide": "Kanalweite Erwähnungen",
"notification_settings.mentions.keywords": "Stichwörter",
"notification_settings.mentions.keywords": "Andere Schlüsselwörter eingeben",
"notification_settings.mentions.keywordsLabel": "Bei den Schlüsselwörtern wird nicht zwischen Groß- und Kleinschreibung unterschieden. Trenne die Schlüsselwörter mit Kommas.",
"notification_settings.mentions.keywords_mention": "Schlüsselwörter, die Erwähnungen auslösen",
"notification_settings.mentions.sensitiveName": "Dein schreibweisenabhängiger Vorname",

View file

@ -517,6 +517,10 @@
"mobile.calls_tablet": "Tablet",
"mobile.calls_thread": "Thread",
"mobile.calls_unmute": "Unmute",
"mobile.calls_user_left_channel_error_message": "You have left the channel, and have been disconnected from the call.",
"mobile.calls_user_left_channel_error_title": "You left the channel",
"mobile.calls_user_removed_from_channel_error_message": "You have been removed from the channel, and have been disconnected from the call.",
"mobile.calls_user_removed_from_channel_error_title": "You were removed from channel",
"mobile.calls_viewing_screen": "You are viewing {name}'s screen",
"mobile.calls_you": "(you)",
"mobile.calls_you_2": "You",
@ -582,13 +586,13 @@
"mobile.ios.plist.NSAppleMusicUsageDescription": "Enabling access to your media library means you can attach files from your media library to your messages in {applicationName}.",
"mobile.ios.plist.NSBluetoothAlwaysUsageDescription": "Enabling access to Bluetooth means we can synchronize content across your devices and clients.",
"mobile.ios.plist.NSBluetoothPeripheralUsageDescription": "Enabling access to Bluetooth means we can connect to audio peripherals for calls, and synchronize content across your devices and clients.",
"mobile.ios.plist.NSCameraUsageDescription": "Enabling access to your device cameras means you can take photos or videos and upload them to {applicationName}.",
"mobile.ios.plist.NSCameraUsageDescription": "Allowing access to your camera enables you to take photos or videos and attach them to messages.",
"mobile.ios.plist.NSFaceIDUsageDescription": "Enabling access to your Face ID means we can restrict unauthorized users from accessing {applicationName} on your device.",
"mobile.ios.plist.NSLocationAlwaysUsageDescription": "Enabling access to your location data means we can add location metadata to pictures and videos you share in {applicationName}.",
"mobile.ios.plist.NSLocationWhenInUseUsageDescription": "Enabling access to your location data means we can add location metadata to pictures and videos you share in {applicationName}.",
"mobile.ios.plist.NSMicrophoneUsageDescription": "Enabling access to your device's microphones means you can capture audio for calls or videos to share in {applicationName}.",
"mobile.ios.plist.NSPhotoLibraryAddUsageDescription": "Enabling write access to your photo library means you can save downloaded photos and videos from {applicationName} to your device.",
"mobile.ios.plist.NSPhotoLibraryUsageDescription": "Enabling read access to your photo library means you can upload photos and videos from your device to {applicationName}.",
"mobile.ios.plist.NSPhotoLibraryUsageDescription": "Allowing access to your photo library enables you to select photos or videos and attach them to messages.",
"mobile.ios.plist.NSSpeechRecognitionUsageDescription": "Enabling your device to send user data to Apple means you can send voice messages to {applicationName}.",
"mobile.join_channel.error": "We couldn't join the channel {displayName}.",
"mobile.leave_and_join_confirmation": "Leave & Join",

View file

@ -782,7 +782,7 @@
"notification_settings.mentions": "Wzmianki",
"notification_settings.mentions..keywordsDescription": "Inne słowa, które uruchamiają wzmianki",
"notification_settings.mentions.channelWide": "Wzmianki na kanale",
"notification_settings.mentions.keywords": "Słowa kluczowe",
"notification_settings.mentions.keywords": "Wprowadź inne słowa kluczowe",
"notification_settings.mentions.keywordsLabel": "W słowach kluczowych nie jest rozróżniana wielkość liter. Słowa kluczowe należy oddzielać przecinkami.",
"notification_settings.mentions.keywords_mention": "Słowa kluczowe, które wywołują wzmianki",
"notification_settings.mentions.sensitiveName": "Twoje imię z rozróżnianiem wielkich i małych liter",

View file

@ -241,7 +241,7 @@
"connection_banner.not_connected": "Нет соединения с интернетом",
"connection_banner.not_reachable": "Сервер недоступен",
"create_direct_message.title": "Создать личное сообщение",
"create_post.deactivated": "Вы просматриваете архивированный канал как деактивированный пользователь.",
"create_post.deactivated": "Вы просматриваете архивированный канал с деактивированным пользователем.",
"create_post.thread_reply": "Ответить в этом обсуждении...",
"create_post.write": "Написать в {channelDisplayName}",
"custom_status.expiry.at": "в",
@ -466,6 +466,8 @@
"mobile.calls_host_rec_stopped": "Вы можете найти запись в чате этого звонка после завершения его обработки.",
"mobile.calls_host_rec_stopped_title": "Запись остановлена. Обработка...",
"mobile.calls_host_rec_title": "Вы записываете",
"mobile.calls_host_transcription": "Подумайте о том, чтобы сообщить всем, что это собрание будет записываться и расшифровываться (транскрибироваться).",
"mobile.calls_host_transcription_title": "Запись и расшифровка уже начались",
"mobile.calls_incoming_dm": "<b>{name}</b> приглашает вас на звонок",
"mobile.calls_incoming_gm": "<b>{name}</b> приглашает Вас на разговор с <b>{num, plural, one {# одним участником} few {# несколькими участниками} other {# несколькими участниками}}</b>",
"mobile.calls_join_call": "Присоединиться к звонку",
@ -492,6 +494,8 @@
"mobile.calls_participant_limit_title_GA": "Этот звонок находится на пределе возможностей",
"mobile.calls_participant_rec": "Ведущий начал запись этой встречи. Оставаясь на встрече, вы даете согласие на запись.",
"mobile.calls_participant_rec_title": "Идет запись",
"mobile.calls_participant_transcription": "Ведущий начал запись и расшифровку этой встречи. Оставаясь на встрече, вы даете согласие на запись и расшифровку.",
"mobile.calls_participant_transcription_title": "Запись и расшифровка в процессе",
"mobile.calls_phone": "Телефон",
"mobile.calls_quality_warning": "Качество звонков может ухудшиться из-за нестабильной работы сети.",
"mobile.calls_raise_hand": "Поднять руку",

View file

@ -782,7 +782,7 @@
"notification_settings.mentions": "提及",
"notification_settings.mentions..keywordsDescription": "其他触发提及的词语",
"notification_settings.mentions.channelWide": "频道范围的提及",
"notification_settings.mentions.keywords": "关键词",
"notification_settings.mentions.keywords": "输入其他关键词",
"notification_settings.mentions.keywordsLabel": "关键词大小写不区分。用逗号分隔关键词。",
"notification_settings.mentions.keywords_mention": "触发提及的关键词",
"notification_settings.mentions.sensitiveName": "您的大小写区分名",

View file

@ -1,3 +1,9 @@
GIT
remote: https://github.com/jonathanneilritchie/fastlane-plugin-find_replace_string
revision: 1b71026318ad40abc710d601be48d67bcb353ddf
specs:
fastlane-plugin-find_replace_string (0.1.0)
GEM
remote: https://rubygems.org/
specs:
@ -108,7 +114,6 @@ GEM
fastlane-plugin-android_change_package_identifier (0.1.0)
fastlane-plugin-android_change_string_app_name (0.1.1)
nokogiri
fastlane-plugin-find_replace_string (0.1.0)
fastlane-plugin-versioning_android (0.1.1)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.53.0)
@ -224,7 +229,7 @@ DEPENDENCIES
fastlane
fastlane-plugin-android_change_package_identifier
fastlane-plugin-android_change_string_app_name
fastlane-plugin-find_replace_string
fastlane-plugin-find_replace_string!
fastlane-plugin-versioning_android
nokogiri

View file

@ -1931,7 +1931,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 507;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@ -1975,7 +1975,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 507;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@ -2118,7 +2118,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 507;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -2167,7 +2167,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 501;
CURRENT_PROJECT_VERSION = 507;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;

View file

@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.12.0</string>
<string>2.13.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>501</string>
<string>507</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@ -47,7 +47,7 @@
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
@ -58,7 +58,7 @@
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Enabling access to Bluetooth means we can connect to audio peripherals for calls, and synchronize content across your devices and clients.</string>
<key>NSCameraUsageDescription</key>
<string>Enabling access to your device cameras means you can take photos or videos and upload them to $(PRODUCT_NAME).</string>
<string>Allowing access to your camera enables you to take photos or videos and attach them to messages.</string>
<key>NSFaceIDUsageDescription</key>
<string>Enabling access to your Face ID means we can restrict unauthorized users from accessing $(PRODUCT_NAME) on your device.</string>
<key>NSLocationAlwaysUsageDescription</key>
@ -70,7 +70,7 @@
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Enabling write access to your photo library means you can save downloaded photos and videos from $(PRODUCT_NAME) to your device.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Enabling read access to your photo library means you can upload photos and videos from your device to $(PRODUCT_NAME).</string>
<string>Allowing access to your photo library enables you to select photos or videos and attach them to messages.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Enabling your device to send user data to Apple means you can send voice messages to $(PRODUCT_NAME).</string>
<key>NSUserActivityTypes</key>

View file

@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2.12.0</string>
<string>2.13.0</string>
<key>CFBundleVersion</key>
<string>501</string>
<string>507</string>
<key>UIAppFonts</key>
<array>
<string>OpenSans-Bold.ttf</string>

View file

@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2.12.0</string>
<string>2.13.0</string>
<key>CFBundleVersion</key>
<string>501</string>
<string>507</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "mattermost-mobile",
"version": "2.11.0",
"version": "2.13.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mattermost-mobile",
"version": "2.11.0",
"version": "2.12.0",
"hasInstallScript": true,
"license": "Apache 2.0",
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "2.12.0",
"version": "2.13.0",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",

View file

@ -0,0 +1,40 @@
diff --git a/node_modules/@gorhom/bottom-sheet/src/hooks/useBottomSheetTimingConfigs.ts b/node_modules/@gorhom/bottom-sheet/src/hooks/useBottomSheetTimingConfigs.ts
index 9d2f61d..ac91e40 100644
--- a/node_modules/@gorhom/bottom-sheet/src/hooks/useBottomSheetTimingConfigs.ts
+++ b/node_modules/@gorhom/bottom-sheet/src/hooks/useBottomSheetTimingConfigs.ts
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
-import type { WithTimingConfig } from 'react-native-reanimated';
+import { ReduceMotion, type WithTimingConfig } from 'react-native-reanimated';
import { ANIMATION_DURATION, ANIMATION_EASING } from '../constants';
/**
@@ -14,6 +14,7 @@ export const useBottomSheetTimingConfigs = (configs: WithTimingConfig) => {
const _configs: WithTimingConfig = {
easing: configs.easing || ANIMATION_EASING,
duration: configs.duration || ANIMATION_DURATION,
+ reduceMotion: ReduceMotion.Never,
};
return _configs;
diff --git a/node_modules/@gorhom/bottom-sheet/src/utilities/animate.ts b/node_modules/@gorhom/bottom-sheet/src/utilities/animate.ts
index 0ce4c9a..c01a069 100644
--- a/node_modules/@gorhom/bottom-sheet/src/utilities/animate.ts
+++ b/node_modules/@gorhom/bottom-sheet/src/utilities/animate.ts
@@ -4,6 +4,7 @@ import {
withTiming,
withSpring,
AnimationCallback,
+ ReduceMotion,
} from 'react-native-reanimated';
import { ANIMATION_CONFIGS, ANIMATION_METHOD } from '../constants';
@@ -26,6 +27,8 @@ export const animate = ({
configs = ANIMATION_CONFIGS;
}
+ configs = {...configs, reduceMotion: ReduceMotion.Never};
+
// detect animation type
const type =
'duration' in configs || 'easing' in configs