duongxuannam / react-native-zoom-lightbox

24 stars 12 forks source link

How can i put text on image with clickable as in link #23

Open OFD16 opened 6 months ago

OFD16 commented 6 months ago

image

I'd like to create an image gallery where the last image includes a text overlay indicating the count of remaining images. However, when I position the text using the 'absolute' property, the image becomes unclickable and doesn't expand to full screen. I've attempted to modify the package source code by adding a 'renderItem' or 'lastItem' prop to ensure the last image remains clickable, but it didn't work due to the Image component's limitations. The source code uses an 'onLayout' prop to enable full-screen functionality, but if I use a View or another element to overlay text on the Image, it doesn't inherit these properties, preventing it from going full screen. Do you have any suggestions or ideas on how to achieve this?

As a solution, applying a blur effect to the image allows for placing text directly onto it, achieving the desired layout. However, when text with a background color is added onto the image, it becomes unclickable and fails to expand to full size.

My suggestion for now is to utilize the 'blur' prop to achieve the desired effect.

image

Source code part:

<Animated.View style={[rStyle]}>
        <Image
          ref={ref}
          onLayout={() => {
            ref?.current?.measure((_, __, width, height, pageX, pageY) => {
              setRef(index, {
                x: pageX,
                y: pageY,
                width,
                height,
              });
            });
          }}
          {...rest}
          source={source || {uri: DEFAULT_IMAGE}}
          style={style}
        />
      </Animated.View>

Thats my solutions:

<View marginT-20 marginB-40>
            <Text h1 sb black marginB-4>
              {'Önceki Etkinliklerden Görüntüler'}
            </Text>

            <View row spread>
              <ZoomImageItem
                key={0}
                index={0}
                style={styles.image2}
                source={{uri: images[0].source}}
              />
              <View spread>
                <ZoomImageItem
                  key={1}
                  index={1}
                  style={styles.image1}
                  source={{uri: images[1].source}}
                />

                <View>
                  <ZoomImageItem
                    key={2}
                    index={2}
                    style={styles.image1}
                    blurRadius={8}
                    source={{uri: images[2].source}}
                  />
                  <View
                    style={{
                      // ...styles.moreImages,
                      // ...styles.image1,
                      position: 'absolute',
                      bottom: 64,
                      left: 60,
                    }}>
                    <Text h4 sb white>
                      {`+${images.length - 3}`}
                    </Text>
                  </View>
                </View>
              </View>
            </View>
          </View>

The solution part is:

<View>
                  <ZoomImageItem
                    key={2}
                    index={2}
                    style={styles.image1}
                    blurRadius={8}
                    source={{uri: images[2].source}}
                  />
                  <View
                    style={{
                      // ...styles.moreImages,
                      // ...styles.image1,
                      position: 'absolute',
                      bottom: 64,
                      left: 60,
                    }}>
                    <Text h4 sb white>
                      {`+${images.length - 3}`}
                    </Text>
                  </View>
                </View>

This is full code if you want to check:

/* eslint-disable react-native/no-inline-styles */
/* eslint-disable react/no-unstable-nested-components */
import React from 'react';
import {NativeStackScreenProps} from '@react-navigation/native-stack';
import {EventType, RootStackParamList} from '../types';
import {RouteProp} from '@react-navigation/native';
import {Avatar, CheckBox, Icon, Text, TicketCard, View} from '../components';
import {ActionBar} from 'react-native-ui-lib';
import {ImageBackground, ScrollView, StyleSheet} from 'react-native';
import Theme from '../theme';
import AvatarGroup from '../components/AvatarGroup/AvatarGroup';
import Images from '../../assets/images';
import {
  ZoomImageItem,
  ZoomImageProvider,
  ZoomImage,
} from 'react-native-zoom-lightbox';

type Props = {
  navigation: NativeStackScreenProps<RootStackParamList, any>['navigation'];
  route: RouteProp<RootStackParamList, 'EventDetailScreen'>;
};

const Header = ({
  event,
  navigation,
}: {
  event?: EventType;
  navigation: NativeStackScreenProps<RootStackParamList, any>['navigation'];
}) => {
  // console.log('eventname', event);

  return (
    <ActionBar
      style={styles.actionBar}
      keepRelative={true}
      backgroundColor={Theme.colors.white}
      actions={[
        {
          color: Theme.colors.black,
          iconSource: () => (
            <Icon
              name={'ArrowBack'}
              fill={'transparent'}
              strokewidth={2}
              onPress={() => navigation.goBack()}
            />
          ),
        },
        {
          color: Theme.colors.black,
          iconSource: () => (
            <View width={'200%'} style={{position: 'absolute'}}>
              <Text marginL-8 h4 sb black numberOfLines={1}>
                {event?.name}
              </Text>
            </View>
          ),
        },
        {
          color: Theme.colors.black,
          iconSource: () => (
            <Icon
              onPress={() => {}}
              name={'LikeOutline'}
              strokewidth={2}
              size={24}
              fill="transparent"
            />
          ),
        },
      ]}
    />
  );
};

