d3ro-voice/apps/mobile-rn/src/components/FreeTierBanner.tsx
2026-08-29 18:33:45 +09:00

98 lines
2.8 KiB
TypeScript

import { useEffect, useState } from 'react'
import { StyleSheet, View } from 'react-native'
import {
BannerAd,
BannerAdSize,
} from 'react-native-google-mobile-ads'
import { useMobileAds } from '../lib/mobile-ads-context'
import { useMobilePreferences } from '../lib/preferences-context'
import { ThemeText } from '../theme/themed-components'
const INITIAL_RETRY_DELAY_MS = 30_000
const MAX_RETRY_DELAY_MS = 5 * 60_000
export default function FreeTierBanner(): React.ReactElement | null {
const ads = useMobileAds()
const { palette } = useMobilePreferences()
const [loaded, setLoaded] = useState(false)
const [failed, setFailed] = useState(false)
const [retryAttempt, setRetryAttempt] = useState(0)
const [retryKey, setRetryKey] = useState(0)
useEffect(() => {
setLoaded(false)
setFailed(false)
setRetryAttempt(0)
setRetryKey(0)
}, [ads.bannerUnitId])
useEffect(() => {
if (!failed || !ads.bannerUnitId) return undefined
const delay = Math.min(
INITIAL_RETRY_DELAY_MS * (2 ** retryAttempt),
MAX_RETRY_DELAY_MS,
)
const timer = setTimeout(() => {
setLoaded(false)
setFailed(false)
setRetryAttempt((current) => current + 1)
setRetryKey((current) => current + 1)
}, delay)
return () => clearTimeout(timer)
}, [ads.bannerUnitId, failed, retryAttempt])
if (!ads.eligible) return null
return (
<View
style={[
styles.slot,
{
backgroundColor: palette.bg.inset,
borderTopColor: palette.border.subtle,
},
]}
testID="free-tier-ad-slot"
accessibilityLabel="Sponsored"
>
{ads.bannerUnitId && !failed ? (
<BannerAd
key={`${ads.bannerUnitId}:${retryKey}`}
unitId={ads.bannerUnitId}
size={BannerAdSize.ANCHORED_ADAPTIVE_BANNER}
requestOptions={{ requestNonPersonalizedAdsOnly: true }}
onAdLoaded={() => {
setLoaded(true)
setFailed(false)
setRetryAttempt(0)
}}
onAdFailedToLoad={() => {
setLoaded(false)
setFailed(true)
}}
/>
) : (
<ThemeText variant="label" color="muted" style={styles.statusText}>
{failed ? 'AD UNAVAILABLE' : ads.status === 'ready' ? 'AD LOADING' : 'AD CONSENT'}
</ThemeText>
)}
{ads.bannerUnitId && !loaded && !failed && (
<ThemeText variant="label" color="muted" style={styles.loadingOverlay}>
AD LOADING
</ThemeText>
)}
</View>
)
}
const styles = StyleSheet.create({
slot: {
minHeight: 50,
alignItems: 'center',
justifyContent: 'center',
borderTopWidth: 1,
overflow: 'hidden',
},
statusText: { fontSize: 9, letterSpacing: 1.2 },
loadingOverlay: { position: 'absolute', fontSize: 9, letterSpacing: 1.2 },
})