Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lightbox mobile #2460

Merged
merged 16 commits into from
May 15, 2024
5 changes: 3 additions & 2 deletions apps/mobile/src/components/IconContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type IconContainerProps = {
onPress: () => void;
size?: 'xs' | 'sm' | 'md';
border?: boolean;
color?: 'default' | 'white' | 'black';
color?: 'default' | 'white' | 'black' | 'faint';
} & GalleryTouchableOpacityProps;

export function IconContainer({
Expand All @@ -28,10 +28,11 @@ export function IconContainer({
md: 'h-8 w-8',
};

const colorVariants: { [color in 'default' | 'white' | 'black']: string } = {
const colorVariants: { [color in 'default' | 'white' | 'black' | 'faint']: string } = {
default: 'bg-faint dark:bg-black-500',
white: 'bg-white dark:bg-black-900',
black: 'bg-black-900 dark:bg-white',
faint: 'bg-black-700',
};

return (
Expand Down
13 changes: 13 additions & 0 deletions apps/mobile/src/components/ProfilePicture/ProfilePicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ export function ProfilePicture({ userRef, style, ...rest }: ProfilePictureProps)
/>
</ReportingErrorBoundary>
);
} else if (token) {
return (
<ReportingErrorBoundary fallback={fallbackProfilePicture}>
<ValidProfilePicture
style={style}
tokenRef={token}
eventElementId={null}
eventName={null}
eventContext={null}
{...rest}
/>
</ReportingErrorBoundary>
);
} else {
return fallbackProfilePicture;
}
Expand Down
10 changes: 10 additions & 0 deletions apps/mobile/src/icons/CloseIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Svg, { Path, SvgProps } from 'react-native-svg';

export function CloseIcon({ ...props }: SvgProps) {
return (
<Svg width="18" height="18" fill="none" viewBox="0 0 16 16" {...props}>
<Path d="M12.6663 3.33398L3.33301 12.6673" stroke="#F9F9F9" stroke-miterlimit="10" />
<Path d="M3.33301 3.33398L12.6663 12.6673" stroke="#F9F9F9" stroke-miterlimit="10" />
</Svg>
);
}
39 changes: 39 additions & 0 deletions apps/mobile/src/icons/MaximizeIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useColorScheme } from 'nativewind';
import React from 'react';
import Svg, { Path, SvgProps } from 'react-native-svg';

import colors from '~/shared/theme/colors';

export function MaximizeIcon({ ...props }: SvgProps) {
const { colorScheme } = useColorScheme();
const strokeColor = colorScheme === 'dark' ? colors.white : colors.black['800'];

return (
<Svg width="16" height="16" viewBox="0 0 16 16" fill="none" {...props}>
<Path
d="M13.9997 6.66667V2H9.33301"
stroke={strokeColor}
stroke-width="0.666667"
stroke-miterlimit="10"
/>
<Path
d="M13.9997 2L9.33301 6.66667"
stroke={strokeColor}
stroke-width="0.666667"
stroke-miterlimit="10"
/>
<Path
d="M2 9.33398V14.0007H6.66667"
stroke={strokeColor}
stroke-width="0.666667"
stroke-miterlimit="10"
/>
<Path
d="M2 13.9993L6.33333 9.66602"
stroke={strokeColor}
stroke-width="0.666667"
stroke-miterlimit="10"
/>
</Svg>
);
}
137 changes: 128 additions & 9 deletions apps/mobile/src/screens/NftDetailScreen/NftDetailSection.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { useColorScheme } from 'nativewind';
import { useCallback, useMemo } from 'react';
import { ScrollView, View } from 'react-native';
import { useCallback, useMemo, useRef, useState } from 'react';
import { Dimensions, ScrollView, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import Lightbox from 'react-native-lightbox-v2';
import { graphql, useFragment } from 'react-relay';
import { useNavigateToCommunityScreen } from 'src/hooks/useNavigateToCommunityScreen';
import { useToggleTokenAdmire } from 'src/hooks/useToggleTokenAdmire';
import { BookmarkIcon } from 'src/icons/BookmarkIcon';
import { CloseIcon } from 'src/icons/CloseIcon';
import { MaximizeIcon } from 'src/icons/MaximizeIcon';
import { PoapIcon } from 'src/icons/PoapIcon';
import { ShareIcon } from 'src/icons/ShareIcon';
import Zoom from 'react-native-zoom-reanimated';

import { BackButton } from '~/components/BackButton';
import { TokenFailureBoundary } from '~/components/Boundaries/TokenFailureBoundary/TokenFailureBoundary';
Expand All @@ -22,6 +26,7 @@ import {
CreatorProfilePictureAndUsernameOrAddress,
OwnerProfilePictureAndUsername,
} from '~/components/ProfilePicture/ProfilePictureAndUserOrAddress';
import { useSafeAreaPadding } from '~/components/SafeAreaViewWithPadding';
import { Typography } from '~/components/Typography';
import { NftDetailSectionQueryFragment$key } from '~/generated/NftDetailSectionQueryFragment.graphql';
import { PostIcon } from '~/navigation/MainTabNavigator/PostIcon';
Expand All @@ -41,6 +46,8 @@ type Props = {
queryRef: NftDetailSectionQueryFragment$key;
};

const { width } = Dimensions.get('window');

export function NftDetailSection({ onShare, queryRef }: Props) {
const route = useRoute<RouteProp<MainTabStackNavigatorParamList, 'NftDetail'>>();

Expand Down Expand Up @@ -96,6 +103,7 @@ export function NftDetailSection({ onShare, queryRef }: Props) {
);

const { colorScheme } = useColorScheme();
const [isLightboxOpen, setIsLightboxOpen] = useState(false);

const token = query.tokenById;
const ownerWalletAddress =
Expand All @@ -120,6 +128,8 @@ export function NftDetailSection({ onShare, queryRef }: Props) {
}
}, [navigateToCommunity, tokenDefinition.community]);

const { top } = useSafeAreaPadding();

const handleCreatePost = useCallback(() => {
if (token.dbid) {
navigation.navigate('PostComposer', {
Expand Down Expand Up @@ -162,13 +172,70 @@ export function NftDetailSection({ onShare, queryRef }: Props) {
queryRef: query,
});

const customHeader = useCallback(
(close: () => void) => {
return (
<View
className="flex-row justify-end items-center px-3 bg-black-800"
style={{
paddingTop: top,
}}
>
<IconContainer
color="faint"
icon={<CloseIcon />}
onPress={close}
eventElementId={null}
eventName={null}
eventContext={null}
/>
</View>
);
},
[top]
);

const { contractName } = extractRelevantMetadataFromToken(token);

const blueToDisplay = useMemo(
() => (colorScheme === 'dark' ? 'darkModeBlue' : 'activeBlue'),
[colorScheme]
);

const handleMaximizeToggle = () => {
setIsLightboxOpen((currIsLightboxOpen) => !currIsLightboxOpen);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

let's make sure to wrap in useCallback


const thumbnailRef = useRef<View | null>(null);
const [thumbnailPosition, setThumbnailPosition] = useState({
width: width,
height: width,
x: 0,
y: 0,
});

const updateThumbnailPosition = () => {
if (thumbnailRef.current) {
thumbnailRef.current.measure((x, y, w, h, pageX, pageY) => {
setThumbnailPosition({
width: w,
height: h,
x: pageX,
y: pageY,
});
});
}
};

const handleOpenLightbox = () => {
updateThumbnailPosition();
setIsLightboxOpen(true);
};

const handleCloseLightbox = () => {
setIsLightboxOpen(false);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

same for these


return (
<ScrollView>
<View className="flex flex-col space-y-3 px-4 pb-4">
Expand All @@ -188,14 +255,58 @@ export function NftDetailSection({ onShare, queryRef }: Props) {
/>
</View>

<View className="w-full mb-3">
<TokenFailureBoundary tokenRef={token} variant="large">
<NftDetailAssetCacheSwapper
cachedPreviewAssetUrl={route.params.cachedPreviewAssetUrl}
<View className="flex justify-between w-full mb-3">
<Lightbox
isOpen={isLightboxOpen}
onClose={handleCloseLightbox}
onOpen={handleOpenLightbox}
backgroundColor={colors.black['800']}
swipeToDismiss={false}
renderHeader={customHeader}
doubleTapZoomEnabled={false}
renderContent={() => (
<TokenFailureBoundary tokenRef={token} variant="large">
<NftDetailAssetCacheSwapper
cachedPreviewAssetUrl={route.params.cachedPreviewAssetUrl}
>
<Zoom
contentContainerStyle={{
display: 'flex',
width: width,
flexGrow: 1,
backgroundColor: colors.black['800'],
}}
style={{ display: 'flex', flexGrow: 1 }}
doubleTapConfig={{
minZoomScale: 1,
}}
>
<NftDetailAsset tokenRef={token} />
</Zoom>
</NftDetailAssetCacheSwapper>
</TokenFailureBoundary>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

let's define this as a useCallback

origin={{
x: thumbnailPosition.x,
y: thumbnailPosition.y,
width: thumbnailPosition.width,
height: thumbnailPosition.height,
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

useMemo where the dependency is [thumbnailPosition]

>
<View
ref={thumbnailRef}
style={{ width: width * 0.92, minHeight: width * 0.92 }}
Copy link
Contributor

Choose a reason for hiding this comment

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

useMemo

onLayout={updateThumbnailPosition}
>
<NftDetailAsset tokenRef={token} />
</NftDetailAssetCacheSwapper>
</TokenFailureBoundary>
<TokenFailureBoundary tokenRef={token} variant="large">
<NftDetailAssetCacheSwapper
cachedPreviewAssetUrl={route.params.cachedPreviewAssetUrl}
>
<NftDetailAsset tokenRef={token} />
</NftDetailAssetCacheSwapper>
</TokenFailureBoundary>
</View>
</Lightbox>
</View>
</View>

Expand All @@ -211,6 +322,14 @@ export function NftDetailSection({ onShare, queryRef }: Props) {
{tokenDefinition.name}
</Typography>
</View>
<GalleryTouchableOpacity
onPress={handleMaximizeToggle}
eventElementId="NFT Detail Maximize Icon"
eventName="NFT Detail Maximize Icon Pressed"
eventContext={contexts['NFT Detail']}
>
<MaximizeIcon />
</GalleryTouchableOpacity>
</View>
<GalleryTouchableOpacity
eventElementId="NFT Detail Contract Name Pill"
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@
"fetch-schema-dev": "get-graphql-schema https://gateway.dev.gallery.so/ > schema.graphql"
},
"dependencies": {
"@openspacelabs/react-native-zoomable-view": "^2.1.6",
"add": "^2.0.6",
"nextjs-routes": "^1.0.8",
"react": "^18.2.0",
"react-hotkeys-hook": "^4.3.8",
"react-native-lightbox-v2": "^0.9.0",
"react-native-zoom-reanimated": "^1.4.5",
"react-relay": "^15.0.0",
"relay-runtime": "^15.0.0",
"web3-utils": "^4.2.1"
"web3-utils": "^4.2.1",
"yarn": "^1.22.22"
},
"engines": {
"node": "20.5.0"
Expand Down
Loading
Loading