diff --git a/android/app/build.gradle b/android/app/build.gradle index 55101a2390..f2f96fc5a4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,7 +90,7 @@ android { applicationId "com.arkhamcards" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 4195457 + versionCode 4195458 versionName "4.8.7" manifestPlaceholders = [ appAuthRedirectScheme: 'arkhamcards' diff --git a/package.json b/package.json index e8d4acdcb5..f65fb8437c 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@invertase/react-native-apple-authentication": "2.2.2", "@miblanchard/react-native-slider": "^2.3.1", "@op-engineering/op-sqlite": "9.1.2", + "@openspacelabs/react-native-zoomable-view": "^2.3.1", "@react-hook/debounce": "4.0.0", "@react-hook/throttle": "2.2.0", "@react-native-async-storage/async-storage": "1.24.0", diff --git a/src/components/card/CardDetailView/CardDetailComponent.tsx b/src/components/card/CardDetailView/CardDetailComponent.tsx index 1fb81b4f9e..c036430364 100644 --- a/src/components/card/CardDetailView/CardDetailComponent.tsx +++ b/src/components/card/CardDetailView/CardDetailComponent.tsx @@ -26,9 +26,10 @@ interface Props { tabooSetId?: number | undefined; toggleShowSpoilers?: (code: string) => void; showInvestigatorCards?: (code: string) => void; + toggleImageMode?: () => void; } -function InvestigatorInfoComponent({ componentId, card, width, simple, showInvestigatorCards }: Props) { +function InvestigatorInfoComponent({ componentId, card, width, simple, toggleImageMode, showInvestigatorCards }: Props) { const { colors, typography } = useContext(StyleContext); const [parallelInvestigators] = useParallelInvestigators(card.type_code === 'investigator' ? card.code : undefined); const showInvestigatorCardsPressed = useCallback(() => { @@ -81,6 +82,7 @@ function InvestigatorInfoComponent({ componentId, card, width, simple, showInves card={parallel} width={width} simple={!!simple} + toggleImageMode={toggleImageMode} /> )) } @@ -152,7 +154,7 @@ function SpoilersComponent({ componentId, card, width, toggleShowSpoilers }: Pro export default function CardDetailComponent({ componentId, card, backCard, width, showSpoilers, tabooSetId, simple, noImage, - toggleShowSpoilers, showInvestigatorCards, + toggleShowSpoilers, showInvestigatorCards, toggleImageMode, }: Props) { const { backgroundStyle } = useContext(StyleContext); const shouldBlur = !showSpoilers && !!(card && card.mythos_card); @@ -181,6 +183,7 @@ export default function CardDetailComponent({ width={width} simple={!!simple} noImage={noImage} + toggleImageMode={toggleImageMode} /> { !simple && ( void; } function imageStyle(card: Card) { @@ -126,7 +127,7 @@ function ImageContent({ card }: { card: Card }) { ); } -export default function PlayerCardImage({ componentId, card }: Props) { +export default function PlayerCardImage({ componentId, card, ...props }: Props) { const { colors } = useContext(StyleContext); const onPress = useCallback(() => { if (componentId) { @@ -144,7 +145,7 @@ export default function PlayerCardImage({ componentId, card }: Props) { if (componentId) { return ( - + ); diff --git a/src/components/card/CardDetailView/TwoSidedCardComponent/index.tsx b/src/components/card/CardDetailView/TwoSidedCardComponent/index.tsx index 2cc7be8b5c..b91266e6f6 100644 --- a/src/components/card/CardDetailView/TwoSidedCardComponent/index.tsx +++ b/src/components/card/CardDetailView/TwoSidedCardComponent/index.tsx @@ -66,10 +66,11 @@ interface Props { width: number; noImage?: boolean; showBack?: boolean; + toggleImageMode?: () => void; } export default function TwoSidedCardComponent(props: Props) { - const { componentId, card, backCard, linked, notFirst, simple, width } = props; + const { componentId, card, backCard, linked, notFirst, simple, width, toggleImageMode } = props; const custom = card.custom(); const { backgroundStyle, fontScale, shadow, colors, typography } = useContext(StyleContext); const [showBack, toggleShowBack] = useFlag(props.showBack ?? false); @@ -286,6 +287,7 @@ export default function TwoSidedCardComponent(props: Props) { width={width} simple={simple} showBack={props.showBack} + toggleImageMode={toggleImageMode} key="linked" /> @@ -367,7 +369,7 @@ export default function TwoSidedCardComponent(props: Props) { ); }, [props.showBack, backCard, card, componentId, simple, width, linked, shadow.large, colors, backgroundStyle, typography, showBack, typeLine, cardFooter, flavorFirst, - toggleShowBack, showTaboo, showFaq]); + toggleImageMode, toggleShowBack, showTaboo, showFaq]); const image = useMemo(() => { if (card.type_code === 'story' || card.type_code === 'scenario' || props.noImage) { @@ -377,17 +379,23 @@ export default function TwoSidedCardComponent(props: Props) { { card.type_code === 'investigator' ? ( - + ) : ( ) } ); - }, [card, componentId, props.noImage]); + }, [card, componentId, props.noImage, toggleImageMode]); const cardText = useMemo(() => { return ( diff --git a/src/components/card/CardImageView.tsx b/src/components/card/CardImageView.tsx index 62eeb115ca..3d284d6e23 100644 --- a/src/components/card/CardImageView.tsx +++ b/src/components/card/CardImageView.tsx @@ -1,10 +1,12 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useCallback, useContext, useEffect } from 'react'; import { + GestureResponderEvent, + PanResponderGestureState, StyleSheet, View, } from 'react-native'; import { Image as FastImage } from 'expo-image'; -import ViewControl from 'react-native-zoom-view'; +import { ReactNativeZoomableView, ZoomableViewEvent } from '@openspacelabs/react-native-zoomable-view'; import { Navigation } from 'react-native-navigation'; import { t } from 'ttag'; @@ -18,6 +20,7 @@ import useSingleCard from './useSingleCard'; export interface CardImageProps { id: string; + embedded?: boolean; } type Props = CardImageProps & NavigationProps; @@ -32,18 +35,36 @@ function CardImageDetail({ card, flipped }: CardImageDetailProps) { const cardRatio = 68 / 95; const cardHeight = (height - HEADER_HEIGHT) * cardRatio; const cardWidth = width - 16; + const onShouldBlockNativeResponderHandler = useCallback(( + event: GestureResponderEvent, + gestureState: PanResponderGestureState, + zoomableViewEventObject: ZoomableViewEvent + ): boolean => { + if (zoomableViewEventObject.zoomLevel === 1) { + return false; + } + return true; + }, []); if (!card) { return null; } if (card.double_sided || (card.linked_card && card.linked_card.hasImage())) { if (!flipped) { return ( - - + ); } return ( - - + ); } return ( - - + ); } -export default function CardImageView({ componentId, id }: Props) { +export default function CardImageView({ componentId, id, embedded }: Props) { const { backgroundStyle } = useContext(StyleContext); const [flipped, toggleFlipped] = useFlag(false); useNavigationButtonPressed(({ buttonId }) => { @@ -102,7 +143,7 @@ export default function CardImageView({ componentId, id }: Props) { }, componentId, [toggleFlipped]); const [card] = useSingleCard(id, 'encounter'); useEffect(() => { - if (!card) { + if (!card || embedded) { return; } const doubleCard: boolean = card.double_sided || !!(card.linked_card && card.linked_card.hasImage()); @@ -116,7 +157,7 @@ export default function CardImageView({ componentId, id }: Props) { }] : [], }, }); - }, [card, componentId]); + }, [card, embedded, componentId]); return ( { return { topBar: { backButton: { + id: 'back', + popStackOnPress: false, title: t`Back`, color: passProps.whiteNav ? 'white' : COLORS.M, }, @@ -164,14 +167,16 @@ function ScrollableCard(props: { showCardSpoiler: (card: Card) => boolean; toggleShowSpoilers: (code: string) => void; showInvestigatorCards: (code: string) => void; + toggleImageMode: () => void; attachment?: AttachableDefinition; attachmentCards: Card[]; + imageMode: boolean; }) { const { componentId, customizationsEditable, card, tabooSetId, width, height, customizations, deckCount, - attachment, attachmentCards, - setChoice, toggleShowSpoilers, showInvestigatorCards, showCardSpoiler, + attachment, attachmentCards, imageMode, + setChoice, toggleShowSpoilers, showInvestigatorCards, showCardSpoiler, toggleImageMode, } = props; const { deckId } = useContext(DeckEditContext); const { backgroundStyle, colors } = useContext(StyleContext); @@ -187,43 +192,53 @@ function ScrollableCard(props: { ); } + if (imageMode && card) { + return ( + + + + ); + } return ( - - - { !!customizedCard.customization_options && !!card && ( - + + - ) } - { !!card && !!deckId && !!attachment && ( - - )} - { deckId !== undefined && } - + { !!customizedCard.customization_options && !!card && ( + + ) } + { !!card && !!deckId && !!attachment && ( + + )} + { deckId !== undefined && } + + ); } @@ -244,6 +259,7 @@ function DbCardDetailSwipeView(props: Props) { const [spoilers, toggleShowSpoilers] = useToggles({}); const [index, setIndex] = useState(initialIndex); const [cards, updateCards] = useCards('code', initialCards); + const [imageMode, toggleImageMode] = useReducer((value) => !value, false); const [currentCode, currentControl] = useMemo(() => { return [ cardCodes[index], @@ -327,13 +343,21 @@ function DbCardDetailSwipeView(props: Props) { }); }, [componentId]); const backPressed = useCallback(() => { - Navigation.pop(componentId); - }, [componentId]); + if (imageMode) { + toggleImageMode(); + } else { + Navigation.pop(componentId); + } + }, [componentId, imageMode, toggleImageMode]); useComponentDidAppear(() => { Navigation.mergeOptions(componentId, options(props)); }, componentId, [componentId]); + useNavigationButtonPressed(({ buttonId }) => { - if (currentCard) { + console.log('BUTTON PRESS!', buttonId) + if (buttonId === 'RNN.back') { + backPressed(); + } else if (currentCard) { if (buttonId === 'share') { const arkhamDbDomain = getArkhamDbDomain(getSystemLanguage()); Linking.openURL(`${arkhamDbDomain}/card/${currentCard.code}#reviews-header`); @@ -360,11 +384,9 @@ function DbCardDetailSwipeView(props: Props) { }); } else if (buttonId === 'investigator') { showInvestigators(currentCard.code); - } else if (buttonId === 'back') { - Navigation.pop(componentId); } } - }, componentId, [currentCard, showInvestigators, showInvestigatorCards]); + }, componentId, [currentCard, imageMode, backPressed, toggleImageMode, showInvestigators, showInvestigatorCards]); const showCardSpoiler = useCallback((card: Card) => { return !!(showAllSpoilers || showSpoilers[card.pack_code] || spoilers[card.code]); @@ -390,6 +412,8 @@ function DbCardDetailSwipeView(props: Props) { ); - }, [attachableCards, data, slots, customizationsEditable, tabooSetId, editable, customizations, componentId, width, height, setChoice, showCardSpoiler, toggleShowSpoilers, showInvestigatorCards]); + }, [ + imageMode, attachableCards, data, slots, customizationsEditable, tabooSetId, + editable, customizations, componentId, width, height, + setChoice, showCardSpoiler, toggleShowSpoilers, showInvestigatorCards, toggleImageMode, + ]); return ( { if (buttonId === 'save') { diff --git a/src/components/core/InvestigatorImage.tsx b/src/components/core/InvestigatorImage.tsx index e275d24ae8..afc9ed0fe2 100644 --- a/src/components/core/InvestigatorImage.tsx +++ b/src/components/core/InvestigatorImage.tsx @@ -35,6 +35,7 @@ interface Props { arkhamCardsImg?: string; imageOffset?: 'right' | 'left'; round?: boolean; + onPress?: () => void; } const IMAGE_SIZE = { @@ -166,6 +167,7 @@ function InvestigatorImage({ imageOffset, round, image, + ...props }: Props) { const { colors, fontScale, shadow } = useContext(StyleContext); @@ -353,7 +355,7 @@ function InvestigatorImage({ if (componentId && card) { return ( - + { imageNode } ); diff --git a/yarn.lock b/yarn.lock index e126e970ab..d59192cd6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4987,6 +4987,18 @@ __metadata: languageName: node linkType: hard +"@openspacelabs/react-native-zoomable-view@npm:^2.3.1": + version: 2.3.1 + resolution: "@openspacelabs/react-native-zoomable-view@npm:2.3.1" + dependencies: + prop-types: "npm:^15.7.2" + peerDependencies: + react: ">=16.8.0" + react-native: ">=0.54.0" + checksum: 10c0/17b37543d8cadc78d4ee09168eed94e5e40e09be3f4bace7fd4bd0e24a51035015bb69fbb31b6ca86bd17dc8dea2c18244a2eeb37e0286b452a420eec483ac3c + languageName: node + linkType: hard + "@peculiar/asn1-schema@npm:^2.3.6": version: 2.3.6 resolution: "@peculiar/asn1-schema@npm:2.3.6" @@ -7143,6 +7155,7 @@ __metadata: "@invertase/react-native-apple-authentication": "npm:2.2.2" "@miblanchard/react-native-slider": "npm:^2.3.1" "@op-engineering/op-sqlite": "npm:9.1.2" + "@openspacelabs/react-native-zoomable-view": "npm:^2.3.1" "@react-hook/debounce": "npm:4.0.0" "@react-hook/throttle": "npm:2.2.0" "@react-native-async-storage/async-storage": "npm:1.24.0"