Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
954188b3e9 | ||
|
|
acc34b3121 | ||
|
|
ebba06a916 | ||
|
|
cf345808a5 | ||
|
|
1c3c925a53 | ||
|
|
340759896e | ||
|
|
305e0e4fde | ||
|
|
2f35a79322 | ||
|
|
fd7bada58e | ||
|
|
4599f41c7a | ||
|
|
62dd814a6d | ||
|
|
35c064ff9a |
29 changed files with 328 additions and 56 deletions
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
5
app/products/calls/errors.ts
Normal file
5
app/products/calls/errors.ts
Normal 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');
|
||||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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})}],
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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})}],
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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})}],
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": "Поднять руку",
|
||||
|
|
|
|||
|
|
@ -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": "您的大小写区分名",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
4
package-lock.json
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
40
patches/@gorhom+bottom-sheet+4.5.1.patch
Normal file
40
patches/@gorhom+bottom-sheet+4.5.1.patch
Normal 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
|
||||
Loading…
Reference in a new issue