const RatingBar = ({rate = 3.5}: {rate?: number}) => {
  const normalizedRate = Math.round(rate);
  return (
    <View flex centerV marginT-20 row spread>
      <View row centerV>
        <Text h5 sb black marginR-2>
          {rate}
        </Text>
        {Array.from({length: normalizedRate}).map((_, index) => (
          <Icon
            height={16}
            width={16}
            name={'StarFill'}
            fill={Theme.colors.primary}
            strokewidth={0}
          />
        ))}
        {Array.from({length: 5 - normalizedRate}).map((_, index) => (
          <Icon
            height={16}
            width={16}
            name={'Star'}
            fill={Theme.colors.primary}
            strokewidth={0}
          />
        ))}
      </View>
      <Text h5 sb gray1>
        Yorumları Gör
      </Text>
    </View>
  );
};
export default function EventDetailScreen({navigation, route}: Props) {
  const {event} = route.params!;
  console.log('event', event);
  const images = event.medias.map((media, i) => ({
    source: media.url,
    id: i,
  }));
  return (
    <ZoomImageProvider data={images}>
      <View flex>
        <Header event={event} navigation={navigation} />
        <ScrollView style={styles.container}>
          <TicketCard event={event} />
          <RatingBar />

          <View flex row marginT-20 spread>
            <Text flex-1 h5 sb black>
              {
                'Şimdiye kadar 17 kişi etkinliğe kayıt oldu. Haydi biletler tükenmeden hemen kayıt ol!'
              }
            </Text>
            <View style={styles.avatarGroupContainer}>
              <AvatarGroup
                userList={[0, 1, 2, 4, 5]}
                renderItem={item => <Avatar source={Images.Logo} size={30} />}
              />
            </View>
          </View>

          <View marginT-20>
            <Text h1 sb black>
              {'Etkinlik Hakkında'}
            </Text>
            <Text marginT-8 h4 black>
              {
                'Talas İlçesinin yamaçlarına kurulduğu Ali Dağı, ülkemizdeki belli başlı yamaç paraşütü merkezlerinden biridir. Ali Dağı, Kayseri il merkezine 15 dakika uzaklıktadır. Zirve çıkış kolaylığı, mesafe yarışları için iyi termik ve hava akımları sunan Ali Dağı, sporcuları 130 kilometre uçabilmelerine olanak sağlamaktadır.'
              }
            </Text>
          </View>
          <View row spread>
            <View marginT-20>
              <Text h1 sb black>
                {'Neler Dahil'}
              </Text>
              {event?.includedItems.map((item, index) => (
                <View key={index + Math.random()} row centerV marginV-4>
                  <CheckBox value={true} />
                  <Text h4 black marginL-4>
                    {item}
                  </Text>
                </View>
              ))}
            </View>
            <View marginT-20>
              <Text h1 sb black>
                {'Neler Getirmeli'}
              </Text>
              {event?.excludedItems.map((item, index) => (
                <View key={index + Math.random()} row centerV marginV-4>
                  <CheckBox value={true} />
                  <Text h4 black marginL-4>
                    {item}
                  </Text>
                </View>
              ))}
            </View>
          </View>

          <View marginT-20 marginB-40>
            <Text h1 sb black marginB-4>
              {'Önceki Etkinliklerden Görüntüler'}
            </Text>

            <View row spread>
              <ZoomImageItem
                key={0}
                index={0}
                style={styles.image2}
                source={{uri: images[0].source}}
              />
              <View spread>
                <ZoomImageItem
                  key={1}
                  index={1}
                  style={styles.image1}
                  source={{uri: images[1].source}}
                />

                <View>
                  <ZoomImageItem
                    key={2}
                    index={2}
                    style={styles.image1}
                    blurRadius={8}
                    source={{uri: images[2].source}}
                  />
                  <View
                    style={{
                      // ...styles.moreImages,
                      // ...styles.image1,
                      position: 'absolute',
                      bottom: 64,
                      left: 60,
                    }}>
                    <Text h4 sb white>
                      {`+${images.length - 3}`}
                    </Text>
                  </View>
                </View>
              </View>
            </View>
            {/* <View bg-yellow10 key={index}>
                    <ZoomImageItem
                      style={styles.animatedContainer}
                      source={{uri: item}}
                      index={index}
                    />
                  </View> */}

            {/* {event?.includedItems.map((item, index) => (
              <View key={index + Math.random()} row centerV marginV-4>
                <CheckBox value={true} />
                <Text h4 black marginL-4>
                  {item}
                </Text>
              </View>
            ))} */}
          </View>
        </ScrollView>
      </View>
    </ZoomImageProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Theme.colors.white,
    padding: 20,
  },
  actionBar: {
    borderBottomWidth: 1,
    borderBottomColor: Theme.colors.gray2,
  },
  avatarGroupContainer: {
    display: 'flex',
    alignItems: 'flex-end',
    marginRight: 12,
  },
  animatedContainer: {
    height: 100,
    width: 150,
    resizeMode: 'cover',
  },
  image1: {
    width: 145,
    aspectRatio: 1,
    borderRadius: 20,
    resizeMode: 'cover',
  },
  image2: {
    height: 300,
    width: 200,
    borderRadius: 20,
    resizeMode: 'cover',
  },
  moreImages: {
    backgroundColor: `${Theme.colors.gray2}80`, // Update opacity to 50%
  },
});
OFD16 commented 6 months ago

Furthermore, if you're not using FlatList like me, ensure to include an 'id' prop for your image data, just as I did.

(Update: If you need to use more than one ImageProvider on a screen, make sure that the id of the data in each imageProvider is different, otherwise it may give the same error.)

const images = event.medias.map((media, i) => ({
    source: media.url,
    id: i,
  }));

This can fix your ERROR TypeError: Cannot read property 'toString' of undefined

image