Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/components/Attachments/AttachmentView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useAttachmentCarouselPagerActions} from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext';
import MultiGestureIcon from '@components/Attachments/MultiGestureIcon';
import type {Attachment, AttachmentSource} from '@components/Attachments/types';
import Button from '@components/Button';
import DistanceEReceipt from '@components/DistanceEReceipt';
Expand All @@ -24,6 +25,7 @@ import useThemeStyles from '@hooks/useThemeStyles';

import {add as addCachedPDFPaths} from '@libs/actions/CachedPDFPaths';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {getFileResolution, isHighResolutionImage} from '@libs/fileDownload/FileUtils';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {hasEReceipt, hasReceiptSource, isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest, isPerDiemRequest} from '@libs/TransactionUtils';
Expand Down Expand Up @@ -214,14 +216,24 @@ function AttachmentView({
additionalStyles = [defaultWorkspaceAvatarColor];
}

if (canUseTouchScreen()) {
return (
<MultiGestureIcon
src={source}
contentSize={{width: variables.defaultAvatarPreviewSize, height: variables.defaultAvatarPreviewSize}}
fill={iconFillColor}
additionalStyles={additionalStyles}
/>
);
}

return (
<Icon
src={source}
height={variables.defaultAvatarPreviewSize}
width={variables.defaultAvatarPreviewSize}
fill={iconFillColor}
additionalStyles={additionalStyles}
enableMultiGestureCanvas
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
import ImageSVG from '@components/ImageSVG';
import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas';

import useCanvasSize from '@hooks/useCanvasSize';

import type IconAsset from '@src/types/utils/IconAsset';
import type {Dimensions} from '@src/types/utils/Layout';

import type {StyleProp, ViewStyle} from 'react-native';

import React from 'react';
import {StyleSheet, View} from 'react-native';

import type {ContentSizedIconProps} from './types';

import useCanvasSize from './useCanvasSize';
import useIconCarouselPager from './useIconCarouselPager';

type MultiGestureIconProps = {
/** Additional styles applied to the icon wrapper. */
additionalStyles: StyleProp<ViewStyle>;

/** Icon asset to render. */
src: IconAsset;

/** Fill color passed to the SVG. */
fill?: string;

/** Intrinsic content size for gesture canvases. */
contentSize: Dimensions;
};

/** Renders an icon inside a multi-gesture canvas for pinch, pan, and swipe interactions. */
function MultiGestureIcon({testID, additionalStyles, src, contentSize, iconWidth, iconHeight, fill, isHovered, isPressed, contentFit}: ContentSizedIconProps) {
const {canvasSize, updateCanvasSize} = useCanvasSize();
function MultiGestureIcon({additionalStyles, src, contentSize, fill}: MultiGestureIconProps) {
const {canvasSize, updateCanvasSize, isCanvasLoading} = useCanvasSize();
const {pagerRef, isScrollEnabled, onTap, onSwipeDown} = useIconCarouselPager();

return (
<View
style={StyleSheet.absoluteFill}
onLayout={updateCanvasSize}
>
{!!canvasSize && (
{!isCanvasLoading && (
<MultiGestureCanvas
isActive
canvasSize={canvasSize}
Expand All @@ -31,18 +49,12 @@ function MultiGestureIcon({testID, additionalStyles, src, contentSize, iconWidth
onTap={onTap}
onSwipeDown={onSwipeDown}
>
<View
testID={testID}
style={[additionalStyles]}
>
<View style={additionalStyles}>
<ImageSVG
src={src}
width={iconWidth}
height={iconHeight}
width={contentSize.width}
height={contentSize.height}
fill={fill}
hovered={isHovered}
pressed={isPressed}
contentFit={contentFit}
/>
</View>
</MultiGestureCanvas>
Expand Down
28 changes: 1 addition & 27 deletions src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import useStyleUtils from '@hooks/useStyleUtils';

import {canUseTouchScreen} from '@libs/DeviceCapabilities';

import variables from '@styles/variables';

import type IconAsset from '@src/types/utils/IconAsset';
import type {Dimensions} from '@src/types/utils/Layout';

import type {ImageContentFit} from 'expo-image';
import type {StyleProp, ViewStyle} from 'react-native';
Expand All @@ -16,7 +13,6 @@ import type {IconSize} from './primitives/types';

import BaseIcon from './primitives/BaseIcon';
import InlineIcon from './primitives/InlineIcon';
import MultiGestureIcon from './primitives/MultiGestureIcon';
import resolveIconSize from './primitives/resolveIconSize';

type IconProps = {
Expand Down Expand Up @@ -80,14 +76,11 @@ type IconProps = {
/** Keeps icon sizing consistent when used inside buttons. */
isButtonIcon?: boolean;

/** Wraps the icon in a multi-gesture canvas on touch devices. */
enableMultiGestureCanvas?: boolean;

/** When set, exposes the icon to assistive tech. Leave unset for decorative icons. */
accessibilityLabel?: string;
};

/** Renders an SVG icon with preset sizes, inline layout, and optional gesture support. */
/** Renders an SVG icon with preset sizes and inline layout. */
function Icon({
src,
width = variables.iconSizeNormal,
Expand All @@ -109,7 +102,6 @@ function Icon({
testID = '',
contentFit = 'cover',
isButtonIcon = false,
enableMultiGestureCanvas = false,
accessibilityLabel,
}: IconProps) {
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -139,24 +131,6 @@ function Icon({
);
}

if (canUseTouchScreen() && enableMultiGestureCanvas) {
const contentSize: Dimensions = {width: iconWidth as number, height: iconHeight as number};
return (
<MultiGestureIcon
testID={testID}
additionalStyles={additionalStyles}
src={src}
iconWidth={iconWidth}
iconHeight={iconHeight}
fill={fill}
isHovered={hovered}
isPressed={pressed}
contentFit={contentFit}
contentSize={contentSize}
/>
);
}

return (
<BaseIcon
testID={testID}
Expand Down
12 changes: 10 additions & 2 deletions src/components/Icon/primitives/InlineIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import ImageSVG from '@components/ImageSVG';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';

import type {Dimensions} from '@src/types/utils/Layout';

import React from 'react';
import {View} from 'react-native';

import type {ContentSizedIconProps} from './types';
import type {BaseIconProps} from './types';

/** Shared props for primitives that also receive a measured or layout content size. */
type ContentSizedIcon = {
/** Layout size for inline icons. */
contentSize: Dimensions;
};

/** Renders an icon positioned inline within surrounding text. */
function InlineIcon({testID, additionalStyles, src, contentSize, iconWidth, iconHeight, fill, isHovered, isPressed, contentFit}: ContentSizedIconProps) {
function InlineIcon({testID, additionalStyles, src, contentSize, iconWidth, iconHeight, fill, isHovered, isPressed, contentFit}: BaseIconProps & ContentSizedIcon) {
const StyleUtils = useStyleUtils();
const styles = useThemeStyles();
const iconStyles = [StyleUtils.getWidthAndHeightStyle(contentSize.width ?? 0, contentSize.height), IconWrapperStyles, styles.pAbsolute, additionalStyles];
Expand Down
9 changes: 1 addition & 8 deletions src/components/Icon/primitives/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type CONST from '@src/CONST';
import type IconAsset from '@src/types/utils/IconAsset';
import type {Dimensions} from '@src/types/utils/Layout';

import type {ImageContentFit} from 'expo-image';
import type {StyleProp, ViewStyle} from 'react-native';
Expand Down Expand Up @@ -39,10 +38,4 @@ type BaseIconProps = {
contentFit: ImageContentFit;
};

/** Shared props for primitives that also receive a measured or layout content size. */
type ContentSizedIconProps = BaseIconProps & {
/** Layout size for inline icons, or intrinsic content size for gesture canvases. */
contentSize: Dimensions;
};

export type {BaseIconProps, ContentSizedIconProps, IconSize};
export type {IconSize, BaseIconProps};
22 changes: 0 additions & 22 deletions src/components/Icon/primitives/useCanvasSize.ts

This file was deleted.

11 changes: 3 additions & 8 deletions src/components/Lightbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCa
import type {OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types';
import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils';

import useCanvasSize from '@hooks/useCanvasSize';
import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -18,7 +19,7 @@ import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan
import CONST from '@src/CONST';
import type {Dimensions} from '@src/types/utils/Layout';

import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';

import React, {useState} from 'react';
import {PixelRatio, StyleSheet, View} from 'react-native';
Expand Down Expand Up @@ -122,13 +123,7 @@ function Lightbox({attachmentID, isAuthTokenRequired = false, uri, onScaleChange
const hasSiblingCarouselItems = isUsedInCarousel && !isSingleCarouselItem;
const isActive = page === activePage;

const [canvasSize, setCanvasSize] = useState<Dimensions>();
const isCanvasLoading = canvasSize === undefined;
const updateCanvasSize = ({
nativeEvent: {
layout: {width, height},
},
}: LayoutChangeEvent) => setCanvasSize({width: PixelRatio.roundToNearestPixel(width), height: PixelRatio.roundToNearestPixel(height)});
const {canvasSize, updateCanvasSize, isCanvasLoading} = useCanvasSize();

const [contentSize, setInternalContentSize] = useState<Dimensions | undefined>(() => cachedImageDimensions.get(uri));
const setContentSize = (newDimensions: Dimensions | undefined) => {
Expand Down
46 changes: 46 additions & 0 deletions src/hooks/useCanvasSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type {Dimensions} from '@src/types/utils/Layout';

import type {LayoutChangeEvent} from 'react-native';

import {useState} from 'react';
import {PixelRatio} from 'react-native';

/**
* Discriminated on `isCanvasLoading` so that checking it narrows `canvasSize`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

* from `Dimensions | undefined` to `Dimensions`.
*/
type CanvasSizeResult =
| {
/** Measured layout size of the canvas. */
canvasSize: Dimensions;

/** Whether the canvas is still waiting for its first layout measurement. */
isCanvasLoading: false;

/** onLayout handler that records the measured canvas size. */
updateCanvasSize: (event: LayoutChangeEvent) => void;
}
| {
canvasSize: undefined;
isCanvasLoading: true;
updateCanvasSize: (event: LayoutChangeEvent) => void;
};

/** Tracks the measured layout size of a multi-gesture canvas. */
function useCanvasSize(): CanvasSizeResult {
const [canvasSize, setCanvasSize] = useState<Dimensions>();

const updateCanvasSize = ({
nativeEvent: {
layout: {width: layoutWidth, height: layoutHeight},
},
}: LayoutChangeEvent) => setCanvasSize({width: PixelRatio.roundToNearestPixel(layoutWidth), height: PixelRatio.roundToNearestPixel(layoutHeight)});

if (canvasSize === undefined) {
return {canvasSize, isCanvasLoading: true, updateCanvasSize};
}

return {canvasSize, isCanvasLoading: false, updateCanvasSize};
}

export default useCanvasSize;
Loading