From ec0faf7e7fb09c680322e1d3778acc93b67af97b Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sat, 20 Mar 2021 17:51:38 +0100 Subject: [PATCH 1/6] Gracefully check reply accounts --- src/components/Timeline/Default.tsx | 4 ++-- src/components/Timeline/Notifications.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index 278cf8c9..0c8f5c39 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -141,8 +141,8 @@ const TimelineDefault: React.FC = ({ ([actualStatus.account] as Mastodon.Account[] & Mastodon.Mention[]) .concat(actualStatus.mentions) .filter(d => d?.id !== instanceAccount?.id), - d => d.id - ).map(d => d.acct)} + d => d?.id + ).map(d => d?.acct)} reblog={item.reblog ? true : false} /> )} diff --git a/src/components/Timeline/Notifications.tsx b/src/components/Timeline/Notifications.tsx index 587ed240..76420b21 100644 --- a/src/components/Timeline/Notifications.tsx +++ b/src/components/Timeline/Notifications.tsx @@ -138,9 +138,9 @@ const TimelineNotifications: React.FC = ({ ([notification.status.account] as Mastodon.Account[] & Mastodon.Mention[]) .concat(notification.status.mentions) - .filter(d => d.id !== instanceAccount?.id), - d => d.id - ).map(d => d.acct)} + .filter(d => d?.id !== instanceAccount?.id), + d => d?.id + ).map(d => d?.acct)} reblog={false} /> ) : null} From 69999dadb65db82bde5f70fe8d94a4930d8d7932 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sat, 20 Mar 2021 19:41:46 +0100 Subject: [PATCH 2/6] Fixed #74 --- src/screens/Compose/utils/post.ts | 2 +- src/screens/Tabs/Me/Push.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/Compose/utils/post.ts b/src/screens/Compose/utils/post.ts index 2ba436cc..37f02d72 100644 --- a/src/screens/Compose/utils/post.ts +++ b/src/screens/Compose/utils/post.ts @@ -15,7 +15,7 @@ const composePost = async ( url: `statuses/${composeState.replyToStatus.id}` }) } catch (err) { - if (err.status == 404) { + if (err.status && err.status == 404) { return Promise.reject({ removeReply: true }) } } diff --git a/src/screens/Tabs/Me/Push.tsx b/src/screens/Tabs/Me/Push.tsx index 306ad6c3..194b5b22 100644 --- a/src/screens/Tabs/Me/Push.tsx +++ b/src/screens/Tabs/Me/Push.tsx @@ -94,7 +94,7 @@ const ScreenMeSettingsPush: React.FC = () => { setPushEnabled(result.granted) setPushCanAskAgain(result.canAskAgain) } else { - Linking.openURL('app-settings:') + Linking.openSettings() } }} /> From 2825e76dadf57df9bbf98ca7b695f9b0a5f22c54 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sun, 21 Mar 2021 12:32:49 +0100 Subject: [PATCH 3/6] Moved submodule to codebase Due to using new dependencies such as `react-native-gesture-handler` --- .github/workflows/build.yml | 2 - .gitmodules | 3 - src/modules/react-native-image-viewing | 1 - .../ImageViewer/@types/extensions.d.ts | 7 + src/screens/ImageViewer/@types/index.ts | 26 ++ src/screens/ImageViewer/Root.tsx | 128 ++++++ .../components/ImageItem.android.tsx | 117 +++++ .../ImageViewer/components/ImageItem.d.ts | 32 ++ .../ImageViewer/components/ImageItem.ios.tsx | 175 ++++++++ .../hooks/useAnimatedComponents.ts | 40 ++ .../ImageViewer/hooks/useDoubleTapToZoom.ts | 64 +++ .../ImageViewer/hooks/useImageIndexChange.ts | 31 ++ .../ImageViewer/hooks/usePanResponder.ts | 407 ++++++++++++++++++ .../ImageViewer/hooks/useRequestClose.ts | 23 + src/screens/ImageViewer/utils.ts | 146 +++++++ src/screens/ImagesViewer.tsx | 69 ++- 16 files changed, 1230 insertions(+), 41 deletions(-) delete mode 100644 .gitmodules delete mode 160000 src/modules/react-native-image-viewing create mode 100644 src/screens/ImageViewer/@types/extensions.d.ts create mode 100644 src/screens/ImageViewer/@types/index.ts create mode 100644 src/screens/ImageViewer/Root.tsx create mode 100644 src/screens/ImageViewer/components/ImageItem.android.tsx create mode 100644 src/screens/ImageViewer/components/ImageItem.d.ts create mode 100644 src/screens/ImageViewer/components/ImageItem.ios.tsx create mode 100644 src/screens/ImageViewer/hooks/useAnimatedComponents.ts create mode 100644 src/screens/ImageViewer/hooks/useDoubleTapToZoom.ts create mode 100644 src/screens/ImageViewer/hooks/useImageIndexChange.ts create mode 100644 src/screens/ImageViewer/hooks/usePanResponder.ts create mode 100644 src/screens/ImageViewer/hooks/useRequestClose.ts create mode 100644 src/screens/ImageViewer/utils.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 515be358..0d991fd2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,8 +16,6 @@ jobs: id: branch - name: -- Step 1 -- Checkout code uses: actions/checkout@v2 - with: - submodules: true - name: -- Step 2 -- Setup node uses: actions/setup-node@v2 with: diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1a0c8f06..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "src/modules/react-native-image-viewing"] - path = src/modules/react-native-image-viewing - url = https://github.com/xmflsct/react-native-image-viewing.git diff --git a/src/modules/react-native-image-viewing b/src/modules/react-native-image-viewing deleted file mode 160000 index bba2f756..00000000 --- a/src/modules/react-native-image-viewing +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bba2f756a9d45c79a5ebf7e5e3124eac49b0c9f7 diff --git a/src/screens/ImageViewer/@types/extensions.d.ts b/src/screens/ImageViewer/@types/extensions.d.ts new file mode 100644 index 00000000..fb7fae83 --- /dev/null +++ b/src/screens/ImageViewer/@types/extensions.d.ts @@ -0,0 +1,7 @@ +import * as rn from "react-native"; + +declare module "react-native" { + class VirtualizedList extends React.Component< + VirtualizedListProps + > {} +} diff --git a/src/screens/ImageViewer/@types/index.ts b/src/screens/ImageViewer/@types/index.ts new file mode 100644 index 00000000..7150b5df --- /dev/null +++ b/src/screens/ImageViewer/@types/index.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export type Dimensions = { + width: number + height: number +} + +export type Position = { + x: number + y: number +} + +export type ImageSource = { + id: string + preview_url: string + remote_url?: string + url: string + width?: number + height?: number +} diff --git a/src/screens/ImageViewer/Root.tsx b/src/screens/ImageViewer/Root.tsx new file mode 100644 index 00000000..6b99a934 --- /dev/null +++ b/src/screens/ImageViewer/Root.tsx @@ -0,0 +1,128 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React, { ComponentType, useCallback, useEffect } from 'react' +import { + Animated, + Dimensions, + StyleSheet, + View, + VirtualizedList +} from 'react-native' +import { ImageSource } from './@types' +import ImageItem from './components/ImageItem' +import useAnimatedComponents from './hooks/useAnimatedComponents' +import useImageIndexChange from './hooks/useImageIndexChange' +import useRequestClose from './hooks/useRequestClose' + +type Props = { + images: ImageSource[] + imageIndex: number + onRequestClose: () => void + onLongPress?: (image: ImageSource) => void + onImageIndexChange?: (imageIndex: number) => void + backgroundColor?: string + swipeToCloseEnabled?: boolean + delayLongPress?: number + HeaderComponent: ComponentType<{ imageIndex: number }> +} + +const DEFAULT_BG_COLOR = '#000' +const DEFAULT_DELAY_LONG_PRESS = 800 +const SCREEN = Dimensions.get('screen') +const SCREEN_WIDTH = SCREEN.width + +function ImageViewer ({ + images, + imageIndex, + onRequestClose, + onLongPress = () => {}, + onImageIndexChange, + backgroundColor = DEFAULT_BG_COLOR, + swipeToCloseEnabled, + delayLongPress = DEFAULT_DELAY_LONG_PRESS, + HeaderComponent +}: Props) { + const imageList = React.createRef>() + const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose) + const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN) + const [headerTransform, toggleBarsVisible] = useAnimatedComponents() + + useEffect(() => { + if (onImageIndexChange) { + onImageIndexChange(currentImageIndex) + } + }, [currentImageIndex]) + + const onZoom = useCallback( + (isScaled: boolean) => { + // @ts-ignore + imageList?.current?.setNativeProps({ scrollEnabled: !isScaled }) + toggleBarsVisible(!isScaled) + }, + [imageList] + ) + + return ( + + + {React.createElement(HeaderComponent, { + imageIndex: currentImageIndex + })} + + images.length - 1 ? images.length - 1 : imageIndex + } + getItem={(_, index) => images[index]} + getItemCount={() => images.length} + getItemLayout={(_, index) => ({ + length: SCREEN_WIDTH, + offset: SCREEN_WIDTH * index, + index + })} + renderItem={({ item: imageSrc }) => ( + + )} + onMomentumScrollEnd={onScroll} + keyExtractor={imageSrc => imageSrc.url} + /> + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#000' + }, + header: { + position: 'absolute', + width: '100%', + zIndex: 1, + top: 0 + } +}) + +export default ImageViewer diff --git a/src/screens/ImageViewer/components/ImageItem.android.tsx b/src/screens/ImageViewer/components/ImageItem.android.tsx new file mode 100644 index 00000000..d907ad33 --- /dev/null +++ b/src/screens/ImageViewer/components/ImageItem.android.tsx @@ -0,0 +1,117 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import GracefullyImage from '@components/GracefullyImage' +import React, { useState, useCallback } from 'react' +import { Animated, Dimensions, StyleSheet } from 'react-native' +import { ImageSource } from '../@types' +import usePanResponder from '../hooks/usePanResponder' +import { getImageStyles, getImageTransform } from '../utils' + +const SCREEN = Dimensions.get('window') +const SCREEN_WIDTH = SCREEN.width +const SCREEN_HEIGHT = SCREEN.height + +type Props = { + imageSrc: ImageSource + onRequestClose: () => void + onZoom: (isZoomed: boolean) => void + onLongPress: (image: ImageSource) => void + delayLongPress: number + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean +} + +const ImageItem = ({ + imageSrc, + onZoom, + onRequestClose, + onLongPress, + delayLongPress, + doubleTapToZoomEnabled = true +}: Props) => { + const imageContainer = React.createRef() + const [imageDimensions, setImageDimensions] = useState({ + width: imageSrc.width || 0, + height: imageSrc.height || 0 + }) + const [translate, scale] = getImageTransform(imageDimensions, SCREEN) + + const onZoomPerformed = (isZoomed: boolean) => { + onZoom(isZoomed) + if (imageContainer?.current) { + // @ts-ignore + imageContainer.current.setNativeProps({ + scrollEnabled: !isZoomed + }) + } + } + + const onLongPressHandler = useCallback(() => { + onLongPress(imageSrc) + }, [imageSrc, onLongPress]) + + const [panHandlers, scaleValue, translateValue] = usePanResponder({ + initialScale: scale || 1, + initialTranslate: translate || { x: 0, y: 0 }, + onZoom: onZoomPerformed, + doubleTapToZoomEnabled, + onLongPress: onLongPressHandler, + delayLongPress, + onRequestClose + }) + + const imagesStyles = getImageStyles( + imageDimensions, + translateValue, + scaleValue + ) + + return ( + + + } + /> + + ) +} + +const styles = StyleSheet.create({ + listItem: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT + }, + imageScrollContainer: { + height: SCREEN_HEIGHT * 2 + } +}) + +export default React.memo(ImageItem) diff --git a/src/screens/ImageViewer/components/ImageItem.d.ts b/src/screens/ImageViewer/components/ImageItem.d.ts new file mode 100644 index 00000000..b1bd632f --- /dev/null +++ b/src/screens/ImageViewer/components/ImageItem.d.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React from "react"; +import { GestureResponderEvent } from "react-native"; +import { ImageSource } from "../@types"; + +declare type Props = { + imageSrc: ImageSource; + onRequestClose: () => void; + onZoom: (isZoomed: boolean) => void; + onLongPress: (image: ImageSource) => void; + delayLongPress: number; + swipeToCloseEnabled?: boolean; + doubleTapToZoomEnabled?: boolean; +}; + +declare const _default: React.MemoExoticComponent<({ + imageSrc, + onZoom, + onRequestClose, + onLongPress, + delayLongPress, + swipeToCloseEnabled, +}: Props) => JSX.Element>; + +export default _default; diff --git a/src/screens/ImageViewer/components/ImageItem.ios.tsx b/src/screens/ImageViewer/components/ImageItem.ios.tsx new file mode 100644 index 00000000..bac05f0d --- /dev/null +++ b/src/screens/ImageViewer/components/ImageItem.ios.tsx @@ -0,0 +1,175 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import GracefullyImage from '@components/GracefullyImage' +import React, { createRef, useCallback, useRef, useState } from 'react' +import { + Animated, + Dimensions, + NativeScrollEvent, + NativeSyntheticEvent, + ScrollView, + StyleSheet +} from 'react-native' +import { + LongPressGestureHandler, + State, + TapGestureHandler +} from 'react-native-gesture-handler' +import { ImageSource } from '../@types' +import useDoubleTapToZoom from '../hooks/useDoubleTapToZoom' +import { getImageStyles, getImageTransform } from '../utils' + +const SWIPE_CLOSE_OFFSET = 75 +const SWIPE_CLOSE_VELOCITY = 0.55 +const SCREEN = Dimensions.get('screen') +const SCREEN_WIDTH = SCREEN.width +const SCREEN_HEIGHT = SCREEN.height + +type Props = { + imageSrc: ImageSource + onRequestClose: () => void + onZoom: (scaled: boolean) => void + onLongPress: (image: ImageSource) => void + swipeToCloseEnabled?: boolean +} + +const doubleTap = createRef() + +const ImageItem = ({ + imageSrc, + onZoom, + onRequestClose, + onLongPress, + swipeToCloseEnabled = true +}: Props) => { + const scrollViewRef = useRef(null) + const [scaled, setScaled] = useState(false) + const [imageDimensions, setImageDimensions] = useState({ + width: imageSrc.width || 0, + height: imageSrc.height || 0 + }) + const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN) + + const [translate, scale] = getImageTransform(imageDimensions, SCREEN) + const scrollValueY = new Animated.Value(0) + const scaleValue = new Animated.Value(scale || 1) + const translateValue = new Animated.ValueXY(translate) + const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1 + + const imageOpacity = scrollValueY.interpolate({ + inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], + outputRange: [0.5, 1, 0.5] + }) + const imagesStyles = getImageStyles( + imageDimensions, + translateValue, + scaleValue + ) + const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity } + + const onScrollEndDrag = useCallback( + ({ nativeEvent }: NativeSyntheticEvent) => { + const velocityY = nativeEvent?.velocity?.y ?? 0 + const scaled = nativeEvent?.zoomScale > 1 + + onZoom(scaled) + setScaled(scaled) + + if ( + !scaled && + swipeToCloseEnabled && + Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY + ) { + onRequestClose() + } + }, + [scaled] + ) + + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const offsetY = nativeEvent?.contentOffset?.y ?? 0 + + if (nativeEvent?.zoomScale > 1) { + return + } + + scrollValueY.setValue(offsetY) + } + + return ( + { + if (nativeEvent.state === State.ACTIVE) { + onLongPress(imageSrc) + } + }} + > + + nativeEvent.state === State.ACTIVE && onRequestClose() + } + waitFor={doubleTap} + > + + + + } + /> + + + + + ) +} + +const styles = StyleSheet.create({ + listItem: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT + }, + imageScrollContainer: { + height: SCREEN_HEIGHT + } +}) + +export default React.memo(ImageItem) diff --git a/src/screens/ImageViewer/hooks/useAnimatedComponents.ts b/src/screens/ImageViewer/hooks/useAnimatedComponents.ts new file mode 100644 index 00000000..099a8ea6 --- /dev/null +++ b/src/screens/ImageViewer/hooks/useAnimatedComponents.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { Animated } from 'react-native' + +const INITIAL_POSITION = { x: 0, y: 0 } +const ANIMATION_CONFIG = { + duration: 200, + useNativeDriver: true +} + +const useAnimatedComponents = () => { + const headerTranslate = new Animated.ValueXY(INITIAL_POSITION) + + const toggleVisible = (isVisible: boolean) => { + if (isVisible) { + Animated.parallel([ + Animated.timing(headerTranslate.y, { ...ANIMATION_CONFIG, toValue: 0 }) + ]).start() + } else { + Animated.parallel([ + Animated.timing(headerTranslate.y, { + ...ANIMATION_CONFIG, + toValue: -300 + }) + ]).start() + } + } + + const headerTransform = headerTranslate.getTranslateTransform() + + return [headerTransform, toggleVisible] as const +} + +export default useAnimatedComponents diff --git a/src/screens/ImageViewer/hooks/useDoubleTapToZoom.ts b/src/screens/ImageViewer/hooks/useDoubleTapToZoom.ts new file mode 100644 index 00000000..80620b44 --- /dev/null +++ b/src/screens/ImageViewer/hooks/useDoubleTapToZoom.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React, { useCallback } from 'react' +import { ScrollView } from 'react-native' +import { + HandlerStateChangeEvent, + State, + TapGestureHandlerEventPayload +} from 'react-native-gesture-handler' +import { Dimensions } from '../@types' + +/** + * This is iOS only. + * Same functionality for Android implemented inside usePanResponder hook. + */ +function useDoubleTapToZoom ( + scrollViewRef: React.RefObject, + scaled: boolean, + screen: Dimensions +) { + const handleDoubleTap = useCallback( + ({ + nativeEvent + }: HandlerStateChangeEvent) => { + if (nativeEvent.state === State.ACTIVE) { + const scrollResponderRef = scrollViewRef?.current?.getScrollResponder() + + const { absoluteX, absoluteY } = nativeEvent + let targetX = 0 + let targetY = 0 + let targetWidth = screen.width + let targetHeight = screen.height + + // Zooming in + // TODO: Add more precise calculation of targetX, targetY based on touch + if (!scaled) { + targetX = absoluteX / 2 + targetY = absoluteY / 2 + targetWidth = screen.width / 2 + targetHeight = screen.height / 2 + } + + scrollResponderRef?.scrollResponderZoomTo({ + x: targetX, + y: targetY, + width: targetWidth, + height: targetHeight, + animated: true + }) + } + }, + [scaled] + ) + + return handleDoubleTap +} + +export default useDoubleTapToZoom diff --git a/src/screens/ImageViewer/hooks/useImageIndexChange.ts b/src/screens/ImageViewer/hooks/useImageIndexChange.ts new file mode 100644 index 00000000..3b02e34a --- /dev/null +++ b/src/screens/ImageViewer/hooks/useImageIndexChange.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { useState } from 'react' +import { NativeSyntheticEvent, NativeScrollEvent } from 'react-native' +import { Dimensions } from '../@types' + +const useImageIndexChange = (imageIndex: number, screen: Dimensions) => { + const [currentImageIndex, setImageIndex] = useState(imageIndex) + const onScroll = (event: NativeSyntheticEvent) => { + const { + nativeEvent: { + contentOffset: { x: scrollX } + } + } = event + + if (screen.width) { + const nextIndex = Math.round(scrollX / screen.width) + setImageIndex(nextIndex < 0 ? 0 : nextIndex) + } + } + + return [currentImageIndex, onScroll] as const +} + +export default useImageIndexChange diff --git a/src/screens/ImageViewer/hooks/usePanResponder.ts b/src/screens/ImageViewer/hooks/usePanResponder.ts new file mode 100644 index 00000000..2be5b685 --- /dev/null +++ b/src/screens/ImageViewer/hooks/usePanResponder.ts @@ -0,0 +1,407 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { useMemo, useEffect, useCallback } from 'react' +import { + Animated, + Dimensions, + GestureResponderEvent, + GestureResponderHandlers, + NativeTouchEvent, + PanResponderGestureState +} from 'react-native' +import { Position } from '../@types' +import { + createPanResponder, + getDistanceBetweenTouches, + getImageTranslate, + getImageDimensionsByTranslate +} from '../utils' + +const SCREEN = Dimensions.get('window') +const SCREEN_WIDTH = SCREEN.width +const SCREEN_HEIGHT = SCREEN.height +const MIN_DIMENSION = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) + +const SCALE_MAX = 1 +const DOUBLE_TAP_DELAY = 300 +const OUT_BOUND_MULTIPLIER = 0.75 + +type Props = { + initialScale: number + initialTranslate: Position + onZoom: (isZoomed: boolean) => void + doubleTapToZoomEnabled: boolean + onLongPress: () => void + delayLongPress: number + onRequestClose: () => void +} + +const usePanResponder = ({ + initialScale, + initialTranslate, + onZoom, + doubleTapToZoomEnabled, + onLongPress, + delayLongPress, + onRequestClose +}: Props): Readonly<[ + GestureResponderHandlers, + Animated.Value, + Animated.ValueXY +]> => { + let numberInitialTouches = 1 + let initialTouches: NativeTouchEvent[] = [] + let currentScale = initialScale + let currentTranslate = initialTranslate + let tmpScale = 0 + let tmpTranslate: Position | null = null + let isDoubleTapPerformed = false + let lastTapTS: number | null = null + let timer: number | null = null + let longPressHandlerRef: number | null = null + + const meaningfulShift = MIN_DIMENSION * 0.01 + const scaleValue = new Animated.Value(initialScale) + const translateValue = new Animated.ValueXY(initialTranslate) + + const imageDimensions = getImageDimensionsByTranslate( + initialTranslate, + SCREEN + ) + + const getBounds = (scale: number) => { + const scaledImageDimensions = { + width: imageDimensions.width * scale, + height: imageDimensions.height * scale + } + const translateDelta = getImageTranslate(scaledImageDimensions, SCREEN) + + const left = initialTranslate.x - translateDelta.x + const right = left - (scaledImageDimensions.width - SCREEN.width) + const top = initialTranslate.y - translateDelta.y + const bottom = top - (scaledImageDimensions.height - SCREEN.height) + + return [top, left, bottom, right] + } + + const getTranslateInBounds = (translate: Position, scale: number) => { + const inBoundTranslate = { x: translate.x, y: translate.y } + const [topBound, leftBound, bottomBound, rightBound] = getBounds(scale) + + if (translate.x > leftBound) { + inBoundTranslate.x = leftBound + } else if (translate.x < rightBound) { + inBoundTranslate.x = rightBound + } + + if (translate.y > topBound) { + inBoundTranslate.y = topBound + } else if (translate.y < bottomBound) { + inBoundTranslate.y = bottomBound + } + + return inBoundTranslate + } + + const fitsScreenByWidth = () => + imageDimensions.width * currentScale < SCREEN_WIDTH + const fitsScreenByHeight = () => + imageDimensions.height * currentScale < SCREEN_HEIGHT + + useEffect(() => { + scaleValue.addListener(({ value }) => { + if (typeof onZoom === 'function') { + onZoom(value !== initialScale) + } + }) + + return () => scaleValue.removeAllListeners() + }) + + const cancelLongPressHandle = () => { + longPressHandlerRef && clearTimeout(longPressHandlerRef) + } + + const handlers = { + onGrant: ( + _: GestureResponderEvent, + gestureState: PanResponderGestureState + ) => { + numberInitialTouches = gestureState.numberActiveTouches + + if (gestureState.numberActiveTouches > 1) return + + longPressHandlerRef = setTimeout(onLongPress, delayLongPress) + }, + onStart: ( + event: GestureResponderEvent, + gestureState: PanResponderGestureState + ) => { + initialTouches = event.nativeEvent.touches + numberInitialTouches = gestureState.numberActiveTouches + + if (gestureState.numberActiveTouches > 1) return + + const tapTS = Date.now() + !timer && + (timer = setTimeout(() => onRequestClose(), DOUBLE_TAP_DELAY + 50)) + // Handle double tap event by calculating diff between first and second taps timestamps + + isDoubleTapPerformed = Boolean( + lastTapTS && tapTS - lastTapTS < DOUBLE_TAP_DELAY + ) + + if (doubleTapToZoomEnabled && isDoubleTapPerformed) { + clearTimeout(timer) + const isScaled = currentTranslate.x !== initialTranslate.x // currentScale !== initialScale; + const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0] + const targetScale = SCALE_MAX + const nextScale = isScaled ? initialScale : targetScale + const nextTranslate = isScaled + ? initialTranslate + : getTranslateInBounds( + { + x: + initialTranslate.x + + (SCREEN_WIDTH / 2 - touchX) * (targetScale / currentScale), + y: + initialTranslate.y + + (SCREEN_HEIGHT / 2 - -touchY) * (targetScale / currentScale) + }, + targetScale + ) + + onZoom(!isScaled) + + Animated.parallel( + [ + Animated.timing(translateValue.x, { + toValue: nextTranslate.x, + duration: 300, + useNativeDriver: true + }), + Animated.timing(translateValue.y, { + toValue: nextTranslate.y, + duration: 300, + useNativeDriver: true + }), + Animated.timing(scaleValue, { + toValue: nextScale, + duration: 300, + useNativeDriver: true + }) + ], + { stopTogether: false } + ).start(() => { + currentScale = nextScale + currentTranslate = nextTranslate + }) + + lastTapTS = null + timer = null + } else { + lastTapTS = Date.now() + } + }, + onMove: ( + event: GestureResponderEvent, + gestureState: PanResponderGestureState + ) => { + const { dx, dy } = gestureState + + if (Math.abs(dx) >= meaningfulShift || Math.abs(dy) >= meaningfulShift) { + cancelLongPressHandle() + timer && clearTimeout(timer) + } + + // Don't need to handle move because double tap in progress (was handled in onStart) + if (doubleTapToZoomEnabled && isDoubleTapPerformed) { + cancelLongPressHandle() + timer && clearTimeout(timer) + return + } + + if ( + numberInitialTouches === 1 && + gestureState.numberActiveTouches === 2 + ) { + numberInitialTouches = 2 + initialTouches = event.nativeEvent.touches + } + + const isTapGesture = + numberInitialTouches == 1 && gestureState.numberActiveTouches === 1 + const isPinchGesture = + numberInitialTouches === 2 && gestureState.numberActiveTouches === 2 + + if (isPinchGesture) { + cancelLongPressHandle() + timer && clearTimeout(timer) + + const initialDistance = getDistanceBetweenTouches(initialTouches) + const currentDistance = getDistanceBetweenTouches( + event.nativeEvent.touches + ) + + let nextScale = (currentDistance / initialDistance) * currentScale + + /** + * In case image is scaling smaller than initial size -> + * slow down this transition by applying OUT_BOUND_MULTIPLIER + */ + if (nextScale < initialScale) { + nextScale = + nextScale + (initialScale - nextScale) * OUT_BOUND_MULTIPLIER + } + + /** + * In case image is scaling down -> move it in direction of initial position + */ + if (currentScale > initialScale && currentScale > nextScale) { + const k = (currentScale - initialScale) / (currentScale - nextScale) + + const nextTranslateX = + nextScale < initialScale + ? initialTranslate.x + : currentTranslate.x - + (currentTranslate.x - initialTranslate.x) / k + + const nextTranslateY = + nextScale < initialScale + ? initialTranslate.y + : currentTranslate.y - + (currentTranslate.y - initialTranslate.y) / k + + translateValue.x.setValue(nextTranslateX) + translateValue.y.setValue(nextTranslateY) + + tmpTranslate = { x: nextTranslateX, y: nextTranslateY } + } + + scaleValue.setValue(nextScale) + tmpScale = nextScale + } + + if (isTapGesture && currentScale > initialScale) { + const { x, y } = currentTranslate + const { dx, dy } = gestureState + const [topBound, leftBound, bottomBound, rightBound] = getBounds( + currentScale + ) + + let nextTranslateX = x + dx + let nextTranslateY = y + dy + + if (nextTranslateX > leftBound) { + nextTranslateX = + nextTranslateX - (nextTranslateX - leftBound) * OUT_BOUND_MULTIPLIER + } + + if (nextTranslateX < rightBound) { + nextTranslateX = + nextTranslateX - + (nextTranslateX - rightBound) * OUT_BOUND_MULTIPLIER + } + + if (nextTranslateY > topBound) { + nextTranslateY = + nextTranslateY - (nextTranslateY - topBound) * OUT_BOUND_MULTIPLIER + } + + if (nextTranslateY < bottomBound) { + nextTranslateY = + nextTranslateY - + (nextTranslateY - bottomBound) * OUT_BOUND_MULTIPLIER + } + + if (fitsScreenByWidth()) { + nextTranslateX = x + } + + if (fitsScreenByHeight()) { + nextTranslateY = y + } + + translateValue.x.setValue(nextTranslateX) + translateValue.y.setValue(nextTranslateY) + + tmpTranslate = { x: nextTranslateX, y: nextTranslateY } + } + }, + onRelease: () => { + cancelLongPressHandle() + + if (isDoubleTapPerformed) { + isDoubleTapPerformed = false + } + + if (tmpScale > 0) { + if (tmpScale < initialScale || tmpScale > SCALE_MAX) { + tmpScale = tmpScale < initialScale ? initialScale : SCALE_MAX + Animated.timing(scaleValue, { + toValue: tmpScale, + duration: 100, + useNativeDriver: true + }).start() + } + + currentScale = tmpScale + tmpScale = 0 + } + + if (tmpTranslate) { + const { x, y } = tmpTranslate + const [topBound, leftBound, bottomBound, rightBound] = getBounds( + currentScale + ) + + let nextTranslateX = x + let nextTranslateY = y + + if (!fitsScreenByWidth()) { + if (nextTranslateX > leftBound) { + nextTranslateX = leftBound + } else if (nextTranslateX < rightBound) { + nextTranslateX = rightBound + } + } + + if (!fitsScreenByHeight()) { + if (nextTranslateY > topBound) { + nextTranslateY = topBound + } else if (nextTranslateY < bottomBound) { + nextTranslateY = bottomBound + } + } + + Animated.parallel([ + Animated.timing(translateValue.x, { + toValue: nextTranslateX, + duration: 100, + useNativeDriver: true + }), + Animated.timing(translateValue.y, { + toValue: nextTranslateY, + duration: 100, + useNativeDriver: true + }) + ]).start() + + currentTranslate = { x: nextTranslateX, y: nextTranslateY } + tmpTranslate = null + } + } + } + + const panResponder = useMemo(() => createPanResponder(handlers), [handlers]) + + return [panResponder.panHandlers, scaleValue, translateValue] +} + +export default usePanResponder diff --git a/src/screens/ImageViewer/hooks/useRequestClose.ts b/src/screens/ImageViewer/hooks/useRequestClose.ts new file mode 100644 index 00000000..9bdf114d --- /dev/null +++ b/src/screens/ImageViewer/hooks/useRequestClose.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { useState } from 'react' + +const useRequestClose = (onRequestClose: () => void) => { + const [opacity, setOpacity] = useState(1) + + return [ + opacity, + () => { + setOpacity(0) + onRequestClose() + } + ] as const +} + +export default useRequestClose diff --git a/src/screens/ImageViewer/utils.ts b/src/screens/ImageViewer/utils.ts new file mode 100644 index 00000000..54703ac5 --- /dev/null +++ b/src/screens/ImageViewer/utils.ts @@ -0,0 +1,146 @@ +/** + * Copyright (c) JOB TODAY S.A. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + Animated, + GestureResponderEvent, + PanResponder, + PanResponderGestureState, + PanResponderInstance, + NativeTouchEvent +} from 'react-native' +import { Dimensions, Position } from './@types' + +export const getImageTransform = ( + image: Dimensions | null, + screen: Dimensions +) => { + if (!image?.width || !image?.height) { + return [] as const + } + + const wScale = screen.width / image.width + const hScale = screen.height / image.height + const scale = Math.min(wScale, hScale) + const { x, y } = getImageTranslate(image, screen) + + return [{ x, y }, scale] as const +} + +export const getImageStyles = ( + image: Dimensions | null, + translate: Animated.ValueXY, + scale?: Animated.Value +) => { + if (!image?.width || !image?.height) { + return { width: 0, height: 0 } + } + + const transform = translate.getTranslateTransform() + + if (scale) { + transform.push({ scale }, { perspective: new Animated.Value(1000) }) + } + + return { + width: image.width, + height: image.height, + transform + } +} + +export const getImageTranslate = ( + image: Dimensions, + screen: Dimensions +): Position => { + const getTranslateForAxis = (axis: 'x' | 'y'): number => { + const imageSize = axis === 'x' ? image.width : image.height + const screenSize = axis === 'x' ? screen.width : screen.height + + return (screenSize - imageSize) / 2 + } + + return { + x: getTranslateForAxis('x'), + y: getTranslateForAxis('y') + } +} + +export const getImageDimensionsByTranslate = ( + translate: Position, + screen: Dimensions +): Dimensions => ({ + width: screen.width - translate.x * 2, + height: screen.height - translate.y * 2 +}) + +export const getImageTranslateForScale = ( + currentTranslate: Position, + targetScale: number, + screen: Dimensions +): Position => { + const { width, height } = getImageDimensionsByTranslate( + currentTranslate, + screen + ) + + const targetImageDimensions = { + width: width * targetScale, + height: height * targetScale + } + + return getImageTranslate(targetImageDimensions, screen) +} + +type HandlerType = ( + event: GestureResponderEvent, + state: PanResponderGestureState +) => void + +type PanResponderProps = { + onGrant: HandlerType + onStart?: HandlerType + onMove: HandlerType + onRelease?: HandlerType + onTerminate?: HandlerType +} + +export const createPanResponder = ({ + onGrant, + onStart, + onMove, + onRelease, + onTerminate +}: PanResponderProps): PanResponderInstance => + PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onStartShouldSetPanResponderCapture: () => true, + onMoveShouldSetPanResponder: () => true, + onMoveShouldSetPanResponderCapture: () => true, + onPanResponderGrant: onGrant, + onPanResponderStart: onStart, + onPanResponderMove: onMove, + onPanResponderRelease: onRelease, + onPanResponderTerminate: onTerminate, + onPanResponderTerminationRequest: () => false, + onShouldBlockNativeResponder: () => false + }) + +export const getDistanceBetweenTouches = ( + touches: NativeTouchEvent[] +): number => { + const [a, b] = touches + + if (a == null || b == null) { + return 0 + } + + return Math.sqrt( + Math.pow(a.pageX - b.pageX, 2) + Math.pow(a.pageY - b.pageY, 2) + ) +} diff --git a/src/screens/ImagesViewer.tsx b/src/screens/ImagesViewer.tsx index 2f10884c..a2ec53b1 100644 --- a/src/screens/ImagesViewer.tsx +++ b/src/screens/ImagesViewer.tsx @@ -4,7 +4,6 @@ import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header' import { useActionSheet } from '@expo/react-native-action-sheet' import CameraRoll from '@react-native-community/cameraroll' import { StackScreenProps } from '@react-navigation/stack' -import ImageView from '@root/modules/react-native-image-viewing/src/index' import { findIndex } from 'lodash' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -19,6 +18,36 @@ import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context' +import ImageViewer from './ImageViewer/Root' + +type ImageUrl = { + url: string + width?: number | undefined + height?: number | undefined + preview_url: string + remote_url?: string | undefined +} + +const saveImage = async (image: ImageUrl) => { + const hasAndroidPermission = async () => { + const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE + + const hasPermission = await PermissionsAndroid.check(permission) + if (hasPermission) { + return true + } + + const status = await PermissionsAndroid.request(permission) + return status === 'granted' + } + + if (Platform.OS === 'android' && !(await hasAndroidPermission())) { + return + } + CameraRoll.save(image.url || image.remote_url || image.preview_url) + .then(() => haptics('Success')) + .catch(() => haptics('Error')) +} const HeaderComponent = React.memo( ({ @@ -28,43 +57,12 @@ const HeaderComponent = React.memo( }: { navigation: ScreenImagesViewerProp['navigation'] currentIndex: number - imageUrls: { - url: string - width?: number | undefined - height?: number | undefined - preview_url: string - remote_url?: string | undefined - }[] + imageUrls: ImageUrl[] }) => { const insets = useSafeAreaInsets() const { t } = useTranslation('screenImageViewer') const { showActionSheetWithOptions } = useActionSheet() - const hasAndroidPermission = async () => { - const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE - - const hasPermission = await PermissionsAndroid.check(permission) - if (hasPermission) { - return true - } - - const status = await PermissionsAndroid.request(permission) - return status === 'granted' - } - - const saveImage = async () => { - if (Platform.OS === 'android' && !(await hasAndroidPermission())) { - return - } - CameraRoll.save( - imageUrls[currentIndex].url || - imageUrls[currentIndex].remote_url || - imageUrls[currentIndex].preview_url - ) - .then(() => haptics('Success')) - .catch(() => haptics('Error')) - } - const onPress = useCallback(() => { analytics('imageviewer_more_press') showActionSheetWithOptions( @@ -80,7 +78,7 @@ const HeaderComponent = React.memo( switch (buttonIndex) { case 0: analytics('imageviewer_more_save_press') - saveImage() + saveImage(imageUrls[currentIndex]) break case 1: analytics('imageviewer_more_share_press') @@ -147,11 +145,12 @@ const ScreenImagesViewer = ({ return ( - setCurrentIndex(index)} onRequestClose={() => navigation.goBack()} + onLongPress={saveImage} HeaderComponent={() => ( Date: Sun, 21 Mar 2021 13:15:43 +0100 Subject: [PATCH 4/6] Provide more debugging information through Sentry --- src/Screens.tsx | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Screens.tsx b/src/Screens.tsx index 6781fb49..f63b52ac 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -45,7 +45,7 @@ const Screens: React.FC = ({ localCorrupt }) => { dark = 'light-content' } - const routeNameRef = useRef() + const routeRef = useRef<{ name?: string; params?: {} }>() const isConnected = useNetInfo().isConnected useEffect(() => { @@ -114,30 +114,32 @@ const Screens: React.FC = ({ localCorrupt }) => { }, [instanceActive]) // Callbacks - const navigationContainerOnReady = useCallback( - () => - (routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name), - [] - ) + const navigationContainerOnReady = useCallback(() => { + const currentRoute = navigationRef.current?.getCurrentRoute() + routeRef.current = { + name: currentRoute?.name, + params: currentRoute?.params + } + }, []) const navigationContainerOnStateChange = useCallback(() => { - const previousRouteName = routeNameRef.current - const currentRouteName = navigationRef.current?.getCurrentRoute()?.name + const previousRoute = routeRef.current + const currentRoute = navigationRef.current?.getCurrentRoute() - const matchTabName = currentRouteName?.match(/(Tab-.*)-Root/) + const matchTabName = currentRoute?.name?.match(/(Tab-.*)-Root/) if (matchTabName) { //@ts-ignore dispatch(updatePreviousTab(matchTabName[1])) } - if (previousRouteName !== currentRouteName) { - Analytics.setCurrentScreen(currentRouteName) + if (previousRoute?.name !== currentRoute?.name) { + Analytics.setCurrentScreen(currentRoute?.name) Sentry.Native.setContext('page', { - previous: previousRouteName, - current: currentRouteName + previous: previousRoute, + current: currentRoute }) } - routeNameRef.current = currentRouteName + routeRef.current = currentRoute }, []) return ( From d15e8cb65294c4c68b58d524233118ca960caa76 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sun, 21 Mar 2021 14:14:10 +0100 Subject: [PATCH 5/6] Update colour naming Fixed #73 Fixed #58 Fixed #47 --- src/@types/react-navigation.d.ts | 4 +- src/Screens.tsx | 2 +- src/components/Button.tsx | 8 +-- src/components/Hashtag.tsx | 2 +- src/components/Header/Center.tsx | 2 +- src/components/Header/Left.tsx | 6 +-- src/components/Header/Right.tsx | 6 +-- src/components/Instance.tsx | 2 +- src/components/Instance/Info.tsx | 4 +- src/components/Menu/Row.tsx | 4 +- src/components/Message.tsx | 8 +-- src/components/Parse/Emojis.tsx | 2 +- src/components/Parse/HTML.tsx | 6 +-- src/components/Separator.tsx | 2 +- src/components/Timeline.tsx | 4 +- src/components/Timeline/Conversation.tsx | 2 +- src/components/Timeline/Default.tsx | 2 +- src/components/Timeline/Empty.tsx | 10 ++-- src/components/Timeline/Notifications.tsx | 2 +- src/components/Timeline/Refresh.tsx | 6 +-- src/components/Timeline/Shared/Actioned.tsx | 2 +- src/components/Timeline/Shared/Actions.tsx | 42 +++++++-------- .../Timeline/Shared/Attachment/Audio.tsx | 2 +- .../Shared/Attachment/Unsupported.tsx | 2 +- src/components/Timeline/Shared/Card.tsx | 4 +- src/components/Timeline/Shared/Poll.tsx | 4 +- src/screens/Actions/Root.tsx | 4 +- src/screens/Announcements.tsx | 20 ++++---- src/screens/Compose/DraftsList/Root.tsx | 6 +-- src/screens/Compose/EditAttachment/Image.tsx | 4 +- src/screens/Compose/EditAttachment/Root.tsx | 4 +- src/screens/Compose/Posting.tsx | 2 +- src/screens/Compose/Root/Actions.tsx | 10 ++-- .../Compose/Root/Footer/Attachments.tsx | 12 ++--- src/screens/Compose/Root/Footer/Poll.tsx | 2 +- .../Compose/Root/Header/SpoilerInput.tsx | 2 +- src/screens/Compose/Root/Header/TextInput.tsx | 2 +- src/screens/Tabs.tsx | 2 +- src/screens/Tabs/Me/Fontsize.tsx | 7 +-- src/screens/Tabs/Me/Settings/Dev.tsx | 2 +- src/screens/Tabs/Me/Switch/Root.tsx | 4 +- .../Tabs/Shared/Account/Attachments.tsx | 2 +- .../Shared/Account/Information/Fields.tsx | 2 +- .../Tabs/Shared/Account/Information/Stats.tsx | 6 +-- src/screens/Tabs/Shared/Account/Nav.tsx | 2 +- src/screens/Tabs/Shared/Search.tsx | 18 +++---- src/screens/Tabs/Shared/sharedScreens.tsx | 10 ++-- src/utils/styles/themes.ts | 51 +++++++++++-------- 48 files changed, 162 insertions(+), 152 deletions(-) diff --git a/src/@types/react-navigation.d.ts b/src/@types/react-navigation.d.ts index a87a6078..58d7b2f1 100644 --- a/src/@types/react-navigation.d.ts +++ b/src/@types/react-navigation.d.ts @@ -1,4 +1,6 @@ declare namespace Nav { + import { QueryKeyTimeline } from '@utils/queryHooks/timeline' + type RootStackParamList = { 'Screen-Tabs': undefined 'Screen-Actions': @@ -90,7 +92,7 @@ declare namespace Nav { 'Tab-Shared-Search': { text: string | undefined } 'Tab-Shared-Toot': { toot: Mastodon.Status - rootQueryKey: any + rootQueryKey?: QueryKeyTimeline } 'Tab-Shared-Users': | { diff --git a/src/Screens.tsx b/src/Screens.tsx index f63b52ac..f5c9c232 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -144,7 +144,7 @@ const Screens: React.FC = ({ localCorrupt }) => { return ( <> - + = ({ if (destructive) { return theme.red } else { - return theme.primary + return theme.primaryDefault } } } @@ -97,16 +97,16 @@ const Button: React.FC = ({ if (destructive) { return theme.red } else { - return theme.primary + return theme.primaryDefault } } } }, [mode, loading, disabled]) const colorBackground = useMemo(() => { if (overlay) { - return theme.backgroundOverlay + return theme.backgroundOverlayInvert } else { - return theme.background + return theme.backgroundDefault } }, [mode]) diff --git a/src/components/Hashtag.tsx b/src/components/Hashtag.tsx index ebc45430..914896a4 100644 --- a/src/components/Hashtag.tsx +++ b/src/components/Hashtag.tsx @@ -29,7 +29,7 @@ const ComponentHashtag: React.FC = ({ return ( - + #{hashtag.name} diff --git a/src/components/Header/Center.tsx b/src/components/Header/Center.tsx index 6edae7aa..74c93c25 100644 --- a/src/components/Header/Center.tsx +++ b/src/components/Header/Center.tsx @@ -17,7 +17,7 @@ const HeaderCenter = React.memo( diff --git a/src/components/Header/Left.tsx b/src/components/Header/Left.tsx index 7fea3d82..4269383b 100644 --- a/src/components/Header/Left.tsx +++ b/src/components/Header/Left.tsx @@ -25,7 +25,7 @@ const HeaderLeft: React.FC = ({ case 'icon': return ( @@ -33,7 +33,7 @@ const HeaderLeft: React.FC = ({ case 'text': return ( ) @@ -47,7 +47,7 @@ const HeaderLeft: React.FC = ({ style={[ styles.base, { - backgroundColor: theme.backgroundGradientStart, + backgroundColor: theme.backgroundOverlayDefault, ...(type === 'icon' && { height: 44, width: 44, diff --git a/src/components/Header/Right.tsx b/src/components/Header/Right.tsx index 81438a6e..1e40f44a 100644 --- a/src/components/Header/Right.tsx +++ b/src/components/Header/Right.tsx @@ -47,7 +47,7 @@ const HeaderRight: React.FC = ({ name={content} style={{ opacity: loading ? 0 : 1 }} size={StyleConstants.Spacing.M * 1.25} - color={disabled ? theme.secondary : theme.primary} + color={disabled ? theme.secondary : theme.primaryDefault} /> {loading && loadingSpinkit} @@ -59,7 +59,7 @@ const HeaderRight: React.FC = ({ style={[ styles.text, { - color: disabled ? theme.secondary : theme.primary, + color: disabled ? theme.secondary : theme.primaryDefault, opacity: loading ? 0 : 1 } ]} @@ -79,7 +79,7 @@ const HeaderRight: React.FC = ({ style={[ styles.base, { - backgroundColor: theme.backgroundGradientStart, + backgroundColor: theme.backgroundOverlayDefault, ...(type === 'icon' && { height: 44, width: 44, diff --git a/src/components/Instance.tsx b/src/components/Instance.tsx index c880c972..deba3c5d 100644 --- a/src/components/Instance.tsx +++ b/src/components/Instance.tsx @@ -142,7 +142,7 @@ const ComponentInstance: React.FC = ({ style={[ styles.textInput, { - color: theme.primary, + color: theme.primaryDefault, borderBottomColor: instanceQuery.isError ? theme.red : theme.border diff --git a/src/components/Instance/Info.tsx b/src/components/Instance/Info.tsx index b651e90f..b0286465 100644 --- a/src/components/Instance/Info.tsx +++ b/src/components/Instance/Info.tsx @@ -17,9 +17,9 @@ const InstanceInfo = React.memo( return ( - {header} + {header} {content ? ( - + {content} ) : ( diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx index f8664cae..d728849f 100644 --- a/src/components/Menu/Row.tsx +++ b/src/components/Menu/Row.tsx @@ -28,7 +28,7 @@ export interface Props { const MenuRow: React.FC = ({ iconFront, - iconFrontColor = 'primary', + iconFrontColor = 'primaryDefault', title, description, content, @@ -73,7 +73,7 @@ const MenuRow: React.FC = ({ )} {title} diff --git a/src/components/Message.tsx b/src/components/Message.tsx index 83053d13..bb80e29c 100644 --- a/src/components/Message.tsx +++ b/src/components/Message.tsx @@ -87,18 +87,18 @@ const Message = React.memo( position='top' floating style={{ - backgroundColor: theme.background, - shadowColor: theme.primary, + backgroundColor: theme.backgroundDefault, + shadowColor: theme.primaryDefault, shadowOffset: { width: 0, height: 0 }, shadowOpacity: mode === 'light' ? 0.16 : 0.24, shadowRadius: 4 }} titleStyle={{ - color: theme.primary, + color: theme.primaryDefault, ...StyleConstants.FontStyle.M, fontWeight: StyleConstants.Font.Weight.Bold }} - textStyle={{ color: theme.primary, ...StyleConstants.FontStyle.S }} + textStyle={{ color: theme.primaryDefault, ...StyleConstants.FontStyle.S }} // @ts-ignore textProps={{ numberOfLines: 2 }} /> diff --git a/src/components/Parse/Emojis.tsx b/src/components/Parse/Emojis.tsx index c27d4407..9e2e3130 100644 --- a/src/components/Parse/Emojis.tsx +++ b/src/components/Parse/Emojis.tsx @@ -38,7 +38,7 @@ const ParseEmojis = React.memo( const styles = useMemo(() => { return StyleSheet.create({ text: { - color: theme.primary, + color: theme.primaryDefault, fontSize: adaptedFontsize, lineHeight: adaptedLineheight, ...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold }) diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx index 456daf09..3a1f6449 100644 --- a/src/components/Parse/HTML.tsx +++ b/src/components/Parse/HTML.tsx @@ -83,7 +83,7 @@ const renderNode = ({ = ({ refreshControl: ( refetch()} /> diff --git a/src/components/Timeline/Conversation.tsx b/src/components/Timeline/Conversation.tsx index eb10058d..871709bb 100644 --- a/src/components/Timeline/Conversation.tsx +++ b/src/components/Timeline/Conversation.tsx @@ -95,7 +95,7 @@ const TimelineConversation: React.FC = ({ = ({ style={[ styles.statusView, { - backgroundColor: theme.background, + backgroundColor: theme.backgroundDefault, paddingBottom: disableDetails && disableOnPress ? StyleConstants.Spacing.Global.PagePadding diff --git a/src/components/Timeline/Empty.tsx b/src/components/Timeline/Empty.tsx index afaa9376..18d9378a 100644 --- a/src/components/Timeline/Empty.tsx +++ b/src/components/Timeline/Empty.tsx @@ -35,9 +35,9 @@ const TimelineEmpty = React.memo( - + {t('empty.error.message')}