diff --git a/.gitignore b/.gitignore index 4ffd99aa9..efb752943 100644 --- a/.gitignore +++ b/.gitignore @@ -64,5 +64,6 @@ artifacts .vscode/ e2e/docker/rc_test_env/docker-compose.yml e2e/docker/data/db +e2e/e2e_account.js *.p8 \ No newline at end of file diff --git a/__tests__/Storyshots.test.js b/__tests__/Storyshots.test.js index 1315f9bb5..638fbb0ee 100644 --- a/__tests__/Storyshots.test.js +++ b/__tests__/Storyshots.test.js @@ -20,6 +20,26 @@ jest.mock('react-native-file-viewer', () => ({ jest.mock('../app/lib/database', () => jest.fn(() => null)); global.Date.now = jest.fn(() => new Date('2019-10-10').getTime()); +jest.mock('react-native-mmkv-storage', () => { + return { + Loader: jest.fn().mockImplementation(() => { + return { + setProcessingMode: jest.fn().mockImplementation(() => { + return { + withEncryption: jest.fn().mockImplementation(() => { + return { + initialize: jest.fn() + }; + }) + }; + }) + }; + }), + create: jest.fn(), + MODES: { MULTI_PROCESS: '' } + }; +}); + const converter = new Stories2SnapsConverter(); initStoryshots({ diff --git a/android/app/build.gradle b/android/app/build.gradle index 5e7aaee86..f7e9a5f4e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,7 +144,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.25.0" + versionName "4.26.1" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index bf7dc5ef7..c0361e78d 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -12,7 +12,6 @@ import com.facebook.soloader.SoLoader; import com.reactnativecommunity.viewpager.RNCViewPagerPackage; import com.facebook.react.bridge.JSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; - import org.unimodules.adapters.react.ModuleRegistryAdapter; import org.unimodules.adapters.react.ReactModuleRegistryProvider; @@ -54,7 +53,7 @@ public class MainApplication extends Application implements ReactApplication { @Override protected JSIModulePackage getJSIModulePackage() { - return new ReanimatedJSIModulePackage(); // <- add + return new ReanimatedJSIModulePackage(); } @Override diff --git a/android/app/src/play/java/chat/rocket/reactnative/Ejson.java b/android/app/src/play/java/chat/rocket/reactnative/Ejson.java index e44c6b722..fb083df13 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/Ejson.java +++ b/android/app/src/play/java/chat/rocket/reactnative/Ejson.java @@ -53,16 +53,8 @@ public class Ejson { String alias = Utils.toHex("com.MMKV.default"); // Retrieve container password - secureKeystore.getSecureKey(alias, new RNCallback() { - @Override - public void invoke(Object... args) { - String error = (String) args[0]; - if (error == null) { - String password = (String) args[1]; - mmkv = MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE, password); - } - } - }); + String password = secureKeystore.getSecureKey(alias); + mmkv = MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE, password); } public String getAvatarUri() { diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 1367b2251..a920e27e2 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -11,7 +11,7 @@ function createRequestTypes(base = {}, types = defaultTypes): Record): ISetUser { }; } +export function clearUser(): Action { + return { + type: types.USER.CLEAR + }; +} + export function setLoginServices(data: Record): ISetServices { return { type: types.LOGIN.SET_SERVICES, diff --git a/app/actions/room.ts b/app/actions/room.ts index 9c817f565..f0d5ed81b 100644 --- a/app/actions/room.ts +++ b/app/actions/room.ts @@ -4,7 +4,7 @@ import { ERoomType } from '../definitions/ERoomType'; import { ROOM } from './actionsTypes'; // TYPE RETURN RELATED -type ISelected = Record; +type ISelected = string[]; export interface ITransferData { roomId: string; diff --git a/app/actions/settings.ts b/app/actions/settings.ts index 77b8dcc77..8db868889 100644 --- a/app/actions/settings.ts +++ b/app/actions/settings.ts @@ -1,26 +1,26 @@ import { Action } from 'redux'; -import { ISettings, TSettings } from '../reducers/settings'; +import { TSettingsState, TSupportedSettings, TSettingsValues } from '../reducers/settings'; import { SETTINGS } from './actionsTypes'; interface IAddSettings extends Action { - payload: ISettings; + payload: TSettingsState; } interface IUpdateSettings extends Action { - payload: { id: string; value: TSettings }; + payload: { id: TSupportedSettings; value: TSettingsValues }; } export type IActionSettings = IAddSettings & IUpdateSettings; -export function addSettings(settings: ISettings): IAddSettings { +export function addSettings(settings: TSettingsState): IAddSettings { return { type: SETTINGS.ADD, payload: settings }; } -export function updateSettings(id: string, value: TSettings): IUpdateSettings { +export function updateSettings(id: TSupportedSettings, value: TSettingsValues): IUpdateSettings { return { type: SETTINGS.UPDATE, payload: { id, value } diff --git a/app/commands.ts b/app/commands.ts index b1aee847c..0624ac3db 100644 --- a/app/commands.ts +++ b/app/commands.ts @@ -143,8 +143,8 @@ export const deleteKeyCommands = (): void => KeyCommands.deleteKeyCommands(keyCo export const KEY_COMMAND = 'KEY_COMMAND'; -interface IKeyCommandEvent extends NativeSyntheticEvent { - input: string; +export interface IKeyCommandEvent extends NativeSyntheticEvent { + input: number & string; modifierFlags: string | number; } diff --git a/app/constants/colors.ts b/app/constants/colors.ts index b28416b86..3920441a2 100644 --- a/app/constants/colors.ts +++ b/app/constants/colors.ts @@ -37,6 +37,7 @@ export const themes: any = { infoText: '#6d6d72', tintColor: '#1d74f5', tintActive: '#549df9', + tintDisabled: '#88B4F5', auxiliaryTintColor: '#6C727A', actionTintColor: '#1d74f5', separatorColor: '#cbcbcc', @@ -66,6 +67,8 @@ export const themes: any = { previewTintColor: '#ffffff', backdropOpacity: 0.3, attachmentLoadingOpacity: 0.7, + collapsibleQuoteBorder: '#CBCED1', + collapsibleChevron: '#6C727A', ...mentions }, dark: { @@ -85,6 +88,7 @@ export const themes: any = { infoText: '#6D6D72', tintColor: '#1d74f5', tintActive: '#549df9', + tintDisabled: '#88B4F5', auxiliaryTintColor: '#f9f9f9', actionTintColor: '#1d74f5', separatorColor: '#2b2b2d', @@ -114,6 +118,8 @@ export const themes: any = { previewTintColor: '#ffffff', backdropOpacity: 0.9, attachmentLoadingOpacity: 0.3, + collapsibleQuoteBorder: '#CBCED1', + collapsibleChevron: '#6C727A', ...mentions }, black: { @@ -133,6 +139,7 @@ export const themes: any = { infoText: '#6d6d72', tintColor: '#1e9bfe', tintActive: '#76b7fc', + tintDisabled: '#88B4F5', // TODO: Evaluate this with design team auxiliaryTintColor: '#f9f9f9', actionTintColor: '#1e9bfe', separatorColor: '#272728', @@ -162,6 +169,8 @@ export const themes: any = { previewTintColor: '#ffffff', backdropOpacity: 0.9, attachmentLoadingOpacity: 0.3, + collapsibleQuoteBorder: '#CBCED1', + collapsibleChevron: '#6C727A', ...mentions } }; diff --git a/app/constants/messageTypeLoad.ts b/app/constants/messageTypeLoad.ts index 4bdaa54de..6836981aa 100644 --- a/app/constants/messageTypeLoad.ts +++ b/app/constants/messageTypeLoad.ts @@ -1,5 +1,7 @@ -export const MESSAGE_TYPE_LOAD_MORE = 'load_more'; -export const MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK = 'load_previous_chunk'; -export const MESSAGE_TYPE_LOAD_NEXT_CHUNK = 'load_next_chunk'; +export enum MessageTypeLoad { + MORE = 'load_more', + PREVIOUS_CHUNK = 'load_previous_chunk', + NEXT_CHUNK = 'load_next_chunk' +} -export const MESSAGE_TYPE_ANY_LOAD = [MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK, MESSAGE_TYPE_LOAD_NEXT_CHUNK]; +export const MESSAGE_TYPE_ANY_LOAD = [MessageTypeLoad.MORE, MessageTypeLoad.PREVIOUS_CHUNK, MessageTypeLoad.NEXT_CHUNK]; diff --git a/app/constants/settings.ts b/app/constants/settings.ts index fb9c7e6b5..b84779427 100644 --- a/app/constants/settings.ts +++ b/app/constants/settings.ts @@ -206,4 +206,4 @@ export default { Canned_Responses_Enable: { type: 'valueAsBoolean' } -}; +} as const; diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx index 11414150c..79303a593 100644 --- a/app/containers/ActionSheet/ActionSheet.tsx +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -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 { 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 { State, TapGestureHandler } from 'react-native-gesture-handler'; 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 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 * as List from '../List'; -import I18n from '../../i18n'; -import { IDimensionsContextProps, useDimensions, useOrientation } from '../../dimensions'; +import { Button } from './Button'; +import { Handle } from './Handle'; +import { IActionSheetItem, Item } from './Item'; +import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider'; +import styles, { ITEM_HEIGHT } from './styles'; -interface IActionSheetData { - options: any; - headerHeight?: number; - hasCancel?: boolean; - customHeader: any; -} - -const getItemLayout = (data: any, index: number) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }); +const getItemLayout = (data: TActionSheetOptionsItem[] | null | undefined, index: number) => ({ + length: ITEM_HEIGHT, + offset: ITEM_HEIGHT * index, + index +}); const HANDLE_HEIGHT = isIOS ? 40 : 56; const MAX_SNAP_HEIGHT = 16; @@ -39,16 +38,17 @@ const ANIMATION_CONFIG = { }; const ActionSheet = React.memo( - forwardRef(({ children, theme }: { children: JSX.Element; theme: string }, ref) => { - const bottomSheetRef: any = useRef(); - const [data, setData] = useState({} as IActionSheetData); + forwardRef(({ children }: { children: React.ReactElement }, ref) => { + const { theme } = useTheme(); + const bottomSheetRef = useRef>(null); + const [data, setData] = useState({} as TActionSheetOptions); const [isVisible, setVisible] = useState(false); - const { height }: Partial = useDimensions(); + const { height } = useDimensions(); const { isLandscape } = useOrientation(); const insets = useSafeAreaInsets(); const maxSnap = Math.max( - height! - + height - // Items height ITEM_HEIGHT * (data?.options?.length || 0) - // Handle height @@ -69,7 +69,7 @@ const ActionSheet = React.memo( * we'll provide more one snap * 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 closedSnapIndex = snaps.length - 1; @@ -79,12 +79,12 @@ const ActionSheet = React.memo( bottomSheetRef.current?.snapTo(closedSnapIndex); }; - const show = (options: any) => { + const show = (options: TActionSheetOptions) => { setData(options); toggleVisible(); }; - const onBackdropPressed = ({ nativeEvent }: any) => { + const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => { if (nativeEvent.oldState === State.ACTIVE) { hide(); } @@ -117,7 +117,7 @@ const ActionSheet = React.memo( const renderHandle = () => ( <> - + {isValidElement(data?.customHeader) ? data.customHeader : null} ); @@ -127,21 +127,23 @@ const ActionSheet = React.memo( ) : null; - const renderItem = ({ item }: any) => ; + const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => ; 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, { inputRange: [0, 1], outputRange: [0, themes[theme].backdropOpacity], 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 ( <> @@ -160,7 +162,7 @@ const ActionSheet = React.memo( ]} /> - testID='action-sheet' ref={bottomSheetRef} componentType='FlatList' @@ -169,18 +171,11 @@ const ActionSheet = React.memo( renderHandle={renderHandle} onSettle={index => index === closedSnapIndex && toggleVisible()} animatedPosition={animatedPosition.current} - containerStyle={ - [ - styles.container, - { backgroundColor: themes[theme].focusedBackground }, - (isLandscape || isTablet) && styles.bottomSheet - ] as any - } + containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }} animationConfig={ANIMATION_CONFIG} - // FlatList props - data={data?.options} + data={data.options} renderItem={renderItem} - keyExtractor={(item: any) => item.title} + keyExtractor={item => item.title} style={{ backgroundColor: themes[theme].focusedBackground }} contentContainerStyle={styles.content} ItemSeparatorComponent={List.Separator} diff --git a/app/containers/ActionSheet/Handle.tsx b/app/containers/ActionSheet/Handle.tsx index d95262d1e..1b2b6a62c 100644 --- a/app/containers/ActionSheet/Handle.tsx +++ b/app/containers/ActionSheet/Handle.tsx @@ -3,9 +3,13 @@ import { View } from 'react-native'; import styles from './styles'; 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 ( + + + + ); +}); diff --git a/app/containers/ActionSheet/Item.tsx b/app/containers/ActionSheet/Item.tsx index 47a3de5a7..9c281f8c1 100644 --- a/app/containers/ActionSheet/Item.tsx +++ b/app/containers/ActionSheet/Item.tsx @@ -3,23 +3,24 @@ import { Text, View } from 'react-native'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; +import { useTheme } from '../../theme'; import { Button } from './Button'; import styles from './styles'; -interface IActionSheetItem { +export interface IActionSheetItem { item: { title: string; icon: string; - danger: boolean; - testID: string; - onPress(): void; - right: Function; + danger?: boolean; + testID?: string; + onPress: () => void; + right?: Function; }; - theme: string; hide(): void; } -export const Item = React.memo(({ item, hide, theme }: IActionSheetItem) => { +export const Item = React.memo(({ item, hide }: IActionSheetItem) => { + const { theme } = useTheme(); const onPress = () => { hide(); item?.onPress(); diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index 8e786b05f..112242263 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -1,14 +1,21 @@ import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react'; 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 { - Provider: any; - Consumer: any; + showActionSheet: (item: TActionSheetOptions) => void; + hideActionSheet: () => void; } -const context: IActionSheetProvider = React.createContext({ +const context = React.createContext({ showActionSheet: () => {}, hideActionSheet: () => {} }); @@ -17,17 +24,16 @@ export const useActionSheet = () => useContext(context); const { Provider, Consumer } = context; -export const withActionSheet = (Component: any): any => - forwardRef((props: any, ref: ForwardedRef) => ( - {(contexts: any) => } +export const withActionSheet = (Component: React.ComponentType): typeof Component => + forwardRef((props: typeof React.Component, ref: ForwardedRef) => ( + {(contexts: IActionSheetProvider) => } )); -export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Element | JSX.Element[] }) => { - const ref: ForwardedRef = useRef(); - const { theme }: any = useTheme(); +export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => { + const ref: ForwardedRef = useRef(null); const getContext = () => ({ - showActionSheet: (options: any) => { + showActionSheet: (options: TActionSheetOptions) => { ref.current?.showActionSheet(options); }, hideActionSheet: () => { @@ -37,7 +43,7 @@ export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Ele return ( - + <>{children} diff --git a/app/containers/ActivityIndicator.tsx b/app/containers/ActivityIndicator.tsx index 69ef22ceb..701926416 100644 --- a/app/containers/ActivityIndicator.tsx +++ b/app/containers/ActivityIndicator.tsx @@ -1,14 +1,11 @@ import React from 'react'; import { ActivityIndicator, ActivityIndicatorProps, StyleSheet } from 'react-native'; +import { useTheme } from '../theme'; import { themes } from '../constants/colors'; -type TTheme = 'light' | 'dark' | 'black' | string; - interface IActivityIndicator extends ActivityIndicatorProps { - theme?: TTheme; absolute?: boolean; - props?: object; } const styles = StyleSheet.create({ @@ -27,8 +24,11 @@ const styles = StyleSheet.create({ } }); -const RCActivityIndicator = ({ theme = 'light', absolute, ...props }: IActivityIndicator) => ( - -); +const RCActivityIndicator = ({ absolute, ...props }: IActivityIndicator): React.ReactElement => { + const { theme } = useTheme(); + return ( + + ); +}; export default RCActivityIndicator; diff --git a/app/containers/Avatar/index.tsx b/app/containers/Avatar/index.tsx index 637596410..5b14fe154 100644 --- a/app/containers/Avatar/index.tsx +++ b/app/containers/Avatar/index.tsx @@ -37,6 +37,21 @@ class AvatarContainer extends React.Component { } } + shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) { + const { avatarETag } = this.state; + const { text, type } = this.props; + if (nextState.avatarETag !== avatarETag) { + return true; + } + if (nextProps.text !== text) { + return true; + } + if (nextProps.type !== type) { + return true; + } + return false; + } + componentWillUnmount() { if (this.subscription?.unsubscribe) { this.subscription.unsubscribe(); diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts index ddec5b276..3bc5dd85e 100644 --- a/app/containers/Avatar/interfaces.ts +++ b/app/containers/Avatar/interfaces.ts @@ -1,3 +1,5 @@ +import React from 'react'; + import { TGetCustomEmoji } from '../../definitions/IEmoji'; export interface IAvatar { @@ -9,7 +11,7 @@ export interface IAvatar { size?: number; borderRadius?: number; type?: string; - children?: JSX.Element; + children?: React.ReactElement | null; user?: { id?: string; token?: string; diff --git a/app/containers/BackgroundContainer/index.tsx b/app/containers/BackgroundContainer/index.tsx index a485611c0..c9fc70d57 100644 --- a/app/containers/BackgroundContainer/index.tsx +++ b/app/containers/BackgroundContainer/index.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { ActivityIndicator, ImageBackground, StyleSheet, Text, View } from 'react-native'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; interface IBackgroundContainer { text?: string; - theme?: string; loading?: boolean; } @@ -32,12 +31,15 @@ const styles = StyleSheet.create({ } }); -const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => ( - - - {text && !loading ? {text} : null} - {loading ? : null} - -); +const BackgroundContainer = ({ text, loading }: IBackgroundContainer): React.ReactElement => { + const { theme } = useTheme(); + return ( + + + {text && !loading ? {text} : null} + {loading ? : null} + + ); +}; -export default withTheme(BackgroundContainer); +export default BackgroundContainer; diff --git a/app/containers/Check.tsx b/app/containers/Check.tsx index 9ee489dfd..c51bc8b46 100644 --- a/app/containers/Check.tsx +++ b/app/containers/Check.tsx @@ -3,11 +3,8 @@ import { StyleSheet } from 'react-native'; import { CustomIcon } from '../lib/Icons'; import { themes } from '../constants/colors'; +import { useTheme } from '../theme'; -interface ICheck { - style?: object; - theme: string; -} const styles = StyleSheet.create({ icon: { width: 22, @@ -16,8 +13,9 @@ const styles = StyleSheet.create({ } }); -const Check = React.memo(({ theme, style }: ICheck) => ( - -)); +const Check = React.memo(() => { + const { theme } = useTheme(); + return ; +}); export default Check; diff --git a/app/containers/FormContainer.tsx b/app/containers/FormContainer.tsx index ff233952a..8c862b54d 100644 --- a/app/containers/FormContainer.tsx +++ b/app/containers/FormContainer.tsx @@ -5,15 +5,15 @@ import { themes } from '../constants/colors'; import sharedStyles from '../views/Styles'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import KeyboardView from '../presentation/KeyboardView'; +import { useTheme } from '../theme'; import StatusBar from './StatusBar'; import AppVersion from './AppVersion'; import { isTablet } from '../utils/deviceInfo'; import SafeAreaView from './SafeAreaView'; interface IFormContainer extends ScrollViewProps { - theme: string; testID: string; - children: React.ReactNode; + children: React.ReactElement | React.ReactElement[] | null; } const styles = StyleSheet.create({ @@ -22,27 +22,31 @@ const styles = StyleSheet.create({ } }); -export const FormContainerInner = ({ children }: { children: React.ReactNode }): JSX.Element => ( +export const FormContainerInner = ({ children }: { children: (React.ReactElement | null)[] }) => ( {children} ); -const FormContainer = ({ children, theme, testID, ...props }: IFormContainer): JSX.Element => ( - - - - - {children} - - - - -); +const FormContainer = ({ children, testID, ...props }: IFormContainer) => { + const { theme } = useTheme(); + + return ( + + + + + {children} + + + + + ); +}; export default FormContainer; diff --git a/app/containers/Header/index.tsx b/app/containers/Header/index.tsx index e9f7837c6..17d888409 100644 --- a/app/containers/Header/index.tsx +++ b/app/containers/Header/index.tsx @@ -5,12 +5,11 @@ import { StyleSheet, View } from 'react-native'; import { themes } from '../../constants/colors'; import { themedHeader } from '../../utils/navigation'; import { isIOS, isTablet } from '../../utils/deviceInfo'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; -// Get from https://github.com/react-navigation/react-navigation/blob/master/packages/stack/src/views/Header/HeaderSegment.tsx#L69 export const headerHeight = isIOS ? 44 : 56; -export const getHeaderHeight = (isLandscape: boolean) => { +export const getHeaderHeight = (isLandscape: boolean): number => { if (isIOS) { if (isLandscape && !isTablet) { return 32; @@ -28,7 +27,13 @@ interface IHeaderTitlePosition { numIconsRight: number; } -export const getHeaderTitlePosition = ({ insets, numIconsRight }: IHeaderTitlePosition) => ({ +export const getHeaderTitlePosition = ({ + insets, + numIconsRight +}: IHeaderTitlePosition): { + left: number; + right: number; +} => ({ left: insets.left + 60, right: insets.right + Math.max(45 * numIconsRight, 15) }); @@ -43,20 +48,22 @@ const styles = StyleSheet.create({ }); interface IHeader { - theme: string; - headerLeft(): void; - headerTitle(): void; - headerRight(): void; + headerLeft: () => React.ReactElement | null; + headerTitle: () => React.ReactElement; + headerRight: () => React.ReactElement | null; } -const Header = ({ theme, headerLeft, headerTitle, headerRight }: IHeader) => ( - - - {headerLeft ? headerLeft() : null} - {headerTitle ? headerTitle() : null} - {headerRight ? headerRight() : null} - - -); +const Header = ({ headerLeft, headerTitle, headerRight }: IHeader): React.ReactElement => { + const { theme } = useTheme(); + return ( + + + {headerLeft ? headerLeft() : null} + {headerTitle ? headerTitle() : null} + {headerRight ? headerRight() : null} + + + ); +}; -export default withTheme(Header); +export default Header; diff --git a/app/containers/HeaderButton/Common.tsx b/app/containers/HeaderButton/Common.tsx index 0877fb4c6..50206f836 100644 --- a/app/containers/HeaderButton/Common.tsx +++ b/app/containers/HeaderButton/Common.tsx @@ -6,20 +6,22 @@ import Container from './HeaderButtonContainer'; import Item from './HeaderButtonItem'; interface IHeaderButtonCommon { - navigation: any; - onPress?(): void; + navigation?: any; // TODO: Evaluate proper type + onPress?: () => void; testID?: string; } // Left -export const Drawer = React.memo(({ navigation, testID, ...props }: Partial) => ( - - navigation.toggleDrawer()} testID={testID} {...props} /> - -)); +export const Drawer = React.memo( + ({ navigation, testID, onPress = () => navigation?.toggleDrawer(), ...props }: IHeaderButtonCommon) => ( + + + + ) +); export const CloseModal = React.memo( - ({ navigation, testID, onPress = () => navigation.pop(), ...props }: IHeaderButtonCommon) => ( + ({ navigation, testID, onPress = () => navigation?.pop(), ...props }: IHeaderButtonCommon) => ( @@ -29,9 +31,9 @@ export const CloseModal = React.memo( export const CancelModal = React.memo(({ onPress, testID }: Partial) => ( {isIOS ? ( - + ) : ( - + )} )); @@ -39,22 +41,24 @@ export const CancelModal = React.memo(({ onPress, testID }: Partial) => ( - + )); -export const Download = React.memo(({ onPress, testID, ...props }: Partial) => ( +export const Download = React.memo(({ onPress, testID, ...props }: IHeaderButtonCommon) => ( - + )); -export const Preferences = React.memo(({ onPress, testID, ...props }: Partial) => ( +export const Preferences = React.memo(({ onPress, testID, ...props }: IHeaderButtonCommon) => ( - + )); -export const Legal = React.memo(({ navigation, testID }: Partial) => ( - navigation.navigate('LegalView')} testID={testID} /> -)); +export const Legal = React.memo( + ({ navigation, testID, onPress = () => navigation?.navigate('LegalView') }: IHeaderButtonCommon) => ( + + ) +); diff --git a/app/containers/HeaderButton/HeaderButtonContainer.tsx b/app/containers/HeaderButton/HeaderButtonContainer.tsx index f757d43d7..abf6db9e7 100644 --- a/app/containers/HeaderButton/HeaderButtonContainer.tsx +++ b/app/containers/HeaderButton/HeaderButtonContainer.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; interface IHeaderButtonContainer { - children: React.ReactNode; + children?: React.ReactElement | (React.ReactElement | null)[] | null; left?: boolean; } @@ -20,7 +20,7 @@ const styles = StyleSheet.create({ } }); -const Container = ({ children, left = false }: IHeaderButtonContainer) => ( +const Container = ({ children, left = false }: IHeaderButtonContainer): React.ReactElement => ( {children} ); diff --git a/app/containers/HeaderButton/HeaderButtonItem.tsx b/app/containers/HeaderButton/HeaderButtonItem.tsx index 08f6b5a3a..987975db2 100644 --- a/app/containers/HeaderButton/HeaderButtonItem.tsx +++ b/app/containers/HeaderButton/HeaderButtonItem.tsx @@ -3,16 +3,15 @@ import { Platform, StyleSheet, Text } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { CustomIcon } from '../../lib/Icons'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { themes } from '../../constants/colors'; import sharedStyles from '../../views/Styles'; interface IHeaderButtonItem { title?: string; iconName?: string; - onPress: (arg: T) => void; + onPress?: (arg: T) => void; testID?: string; - theme?: string; badge?(): void; } @@ -40,19 +39,22 @@ const styles = StyleSheet.create({ } }); -const Item = ({ title, iconName, onPress, testID, theme, badge }: IHeaderButtonItem) => ( - - <> - {iconName ? ( - - ) : ( - {title} - )} - {badge ? badge() : null} - - -); +const Item = ({ title, iconName, onPress, testID, badge }: IHeaderButtonItem): React.ReactElement => { + const { theme } = useTheme(); + return ( + + <> + {iconName ? ( + + ) : ( + {title} + )} + {badge ? badge() : null} + + + ); +}; Item.displayName = 'HeaderButton.Item'; -export default withTheme(Item); +export default Item; diff --git a/app/containers/HeaderButton/HeaderButtonItemBadge.tsx b/app/containers/HeaderButton/HeaderButtonItemBadge.tsx index 9634c8bde..4bd2d2682 100644 --- a/app/containers/HeaderButton/HeaderButtonItemBadge.tsx +++ b/app/containers/HeaderButton/HeaderButtonItemBadge.tsx @@ -15,6 +15,6 @@ const styles = StyleSheet.create({ } }); -export const Badge = ({ ...props }) => ; +export const Badge = ({ ...props }): React.ReactElement => ; export default Badge; diff --git a/app/containers/InAppNotification/NotifierComponent.tsx b/app/containers/InAppNotification/NotifierComponent.tsx index 4264b2eea..c0c16a9c3 100644 --- a/app/containers/InAppNotification/NotifierComponent.tsx +++ b/app/containers/InAppNotification/NotifierComponent.tsx @@ -14,9 +14,18 @@ import { ROW_HEIGHT } from '../../presentation/RoomItem'; import { goRoom } from '../../utils/goRoom'; import Navigation from '../../lib/Navigation'; import { useOrientation } from '../../dimensions'; +import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions'; -interface INotifierComponent { - notification: object; +export interface INotifierComponent { + notification: { + text: string; + payload: { + sender: { username: string }; + type: SubscriptionType; + } & Pick; + title: string; + avatar: string; + }; isMasterDetail: boolean; } @@ -67,15 +76,15 @@ const styles = StyleSheet.create({ const hideNotification = () => Notifier.hideNotification(); const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifierComponent) => { - const { theme }: any = useTheme(); + const { theme } = useTheme(); const insets = useSafeAreaInsets(); const { isLandscape } = useOrientation(); - const { text, payload }: any = notification; + const { text, payload } = notification; const { type, rid } = payload; 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 - const { title = name, avatar = name }: any = notification; + const { title = name, avatar = name } = notification; const onPress = () => { 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 }); diff --git a/app/containers/InAppNotification/index.tsx b/app/containers/InAppNotification/index.tsx index e708231b1..7e5d15889 100644 --- a/app/containers/InAppNotification/index.tsx +++ b/app/containers/InAppNotification/index.tsx @@ -3,16 +3,18 @@ import { Easing, Notifier, NotifierRoot } from 'react-native-notifier'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; -import NotifierComponent from './NotifierComponent'; +import NotifierComponent, { INotifierComponent } from './NotifierComponent'; import EventEmitter from '../../utils/events'; import Navigation from '../../lib/Navigation'; import { getActiveRoute } from '../../utils/navigation'; +import { IApplicationState } from '../../definitions'; +import { IRoom } from '../../reducers/room'; export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; const InAppNotification = memo( - ({ rooms, appState }: { rooms: any; appState: string }) => { - const show = (notification: any) => { + ({ rooms, appState }: { rooms: IRoom['rooms']; appState: string }) => { + const show = (notification: INotifierComponent['notification']) => { if (appState !== 'foreground') { return; } @@ -46,7 +48,7 @@ const InAppNotification = memo( (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms) ); -const mapStateToProps = (state: any) => ({ +const mapStateToProps = (state: IApplicationState) => ({ rooms: state.room.rooms, appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' }); diff --git a/app/containers/List/ListContainer.tsx b/app/containers/List/ListContainer.tsx index deb9c8a71..349c71bee 100644 --- a/app/containers/List/ListContainer.tsx +++ b/app/containers/List/ListContainer.tsx @@ -11,7 +11,7 @@ const styles = StyleSheet.create({ }); interface IListContainer { - children: React.ReactNode; + children: (React.ReactElement | null)[] | React.ReactElement | null; testID?: string; } const ListContainer = React.memo(({ children, ...props }: IListContainer) => ( diff --git a/app/containers/List/ListHeader.tsx b/app/containers/List/ListHeader.tsx index 9a0b97731..469d4cec6 100644 --- a/app/containers/List/ListHeader.tsx +++ b/app/containers/List/ListHeader.tsx @@ -4,7 +4,7 @@ import { StyleSheet, Text, View } from 'react-native'; import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { PADDING_HORIZONTAL } from './constants'; const styles = StyleSheet.create({ @@ -20,18 +20,21 @@ const styles = StyleSheet.create({ interface IListHeader { title: string; - theme?: string; translateTitle?: boolean; } -const ListHeader = React.memo(({ title, theme, translateTitle = true }: IListHeader) => ( - - - {translateTitle ? I18n.t(title) : title} - - -)); +const ListHeader = React.memo(({ title, translateTitle = true }: IListHeader) => { + const { theme } = useTheme(); + + return ( + + + {translateTitle ? I18n.t(title) : title} + + + ); +}); ListHeader.displayName = 'List.Header'; -export default withTheme(ListHeader); +export default ListHeader; diff --git a/app/containers/List/ListIcon.tsx b/app/containers/List/ListIcon.tsx index 71e4fbdf2..c134b1690 100644 --- a/app/containers/List/ListIcon.tsx +++ b/app/containers/List/ListIcon.tsx @@ -3,11 +3,10 @@ import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { ICON_SIZE } from './constants'; interface IListIcon { - theme?: string; name: string; color?: string; style?: StyleProp; @@ -21,12 +20,16 @@ const styles = StyleSheet.create({ } }); -const ListIcon = React.memo(({ theme, name, color, style, testID }: IListIcon) => ( - - - -)); +const ListIcon = React.memo(({ name, color, style, testID }: IListIcon) => { + const { theme } = useTheme(); + + return ( + + + + ); +}); ListIcon.displayName = 'List.Icon'; -export default withTheme(ListIcon); +export default ListIcon; diff --git a/app/containers/List/ListInfo.tsx b/app/containers/List/ListInfo.tsx index 2bfe68e32..baac47ccc 100644 --- a/app/containers/List/ListInfo.tsx +++ b/app/containers/List/ListInfo.tsx @@ -3,7 +3,7 @@ import { StyleSheet, Text, View } from 'react-native'; import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { PADDING_HORIZONTAL } from './constants'; import I18n from '../../i18n'; @@ -18,18 +18,20 @@ const styles = StyleSheet.create({ } }); -interface IListHeader { +interface IListInfo { info: string; - theme?: string; translateInfo?: boolean; } -const ListInfo = React.memo(({ info, theme, translateInfo = true }: IListHeader) => ( - - {translateInfo ? I18n.t(info) : info} - -)); +const ListInfo = React.memo(({ info, translateInfo = true }: IListInfo) => { + const { theme } = useTheme(); + return ( + + {translateInfo ? I18n.t(info) : info} + + ); +}); ListInfo.displayName = 'List.Info'; -export default withTheme(ListInfo); +export default ListInfo; diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/ListItem.tsx index 87abfd2dd..c09266a41 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/ListItem.tsx @@ -4,11 +4,11 @@ import { I18nManager, StyleSheet, Text, View } from 'react-native'; import Touch from '../../utils/touch'; import { themes } from '../../constants/colors'; import sharedStyles from '../../views/Styles'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import I18n from '../../i18n'; import { Icon } from '.'; import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants'; -import { withDimensions } from '../../dimensions'; +import { useDimensions } from '../../dimensions'; import { CustomIcon } from '../../lib/Icons'; const styles = StyleSheet.create({ @@ -59,13 +59,12 @@ interface IListItemContent { left?: () => JSX.Element | null; right?: () => JSX.Element | null; disabled?: boolean; + theme: string; testID?: string; - theme?: string; color?: string; translateTitle?: boolean; translateSubtitle?: boolean; showActionIndicator?: boolean; - fontScale?: number; alert?: boolean; } @@ -78,78 +77,85 @@ const Content = React.memo( left, right, color, - theme, - fontScale, alert, translateTitle = true, translateSubtitle = true, - showActionIndicator = false - }: IListItemContent) => ( - - {left ? {left()} : null} - - - - {translateTitle ? I18n.t(title) : title} - - {alert ? ( - + showActionIndicator = false, + theme + }: IListItemContent) => { + const { fontScale } = useDimensions(); + + return ( + + {left ? {left()} : null} + + + + {translateTitle ? I18n.t(title) : title} + + {alert ? ( + + ) : null} + + {subtitle ? ( + + {translateSubtitle ? I18n.t(subtitle) : subtitle} + ) : null} - {subtitle ? ( - - {translateSubtitle ? I18n.t(subtitle) : subtitle} - + {right || showActionIndicator ? ( + + {right ? right() : null} + {showActionIndicator ? : null} + ) : null} - {right || showActionIndicator ? ( - - {right ? right() : null} - {showActionIndicator ? : null} - - ) : null} - - ) + ); + } ); -interface IListButtonPress { - onPress?: Function; +interface IListButtonPress extends IListItemButton { + onPress: Function; } -interface IListItemButton extends IListButtonPress { +interface IListItemButton { title?: string; disabled?: boolean; - theme?: string; + theme: string; backgroundColor?: string; underlayColor?: string; } -const Button = React.memo(({ onPress, backgroundColor, underlayColor, ...props }: IListItemButton) => ( +const Button = React.memo(({ onPress, backgroundColor, underlayColor, ...props }: IListButtonPress) => ( onPress!(props.title)} - style={{ backgroundColor: backgroundColor || themes[props.theme!].backgroundColor }} + onPress={() => onPress(props.title)} + style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }} underlayColor={underlayColor} enabled={!props.disabled} - theme={props.theme!}> + theme={props.theme}> )); -interface IListItem extends IListItemContent, IListButtonPress { +interface IListItem extends Omit, Omit { backgroundColor?: string; + onPress?: Function; } -const ListItem = React.memo(({ ...props }: IListItem) => { +const ListItem = React.memo(({ ...props }: IListItem) => { + const { theme } = useTheme(); + if (props.onPress) { - return