Merge develop into beta 4.26.0-rc.1
This commit is contained in:
commit
062dace1f1
|
@ -1,30 +1,29 @@
|
||||||
|
import { useBackHandler } from '@react-native-community/hooks';
|
||||||
|
import * as Haptics from 'expo-haptics';
|
||||||
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||||
import { Keyboard, Text } from 'react-native';
|
import { Keyboard, Text } from 'react-native';
|
||||||
|
import { HandlerStateChangeEventPayload, State, TapGestureHandler } from 'react-native-gesture-handler';
|
||||||
|
import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-native-reanimated';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { State, TapGestureHandler } from 'react-native-gesture-handler';
|
|
||||||
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
|
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
|
||||||
import Animated, { Easing, Extrapolate, Value, interpolateNode } from 'react-native-reanimated';
|
|
||||||
import * as Haptics from 'expo-haptics';
|
|
||||||
import { useBackHandler } from '@react-native-community/hooks';
|
|
||||||
|
|
||||||
import { Item } from './Item';
|
|
||||||
import { Handle } from './Handle';
|
|
||||||
import { Button } from './Button';
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import styles, { ITEM_HEIGHT } from './styles';
|
import { useDimensions, useOrientation } from '../../dimensions';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||||
import * as List from '../List';
|
import * as List from '../List';
|
||||||
import I18n from '../../i18n';
|
import { Button } from './Button';
|
||||||
import { IDimensionsContextProps, useDimensions, useOrientation } from '../../dimensions';
|
import { Handle } from './Handle';
|
||||||
|
import { IActionSheetItem, Item } from './Item';
|
||||||
|
import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider';
|
||||||
|
import styles, { ITEM_HEIGHT } from './styles';
|
||||||
|
|
||||||
interface IActionSheetData {
|
const getItemLayout = (data: TActionSheetOptionsItem[] | null | undefined, index: number) => ({
|
||||||
options: any;
|
length: ITEM_HEIGHT,
|
||||||
headerHeight?: number;
|
offset: ITEM_HEIGHT * index,
|
||||||
hasCancel?: boolean;
|
index
|
||||||
customHeader: any;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const getItemLayout = (data: any, index: number) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index });
|
|
||||||
|
|
||||||
const HANDLE_HEIGHT = isIOS ? 40 : 56;
|
const HANDLE_HEIGHT = isIOS ? 40 : 56;
|
||||||
const MAX_SNAP_HEIGHT = 16;
|
const MAX_SNAP_HEIGHT = 16;
|
||||||
|
@ -39,16 +38,17 @@ const ANIMATION_CONFIG = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionSheet = React.memo(
|
const ActionSheet = React.memo(
|
||||||
forwardRef(({ children, theme }: { children: JSX.Element; theme: string }, ref) => {
|
forwardRef(({ children }: { children: React.ReactElement }, ref) => {
|
||||||
const bottomSheetRef: any = useRef();
|
const { theme } = useTheme();
|
||||||
const [data, setData] = useState<IActionSheetData>({} as IActionSheetData);
|
const bottomSheetRef = useRef<ScrollBottomSheet<TActionSheetOptionsItem>>(null);
|
||||||
|
const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions);
|
||||||
const [isVisible, setVisible] = useState(false);
|
const [isVisible, setVisible] = useState(false);
|
||||||
const { height }: Partial<IDimensionsContextProps> = useDimensions();
|
const { height } = useDimensions();
|
||||||
const { isLandscape } = useOrientation();
|
const { isLandscape } = useOrientation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const maxSnap = Math.max(
|
const maxSnap = Math.max(
|
||||||
height! -
|
height -
|
||||||
// Items height
|
// Items height
|
||||||
ITEM_HEIGHT * (data?.options?.length || 0) -
|
ITEM_HEIGHT * (data?.options?.length || 0) -
|
||||||
// Handle height
|
// Handle height
|
||||||
|
@ -69,7 +69,7 @@ const ActionSheet = React.memo(
|
||||||
* we'll provide more one snap
|
* we'll provide more one snap
|
||||||
* that point 50% of the whole screen
|
* that point 50% of the whole screen
|
||||||
*/
|
*/
|
||||||
const snaps: any = height! - maxSnap > height! * 0.6 && !isLandscape ? [maxSnap, height! * 0.5, height] : [maxSnap, height];
|
const snaps = height - maxSnap > height * 0.6 && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height];
|
||||||
const openedSnapIndex = snaps.length > 2 ? 1 : 0;
|
const openedSnapIndex = snaps.length > 2 ? 1 : 0;
|
||||||
const closedSnapIndex = snaps.length - 1;
|
const closedSnapIndex = snaps.length - 1;
|
||||||
|
|
||||||
|
@ -79,12 +79,12 @@ const ActionSheet = React.memo(
|
||||||
bottomSheetRef.current?.snapTo(closedSnapIndex);
|
bottomSheetRef.current?.snapTo(closedSnapIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = (options: any) => {
|
const show = (options: TActionSheetOptions) => {
|
||||||
setData(options);
|
setData(options);
|
||||||
toggleVisible();
|
toggleVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBackdropPressed = ({ nativeEvent }: any) => {
|
const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
||||||
if (nativeEvent.oldState === State.ACTIVE) {
|
if (nativeEvent.oldState === State.ACTIVE) {
|
||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ const ActionSheet = React.memo(
|
||||||
|
|
||||||
const renderHandle = () => (
|
const renderHandle = () => (
|
||||||
<>
|
<>
|
||||||
<Handle theme={theme} />
|
<Handle />
|
||||||
{isValidElement(data?.customHeader) ? data.customHeader : null}
|
{isValidElement(data?.customHeader) ? data.customHeader : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -127,21 +127,23 @@ const ActionSheet = React.memo(
|
||||||
<Button
|
<Button
|
||||||
onPress={hide}
|
onPress={hide}
|
||||||
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||||
|
// TODO: Remove when migrate Touch
|
||||||
theme={theme}
|
theme={theme}
|
||||||
accessibilityLabel={I18n.t('Cancel')}>
|
accessibilityLabel={I18n.t('Cancel')}>
|
||||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text>
|
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const renderItem = ({ item }: any) => <Item item={item} hide={hide} theme={theme} />;
|
const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => <Item item={item} hide={hide} />;
|
||||||
|
|
||||||
const animatedPosition = React.useRef(new Value(0));
|
const animatedPosition = React.useRef(new Value(0));
|
||||||
// TODO: Similar to https://github.com/wcandillon/react-native-redash/issues/307#issuecomment-827442320
|
|
||||||
const opacity = interpolateNode(animatedPosition.current, {
|
const opacity = interpolateNode(animatedPosition.current, {
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
outputRange: [0, themes[theme].backdropOpacity],
|
outputRange: [0, themes[theme].backdropOpacity],
|
||||||
extrapolate: Extrapolate.CLAMP
|
extrapolate: Extrapolate.CLAMP
|
||||||
}) as any;
|
}) as any; // The function's return differs from the expected type of opacity, however this problem is something related to lib, maybe when updating the types will be fixed.
|
||||||
|
|
||||||
|
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -160,7 +162,7 @@ const ActionSheet = React.memo(
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</TapGestureHandler>
|
</TapGestureHandler>
|
||||||
<ScrollBottomSheet
|
<ScrollBottomSheet<TActionSheetOptionsItem>
|
||||||
testID='action-sheet'
|
testID='action-sheet'
|
||||||
ref={bottomSheetRef}
|
ref={bottomSheetRef}
|
||||||
componentType='FlatList'
|
componentType='FlatList'
|
||||||
|
@ -169,18 +171,11 @@ const ActionSheet = React.memo(
|
||||||
renderHandle={renderHandle}
|
renderHandle={renderHandle}
|
||||||
onSettle={index => index === closedSnapIndex && toggleVisible()}
|
onSettle={index => index === closedSnapIndex && toggleVisible()}
|
||||||
animatedPosition={animatedPosition.current}
|
animatedPosition={animatedPosition.current}
|
||||||
containerStyle={
|
containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }}
|
||||||
[
|
|
||||||
styles.container,
|
|
||||||
{ backgroundColor: themes[theme].focusedBackground },
|
|
||||||
(isLandscape || isTablet) && styles.bottomSheet
|
|
||||||
] as any
|
|
||||||
}
|
|
||||||
animationConfig={ANIMATION_CONFIG}
|
animationConfig={ANIMATION_CONFIG}
|
||||||
// FlatList props
|
data={data.options}
|
||||||
data={data?.options}
|
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={(item: any) => item.title}
|
keyExtractor={item => item.title}
|
||||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||||
contentContainerStyle={styles.content}
|
contentContainerStyle={styles.content}
|
||||||
ItemSeparatorComponent={List.Separator}
|
ItemSeparatorComponent={List.Separator}
|
||||||
|
|
|
@ -3,9 +3,13 @@ import { View } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
export const Handle = React.memo(({ theme }: { theme: string }) => (
|
export const Handle = React.memo(() => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
|
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
|
||||||
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
|
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
|
||||||
</View>
|
</View>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -3,23 +3,24 @@ import { Text, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IActionSheetItem {
|
export interface IActionSheetItem {
|
||||||
item: {
|
item: {
|
||||||
title: string;
|
title: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
danger: boolean;
|
danger?: boolean;
|
||||||
testID: string;
|
testID?: string;
|
||||||
onPress(): void;
|
onPress: () => void;
|
||||||
right: Function;
|
right?: Function;
|
||||||
};
|
};
|
||||||
theme: string;
|
|
||||||
hide(): void;
|
hide(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Item = React.memo(({ item, hide, theme }: IActionSheetItem) => {
|
export const Item = React.memo(({ item, hide }: IActionSheetItem) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
hide();
|
hide();
|
||||||
item?.onPress();
|
item?.onPress();
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
||||||
|
|
||||||
import ActionSheet from './ActionSheet';
|
import ActionSheet from './ActionSheet';
|
||||||
import { useTheme } from '../../theme';
|
|
||||||
|
|
||||||
|
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void };
|
||||||
|
|
||||||
|
export type TActionSheetOptions = {
|
||||||
|
options: TActionSheetOptionsItem[];
|
||||||
|
headerHeight: number;
|
||||||
|
customHeader: React.ReactElement | null;
|
||||||
|
hasCancel?: boolean;
|
||||||
|
};
|
||||||
interface IActionSheetProvider {
|
interface IActionSheetProvider {
|
||||||
Provider: any;
|
showActionSheet: (item: TActionSheetOptions) => void;
|
||||||
Consumer: any;
|
hideActionSheet: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context: IActionSheetProvider = React.createContext({
|
const context = React.createContext<IActionSheetProvider>({
|
||||||
showActionSheet: () => {},
|
showActionSheet: () => {},
|
||||||
hideActionSheet: () => {}
|
hideActionSheet: () => {}
|
||||||
});
|
});
|
||||||
|
@ -17,17 +24,16 @@ export const useActionSheet = () => useContext(context);
|
||||||
|
|
||||||
const { Provider, Consumer } = context;
|
const { Provider, Consumer } = context;
|
||||||
|
|
||||||
export const withActionSheet = (Component: any): any =>
|
export const withActionSheet = (Component: React.ComponentType<any>): typeof Component =>
|
||||||
forwardRef((props: any, ref: ForwardedRef<any>) => (
|
forwardRef((props: typeof React.Component, ref: ForwardedRef<IActionSheetProvider>) => (
|
||||||
<Consumer>{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
|
<Consumer>{(contexts: IActionSheetProvider) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
|
||||||
));
|
));
|
||||||
|
|
||||||
export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Element | JSX.Element[] }) => {
|
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
||||||
const ref: ForwardedRef<any> = useRef();
|
const ref: ForwardedRef<IActionSheetProvider> = useRef(null);
|
||||||
const { theme }: any = useTheme();
|
|
||||||
|
|
||||||
const getContext = () => ({
|
const getContext = () => ({
|
||||||
showActionSheet: (options: any) => {
|
showActionSheet: (options: TActionSheetOptions) => {
|
||||||
ref.current?.showActionSheet(options);
|
ref.current?.showActionSheet(options);
|
||||||
},
|
},
|
||||||
hideActionSheet: () => {
|
hideActionSheet: () => {
|
||||||
|
@ -37,7 +43,7 @@ export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Ele
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider value={getContext()}>
|
<Provider value={getContext()}>
|
||||||
<ActionSheet ref={ref} theme={theme}>
|
<ActionSheet ref={ref}>
|
||||||
<>{children}</>
|
<>{children}</>
|
||||||
</ActionSheet>
|
</ActionSheet>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -3,11 +3,8 @@ import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
|
import { useTheme } from '../theme';
|
||||||
|
|
||||||
interface ICheck {
|
|
||||||
style?: object;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
icon: {
|
icon: {
|
||||||
width: 22,
|
width: 22,
|
||||||
|
@ -16,8 +13,9 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Check = React.memo(({ theme, style }: ICheck) => (
|
const Check = React.memo(() => {
|
||||||
<CustomIcon style={[styles.icon, style]} color={themes[theme].tintColor} size={22} name='check' />
|
const { theme } = useTheme();
|
||||||
));
|
return <CustomIcon style={styles.icon} color={themes[theme].tintColor} size={22} name='check' />;
|
||||||
|
});
|
||||||
|
|
||||||
export default Check;
|
export default Check;
|
||||||
|
|
|
@ -14,9 +14,18 @@ import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
||||||
import { goRoom } from '../../utils/goRoom';
|
import { goRoom } from '../../utils/goRoom';
|
||||||
import Navigation from '../../lib/Navigation';
|
import Navigation from '../../lib/Navigation';
|
||||||
import { useOrientation } from '../../dimensions';
|
import { useOrientation } from '../../dimensions';
|
||||||
|
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';
|
||||||
|
|
||||||
interface INotifierComponent {
|
export interface INotifierComponent {
|
||||||
notification: object;
|
notification: {
|
||||||
|
text: string;
|
||||||
|
payload: {
|
||||||
|
sender: { username: string };
|
||||||
|
type: SubscriptionType;
|
||||||
|
} & Pick<ISubscription, '_id' | 'name' | 'rid' | 'prid'>;
|
||||||
|
title: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,15 +76,15 @@ const styles = StyleSheet.create({
|
||||||
const hideNotification = () => Notifier.hideNotification();
|
const hideNotification = () => Notifier.hideNotification();
|
||||||
|
|
||||||
const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifierComponent) => {
|
const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifierComponent) => {
|
||||||
const { theme }: any = useTheme();
|
const { theme } = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { isLandscape } = useOrientation();
|
const { isLandscape } = useOrientation();
|
||||||
|
|
||||||
const { text, payload }: any = notification;
|
const { text, payload } = notification;
|
||||||
const { type, rid } = payload;
|
const { type, rid } = payload;
|
||||||
const name = type === 'd' ? payload.sender.username : payload.name;
|
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||||
// if sub is not on local database, title and avatar will be null, so we use payload from notification
|
// if sub is not on local database, title and avatar will be null, so we use payload from notification
|
||||||
const { title = name, avatar = name }: any = notification;
|
const { title = name, avatar = name } = notification;
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
const { prid, _id } = payload;
|
const { prid, _id } = payload;
|
||||||
|
@ -133,7 +142,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,18 @@ import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
|
||||||
import NotifierComponent from './NotifierComponent';
|
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import Navigation from '../../lib/Navigation';
|
import Navigation from '../../lib/Navigation';
|
||||||
import { getActiveRoute } from '../../utils/navigation';
|
import { getActiveRoute } from '../../utils/navigation';
|
||||||
|
import { IApplicationState } from '../../definitions';
|
||||||
|
import { IRoom } from '../../reducers/room';
|
||||||
|
|
||||||
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
||||||
|
|
||||||
const InAppNotification = memo(
|
const InAppNotification = memo(
|
||||||
({ rooms, appState }: { rooms: any; appState: string }) => {
|
({ rooms, appState }: { rooms: IRoom['rooms']; appState: string }) => {
|
||||||
const show = (notification: any) => {
|
const show = (notification: INotifierComponent['notification']) => {
|
||||||
if (appState !== 'foreground') {
|
if (appState !== 'foreground') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +48,7 @@ const InAppNotification = memo(
|
||||||
(prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)
|
(prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
rooms: state.room.rooms,
|
rooms: state.room.rooms,
|
||||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Animated, Easing, Linking, StyleSheet, Text, View } from 'react-native'
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
|
@ -15,6 +16,9 @@ import random from '../utils/random';
|
||||||
import { events, logEvent } from '../utils/log';
|
import { events, logEvent } from '../utils/log';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
|
import { IServices } from '../selectors/login';
|
||||||
|
import { OutsideParamList } from '../stacks/types';
|
||||||
|
import { IApplicationState } from '../definitions';
|
||||||
|
|
||||||
const BUTTON_HEIGHT = 48;
|
const BUTTON_HEIGHT = 48;
|
||||||
const SERVICE_HEIGHT = 58;
|
const SERVICE_HEIGHT = 58;
|
||||||
|
@ -58,31 +62,40 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IOpenOAuth {
|
interface IOpenOAuth {
|
||||||
url?: string;
|
url: string;
|
||||||
ssoToken?: string;
|
ssoToken?: string;
|
||||||
authType?: string;
|
authType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IService {
|
interface IItemService {
|
||||||
name: string;
|
name: string;
|
||||||
service: string;
|
service: string;
|
||||||
authType: string;
|
authType: string;
|
||||||
buttonColor: string;
|
buttonColor: string;
|
||||||
buttonLabelColor: string;
|
buttonLabelColor: string;
|
||||||
|
clientConfig: { provider: string };
|
||||||
|
serverURL: string;
|
||||||
|
authorizePath: string;
|
||||||
|
clientId: string;
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IOauthProvider {
|
||||||
|
[key: string]: () => void;
|
||||||
|
facebook: () => void;
|
||||||
|
github: () => void;
|
||||||
|
gitlab: () => void;
|
||||||
|
google: () => void;
|
||||||
|
linkedin: () => void;
|
||||||
|
'meteor-developer': () => void;
|
||||||
|
twitter: () => void;
|
||||||
|
wordpress: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILoginServicesProps {
|
interface ILoginServicesProps {
|
||||||
navigation: any;
|
navigation: StackNavigationProp<OutsideParamList>;
|
||||||
server: string;
|
server: string;
|
||||||
services: {
|
services: IServices;
|
||||||
facebook: { clientId: string };
|
|
||||||
github: { clientId: string };
|
|
||||||
gitlab: { clientId: string };
|
|
||||||
google: { clientId: string };
|
|
||||||
linkedin: { clientId: string };
|
|
||||||
'meteor-developer': { clientId: string };
|
|
||||||
wordpress: { clientId: string; serverURL: string };
|
|
||||||
};
|
|
||||||
Gitlab_URL: string;
|
Gitlab_URL: string;
|
||||||
CAS_enabled: boolean;
|
CAS_enabled: boolean;
|
||||||
CAS_login_url: string;
|
CAS_login_url: string;
|
||||||
|
@ -90,12 +103,13 @@ interface ILoginServicesProps {
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
interface ILoginServicesState {
|
||||||
private _animation: any;
|
collapsed: boolean;
|
||||||
|
servicesHeight: Animated.Value;
|
||||||
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServicesState> {
|
||||||
separator: true
|
private _animation?: Animated.CompositeAnimation | void;
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
|
@ -194,7 +208,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
this.openOAuth({ url: `${endpoint}${params}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
onPressCustomOAuth = (loginService: any) => {
|
onPressCustomOAuth = (loginService: IItemService) => {
|
||||||
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
||||||
const { server } = this.props;
|
const { server } = this.props;
|
||||||
const { serverURL, authorizePath, clientId, scope, service } = loginService;
|
const { serverURL, authorizePath, clientId, scope, service } = loginService;
|
||||||
|
@ -207,7 +221,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
this.openOAuth({ url });
|
this.openOAuth({ url });
|
||||||
};
|
};
|
||||||
|
|
||||||
onPressSaml = (loginService: any) => {
|
onPressSaml = (loginService: IItemService) => {
|
||||||
logEvent(events.ENTER_WITH_SAML);
|
logEvent(events.ENTER_WITH_SAML);
|
||||||
const { server } = this.props;
|
const { server } = this.props;
|
||||||
const { clientConfig } = loginService;
|
const { clientConfig } = loginService;
|
||||||
|
@ -234,7 +248,6 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
// @ts-ignore
|
|
||||||
await RocketChat.loginOAuthOrSso({ fullName, email, identityToken });
|
await RocketChat.loginOAuthOrSso({ fullName, email, identityToken });
|
||||||
} catch {
|
} catch {
|
||||||
logEvent(events.ENTER_WITH_APPLE_F);
|
logEvent(events.ENTER_WITH_APPLE_F);
|
||||||
|
@ -243,7 +256,12 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
|
|
||||||
getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
|
getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
|
||||||
const credentialToken = random(43);
|
const credentialToken = random(43);
|
||||||
let obj: any = { loginStyle, credentialToken, isCordova: true };
|
let obj: {
|
||||||
|
loginStyle: string;
|
||||||
|
credentialToken: string;
|
||||||
|
isCordova: boolean;
|
||||||
|
redirectUrl?: string;
|
||||||
|
} = { loginStyle, credentialToken, isCordova: true };
|
||||||
if (loginStyle === LOGIN_STYPE_REDIRECT) {
|
if (loginStyle === LOGIN_STYPE_REDIRECT) {
|
||||||
obj = {
|
obj = {
|
||||||
...obj,
|
...obj,
|
||||||
|
@ -263,12 +281,11 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
if (this._animation) {
|
if (this._animation) {
|
||||||
this._animation.stop();
|
this._animation.stop();
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
|
||||||
this._animation = Animated.timing(servicesHeight, {
|
this._animation = Animated.timing(servicesHeight, {
|
||||||
toValue: height,
|
toValue: height,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
// @ts-ignore
|
easing: Easing.inOut(Easing.quad),
|
||||||
easing: Easing.easeOutCubic
|
useNativeDriver: true
|
||||||
}).start();
|
}).start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -281,11 +298,11 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
} else {
|
} else {
|
||||||
this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT);
|
this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT);
|
||||||
}
|
}
|
||||||
this.setState((prevState: any) => ({ collapsed: !prevState.collapsed }));
|
this.setState((prevState: ILoginServicesState) => ({ collapsed: !prevState.collapsed }));
|
||||||
};
|
};
|
||||||
|
|
||||||
getSocialOauthProvider = (name: string) => {
|
getSocialOauthProvider = (name: string) => {
|
||||||
const oauthProviders: any = {
|
const oauthProviders: IOauthProvider = {
|
||||||
facebook: this.onPressFacebook,
|
facebook: this.onPressFacebook,
|
||||||
github: this.onPressGithub,
|
github: this.onPressGithub,
|
||||||
gitlab: this.onPressGitlab,
|
gitlab: this.onPressGitlab,
|
||||||
|
@ -324,7 +341,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderItem = (service: IService) => {
|
renderItem = (service: IItemService) => {
|
||||||
const { CAS_enabled, theme } = this.props;
|
const { CAS_enabled, theme } = this.props;
|
||||||
let { name } = service;
|
let { name } = service;
|
||||||
name = name === 'meteor-developer' ? 'meteor' : name;
|
name = name === 'meteor-developer' ? 'meteor' : name;
|
||||||
|
@ -401,26 +418,28 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, any> {
|
||||||
if (length > 3 && separator) {
|
if (length > 3 && separator) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Animated.View style={style}>{Object.values(services).map((service: any) => this.renderItem(service))}</Animated.View>
|
<Animated.View style={style}>
|
||||||
|
{Object.values(services).map((service: IItemService) => this.renderItem(service))}
|
||||||
|
</Animated.View>
|
||||||
{this.renderServicesSeparator()}
|
{this.renderServicesSeparator()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Object.values(services).map((service: any) => this.renderItem(service))}
|
{Object.values(services).map((service: IItemService) => this.renderItem(service))}
|
||||||
{this.renderServicesSeparator()}
|
{this.renderServicesSeparator()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
Gitlab_URL: state.settings.API_Gitlab_URL,
|
Gitlab_URL: state.settings.API_Gitlab_URL as string,
|
||||||
CAS_enabled: state.settings.CAS_enabled,
|
CAS_enabled: state.settings.CAS_enabled as boolean,
|
||||||
CAS_login_url: state.settings.CAS_login_url,
|
CAS_login_url: state.settings.CAS_login_url as string,
|
||||||
services: state.login.services
|
services: state.login.services as IServices
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(LoginServices)) as any;
|
export default connect(mapStateToProps)(withTheme(LoginServices));
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Q } from '@nozbe/watermelondb';
|
||||||
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import { generateTriggerId } from '../../lib/methods/actions';
|
import { generateTriggerId } from '../../lib/methods/actions';
|
||||||
import TextInput from '../../presentation/TextInput';
|
import TextInput, { IThemedTextInput } from '../../presentation/TextInput';
|
||||||
import { userTyping as userTypingAction } from '../../actions/room';
|
import { userTyping as userTypingAction } from '../../actions/room';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -1038,7 +1038,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
tmid
|
tmid
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const isAndroidTablet =
|
const isAndroidTablet: Partial<IThemedTextInput> =
|
||||||
isTablet && isAndroid
|
isTablet && isAndroid
|
||||||
? {
|
? {
|
||||||
multiline: false,
|
multiline: false,
|
||||||
|
@ -1090,7 +1090,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={component => (this.component = component)}
|
ref={component => (this.component = component)}
|
||||||
style={[styles.textBoxInput, { color: themes[theme].bodyText }]}
|
style={[styles.textBoxInput, { color: themes[theme].bodyText }]}
|
||||||
// @ts-ignore
|
|
||||||
returnKeyType='default'
|
returnKeyType='default'
|
||||||
keyboardType='twitter'
|
keyboardType='twitter'
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { forwardRef, useImperativeHandle } from 'react';
|
import { forwardRef, useImperativeHandle } from 'react';
|
||||||
|
import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
|
@ -6,18 +7,20 @@ import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
import { useActionSheet } from './ActionSheet';
|
import { useActionSheet } from './ActionSheet';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
|
import { TMessageModel } from '../definitions';
|
||||||
|
|
||||||
const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
|
const MessageErrorActions = forwardRef(({ tmid }: { tmid: string }, ref) => {
|
||||||
|
// TODO - remove this any after merge ActionSheet evaluate
|
||||||
const { showActionSheet }: any = useActionSheet();
|
const { showActionSheet }: any = useActionSheet();
|
||||||
|
|
||||||
const handleResend = protectedFunction(async (message: any) => {
|
const handleResend = protectedFunction(async (message: TMessageModel) => {
|
||||||
await RocketChat.resendMessage(message, tmid);
|
await RocketChat.resendMessage(message, tmid);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async (message: any) => {
|
const handleDelete = async (message: TMessageModel) => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const deleteBatch: any = [];
|
const deleteBatch: Model[] = [];
|
||||||
const msgCollection = db.get('messages');
|
const msgCollection = db.get('messages');
|
||||||
const threadCollection = db.get('threads');
|
const threadCollection = db.get('threads');
|
||||||
|
|
||||||
|
@ -38,7 +41,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
|
||||||
const msg = await msgCollection.find(tmid);
|
const msg = await msgCollection.find(tmid);
|
||||||
if (msg?.tcount && msg.tcount <= 1) {
|
if (msg?.tcount && msg.tcount <= 1) {
|
||||||
deleteBatch.push(
|
deleteBatch.push(
|
||||||
msg.prepareUpdate((m: any) => {
|
msg.prepareUpdate(m => {
|
||||||
m.tcount = null;
|
m.tcount = null;
|
||||||
m.tlm = null;
|
m.tlm = null;
|
||||||
})
|
})
|
||||||
|
@ -53,8 +56,10 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleteBatch.push(
|
deleteBatch.push(
|
||||||
msg.prepareUpdate((m: any) => {
|
msg.prepareUpdate(m => {
|
||||||
|
if (m.tcount) {
|
||||||
m.tcount -= 1;
|
m.tcount -= 1;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +75,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showMessageErrorActions = (message: any) => {
|
const showMessageErrorActions = (message: TMessageModel) => {
|
||||||
showActionSheet({
|
showActionSheet({
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,16 +5,18 @@ import styles from './styles';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import Touch from '../../../utils/touch';
|
import Touch from '../../../utils/touch';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IPasscodeButton {
|
interface IPasscodeButton {
|
||||||
text?: string;
|
text?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
theme: string;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress?: Function;
|
onPress?: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.memo(({ text, disabled, theme, onPress, icon }: IPasscodeButton) => {
|
const Button = React.memo(({ text, disabled, onPress, icon }: IPasscodeButton) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const press = () => onPress && onPress(text);
|
const press = () => onPress && onPress(text);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,17 +4,20 @@ import range from 'lodash/range';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
const SIZE_EMPTY = 12;
|
const SIZE_EMPTY = 12;
|
||||||
const SIZE_FULL = 16;
|
const SIZE_FULL = 16;
|
||||||
|
|
||||||
interface IPasscodeDots {
|
interface IPasscodeDots {
|
||||||
passcode: string;
|
passcode: string;
|
||||||
theme: string;
|
|
||||||
length: number;
|
length: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dots = React.memo(({ passcode, theme, length }: IPasscodeDots) => (
|
const Dots = React.memo(({ passcode, length }: IPasscodeDots) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
<View style={styles.dotsContainer}>
|
<View style={styles.dotsContainer}>
|
||||||
{range(length).map(val => {
|
{range(length).map(val => {
|
||||||
const lengthSup = passcode.length >= val + 1;
|
const lengthSup = passcode.length >= val + 1;
|
||||||
|
@ -45,6 +48,7 @@ const Dots = React.memo(({ passcode, theme, length }: IPasscodeDots) => (
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default Dots;
|
export default Dots;
|
||||||
|
|
|
@ -5,13 +5,18 @@ import { Row } from 'react-native-easy-grid';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
const LockIcon = React.memo(({ theme }: { theme: string }) => (
|
const LockIcon = React.memo(() => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
<Row style={styles.row}>
|
<Row style={styles.row}>
|
||||||
<View style={styles.iconView}>
|
<View style={styles.iconView}>
|
||||||
<CustomIcon name='auth' size={40} color={themes[theme].passcodeLockIcon} />
|
<CustomIcon name='auth' size={40} color={themes[theme].passcodeLockIcon} />
|
||||||
</View>
|
</View>
|
||||||
</Row>
|
</Row>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default LockIcon;
|
export default LockIcon;
|
||||||
|
|
|
@ -6,36 +6,35 @@ import { resetAttempts } from '../../../utils/localAuthentication';
|
||||||
import { TYPE } from '../constants';
|
import { TYPE } from '../constants';
|
||||||
import { getDiff, getLockedUntil } from '../utils';
|
import { getDiff, getLockedUntil } from '../utils';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import Subtitle from './Subtitle';
|
import Subtitle from './Subtitle';
|
||||||
import LockIcon from './LockIcon';
|
import LockIcon from './LockIcon';
|
||||||
|
|
||||||
interface IPasscodeTimer {
|
interface IPasscodeTimer {
|
||||||
time: string;
|
time: Date | null;
|
||||||
theme: string;
|
|
||||||
setStatus: Function;
|
setStatus: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPasscodeLocked {
|
interface IPasscodeLocked {
|
||||||
theme: string;
|
|
||||||
setStatus: Function;
|
setStatus: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Timer = React.memo(({ time, theme, setStatus }: IPasscodeTimer) => {
|
const Timer = React.memo(({ time, setStatus }: IPasscodeTimer) => {
|
||||||
const calcTimeLeft = () => {
|
const calcTimeLeft = () => {
|
||||||
const diff = getDiff(time);
|
const diff = getDiff(time || 0);
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
return Math.floor((diff / 1000) % 60);
|
return Math.floor((diff / 1000) % 60);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const [timeLeft, setTimeLeft] = useState<any>(calcTimeLeft());
|
const [timeLeft, setTimeLeft] = useState(calcTimeLeft());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setTimeLeft(calcTimeLeft());
|
setTimeLeft(calcTimeLeft());
|
||||||
if (timeLeft <= 1) {
|
if (timeLeft && timeLeft <= 1) {
|
||||||
resetAttempts();
|
resetAttempts();
|
||||||
setStatus(TYPE.ENTER);
|
setStatus(TYPE.ENTER);
|
||||||
}
|
}
|
||||||
|
@ -46,11 +45,12 @@ const Timer = React.memo(({ time, theme, setStatus }: IPasscodeTimer) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Subtitle text={I18n.t('Passcode_app_locked_subtitle', { timeLeft })} theme={theme} />;
|
return <Subtitle text={I18n.t('Passcode_app_locked_subtitle', { timeLeft })} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
const Locked = React.memo(({ theme, setStatus }: IPasscodeLocked) => {
|
const Locked = React.memo(({ setStatus }: IPasscodeLocked) => {
|
||||||
const [lockedUntil, setLockedUntil] = useState<any>(null);
|
const [lockedUntil, setLockedUntil] = useState<Date | null>(null);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const readItemFromStorage = async () => {
|
const readItemFromStorage = async () => {
|
||||||
const l = await getLockedUntil();
|
const l = await getLockedUntil();
|
||||||
|
@ -63,9 +63,9 @@ const Locked = React.memo(({ theme, setStatus }: IPasscodeLocked) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}>
|
<Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}>
|
||||||
<LockIcon theme={theme} />
|
<LockIcon />
|
||||||
<Title text={I18n.t('Passcode_app_locked_title')} theme={theme} />
|
<Title text={I18n.t('Passcode_app_locked_title')} />
|
||||||
<Timer theme={theme} time={lockedUntil} setStatus={setStatus} />
|
<Timer time={lockedUntil} setStatus={setStatus} />
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,18 +4,22 @@ import { Row } from 'react-native-easy-grid';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IPasscodeSubtitle {
|
interface IPasscodeSubtitle {
|
||||||
text: string;
|
text: string;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Subtitle = React.memo(({ text, theme }: IPasscodeSubtitle) => (
|
const Subtitle = React.memo(({ text }: IPasscodeSubtitle) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
<Row style={styles.row}>
|
<Row style={styles.row}>
|
||||||
<View style={styles.subtitleView}>
|
<View style={styles.subtitleView}>
|
||||||
<Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text>
|
<Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Row>
|
</Row>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default Subtitle;
|
export default Subtitle;
|
||||||
|
|
|
@ -4,18 +4,22 @@ import { Row } from 'react-native-easy-grid';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IPasscodeTitle {
|
interface IPasscodeTitle {
|
||||||
text: string;
|
text: string;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Title = React.memo(({ text, theme }: IPasscodeTitle) => (
|
const Title = React.memo(({ text }: IPasscodeTitle) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
<Row style={styles.row}>
|
<Row style={styles.row}>
|
||||||
<View style={styles.titleView}>
|
<View style={styles.titleView}>
|
||||||
<Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text>
|
<Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Row>
|
</Row>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default Title;
|
export default Title;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||||
import { Col, Grid, Row } from 'react-native-easy-grid';
|
import { Col, Grid, Row } from 'react-native-easy-grid';
|
||||||
import range from 'lodash/range';
|
import range from 'lodash/range';
|
||||||
|
import { View } from 'react-native';
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
|
||||||
|
@ -10,12 +11,12 @@ import Dots from './Dots';
|
||||||
import { TYPE } from '../constants';
|
import { TYPE } from '../constants';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import { PASSCODE_LENGTH } from '../../../constants/localAuthentication';
|
import { PASSCODE_LENGTH } from '../../../constants/localAuthentication';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
import LockIcon from './LockIcon';
|
import LockIcon from './LockIcon';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import Subtitle from './Subtitle';
|
import Subtitle from './Subtitle';
|
||||||
|
|
||||||
interface IPasscodeBase {
|
interface IPasscodeBase {
|
||||||
theme: string;
|
|
||||||
type: string;
|
type: string;
|
||||||
previousPasscode?: string;
|
previousPasscode?: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -26,25 +27,30 @@ interface IPasscodeBase {
|
||||||
onBiometryPress?(): void;
|
onBiometryPress?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Base = forwardRef(
|
export interface IBase {
|
||||||
(
|
clearPasscode: () => void;
|
||||||
{ theme, type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }: IPasscodeBase,
|
wrongPasscode: () => void;
|
||||||
ref
|
animate: (animation: Animatable.Animation, duration?: number) => void;
|
||||||
) => {
|
}
|
||||||
const rootRef = useRef<any>();
|
|
||||||
const dotsRef = useRef<any>();
|
const Base = forwardRef<IBase, IPasscodeBase>(
|
||||||
|
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
const rootRef = useRef<Animatable.View & View>(null);
|
||||||
|
const dotsRef = useRef<Animatable.View & View>(null);
|
||||||
const [passcode, setPasscode] = useState('');
|
const [passcode, setPasscode] = useState('');
|
||||||
|
|
||||||
const clearPasscode = () => setPasscode('');
|
const clearPasscode = () => setPasscode('');
|
||||||
|
|
||||||
const wrongPasscode = () => {
|
const wrongPasscode = () => {
|
||||||
clearPasscode();
|
clearPasscode();
|
||||||
dotsRef?.current?.shake(500);
|
dotsRef?.current?.shake?.(500);
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||||
};
|
};
|
||||||
|
|
||||||
const animate = (animation: string, duration = 500) => {
|
const animate = (animation: Animatable.Animation, duration = 500) => {
|
||||||
rootRef?.current?.[animation](duration);
|
rootRef?.current?.[animation]?.(duration);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPressNumber = (text: string) =>
|
const onPressNumber = (text: string) =>
|
||||||
|
@ -90,48 +96,48 @@ const Base = forwardRef(
|
||||||
return (
|
return (
|
||||||
<Animatable.View ref={rootRef} style={styles.container}>
|
<Animatable.View ref={rootRef} style={styles.container}>
|
||||||
<Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}>
|
<Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}>
|
||||||
<LockIcon theme={theme} />
|
<LockIcon />
|
||||||
<Title text={title} theme={theme} />
|
<Title text={title} />
|
||||||
<Subtitle text={subtitle!} theme={theme} />
|
{subtitle ? <Subtitle text={subtitle} /> : null}
|
||||||
<Row style={styles.row}>
|
<Row style={styles.row}>
|
||||||
<Animatable.View ref={dotsRef}>
|
<Animatable.View ref={dotsRef}>
|
||||||
<Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} />
|
<Dots passcode={passcode} length={PASSCODE_LENGTH} />
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, styles.buttonRow]}>
|
||||||
{range(1, 4).map((i: any) => (
|
{range(1, 4).map(i => (
|
||||||
<Col key={i} style={styles.colButton}>
|
<Col key={i} style={styles.colButton}>
|
||||||
<Button text={i} theme={theme} onPress={onPressNumber} />
|
<Button text={i.toString()} onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, styles.buttonRow]}>
|
||||||
{range(4, 7).map((i: any) => (
|
{range(4, 7).map(i => (
|
||||||
<Col key={i} style={styles.colButton}>
|
<Col key={i} style={styles.colButton}>
|
||||||
<Button text={i} theme={theme} onPress={onPressNumber} />
|
<Button text={i.toString()} onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, styles.buttonRow]}>
|
||||||
{range(7, 10).map((i: any) => (
|
{range(7, 10).map(i => (
|
||||||
<Col key={i} style={styles.colButton}>
|
<Col key={i} style={styles.colButton}>
|
||||||
<Button text={i} theme={theme} onPress={onPressNumber} />
|
<Button text={i.toString()} onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, styles.buttonRow]}>
|
||||||
{showBiometry ? (
|
{showBiometry ? (
|
||||||
<Col style={styles.colButton}>
|
<Col style={styles.colButton}>
|
||||||
<Button icon='fingerprint' theme={theme} onPress={onBiometryPress} />
|
<Button icon='fingerprint' onPress={onBiometryPress} />
|
||||||
</Col>
|
</Col>
|
||||||
) : (
|
) : (
|
||||||
<Col style={styles.colButton} />
|
<Col style={styles.colButton} />
|
||||||
)}
|
)}
|
||||||
<Col style={styles.colButton}>
|
<Col style={styles.colButton}>
|
||||||
<Button text='0' theme={theme} onPress={onPressNumber} />
|
<Button text='0' onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={styles.colButton}>
|
<Col style={styles.colButton}>
|
||||||
<Button icon='backspace' theme={theme} onPress={onPressDelete} />
|
<Button icon='backspace' onPress={onPressDelete} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -2,24 +2,23 @@ import React, { useRef, useState } from 'react';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import Base from './Base';
|
import Base, { IBase } from './Base';
|
||||||
import { TYPE } from './constants';
|
import { TYPE } from './constants';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
interface IPasscodeChoose {
|
interface IPasscodeChoose {
|
||||||
theme: string;
|
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
finishProcess: Function;
|
finishProcess: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose) => {
|
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
|
||||||
const chooseRef = useRef<any>(null);
|
const chooseRef = useRef<IBase>(null);
|
||||||
const confirmRef = useRef<any>(null);
|
const confirmRef = useRef<IBase>(null);
|
||||||
const [subtitle, setSubtitle] = useState(null);
|
const [subtitle, setSubtitle] = useState(null);
|
||||||
const [status, setStatus] = useState(TYPE.CHOOSE);
|
const [status, setStatus] = useState(TYPE.CHOOSE);
|
||||||
const [previousPasscode, setPreviouPasscode] = useState<any>(null);
|
const [previousPasscode, setPreviouPasscode] = useState('');
|
||||||
|
|
||||||
const firstStep = (p: any) => {
|
const firstStep = (p: string) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setStatus(TYPE.CONFIRM);
|
setStatus(TYPE.CONFIRM);
|
||||||
setPreviouPasscode(p);
|
setPreviouPasscode(p);
|
||||||
|
@ -43,7 +42,6 @@ const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose
|
||||||
return (
|
return (
|
||||||
<Base
|
<Base
|
||||||
ref={confirmRef}
|
ref={confirmRef}
|
||||||
theme={theme}
|
|
||||||
type={TYPE.CONFIRM}
|
type={TYPE.CONFIRM}
|
||||||
onEndProcess={changePasscode}
|
onEndProcess={changePasscode}
|
||||||
previousPasscode={previousPasscode}
|
previousPasscode={previousPasscode}
|
||||||
|
@ -56,7 +54,6 @@ const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose
|
||||||
return (
|
return (
|
||||||
<Base
|
<Base
|
||||||
ref={chooseRef}
|
ref={chooseRef}
|
||||||
theme={theme}
|
|
||||||
type={TYPE.CHOOSE}
|
type={TYPE.CHOOSE}
|
||||||
onEndProcess={firstStep}
|
onEndProcess={firstStep}
|
||||||
title={I18n.t('Passcode_choose_title')}
|
title={I18n.t('Passcode_choose_title')}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { sha256 } from 'js-sha256';
|
import { sha256 } from 'js-sha256';
|
||||||
|
|
||||||
import Base from './Base';
|
import Base, { IBase } from './Base';
|
||||||
import Locked from './Base/Locked';
|
import Locked from './Base/Locked';
|
||||||
import { TYPE } from './constants';
|
import { TYPE } from './constants';
|
||||||
import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../constants/localAuthentication';
|
import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../constants/localAuthentication';
|
||||||
|
@ -14,18 +14,17 @@ import { useUserPreferences } from '../../lib/userPreferences';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
interface IPasscodePasscodeEnter {
|
interface IPasscodePasscodeEnter {
|
||||||
theme: string;
|
|
||||||
hasBiometry: boolean;
|
hasBiometry: boolean;
|
||||||
finishProcess: Function;
|
finishProcess: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeEnter) => {
|
const PasscodeEnter = ({ hasBiometry, finishProcess }: IPasscodePasscodeEnter) => {
|
||||||
const ref = useRef(null);
|
const ref = useRef<IBase>(null);
|
||||||
let attempts: any = 0;
|
let attempts = 0;
|
||||||
let lockedUntil: any = false;
|
let lockedUntil: any = false;
|
||||||
const [passcode] = useUserPreferences(PASSCODE_KEY);
|
const [passcode] = useUserPreferences(PASSCODE_KEY);
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState<TYPE | null>(null);
|
||||||
const { getItem: getAttempts, setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY);
|
const { setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY);
|
||||||
const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY);
|
const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY);
|
||||||
|
|
||||||
const biometry = async () => {
|
const biometry = async () => {
|
||||||
|
@ -45,7 +44,6 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE
|
||||||
await resetAttempts();
|
await resetAttempts();
|
||||||
setStatus(TYPE.ENTER);
|
setStatus(TYPE.ENTER);
|
||||||
} else {
|
} else {
|
||||||
attempts = await getAttempts();
|
|
||||||
setStatus(TYPE.LOCKED);
|
setStatus(TYPE.LOCKED);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,7 +56,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE
|
||||||
readStorage();
|
readStorage();
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
const onEndProcess = (p: any) => {
|
const onEndProcess = (p: string) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (sha256(p) === passcode) {
|
if (sha256(p) === passcode) {
|
||||||
finishProcess();
|
finishProcess();
|
||||||
|
@ -69,8 +67,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE
|
||||||
setLockedUntil(new Date().toISOString());
|
setLockedUntil(new Date().toISOString());
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
ref?.current?.wrongPasscode();
|
||||||
ref.current.wrongPasscode();
|
|
||||||
setAttempts(attempts?.toString());
|
setAttempts(attempts?.toString());
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||||
}
|
}
|
||||||
|
@ -79,13 +76,12 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE
|
||||||
};
|
};
|
||||||
|
|
||||||
if (status === TYPE.LOCKED) {
|
if (status === TYPE.LOCKED) {
|
||||||
return <Locked theme={theme} setStatus={setStatus} />;
|
return <Locked setStatus={setStatus} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base
|
<Base
|
||||||
ref={ref}
|
ref={ref}
|
||||||
theme={theme}
|
|
||||||
type={TYPE.ENTER}
|
type={TYPE.ENTER}
|
||||||
title={I18n.t('Passcode_enter_title')}
|
title={I18n.t('Passcode_enter_title')}
|
||||||
showBiometry={hasBiometry}
|
showBiometry={hasBiometry}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export const TYPE: any = {
|
export enum TYPE {
|
||||||
CHOOSE: 'choose',
|
CHOOSE = 'choose',
|
||||||
CONFIRM: 'confirm',
|
CONFIRM = 'confirm',
|
||||||
ENTER: 'enter',
|
ENTER = 'enter',
|
||||||
LOCKED: 'locked'
|
LOCKED = 'locked'
|
||||||
};
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ import moment from 'moment';
|
||||||
import { LOCKED_OUT_TIMER_KEY, TIME_TO_LOCK } from '../../constants/localAuthentication';
|
import { LOCKED_OUT_TIMER_KEY, TIME_TO_LOCK } from '../../constants/localAuthentication';
|
||||||
|
|
||||||
export const getLockedUntil = async () => {
|
export const getLockedUntil = async () => {
|
||||||
const t: any = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY);
|
const t = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY);
|
||||||
if (t) {
|
if (t) {
|
||||||
return moment(t).add(TIME_TO_LOCK);
|
return moment(t).add(TIME_TO_LOCK).toDate();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
|
||||||
export const getDiff = t => new Date(t) - new Date();
|
export const getDiff = (t: string | number | Date) => new Date(t).getTime() - new Date().getTime();
|
||||||
|
|
|
@ -52,10 +52,7 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
export interface IRCTextInputProps extends TextInputProps {
|
export interface IRCTextInputProps extends TextInputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
error?: {
|
error?: any;
|
||||||
error: any;
|
|
||||||
reason: any;
|
|
||||||
};
|
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
containerStyle?: StyleProp<ViewStyle>;
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
inputStyle?: StyleProp<TextStyle>;
|
inputStyle?: StyleProp<TextStyle>;
|
||||||
|
@ -68,7 +65,11 @@ export interface IRCTextInputProps extends TextInputProps {
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RCTextInput extends React.PureComponent<IRCTextInputProps, any> {
|
interface IRCTextInputState {
|
||||||
|
showPassword: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RCTextInput extends React.PureComponent<IRCTextInputProps, IRCTextInputState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
error: {},
|
error: {},
|
||||||
theme: 'light'
|
theme: 'light'
|
||||||
|
@ -120,7 +121,7 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
tooglePassword = () => {
|
tooglePassword = () => {
|
||||||
this.setState((prevState: any) => ({ showPassword: !prevState.showPassword }));
|
this.setState(prevState => ({ showPassword: !prevState.showPassword }));
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -41,7 +41,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IThreadDetails {
|
interface IThreadDetails {
|
||||||
item: Partial<TThreadModel>;
|
item: Pick<TThreadModel, 'tcount' | 'replies' | 'id'>;
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
@ -52,7 +52,7 @@ interface IThreadDetails {
|
||||||
|
|
||||||
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IThreadDetails): JSX.Element => {
|
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IThreadDetails): JSX.Element => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
let count: string | number | undefined = item.tcount;
|
let count: string | number | undefined | null = item.tcount;
|
||||||
if (count && count >= 1000) {
|
if (count && count >= 1000) {
|
||||||
count = '+999';
|
count = '+999';
|
||||||
}
|
}
|
||||||
|
@ -62,21 +62,21 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IT
|
||||||
replies = '+999';
|
replies = '+999';
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFollowing = item.replies?.find((u: any) => u === user?.id);
|
const isFollowing = item.replies?.find((u: string) => u === user?.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
<View style={styles.detailsContainer}>
|
<View style={styles.detailsContainer}>
|
||||||
<View style={styles.detailContainer}>
|
<View style={styles.detailContainer}>
|
||||||
<CustomIcon name='threads' size={24} color={themes[theme!].auxiliaryText} />
|
<CustomIcon name='threads' size={24} color={themes[theme].auxiliaryText} />
|
||||||
<Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||||
{count}
|
{count}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.detailContainer}>
|
<View style={styles.detailContainer}>
|
||||||
<CustomIcon name='user' size={24} color={themes[theme!].auxiliaryText} />
|
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} />
|
||||||
<Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||||
{replies}
|
{replies}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -87,7 +87,7 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IT
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
size={24}
|
size={24}
|
||||||
name={isFollowing ? 'notification' : 'notification-disabled'}
|
name={isFollowing ? 'notification' : 'notification-disabled'}
|
||||||
color={themes[theme!].auxiliaryTintColor}
|
color={themes[theme].auxiliaryTintColor}
|
||||||
/>
|
/>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -9,21 +9,36 @@ import { connect } from 'react-redux';
|
||||||
import TextInput from '../TextInput';
|
import TextInput from '../TextInput';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import { withTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { IApplicationState } from '../../definitions';
|
||||||
|
|
||||||
export const TWO_FACTOR = 'TWO_FACTOR';
|
export const TWO_FACTOR = 'TWO_FACTOR';
|
||||||
|
|
||||||
interface ITwoFactor {
|
interface IMethodsProp {
|
||||||
theme?: string;
|
text: string;
|
||||||
isMasterDetail: boolean;
|
keyboardType: 'numeric' | 'default';
|
||||||
|
title?: string;
|
||||||
|
secureTextEntry?: boolean;
|
||||||
|
}
|
||||||
|
interface IMethods {
|
||||||
|
totp: IMethodsProp;
|
||||||
|
email: IMethodsProp;
|
||||||
|
password: IMethodsProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
const methods: any = {
|
interface EventListenerMethod {
|
||||||
|
method?: keyof IMethods;
|
||||||
|
submit?: (param: string) => void;
|
||||||
|
cancel?: () => void;
|
||||||
|
invalid?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const methods: IMethods = {
|
||||||
totp: {
|
totp: {
|
||||||
text: 'Open_your_authentication_app_and_enter_the_code',
|
text: 'Open_your_authentication_app_and_enter_the_code',
|
||||||
keyboardType: 'numeric'
|
keyboardType: 'numeric'
|
||||||
|
@ -40,14 +55,14 @@ const methods: any = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
|
const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [data, setData] = useState<any>({});
|
const [data, setData] = useState<EventListenerMethod>({});
|
||||||
const [code, setCode] = useState<any>('');
|
const [code, setCode] = useState<string>('');
|
||||||
|
|
||||||
const method = methods[data.method];
|
const method = data.method ? methods[data.method] : null;
|
||||||
const isEmail = data.method === 'email';
|
const isEmail = data.method === 'email';
|
||||||
|
|
||||||
const sendEmail = () => RocketChat.sendEmailCode();
|
const sendEmail = () => RocketChat.sendEmailCode();
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
|
@ -59,7 +74,7 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const showTwoFactor = (args: any) => setData(args);
|
const showTwoFactor = (args: EventListenerMethod) => setData(args);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor);
|
const listener = EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor);
|
||||||
|
@ -87,26 +102,19 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
|
||||||
setData({});
|
setData({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const color = themes[theme!].titleText;
|
const color = themes[theme].titleText;
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating>
|
||||||
// @ts-ignore
|
|
||||||
transparent
|
|
||||||
avoidKeyboard
|
|
||||||
useNativeDriver
|
|
||||||
isVisible={visible}
|
|
||||||
hideModalContentWhileAnimating>
|
|
||||||
<View style={styles.container} testID='two-factor'>
|
<View style={styles.container} testID='two-factor'>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.content,
|
styles.content,
|
||||||
isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet],
|
isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet],
|
||||||
{ backgroundColor: themes[theme!].backgroundColor }
|
{ backgroundColor: themes[theme].backgroundColor }
|
||||||
]}>
|
]}>
|
||||||
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
||||||
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
||||||
<TextInput
|
<TextInput
|
||||||
/* @ts-ignore*/
|
|
||||||
value={code}
|
value={code}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
||||||
|
@ -116,19 +124,19 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
|
||||||
onSubmitEditing={onSubmit}
|
onSubmitEditing={onSubmit}
|
||||||
keyboardType={method?.keyboardType}
|
keyboardType={method?.keyboardType}
|
||||||
secureTextEntry={method?.secureTextEntry}
|
secureTextEntry={method?.secureTextEntry}
|
||||||
error={data.invalid && { error: 'totp-invalid', reason: I18n.t('Code_or_password_invalid') }}
|
error={data.invalid ? { error: 'totp-invalid', reason: I18n.t('Code_or_password_invalid') } : undefined}
|
||||||
testID='two-factor-input'
|
testID='two-factor-input'
|
||||||
/>
|
/>
|
||||||
{isEmail && (
|
{isEmail ? (
|
||||||
<Text style={[styles.sendEmail, { color }]} onPress={sendEmail}>
|
<Text style={[styles.sendEmail, { color }]} onPress={sendEmail}>
|
||||||
{I18n.t('Send_me_the_code_again')}
|
{I18n.t('Send_me_the_code_again')}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
) : null}
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
title={I18n.t('Cancel')}
|
title={I18n.t('Cancel')}
|
||||||
type='secondary'
|
type='secondary'
|
||||||
backgroundColor={themes[theme!].chatComponentBackground}
|
backgroundColor={themes[theme].chatComponentBackground}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
onPress={onCancel}
|
onPress={onCancel}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
@ -148,8 +156,8 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(TwoFactor));
|
export default connect(mapStateToProps)(TwoFactor);
|
||||||
|
|
|
@ -41,7 +41,7 @@ const Item = ({ item, selected, onSelect, theme }: IItem) => {
|
||||||
<>
|
<>
|
||||||
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
|
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
|
||||||
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
|
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
|
||||||
{selected ? <Check theme={theme} /> : null}
|
{selected ? <Check /> : null}
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
||||||
|
|
|
@ -126,7 +126,6 @@ export const MultiSelect = React.memo(
|
||||||
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<TextInput
|
<TextInput
|
||||||
testID='multi-select-search'
|
testID='multi-select-search'
|
||||||
/* @ts-ignore*/
|
|
||||||
onChangeText={onSearch || onSearchChange}
|
onChangeText={onSearch || onSearchChange}
|
||||||
placeholder={I18n.t('Search')}
|
placeholder={I18n.t('Search')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
|
@ -187,16 +187,14 @@ class ModalParser extends UiKitParserModal {
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<TextInput
|
<TextInput
|
||||||
id={actionId}
|
key={actionId}
|
||||||
placeholder={plainText(placeholder)}
|
placeholder={plainText(placeholder)}
|
||||||
onInput={action}
|
|
||||||
multiline={multiline}
|
multiline={multiline}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChangeText={(text: any) => action({ value: text })}
|
onChangeText={text => action({ value: text })}
|
||||||
inputStyle={multiline && styles.multiline}
|
inputStyle={multiline && styles.multiline}
|
||||||
containerStyle={styles.input}
|
containerStyle={styles.input}
|
||||||
value={value}
|
value={value}
|
||||||
// @ts-ignore
|
|
||||||
error={{ error }}
|
error={{ error }}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Easing, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
||||||
import { Audio } from 'expo-av';
|
import { Audio } from 'expo-av';
|
||||||
import Slider from '@react-native-community/slider';
|
import Slider from '@react-native-community/slider';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -84,12 +84,6 @@ const formatTime = (seconds: number) => moment.utc(seconds * 1000).format('mm:ss
|
||||||
|
|
||||||
const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 };
|
const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 };
|
||||||
|
|
||||||
const sliderAnimationConfig = {
|
|
||||||
duration: 250,
|
|
||||||
easing: Easing.linear,
|
|
||||||
delay: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const Button = React.memo(({ loading, paused, onPress, disabled, theme }: IButton) => (
|
const Button = React.memo(({ loading, paused, onPress, disabled, theme }: IButton) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
style={styles.playPauseButton}
|
style={styles.playPauseButton}
|
||||||
|
@ -285,10 +279,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
value={currentTime}
|
value={currentTime}
|
||||||
maximumValue={duration}
|
maximumValue={duration}
|
||||||
minimumValue={0}
|
minimumValue={0}
|
||||||
// @ts-ignore
|
thumbTintColor={isReply && isAndroid ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
|
||||||
animateTransitions
|
|
||||||
animationConfig={sliderAnimationConfig}
|
|
||||||
thumbTintColor={isReply ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
|
|
||||||
minimumTrackTintColor={themes[theme].tintColor}
|
minimumTrackTintColor={themes[theme].tintColor}
|
||||||
maximumTrackTintColor={themes[theme].auxiliaryText}
|
maximumTrackTintColor={themes[theme].auxiliaryText}
|
||||||
onValueChange={this.onValueChange}
|
onValueChange={this.onValueChange}
|
||||||
|
|
|
@ -39,9 +39,7 @@ const Content = React.memo(
|
||||||
const isPreview: any = props.tmid && !props.isThreadRoom;
|
const isPreview: any = props.tmid && !props.isThreadRoom;
|
||||||
let content = null;
|
let content = null;
|
||||||
|
|
||||||
if (props.tmid && !props.msg) {
|
if (props.isEncrypted) {
|
||||||
content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>;
|
|
||||||
} else if (props.isEncrypted) {
|
|
||||||
content = (
|
content = (
|
||||||
<Text
|
<Text
|
||||||
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
|
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
|
||||||
|
|
|
@ -98,7 +98,7 @@ export interface IMessageEmoji {
|
||||||
|
|
||||||
export interface IMessageThread {
|
export interface IMessageThread {
|
||||||
msg?: string;
|
msg?: string;
|
||||||
tcount?: number;
|
tcount?: number | null;
|
||||||
theme: string;
|
theme: string;
|
||||||
tlm?: Date;
|
tlm?: Date;
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { AppleAuthenticationFullName } from 'expo-apple-authentication';
|
||||||
|
|
||||||
export interface ICredentials {
|
export interface ICredentials {
|
||||||
resume?: string;
|
resume?: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
|
@ -13,4 +15,7 @@ export interface ICredentials {
|
||||||
login: ICredentials;
|
login: ICredentials;
|
||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
fullName?: AppleAuthenticationFullName | null;
|
||||||
|
email?: string | null;
|
||||||
|
identityToken?: string | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,8 +130,8 @@ export interface IMessage extends IMessageFromServer {
|
||||||
dcount?: number;
|
dcount?: number;
|
||||||
dlm?: string | Date;
|
dlm?: string | Date;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
tcount?: number;
|
tcount?: number | null;
|
||||||
tlm?: string | Date;
|
tlm?: string | Date | null;
|
||||||
replies?: string[];
|
replies?: string[];
|
||||||
unread?: boolean;
|
unread?: boolean;
|
||||||
autoTranslate?: boolean;
|
autoTranslate?: boolean;
|
||||||
|
|
|
@ -6,10 +6,10 @@ import { TNavigationOptions } from './definitions/navigationTypes';
|
||||||
|
|
||||||
export interface IDimensionsContextProps {
|
export interface IDimensionsContextProps {
|
||||||
width: number;
|
width: number;
|
||||||
height?: number;
|
height: number;
|
||||||
scale: number;
|
scale?: number;
|
||||||
fontScale: number;
|
fontScale?: number;
|
||||||
setDimensions: ({
|
setDimensions?: ({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
scale,
|
scale,
|
||||||
|
@ -22,7 +22,7 @@ export interface IDimensionsContextProps {
|
||||||
}) => void;
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DimensionsContext = React.createContext<Partial<IDimensionsContextProps>>(Dimensions.get('window'));
|
export const DimensionsContext = React.createContext<IDimensionsContextProps>(Dimensions.get('window'));
|
||||||
|
|
||||||
export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
|
export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
|
||||||
const DimensionsComponent = (props: T) => (
|
const DimensionsComponent = (props: T) => (
|
||||||
|
@ -37,7 +37,7 @@ export const useDimensions = () => React.useContext(DimensionsContext);
|
||||||
|
|
||||||
export const useOrientation = () => {
|
export const useOrientation = () => {
|
||||||
const { width, height } = React.useContext(DimensionsContext);
|
const { width, height } = React.useContext(DimensionsContext);
|
||||||
const isPortrait = height! > width!;
|
const isPortrait = height > width;
|
||||||
return {
|
return {
|
||||||
isPortrait,
|
isPortrait,
|
||||||
isLandscape: !isPortrait
|
isLandscape: !isPortrait
|
||||||
|
|
|
@ -59,7 +59,7 @@ const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck, theme }:
|
||||||
{item.id}
|
{item.id}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{hasCheck ? <Check theme={theme!} /> : null}
|
{hasCheck ? <Check /> : null}
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
));
|
));
|
||||||
|
|
|
@ -10,7 +10,7 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IThemedTextInput extends IRCTextInputProps {
|
export interface IThemedTextInput extends IRCTextInputProps {
|
||||||
style: StyleProp<TextStyle>;
|
style: StyleProp<TextStyle>;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import { IApplicationState, IUser } from '../definitions';
|
import { IApplicationState, IUser } from '../definitions';
|
||||||
|
|
||||||
interface IServices {
|
export interface IServices {
|
||||||
facebook: { clientId: string };
|
facebook: { clientId: string };
|
||||||
github: { clientId: string };
|
github: { clientId: string };
|
||||||
gitlab: { clientId: string };
|
gitlab: { clientId: string };
|
||||||
|
|
|
@ -272,8 +272,14 @@ export type OutsideParamList = {
|
||||||
};
|
};
|
||||||
RegisterView: {
|
RegisterView: {
|
||||||
title: string;
|
title: string;
|
||||||
|
username?: string;
|
||||||
};
|
};
|
||||||
LegalView: undefined;
|
LegalView: undefined;
|
||||||
|
AuthenticationWebView: {
|
||||||
|
authType: string;
|
||||||
|
url: string;
|
||||||
|
ssoToken?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OutsideModalParamList = {
|
export type OutsideModalParamList = {
|
||||||
|
|
|
@ -80,7 +80,7 @@ const ChangePasscodeView = React.memo(() => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal useNativeDriver isVisible={visible} hideModalContentWhileAnimating style={styles.modal}>
|
<Modal useNativeDriver isVisible={visible} hideModalContentWhileAnimating style={styles.modal}>
|
||||||
<PasscodeChoose theme={theme} finishProcess={onSubmit} force={data?.force} />
|
<PasscodeChoose finishProcess={onSubmit} force={data?.force} />
|
||||||
{!data?.force ? (
|
{!data?.force ? (
|
||||||
<Touchable onPress={onCancel} style={styles.close}>
|
<Touchable onPress={onCancel} style={styles.close}>
|
||||||
<CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} />
|
<CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} />
|
||||||
|
|
|
@ -175,7 +175,6 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, any> {
|
||||||
testID='multi-select-discussion-name'
|
testID='multi-select-discussion-name'
|
||||||
placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')}
|
placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')}
|
||||||
containerStyle={styles.inputStyle}
|
containerStyle={styles.inputStyle}
|
||||||
/* @ts-ignore*/
|
|
||||||
defaultValue={name}
|
defaultValue={name}
|
||||||
onChangeText={(text: string) => this.setState({ name: text })}
|
onChangeText={(text: string) => this.setState({ name: text })}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
|
||||||
<View style={styles.dropdownItemContainer}>
|
<View style={styles.dropdownItemContainer}>
|
||||||
<CustomIcon style={[styles.dropdownItemIcon, { color: themes[theme].bodyText }]} size={22} name={icon} />
|
<CustomIcon style={[styles.dropdownItemIcon, { color: themes[theme].bodyText }]} size={22} name={icon} />
|
||||||
<Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text>
|
<Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text>
|
||||||
{propType === itemType ? <Check theme={theme} /> : null}
|
{propType === itemType ? <Check /> : null}
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
);
|
);
|
||||||
|
|
|
@ -231,7 +231,7 @@ class LoginView extends React.Component<ILoginViewProps, any> {
|
||||||
return (
|
return (
|
||||||
<FormContainer theme={theme} testID='login-view'>
|
<FormContainer theme={theme} testID='login-view'>
|
||||||
<FormContainerInner>
|
<FormContainerInner>
|
||||||
<LoginServices separator={Accounts_ShowFormLogin} navigation={navigation} />
|
<LoginServices separator={Accounts_ShowFormLogin} navigation={navigation} theme={theme} />
|
||||||
{this.renderUserForm()}
|
{this.renderUserForm()}
|
||||||
</FormContainerInner>
|
</FormContainerInner>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
|
|
|
@ -186,7 +186,7 @@ class RegisterView extends React.Component<IProps, any> {
|
||||||
}}
|
}}
|
||||||
value={customFields[key]}>
|
value={customFields[key]}>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e: any) => {
|
inputRef={e => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this[key] = e;
|
this[key] = e;
|
||||||
}}
|
}}
|
||||||
|
@ -237,7 +237,7 @@ class RegisterView extends React.Component<IProps, any> {
|
||||||
return (
|
return (
|
||||||
<FormContainer theme={theme} testID='register-view'>
|
<FormContainer theme={theme} testID='register-view'>
|
||||||
<FormContainerInner>
|
<FormContainerInner>
|
||||||
<LoginServices navigation={navigation} />
|
<LoginServices navigation={navigation} theme={theme} separator />
|
||||||
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>
|
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={I18n.t('Name')}
|
label={I18n.t('Name')}
|
||||||
|
|
|
@ -666,4 +666,4 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
viewAllTeamsPermission: state.permissions['view-all-teams']
|
viewAllTeamsPermission: state.permissions['view-all-teams']
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView)));
|
export default connect(mapStateToProps)(withTheme(withActionSheet(RoomMembersView)));
|
||||||
|
|
|
@ -72,8 +72,7 @@ const JoinCode = React.memo(
|
||||||
useImperativeHandle(ref, () => ({ show }));
|
useImperativeHandle(ref, () => ({ show }));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore TODO: `transparent` seems to exist, but types are incorrect on the lib
|
<Modal avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating>
|
||||||
<Modal transparent={true} avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating>
|
|
||||||
<View style={styles.container} testID='join-code'>
|
<View style={styles.container} testID='join-code'>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
|
|
@ -4,7 +4,6 @@ import useDeepCompareEffect from 'use-deep-compare-effect';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import Orientation from 'react-native-orientation-locker';
|
import Orientation from 'react-native-orientation-locker';
|
||||||
|
|
||||||
import { useTheme } from '../theme';
|
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import { LOCAL_AUTHENTICATE_EMITTER } from '../constants/localAuthentication';
|
import { LOCAL_AUTHENTICATE_EMITTER } from '../constants/localAuthentication';
|
||||||
import { isTablet } from '../utils/deviceInfo';
|
import { isTablet } from '../utils/deviceInfo';
|
||||||
|
@ -19,8 +18,6 @@ const ScreenLockedView = (): JSX.Element => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [data, setData] = useState<IData>({});
|
const [data, setData] = useState<IData>({});
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
if (!isEmpty(data)) {
|
if (!isEmpty(data)) {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
@ -62,7 +59,7 @@ const ScreenLockedView = (): JSX.Element => {
|
||||||
style={{ margin: 0 }}
|
style={{ margin: 0 }}
|
||||||
animationIn='fadeIn'
|
animationIn='fadeIn'
|
||||||
animationOut='fadeOut'>
|
animationOut='fadeOut'>
|
||||||
<PasscodeEnter theme={theme} hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} />
|
<PasscodeEnter hasBiometry={!!data?.hasBiometry} finishProcess={onSubmit} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,7 +70,6 @@ const Item = ({ item, useRealName, user, badgeColor, onPress, toggleFollowThread
|
||||||
const username = (useRealName && item?.u?.name) || item?.u?.username;
|
const username = (useRealName && item?.u?.name) || item?.u?.username;
|
||||||
let time;
|
let time;
|
||||||
if (item?.ts) {
|
if (item?.ts) {
|
||||||
// @ts-ignore TODO: to be fixed after we unify our types
|
|
||||||
time = formatDateThreads(item.ts);
|
time = formatDateThreads(item.ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,15 +77,15 @@ const Item = ({ item, useRealName, user, badgeColor, onPress, toggleFollowThread
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => onPress(item)}
|
onPress={() => onPress(item)}
|
||||||
testID={`thread-messages-view-${item.msg}`}
|
testID={`thread-messages-view-${item.msg}`}
|
||||||
style={{ backgroundColor: themes[theme!].backgroundColor }}>
|
style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} theme={theme} />
|
<Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} theme={theme} />
|
||||||
<View style={styles.contentContainer}>
|
<View style={styles.contentContainer}>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<Text style={[styles.title, { color: themes[theme!].titleText }]} numberOfLines={1}>
|
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||||
{username}
|
{username}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.time, { color: themes[theme!].auxiliaryText }]}>{time}</Text>
|
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.messageContainer}>
|
<View style={styles.messageContainer}>
|
||||||
<MarkdownPreview msg={makeThreadName(item)} numberOfLines={2} style={[styles.markdown]} />
|
<MarkdownPreview msg={makeThreadName(item)} numberOfLines={2} style={[styles.markdown]} />
|
||||||
|
|
|
@ -19,7 +19,18 @@ const item = {
|
||||||
iconURL: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png'
|
iconURL: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png'
|
||||||
};
|
};
|
||||||
|
|
||||||
const ServerItem = props => <ServerItemComponent item={item} hasCheck={false} {...props} />;
|
const ServerItem = ({ theme = themes.light, ...props }) => (
|
||||||
|
<ThemeContext.Provider
|
||||||
|
value={{
|
||||||
|
theme,
|
||||||
|
themePreferences: {
|
||||||
|
currentTheme: theme,
|
||||||
|
darkLevel: theme
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<ServerItemComponent item={item} hasCheck={false} {...props} />
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
stories.add('content', () => (
|
stories.add('content', () => (
|
||||||
<>
|
<>
|
||||||
|
@ -47,16 +58,10 @@ stories.add('touchable', () => (
|
||||||
</>
|
</>
|
||||||
));
|
));
|
||||||
|
|
||||||
const ThemeStory = ({ theme }) => (
|
|
||||||
<ThemeContext.Provider value={theme}>
|
|
||||||
<ServerItem theme={theme} hasCheck />
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
stories.add('themes', () => (
|
stories.add('themes', () => (
|
||||||
<>
|
<>
|
||||||
<ThemeStory theme={themes.light} />
|
<ServerItem theme={themes.light} />
|
||||||
<ThemeStory theme={themes.dark} />
|
<ServerItem theme={themes.dark} />
|
||||||
<ThemeStory theme={themes.black} />
|
<ServerItem theme={themes.black} />
|
||||||
</>
|
</>
|
||||||
));
|
));
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue