[FIX] Jump to message stuck on loading animation (#4410)
This commit is contained in:
parent
e723990e82
commit
17be449d4e
|
@ -1,73 +0,0 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { Modal, StyleSheet, View, PixelRatio } from 'react-native';
|
|
||||||
import Animated, {
|
|
||||||
cancelAnimation,
|
|
||||||
Extrapolate,
|
|
||||||
interpolate,
|
|
||||||
useAnimatedStyle,
|
|
||||||
useSharedValue,
|
|
||||||
withRepeat,
|
|
||||||
withSequence,
|
|
||||||
withTiming
|
|
||||||
} from 'react-native-reanimated';
|
|
||||||
|
|
||||||
import { useTheme } from '../theme';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
width: PixelRatio.get() * 40,
|
|
||||||
height: PixelRatio.get() * 40,
|
|
||||||
resizeMode: 'contain'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface ILoadingProps {
|
|
||||||
visible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Loading = ({ visible }: ILoadingProps): React.ReactElement => {
|
|
||||||
const opacity = useSharedValue(0);
|
|
||||||
const scale = useSharedValue(1);
|
|
||||||
const { colors } = useTheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
opacity.value = withTiming(1, {
|
|
||||||
duration: 200
|
|
||||||
});
|
|
||||||
scale.value = withRepeat(withSequence(withTiming(0, { duration: 1000 }), withTiming(1, { duration: 1000 })), -1);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
cancelAnimation(scale);
|
|
||||||
};
|
|
||||||
}, [opacity, scale, visible]);
|
|
||||||
|
|
||||||
const animatedOpacity = useAnimatedStyle(() => ({
|
|
||||||
opacity: interpolate(opacity.value, [0, 1], [0, colors.backdropOpacity], Extrapolate.CLAMP)
|
|
||||||
}));
|
|
||||||
const animatedScale = useAnimatedStyle(() => ({ transform: [{ scale: interpolate(scale.value, [0, 0.5, 1], [1, 1.1, 1]) }] }));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal visible={visible} transparent onRequestClose={() => {}}>
|
|
||||||
<View style={styles.container} testID='loading'>
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
...StyleSheet.absoluteFillObject,
|
|
||||||
backgroundColor: colors.backdropColor
|
|
||||||
},
|
|
||||||
animatedOpacity
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Animated.Image source={require('../static/images/logo.png')} style={[styles.image, animatedScale]} />
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Loading;
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { act, fireEvent, render, waitFor } from '@testing-library/react-native';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Loading, { sendLoadingEvent, LOADING_BUTTON_TEST_ID, LOADING_IMAGE_TEST_ID, LOADING_TEST_ID } from '.';
|
||||||
|
|
||||||
|
const Render = () => <Loading />;
|
||||||
|
|
||||||
|
const getByTestIdAndThrow = (fn: Function, testID: string) =>
|
||||||
|
expect(() => fn(testID)).toThrow(`Unable to find an element with testID: ${testID}`);
|
||||||
|
|
||||||
|
describe('Loading', () => {
|
||||||
|
it('starts invisible and shows/hides when event is received', async () => {
|
||||||
|
const { getByTestId } = render(<Render />);
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
// receive event and expect loading to be rendered
|
||||||
|
act(() => sendLoadingEvent({ visible: true }));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(() => getByTestId(LOADING_TEST_ID));
|
||||||
|
});
|
||||||
|
expect(() => getByTestId(LOADING_IMAGE_TEST_ID));
|
||||||
|
// receive event and expect loading not to be rendered
|
||||||
|
act(() => sendLoadingEvent({ visible: false }));
|
||||||
|
await waitFor(() => {
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doesnt have onCancel and doesnt hide when pressed', async () => {
|
||||||
|
const { getByTestId } = render(<Render />);
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
act(() => sendLoadingEvent({ visible: true }));
|
||||||
|
expect(() => getByTestId(LOADING_TEST_ID));
|
||||||
|
fireEvent.press(getByTestId(LOADING_BUTTON_TEST_ID));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(() => getByTestId(LOADING_TEST_ID));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has onCancel and hides when pressed', async () => {
|
||||||
|
const mockFn = jest.fn();
|
||||||
|
const { getByTestId } = render(<Render />);
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
act(() => sendLoadingEvent({ visible: true, onCancel: mockFn }));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(() => getByTestId(LOADING_TEST_ID));
|
||||||
|
});
|
||||||
|
fireEvent.press(getByTestId(LOADING_BUTTON_TEST_ID));
|
||||||
|
await waitFor(() => {
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
});
|
||||||
|
expect(mockFn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('asserts onCancel return', async () => {
|
||||||
|
const mockFn = jest.fn();
|
||||||
|
const mockFn2 = jest.fn(() => 'test');
|
||||||
|
const { getByTestId } = render(<Render />);
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
act(() => sendLoadingEvent({ visible: true, onCancel: mockFn }));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(() => getByTestId(LOADING_TEST_ID));
|
||||||
|
});
|
||||||
|
act(() => sendLoadingEvent({ visible: true, onCancel: mockFn2 }));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(() => getByTestId(LOADING_TEST_ID));
|
||||||
|
});
|
||||||
|
fireEvent.press(getByTestId(LOADING_BUTTON_TEST_ID));
|
||||||
|
await waitFor(() => {
|
||||||
|
getByTestIdAndThrow(getByTestId, LOADING_TEST_ID);
|
||||||
|
});
|
||||||
|
expect(mockFn).not.toHaveBeenCalled();
|
||||||
|
expect(mockFn2).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,128 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { StyleSheet, View, PixelRatio, TouchableWithoutFeedback } from 'react-native';
|
||||||
|
import Animated, {
|
||||||
|
cancelAnimation,
|
||||||
|
Extrapolate,
|
||||||
|
interpolate,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withRepeat,
|
||||||
|
withSequence,
|
||||||
|
withTiming
|
||||||
|
} from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
|
|
||||||
|
const LOADING_EVENT = 'LOADING_EVENT';
|
||||||
|
export const LOADING_TEST_ID = 'loading';
|
||||||
|
export const LOADING_BUTTON_TEST_ID = 'loading-button';
|
||||||
|
export const LOADING_IMAGE_TEST_ID = 'loading-image';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: PixelRatio.get() * 40,
|
||||||
|
height: PixelRatio.get() * 40,
|
||||||
|
resizeMode: 'contain'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ILoadingEvent {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel?: null | Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendLoadingEvent = ({ visible, onCancel }: ILoadingEvent): void =>
|
||||||
|
EventEmitter.emit(LOADING_EVENT, { visible, onCancel });
|
||||||
|
|
||||||
|
const Loading = (): React.ReactElement | null => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [onCancel, setOnCancel] = useState<null | Function>(null);
|
||||||
|
const opacity = useSharedValue(0);
|
||||||
|
const scale = useSharedValue(1);
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
const onEventReceived = ({ visible: _visible, onCancel: _onCancel = null }: ILoadingEvent) => {
|
||||||
|
if (_visible) {
|
||||||
|
// if it's already visible, ignore it
|
||||||
|
if (!visible) {
|
||||||
|
setVisible(_visible);
|
||||||
|
opacity.value = 0;
|
||||||
|
scale.value = 1;
|
||||||
|
opacity.value = withTiming(1, {
|
||||||
|
// 300ms doens't work on expensive navigation animations, like jump to message
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
scale.value = withRepeat(withSequence(withTiming(0, { duration: 1000 }), withTiming(1, { duration: 1000 })), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allows to override the onCancel function
|
||||||
|
if (_onCancel) {
|
||||||
|
setOnCancel(() => () => _onCancel());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setVisible(false);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = EventEmitter.addEventListener(LOADING_EVENT, onEventReceived);
|
||||||
|
|
||||||
|
return () => EventEmitter.removeListener(LOADING_EVENT, listener);
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
cancelAnimation(scale);
|
||||||
|
cancelAnimation(opacity);
|
||||||
|
setVisible(false);
|
||||||
|
setOnCancel(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancelHandler = () => {
|
||||||
|
if (!onCancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onCancel();
|
||||||
|
setVisible(false);
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const animatedOpacity = useAnimatedStyle(() => ({
|
||||||
|
opacity: interpolate(opacity.value, [0, 1], [0, colors.backdropOpacity], Extrapolate.CLAMP)
|
||||||
|
}));
|
||||||
|
const animatedScale = useAnimatedStyle(() => ({ transform: [{ scale: interpolate(scale.value, [0, 0.5, 1], [1, 1.1, 1]) }] }));
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={StyleSheet.absoluteFill} testID={LOADING_TEST_ID}>
|
||||||
|
<TouchableWithoutFeedback onPress={() => onCancelHandler()} testID={LOADING_BUTTON_TEST_ID}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
backgroundColor: colors.backdropColor
|
||||||
|
},
|
||||||
|
animatedOpacity
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Animated.Image
|
||||||
|
source={require('../../static/images/logo.png')}
|
||||||
|
style={[styles.image, animatedScale]}
|
||||||
|
testID={LOADING_IMAGE_TEST_ID}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loading;
|
|
@ -14,6 +14,7 @@ import { ActionSheetProvider } from './containers/ActionSheet';
|
||||||
import InAppNotification from './containers/InAppNotification';
|
import InAppNotification from './containers/InAppNotification';
|
||||||
import Toast from './containers/Toast';
|
import Toast from './containers/Toast';
|
||||||
import TwoFactor from './containers/TwoFactor';
|
import TwoFactor from './containers/TwoFactor';
|
||||||
|
import Loading from './containers/Loading';
|
||||||
import { ICommand } from './definitions/ICommand';
|
import { ICommand } from './definitions/ICommand';
|
||||||
import { IThemePreference } from './definitions/ITheme';
|
import { IThemePreference } from './definitions/ITheme';
|
||||||
import { DimensionsContext } from './dimensions';
|
import { DimensionsContext } from './dimensions';
|
||||||
|
@ -241,6 +242,7 @@ export default class Root extends React.Component<{}, IState> {
|
||||||
<ChangePasscodeView />
|
<ChangePasscodeView />
|
||||||
<InAppNotification />
|
<InAppNotification />
|
||||||
<Toast />
|
<Toast />
|
||||||
|
<Loading />
|
||||||
</ActionSheetProvider>
|
</ActionSheetProvider>
|
||||||
</GestureHandlerRootView>
|
</GestureHandlerRootView>
|
||||||
</DimensionsContext.Provider>
|
</DimensionsContext.Provider>
|
||||||
|
|
|
@ -10,6 +10,7 @@ type TEventEmitterEmmitArgs =
|
||||||
| { invalid: boolean }
|
| { invalid: boolean }
|
||||||
| { force: boolean }
|
| { force: boolean }
|
||||||
| { hasBiometry: boolean }
|
| { hasBiometry: boolean }
|
||||||
|
| { visible: boolean; onCancel?: null | Function }
|
||||||
| { event: string | ICommand }
|
| { event: string | ICommand }
|
||||||
| { cancel: () => void }
|
| { cancel: () => void }
|
||||||
| { submit: (param: string) => void }
|
| { submit: (param: string) => void }
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AuthLoadingView from './views/AuthLoadingView';
|
||||||
import { DimensionsContext } from './dimensions';
|
import { DimensionsContext } from './dimensions';
|
||||||
import { ShareInsideStackParamList, ShareOutsideStackParamList, ShareAppStackParamList } from './definitions/navigationTypes';
|
import { ShareInsideStackParamList, ShareOutsideStackParamList, ShareAppStackParamList } from './definitions/navigationTypes';
|
||||||
import { colors, CURRENT_SERVER } from './lib/constants';
|
import { colors, CURRENT_SERVER } from './lib/constants';
|
||||||
|
import Loading from './containers/Loading';
|
||||||
|
|
||||||
initStore(store);
|
initStore(store);
|
||||||
|
|
||||||
|
@ -131,6 +132,7 @@ const Root = (): React.ReactElement => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<App root={root} />
|
<App root={root} />
|
||||||
|
<Loading />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
<ScreenLockedView />
|
<ScreenLockedView />
|
||||||
</DimensionsContext.Provider>
|
</DimensionsContext.Provider>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import StatusBar from '../containers/StatusBar';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { TSupportedThemes, withTheme } from '../theme';
|
import { TSupportedThemes, withTheme } from '../theme';
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import Loading from '../containers/Loading';
|
import { sendLoadingEvent } from '../containers/Loading';
|
||||||
import { animateNextTransition } from '../lib/methods/helpers/layoutAnimation';
|
import { animateNextTransition } from '../lib/methods/helpers/layoutAnimation';
|
||||||
import { goRoom } from '../lib/methods/helpers/goRoom';
|
import { goRoom } from '../lib/methods/helpers/goRoom';
|
||||||
import { showErrorAlert } from '../lib/methods/helpers/info';
|
import { showErrorAlert } from '../lib/methods/helpers/info';
|
||||||
|
@ -28,7 +28,6 @@ interface IAddExistingChannelViewState {
|
||||||
search: TSubscriptionModel[];
|
search: TSubscriptionModel[];
|
||||||
channels: TSubscriptionModel[];
|
channels: TSubscriptionModel[];
|
||||||
selected: string[];
|
selected: string[];
|
||||||
loading: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAddExistingChannelViewProps {
|
interface IAddExistingChannelViewProps {
|
||||||
|
@ -51,8 +50,7 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
this.state = {
|
this.state = {
|
||||||
search: [],
|
search: [],
|
||||||
channels: [],
|
channels: [],
|
||||||
selected: [],
|
selected: []
|
||||||
loading: false
|
|
||||||
};
|
};
|
||||||
this.setHeader();
|
this.setHeader();
|
||||||
}
|
}
|
||||||
|
@ -130,12 +128,12 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
const { isMasterDetail } = this.props;
|
const { isMasterDetail } = this.props;
|
||||||
|
|
||||||
this.setState({ loading: true });
|
sendLoadingEvent({ visible: true });
|
||||||
try {
|
try {
|
||||||
logEvent(events.CT_ADD_ROOM_TO_TEAM);
|
logEvent(events.CT_ADD_ROOM_TO_TEAM);
|
||||||
const result = await Services.addRoomsToTeam({ rooms: selected, teamId: this.teamId });
|
const result = await Services.addRoomsToTeam({ rooms: selected, teamId: this.teamId });
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({ loading: false });
|
sendLoadingEvent({ visible: false });
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// TODO: Verify goRoom interface for return of call
|
// TODO: Verify goRoom interface for return of call
|
||||||
goRoom({ item: result, isMasterDetail });
|
goRoom({ item: result, isMasterDetail });
|
||||||
|
@ -143,7 +141,7 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
|
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
|
||||||
showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {});
|
showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {});
|
||||||
this.setState({ loading: false });
|
sendLoadingEvent({ visible: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -209,13 +207,10 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView testID='add-existing-channel-view'>
|
<SafeAreaView testID='add-existing-channel-view'>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
<Loading visible={loading} />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { dequal } from 'dequal';
|
||||||
|
|
||||||
import * as List from '../containers/List';
|
import * as List from '../containers/List';
|
||||||
import { TextInput } from '../containers/TextInput';
|
import { TextInput } from '../containers/TextInput';
|
||||||
import Loading from '../containers/Loading';
|
import { sendLoadingEvent } from '../containers/Loading';
|
||||||
import { createChannelRequest } from '../actions/createChannel';
|
import { createChannelRequest } from '../actions/createChannel';
|
||||||
import { removeUser } from '../actions/selectedUsers';
|
import { removeUser } from '../actions/selectedUsers';
|
||||||
import KeyboardView from '../containers/KeyboardView';
|
import KeyboardView from '../containers/KeyboardView';
|
||||||
|
@ -169,13 +169,16 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ICreateChannelViewProps) {
|
componentDidUpdate(prevProps: ICreateChannelViewProps) {
|
||||||
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
|
const { createPublicChannelPermission, createPrivateChannelPermission, isFetching } = this.props;
|
||||||
if (
|
if (
|
||||||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
||||||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission)
|
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission)
|
||||||
) {
|
) {
|
||||||
this.handleHasPermission();
|
this.handleHasPermission();
|
||||||
}
|
}
|
||||||
|
if (isFetching !== prevProps.isFetching) {
|
||||||
|
sendLoadingEvent({ visible: isFetching });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setHeader = () => {
|
setHeader = () => {
|
||||||
|
@ -368,7 +371,7 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { channelName, isTeam } = this.state;
|
const { channelName, isTeam } = this.state;
|
||||||
const { users, isFetching, theme } = this.props;
|
const { users, theme } = this.props;
|
||||||
const userCount = users.length;
|
const userCount = users.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -409,7 +412,6 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{this.renderInvitedList()}
|
{this.renderInvitedList()}
|
||||||
<Loading visible={isFetching} />
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import { ScrollView, Switch, Text } from 'react-native';
|
import { ScrollView, Switch, Text } from 'react-native';
|
||||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||||
|
|
||||||
import Loading from '../../containers/Loading';
|
import { sendLoadingEvent } from '../../containers/Loading';
|
||||||
import KeyboardView from '../../containers/KeyboardView';
|
import KeyboardView from '../../containers/KeyboardView';
|
||||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -52,8 +52,9 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
this.setHeader();
|
this.setHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loading && loading !== prevProps.loading) {
|
if (loading !== prevProps.loading) {
|
||||||
setTimeout(() => {
|
sendLoadingEvent({ visible: loading });
|
||||||
|
if (!loading) {
|
||||||
if (failure) {
|
if (failure) {
|
||||||
const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_discussion') });
|
const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_discussion') });
|
||||||
showErrorAlert(msg);
|
showErrorAlert(msg);
|
||||||
|
@ -72,7 +73,7 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
};
|
};
|
||||||
goRoom({ item, isMasterDetail });
|
goRoom({ item, isMasterDetail });
|
||||||
}
|
}
|
||||||
}, 300);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, users, encrypted } = this.state;
|
const { name, users, encrypted } = this.state;
|
||||||
const { server, user, loading, blockUnauthenticatedAccess, theme, serverVersion } = this.props;
|
const { server, user, blockUnauthenticatedAccess, theme, serverVersion } = this.props;
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
|
@ -189,7 +190,6 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
||||||
<Switch value={encrypted} onValueChange={this.onEncryptedChange} trackColor={SWITCH_TRACK_COLOR} />
|
<Switch value={encrypted} onValueChange={this.onEncryptedChange} trackColor={SWITCH_TRACK_COLOR} />
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<Loading visible={loading} />
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Subscription } from 'rxjs';
|
||||||
import { deleteRoom } from '../../actions/room';
|
import { deleteRoom } from '../../actions/room';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import Loading from '../../containers/Loading';
|
import { sendLoadingEvent } from '../../containers/Loading';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import { FormTextInput } from '../../containers/TextInput';
|
import { FormTextInput } from '../../containers/TextInput';
|
||||||
|
@ -62,7 +62,6 @@ interface IRoomInfoEditViewState {
|
||||||
announcement?: string;
|
announcement?: string;
|
||||||
joinCode: string;
|
joinCode: string;
|
||||||
nameError: any;
|
nameError: any;
|
||||||
saving: boolean;
|
|
||||||
t: boolean;
|
t: boolean;
|
||||||
ro: boolean;
|
ro: boolean;
|
||||||
reactWhenReadOnly?: boolean;
|
reactWhenReadOnly?: boolean;
|
||||||
|
@ -111,7 +110,6 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
announcement: '',
|
announcement: '',
|
||||||
joinCode: '',
|
joinCode: '',
|
||||||
nameError: {},
|
nameError: {},
|
||||||
saving: false,
|
|
||||||
t: false,
|
t: false,
|
||||||
ro: false,
|
ro: false,
|
||||||
reactWhenReadOnly: false,
|
reactWhenReadOnly: false,
|
||||||
|
@ -269,7 +267,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
avatar
|
avatar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
this.setState({ saving: true });
|
sendLoadingEvent({ visible: true });
|
||||||
let error = false;
|
let error = false;
|
||||||
|
|
||||||
if (!this.formIsChanged()) {
|
if (!this.formIsChanged()) {
|
||||||
|
@ -339,7 +337,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.setState({ saving: false });
|
sendLoadingEvent({ visible: false });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logEvent(events.RI_EDIT_SAVE_F);
|
logEvent(events.RI_EDIT_SAVE_F);
|
||||||
|
@ -548,7 +546,6 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
reactWhenReadOnly,
|
reactWhenReadOnly,
|
||||||
room,
|
room,
|
||||||
joinCode,
|
joinCode,
|
||||||
saving,
|
|
||||||
permissions,
|
permissions,
|
||||||
archived,
|
archived,
|
||||||
enableSysMes,
|
enableSysMes,
|
||||||
|
@ -811,7 +808,6 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
{I18n.t('DELETE')}
|
{I18n.t('DELETE')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Loading visible={saving} />
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -62,6 +62,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
|
||||||
private mounted = false;
|
private mounted = false;
|
||||||
private animated = false;
|
private animated = false;
|
||||||
private jumping = false;
|
private jumping = false;
|
||||||
|
private cancelJump = false;
|
||||||
private y = new Value(0);
|
private y = new Value(0);
|
||||||
private onScroll = onScroll({ y: this.y });
|
private onScroll = onScroll({ y: this.y });
|
||||||
private unsubscribeFocus: () => void;
|
private unsubscribeFocus: () => void;
|
||||||
|
@ -138,6 +139,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
|
||||||
console.countReset(`${this.constructor.name}.render calls`);
|
console.countReset(`${this.constructor.name}.render calls`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clears previous highlighted message timeout, if exists
|
||||||
clearHighlightedMessageTimeout = () => {
|
clearHighlightedMessageTimeout = () => {
|
||||||
if (this.highlightedMessageTimeout) {
|
if (this.highlightedMessageTimeout) {
|
||||||
clearTimeout(this.highlightedMessageTimeout);
|
clearTimeout(this.highlightedMessageTimeout);
|
||||||
|
@ -276,40 +278,60 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
|
||||||
|
|
||||||
jumpToMessage = (messageId: string) =>
|
jumpToMessage = (messageId: string) =>
|
||||||
new Promise<void>(async resolve => {
|
new Promise<void>(async resolve => {
|
||||||
this.jumping = true;
|
|
||||||
const { messages } = this.state;
|
const { messages } = this.state;
|
||||||
const { listRef } = this.props;
|
const { listRef } = this.props;
|
||||||
const index = messages.findIndex(item => item.id === messageId);
|
|
||||||
if (index > -1) {
|
// if jump to message was cancelled, reset variables and stop
|
||||||
listRef.current?.scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
|
if (this.cancelJump) {
|
||||||
await new Promise(res => setTimeout(res, 300));
|
this.resetJumpToMessage();
|
||||||
if (!this.viewableItems?.map(vi => vi.key).includes(messageId)) {
|
|
||||||
if (!this.jumping) {
|
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
this.jumping = true;
|
||||||
|
|
||||||
|
// look for the message on the state
|
||||||
|
const index = messages.findIndex(item => item.id === messageId);
|
||||||
|
|
||||||
|
// if found message, scroll to it
|
||||||
|
if (index > -1) {
|
||||||
|
listRef.current?.scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
|
||||||
|
|
||||||
|
// wait for scroll animation to finish
|
||||||
|
await new Promise(res => setTimeout(res, 300));
|
||||||
|
|
||||||
|
// if message is not visible
|
||||||
|
if (!this.viewableItems?.map(vi => vi.key).includes(messageId)) {
|
||||||
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
|
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// if message is visible, highlight it
|
||||||
this.setState({ highlightedMessage: messageId });
|
this.setState({ highlightedMessage: messageId });
|
||||||
this.clearHighlightedMessageTimeout();
|
this.clearHighlightedMessageTimeout();
|
||||||
|
// clears highlighted message after some time
|
||||||
this.highlightedMessageTimeout = setTimeout(() => {
|
this.highlightedMessageTimeout = setTimeout(() => {
|
||||||
this.setState({ highlightedMessage: null });
|
this.setState({ highlightedMessage: null });
|
||||||
}, 10000);
|
}, 5000);
|
||||||
await setTimeout(() => resolve(), 300);
|
this.resetJumpToMessage();
|
||||||
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
listRef.current?.scrollToIndex({ index: messages.length - 1, animated: false });
|
// if message not found, wait for scroll to top and then jump to message
|
||||||
if (!this.jumping) {
|
listRef.current?.scrollToIndex({ index: messages.length - 1, animated: true });
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
|
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// this.jumping is checked in between operations to make sure we're not stuck
|
resetJumpToMessage = () => {
|
||||||
cancelJumpToMessage = () => {
|
this.cancelJump = false;
|
||||||
this.jumping = false;
|
this.jumping = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cancelJumpToMessage = () => {
|
||||||
|
if (this.jumping) {
|
||||||
|
this.cancelJump = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.resetJumpToMessage();
|
||||||
|
};
|
||||||
|
|
||||||
jumpToBottom = () => {
|
jumpToBottom = () => {
|
||||||
const { listRef } = this.props;
|
const { listRef } = this.props;
|
||||||
listRef.current?.scrollToOffset({ offset: -100 });
|
listRef.current?.scrollToOffset({ offset: -100 });
|
||||||
|
|
|
@ -42,7 +42,7 @@ import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { withDimensions } from '../../dimensions';
|
||||||
import { takeInquiry, takeResume } from '../../ee/omnichannel/lib';
|
import { takeInquiry, takeResume } from '../../ee/omnichannel/lib';
|
||||||
import Loading from '../../containers/Loading';
|
import { sendLoadingEvent } from '../../containers/Loading';
|
||||||
import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
|
import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
|
||||||
import getThreadName from '../../lib/methods/getThreadName';
|
import getThreadName from '../../lib/methods/getThreadName';
|
||||||
import getRoomInfo from '../../lib/methods/getRoomInfo';
|
import getRoomInfo from '../../lib/methods/getRoomInfo';
|
||||||
|
@ -115,7 +115,6 @@ const stateAttrsUpdate = [
|
||||||
'reacting',
|
'reacting',
|
||||||
'readOnly',
|
'readOnly',
|
||||||
'member',
|
'member',
|
||||||
'showingBlockingLoader',
|
|
||||||
'canForwardGuest',
|
'canForwardGuest',
|
||||||
'canReturnQueue',
|
'canReturnQueue',
|
||||||
'canViewCannedResponse'
|
'canViewCannedResponse'
|
||||||
|
@ -198,7 +197,6 @@ interface IRoomViewState {
|
||||||
selectedMessage?: TAnyMessageModel;
|
selectedMessage?: TAnyMessageModel;
|
||||||
canAutoTranslate: boolean;
|
canAutoTranslate: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
showingBlockingLoader: boolean;
|
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
replying: boolean;
|
replying: boolean;
|
||||||
replyWithMention: boolean;
|
replyWithMention: boolean;
|
||||||
|
@ -273,7 +271,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
selectedMessage,
|
selectedMessage,
|
||||||
canAutoTranslate: false,
|
canAutoTranslate: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
showingBlockingLoader: false,
|
|
||||||
editing: false,
|
editing: false,
|
||||||
replying: !!selectedMessage,
|
replying: !!selectedMessage,
|
||||||
replyWithMention: false,
|
replyWithMention: false,
|
||||||
|
@ -940,25 +937,23 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.setState({ showingBlockingLoader: true });
|
|
||||||
const parsedUrl = parse(messageUrl, true);
|
const parsedUrl = parse(messageUrl, true);
|
||||||
const messageId = parsedUrl.query.msg;
|
const messageId = parsedUrl.query.msg;
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await this.jumpToMessage(messageId);
|
await this.jumpToMessage(messageId);
|
||||||
}
|
}
|
||||||
this.setState({ showingBlockingLoader: false });
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ showingBlockingLoader: false });
|
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
jumpToMessage = async (messageId: string) => {
|
jumpToMessage = async (messageId: string) => {
|
||||||
try {
|
try {
|
||||||
this.setState({ showingBlockingLoader: true });
|
sendLoadingEvent({ visible: true, onCancel: this.cancelJumpToMessage });
|
||||||
const message = await RoomServices.getMessageInfo(messageId);
|
const message = await RoomServices.getMessageInfo(messageId);
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
|
this.cancelJumpToMessage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -977,15 +972,19 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
await loadSurroundingMessages({ messageId, rid: this.rid });
|
await loadSurroundingMessages({ messageId, rid: this.rid });
|
||||||
}
|
}
|
||||||
await Promise.race([this.list.current?.jumpToMessage(message.id), new Promise(res => setTimeout(res, 5000))]);
|
await Promise.race([this.list.current?.jumpToMessage(message.id), new Promise(res => setTimeout(res, 5000))]);
|
||||||
this.list.current?.cancelJumpToMessage();
|
this.cancelJumpToMessage();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
} finally {
|
this.cancelJumpToMessage();
|
||||||
this.setState({ showingBlockingLoader: false });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cancelJumpToMessage = () => {
|
||||||
|
this.list.current?.cancelJumpToMessage();
|
||||||
|
sendLoadingEvent({ visible: false });
|
||||||
|
};
|
||||||
|
|
||||||
replyBroadcast = (message: IMessage) => {
|
replyBroadcast = (message: IMessage) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch(replyBroadcast(message));
|
dispatch(replyBroadcast(message));
|
||||||
|
@ -1137,10 +1136,12 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
name = item.tmsg ?? '';
|
name = item.tmsg ?? '';
|
||||||
jumpToMessageId = item.id;
|
jumpToMessageId = item.id;
|
||||||
}
|
}
|
||||||
|
sendLoadingEvent({ visible: true, onCancel: this.cancelJumpToMessage });
|
||||||
if (!name) {
|
if (!name) {
|
||||||
const result = await this.getThreadName(item.tmid, jumpToMessageId);
|
const result = await this.getThreadName(item.tmid, jumpToMessageId);
|
||||||
// test if there isn't a thread
|
// test if there isn't a thread
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
sendLoadingEvent({ visible: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
name = result;
|
name = result;
|
||||||
|
@ -1484,7 +1485,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.count(`${this.constructor.name}.render calls`);
|
console.count(`${this.constructor.name}.render calls`);
|
||||||
const { room, selectedMessage, loading, reacting, showingBlockingLoader } = this.state;
|
const { room, selectedMessage, loading, reacting } = this.state;
|
||||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props;
|
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props;
|
||||||
const { rid, t } = room;
|
const { rid, t } = room;
|
||||||
let sysMes;
|
let sysMes;
|
||||||
|
@ -1528,7 +1529,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
/>
|
/>
|
||||||
<UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
|
<UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
|
||||||
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
|
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
|
||||||
<Loading visible={showingBlockingLoader} />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { addUser, removeUser, reset } from '../actions/selectedUsers';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import * as HeaderButton from '../containers/HeaderButton';
|
import * as HeaderButton from '../containers/HeaderButton';
|
||||||
import * as List from '../containers/List';
|
import * as List from '../containers/List';
|
||||||
import Loading from '../containers/Loading';
|
import { sendLoadingEvent } from '../containers/Loading';
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import SearchBox from '../containers/SearchBox';
|
import SearchBox from '../containers/SearchBox';
|
||||||
import StatusBar from '../containers/StatusBar';
|
import StatusBar from '../containers/StatusBar';
|
||||||
|
@ -65,12 +65,15 @@ class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelect
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ISelectedUsersViewProps) {
|
componentDidUpdate(prevProps: ISelectedUsersViewProps) {
|
||||||
|
const { users, loading } = this.props;
|
||||||
if (this.isGroupChat()) {
|
if (this.isGroupChat()) {
|
||||||
const { users } = this.props;
|
|
||||||
if (prevProps.users.length !== users.length) {
|
if (prevProps.users.length !== users.length) {
|
||||||
this.setHeader(users.length > 0);
|
this.setHeader(users.length > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (loading !== prevProps.loading) {
|
||||||
|
sendLoadingEvent({ visible: loading });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -273,16 +276,14 @@ class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelect
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render = () => {
|
render() {
|
||||||
const { loading } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView testID='select-users-view'>
|
<SafeAreaView testID='select-users-view'>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
<Loading visible={loading} />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Q } from '@nozbe/watermelondb';
|
||||||
import { InsideStackParamList } from '../../stacks/types';
|
import { InsideStackParamList } from '../../stacks/types';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import Loading from '../../containers/Loading';
|
import { sendLoadingEvent } from '../../containers/Loading';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { FormTextInput } from '../../containers/TextInput';
|
import { FormTextInput } from '../../containers/TextInput';
|
||||||
|
@ -207,6 +207,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
|
||||||
// if it's share extension this should show loading
|
// if it's share extension this should show loading
|
||||||
if (this.isShareExtension) {
|
if (this.isShareExtension) {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
sendLoadingEvent({ visible: true });
|
||||||
|
|
||||||
// if it's not share extension this can close
|
// if it's not share extension this can close
|
||||||
} else {
|
} else {
|
||||||
|
@ -248,6 +249,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
|
||||||
|
|
||||||
// if it's share extension this should close
|
// if it's share extension this should close
|
||||||
if (this.isShareExtension) {
|
if (this.isShareExtension) {
|
||||||
|
sendLoadingEvent({ visible: false });
|
||||||
ShareExtension.close();
|
ShareExtension.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -346,7 +348,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.count(`${this.constructor.name}.render calls`);
|
console.count(`${this.constructor.name}.render calls`);
|
||||||
const { readOnly, room, loading } = this.state;
|
const { readOnly, room } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (readOnly || isBlocked(room)) {
|
if (readOnly || isBlocked(room)) {
|
||||||
return (
|
return (
|
||||||
|
@ -361,7 +363,6 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
|
||||||
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||||
<StatusBar barStyle='light-content' backgroundColor={themes[theme].previewBackground} />
|
<StatusBar barStyle='light-content' backgroundColor={themes[theme].previewBackground} />
|
||||||
{this.renderContent()}
|
{this.renderContent()}
|
||||||
<Loading visible={loading} />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { setUser } from '../../actions/login';
|
import { setUser } from '../../actions/login';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
import Loading from '../../containers/Loading';
|
import { sendLoadingEvent } from '../../containers/Loading';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import StatusIcon from '../../containers/Status/Status';
|
import StatusIcon from '../../containers/Status/Status';
|
||||||
import { FormTextInput } from '../../containers/TextInput';
|
import { FormTextInput } from '../../containers/TextInput';
|
||||||
|
@ -91,7 +91,6 @@ const StatusView = (): React.ReactElement => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const [statusText, setStatusText] = useState(user.statusText || '');
|
const [statusText, setStatusText] = useState(user.statusText || '');
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [status, setStatus] = useState(user.status);
|
const [status, setStatus] = useState(user.status);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -120,7 +119,7 @@ const StatusView = (): React.ReactElement => {
|
||||||
}, [statusText, status]);
|
}, [statusText, status]);
|
||||||
|
|
||||||
const setCustomStatus = async (status: TUserStatus, statusText: string) => {
|
const setCustomStatus = async (status: TUserStatus, statusText: string) => {
|
||||||
setLoading(true);
|
sendLoadingEvent({ visible: true });
|
||||||
try {
|
try {
|
||||||
await Services.setUserStatus(status, statusText);
|
await Services.setUserStatus(status, statusText);
|
||||||
dispatch(setUser({ statusText, status }));
|
dispatch(setUser({ statusText, status }));
|
||||||
|
@ -135,7 +134,7 @@ const StatusView = (): React.ReactElement => {
|
||||||
showErrorAlert(messageError);
|
showErrorAlert(messageError);
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
sendLoadingEvent({ visible: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusType = Accounts_AllowInvisibleStatusOption ? STATUS : STATUS.filter(s => s.id !== 'offline');
|
const statusType = Accounts_AllowInvisibleStatusOption ? STATUS : STATUS.filter(s => s.id !== 'offline');
|
||||||
|
@ -163,7 +162,6 @@ const StatusView = (): React.ReactElement => {
|
||||||
ListFooterComponent={List.Separator}
|
ListFooterComponent={List.Separator}
|
||||||
ItemSeparatorComponent={List.Separator}
|
ItemSeparatorComponent={List.Separator}
|
||||||
/>
|
/>
|
||||||
<Loading visible={loading} />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,10 +49,10 @@ async function waitForLoading() {
|
||||||
await sleep(10000);
|
await sleep(10000);
|
||||||
return; // FIXME: Loading indicator doesn't animate properly on android
|
return; // FIXME: Loading indicator doesn't animate properly on android
|
||||||
}
|
}
|
||||||
await waitFor(element(by.id('loading')))
|
await waitFor(element(by.id('loading-image')))
|
||||||
.toBeVisible()
|
.toBeVisible()
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
await waitFor(element(by.id('loading')))
|
await waitFor(element(by.id('loading-image')))
|
||||||
.toBeNotVisible()
|
.toBeNotVisible()
|
||||||
.withTimeout(10000);
|
.withTimeout(10000);
|
||||||
}
|
}
|
||||||
|
@ -67,9 +67,10 @@ describe('Room', () => {
|
||||||
|
|
||||||
it('should jump to an old message and load its surroundings', async () => {
|
it('should jump to an old message and load its surroundings', async () => {
|
||||||
await navigateToRoom('jumping');
|
await navigateToRoom('jumping');
|
||||||
await waitFor(element(by[textMatcher]('300')))
|
await waitFor(element(by[textMatcher]('295')))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
|
await sleep(2000);
|
||||||
await element(by[textMatcher]('1')).atIndex(0).tap();
|
await element(by[textMatcher]('1')).atIndex(0).tap();
|
||||||
await waitForLoading();
|
await waitForLoading();
|
||||||
await waitFor(element(by[textMatcher]('1')).atIndex(0))
|
await waitFor(element(by[textMatcher]('1')).atIndex(0))
|
||||||
|
@ -83,7 +84,7 @@ describe('Room', () => {
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(15000);
|
.withTimeout(15000);
|
||||||
await element(by.id('nav-jump-to-bottom')).tap();
|
await element(by.id('nav-jump-to-bottom')).tap();
|
||||||
await waitFor(element(by[textMatcher]('Quote first message')))
|
await waitFor(element(by[textMatcher]("Go to jumping-thread's thread")))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(15000);
|
.withTimeout(15000);
|
||||||
await clearCache();
|
await clearCache();
|
||||||
|
@ -164,7 +165,7 @@ describe('Room', () => {
|
||||||
await waitFor(element(by[textMatcher]('50')))
|
await waitFor(element(by[textMatcher]('50')))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'slow', 0.5);
|
await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'slow', 0.4);
|
||||||
await waitFor(element(by[textMatcher]('Load Newer')))
|
await waitFor(element(by[textMatcher]('Load Newer')))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
|
@ -197,8 +198,9 @@ const expectThreadMessages = async message => {
|
||||||
await waitFor(element(by.id('room-view-title-thread 1')))
|
await waitFor(element(by.id('room-view-title-thread 1')))
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
await waitForLoading();
|
await waitFor(element(by[textMatcher](message)).atIndex(0))
|
||||||
await expect(element(by[textMatcher](message)).atIndex(0)).toExist();
|
.toExist()
|
||||||
|
.withTimeout(10000);
|
||||||
await element(by[textMatcher](message)).atIndex(0).tap();
|
await element(by[textMatcher](message)).atIndex(0).tap();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||||
"@typescript-eslint/parser": "^4.28.5",
|
"@typescript-eslint/parser": "^4.28.5",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"babel-jest": "^27.0.6",
|
"babel-jest": "^28.1.3",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"codecov": "^3.8.3",
|
"codecov": "^3.8.3",
|
||||||
"detox": "19.7.0",
|
"detox": "19.7.0",
|
||||||
|
@ -190,9 +190,9 @@
|
||||||
"eslint-plugin-react-native": "4.0.0",
|
"eslint-plugin-react-native": "4.0.0",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^27.0.6",
|
"jest": "^28.1.3",
|
||||||
"jest-cli": "^27.0.6",
|
"jest-cli": "^28.1.3",
|
||||||
"jest-expo": "^45.0.1",
|
"jest-expo": "^46.0.1",
|
||||||
"metro-react-native-babel-preset": "^0.67.0",
|
"metro-react-native-babel-preset": "^0.67.0",
|
||||||
"mocha": "9.0.1",
|
"mocha": "9.0.1",
|
||||||
"otp.js": "1.2.0",
|
"otp.js": "1.2.0",
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
diff --git a/node_modules/react-native-reanimated/lib/reanimated2/jestUtils.js b/node_modules/react-native-reanimated/lib/reanimated2/jestUtils.js
|
||||||
|
index 5ae42ec..273bbc0 100644
|
||||||
|
--- a/node_modules/react-native-reanimated/lib/reanimated2/jestUtils.js
|
||||||
|
+++ b/node_modules/react-native-reanimated/lib/reanimated2/jestUtils.js
|
||||||
|
@@ -145,7 +145,8 @@ export const advanceAnimationByFrame = (count) => {
|
||||||
|
jest.advanceTimersByTime(frameTime);
|
||||||
|
};
|
||||||
|
export const setUpTests = (userConfig = {}) => {
|
||||||
|
- const expect = require('expect');
|
||||||
|
+ // https://github.com/software-mansion/react-native-reanimated/issues/3215
|
||||||
|
+ const { expect } = require('@jest/globals');
|
||||||
|
require('setimmediate');
|
||||||
|
frameTime = Math.round(1000 / config.fps);
|
||||||
|
config = Object.assign(Object.assign({}, config), userConfig);
|
Loading…
Reference in New Issue