/** * Provides pre-styled React Native components for each native ad asset view: * * - TitleView * - AdvertiserView * - BodyView * - CallToActionView * - IconView * - OptionsView * - MediaView * - StarRatingView * * Each component pulls ad content and view refs from NativeAdView context, * and must be rendered inside a {@link NativeAdView}. */ import * as React from 'react'; import { useContext, useMemo } from 'react'; import { Text, Image, View, TouchableOpacity, StyleSheet, Platform } from 'react-native'; import type { ViewProps, ImageProps, TextProps } from 'react-native'; import { NativeAdViewContext } from './NativeAdViewProvider'; /** * Renders the native ad’s title. */ export const TitleView = (props: TextProps) => { const { titleRef, nativeAd } = useContext(NativeAdViewContext); return ( {nativeAd.title ?? ''} ); }; /** * Renders the advertiser name. */ export const AdvertiserView = (props: TextProps) => { const { advertiserRef, nativeAd } = useContext(NativeAdViewContext); return ( {nativeAd.advertiser ?? ''} ); }; /** * Renders the ad’s body text (description). */ export const BodyView = (props: TextProps) => { const { bodyRef, nativeAd } = useContext(NativeAdViewContext); return ( {nativeAd.body ?? ''} ); }; /** * Renders the call-to-action label. * On iOS, wraps the text with a TouchableOpacity for better click behavior. */ export const CallToActionView = (props: TextProps) => { const { callToActionRef, nativeAd } = useContext(NativeAdViewContext); if (Platform.OS === 'android') { return ( {nativeAd.callToAction ?? ''} ); } return ( {nativeAd.callToAction ?? ''} ); }; /** * Renders the icon image for the native ad. * Falls back to a blank placeholder if not available. */ export const IconView = (props: Omit) => { const { imageRef, nativeAd } = useContext(NativeAdViewContext); const defaultIcon = require('./img/blank_icon.png'); const imageSource = useMemo(() => { if (nativeAd?.url) { return { uri: nativeAd.url }; } if (nativeAd?.imageSource) { return { uri: `data:image/jpeg;base64,${nativeAd.imageSource}` }; } return defaultIcon; }, [nativeAd.url, nativeAd.imageSource, defaultIcon]); return ; }; /** * Renders the native ad’s options view. */ export const OptionsView = (props: ViewProps) => { const { optionViewRef } = useContext(NativeAdViewContext); return ; }; /** * Renders the native ad’s media content. */ export const MediaView = (props: ViewProps) => { const { mediaViewRef } = useContext(NativeAdViewContext); return ; }; /** * Props for the {@link StarRatingView} component, which displays a star rating * using Unicode stars (★ and ☆) styled with color, shadow, and size. */ type StarRatingViewProps = ViewProps & { /** * The color used for filled (active) stars. * Defaults to gold (#ffe234). */ color?: string; /** * The color used for empty (inactive) stars. * Defaults to light gray (#dedede). */ shadowColor?: string; /** * The size of each star, which also determines their visual size. * Defaults to 10. */ size?: number; }; /** * Renders the star rating of the ad, using Unicode stars (★ and ☆). * Filled stars are rendered over hollow stars using a clipped view. */ export const StarRatingView = ({ color = '#ffe234', shadowColor = '#dedede', size = 10, style }: StarRatingViewProps) => { const { nativeAd } = useContext(NativeAdViewContext); const maxStarCount = 5; const containerStyle = useMemo(() => StyleSheet.flatten([style, styles.starRatingContainer]), [style]); const stars = useMemo(() => { const starRating = Math.max(0, Math.min(maxStarCount, nativeAd.starRating ?? 0)); return Array.from({ length: maxStarCount }).map((_, index) => { const isFull = starRating > index; const width = Math.min(size, Math.max(0, (starRating - index) * size)); return ( {String.fromCodePoint(0x2606)} {isFull && ( {String.fromCodePoint(0x2605)} )} ); }); }, [nativeAd.starRating, color, shadowColor, size]); if (!nativeAd.starRating) { return ; } return {stars}; }; const styles = StyleSheet.create({ starRatingContainer: { flexDirection: 'row', alignItems: 'center', }, starRating: { overflow: 'hidden', position: 'absolute', }, });