feat(app): Stitch paywall — single plan card with free-trial toggle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, ActivityIndicator, Alert, Linking, BackHandler, ImageBackground, Platform, useWindowDimensions } from 'react-native';
|
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, ActivityIndicator, Alert, Linking, BackHandler, ImageBackground, Platform, Switch } from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||||
@@ -175,6 +175,20 @@ const getBillingCopy = (language: Language) => {
|
|||||||
autoRenewMonthly: 'Verlängert sich monatlich automatisch. Jederzeit über iOS-Einstellungen kündbar.',
|
autoRenewMonthly: 'Verlängert sich monatlich automatisch. Jederzeit über iOS-Einstellungen kündbar.',
|
||||||
autoRenewYearly: 'Verlängert sich jährlich automatisch. Jederzeit über iOS-Einstellungen kündbar.',
|
autoRenewYearly: 'Verlängert sich jährlich automatisch. Jederzeit über iOS-Einstellungen kündbar.',
|
||||||
manageInSettings: 'In iOS-Einstellungen verwalten',
|
manageInSettings: 'In iOS-Einstellungen verwalten',
|
||||||
|
paywallEyebrow: 'GreenLens Pro',
|
||||||
|
paywallHeadline: 'Unbegrenzter Zugriff',
|
||||||
|
paywallSub: 'Unbegrenzte Scans, Health-Checks und dein persönlicher Pflegeplan.',
|
||||||
|
planCardTitle: 'GreenLens Pro',
|
||||||
|
planCardBody: 'Unbegrenzte KI-Scans, Gesundheitsdiagnose, 7-Tage-Rettungspläne, 100 Credits/Monat',
|
||||||
|
planCardPriceTrial: (price: string) => `7 Tage gratis, dann ${price}/Jahr`,
|
||||||
|
planCardPriceMonthly: (price: string) => `${price}/Monat`,
|
||||||
|
trialToggleLabel: 'Gratis-Test aktiviert',
|
||||||
|
dueTodayTrial: 'Fällig heute — 7 Tage gratis',
|
||||||
|
dueTodayAmount: '0,00 €',
|
||||||
|
dueLater: (date: string) => `Fällig am ${date}`,
|
||||||
|
ctaTrial: 'Gratis testen',
|
||||||
|
ctaMonthly: 'Jetzt starten',
|
||||||
|
cancelAnytime: 'Jederzeit kündbar',
|
||||||
};
|
};
|
||||||
} else if (language === 'es') {
|
} else if (language === 'es') {
|
||||||
return {
|
return {
|
||||||
@@ -249,6 +263,20 @@ const getBillingCopy = (language: Language) => {
|
|||||||
autoRenewMonthly: 'Se renueva mensualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.',
|
autoRenewMonthly: 'Se renueva mensualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.',
|
||||||
autoRenewYearly: 'Se renueva anualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.',
|
autoRenewYearly: 'Se renueva anualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.',
|
||||||
manageInSettings: 'Administrar en Ajustes de iOS',
|
manageInSettings: 'Administrar en Ajustes de iOS',
|
||||||
|
paywallEyebrow: 'GreenLens Pro',
|
||||||
|
paywallHeadline: 'Acceso ilimitado',
|
||||||
|
paywallSub: 'Escaneos ilimitados, chequeos de salud y tu plan de cuidados personal.',
|
||||||
|
planCardTitle: 'GreenLens Pro',
|
||||||
|
planCardBody: 'Escaneos IA ilimitados, diagnóstico de salud, planes de rescate de 7 días, 100 créditos/mes',
|
||||||
|
planCardPriceTrial: (price: string) => `7 días gratis, luego ${price}/año`,
|
||||||
|
planCardPriceMonthly: (price: string) => `${price}/mes`,
|
||||||
|
trialToggleLabel: 'Prueba gratis activada',
|
||||||
|
dueTodayTrial: 'Hoy — 7 días gratis',
|
||||||
|
dueTodayAmount: '0,00 €',
|
||||||
|
dueLater: (date: string) => `El ${date}`,
|
||||||
|
ctaTrial: 'Probar gratis',
|
||||||
|
ctaMonthly: 'Empezar ahora',
|
||||||
|
cancelAnytime: 'Cancela cuando quieras',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -323,6 +351,20 @@ const getBillingCopy = (language: Language) => {
|
|||||||
autoRenewMonthly: 'Auto-renews monthly. Cancel anytime in iOS Settings.',
|
autoRenewMonthly: 'Auto-renews monthly. Cancel anytime in iOS Settings.',
|
||||||
autoRenewYearly: 'Auto-renews annually. Cancel anytime in iOS Settings.',
|
autoRenewYearly: 'Auto-renews annually. Cancel anytime in iOS Settings.',
|
||||||
manageInSettings: 'Manage in iOS Settings',
|
manageInSettings: 'Manage in iOS Settings',
|
||||||
|
paywallEyebrow: 'GreenLens Pro',
|
||||||
|
paywallHeadline: 'Get Unlimited Access',
|
||||||
|
paywallSub: 'Unlimited scans, health checks and your personal care plan.',
|
||||||
|
planCardTitle: 'GreenLens Pro',
|
||||||
|
planCardBody: 'Unlimited AI scans, health diagnosis, 7-day rescue plans, 100 credits/month',
|
||||||
|
planCardPriceTrial: (price: string) => `Free for 7 days, then ${price}/year`,
|
||||||
|
planCardPriceMonthly: (price: string) => `${price}/month`,
|
||||||
|
trialToggleLabel: 'Free Trial Enabled',
|
||||||
|
dueTodayTrial: 'Due today — 7 days free',
|
||||||
|
dueTodayAmount: '€0.00',
|
||||||
|
dueLater: (date: string) => `Due ${date}`,
|
||||||
|
ctaTrial: 'Try Free',
|
||||||
|
ctaMonthly: 'Start Now',
|
||||||
|
cancelAnytime: 'Cancel Anytime',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -338,8 +380,6 @@ export default function BillingScreen() {
|
|||||||
const posthog = useSafeAnalytics();
|
const posthog = useSafeAnalytics();
|
||||||
const copy = getBillingCopy(language);
|
const copy = getBillingCopy(language);
|
||||||
const isExpoGo = Constants.appOwnership === 'expo';
|
const isExpoGo = Constants.appOwnership === 'expo';
|
||||||
const { height: windowHeight } = useWindowDimensions();
|
|
||||||
const compactPaywall = windowHeight < 760;
|
|
||||||
|
|
||||||
const [subModalVisible, setSubModalVisible] = useState(false);
|
const [subModalVisible, setSubModalVisible] = useState(false);
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
@@ -407,8 +447,16 @@ export default function BillingScreen() {
|
|||||||
};
|
};
|
||||||
}, [isExpoGo]);
|
}, [isExpoGo]);
|
||||||
|
|
||||||
|
const trialEnabled = selectedPaywallPlan === 'yearly';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try { posthog.capture('paywall_viewed', { plan_id: planId }); } catch {}
|
try {
|
||||||
|
posthog.capture('paywall_viewed', {
|
||||||
|
plan_id: planId,
|
||||||
|
context: onboardingContext ? 'onboarding' : 'in_app',
|
||||||
|
trial_enabled: trialEnabled,
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
if (showPaywallPlans) {
|
if (showPaywallPlans) {
|
||||||
try {
|
try {
|
||||||
posthog.capture('hard_paywall_viewed', {
|
posthog.capture('hard_paywall_viewed', {
|
||||||
@@ -417,18 +465,19 @@ export default function BillingScreen() {
|
|||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}, [posthog, planId, session?.serverUserId, showPaywallPlans]);
|
}, [posthog, planId, session?.serverUserId, showPaywallPlans, onboardingContext, trialEnabled]);
|
||||||
|
|
||||||
const monthlyPackage = subscriptionPackages.monthly_pro;
|
const monthlyPackage = subscriptionPackages.monthly_pro;
|
||||||
const yearlyPackage = subscriptionPackages.yearly_pro;
|
const yearlyPackage = subscriptionPackages.yearly_pro;
|
||||||
|
|
||||||
const monthlyPrice = monthlyPackage?.product.priceString ?? copy.proPlanPrice;
|
const monthlyPrice = monthlyPackage?.product.priceString ?? copy.proPlanPrice;
|
||||||
const yearlyPrice = yearlyPackage?.product.priceString ?? copy.proYearlyPlanPrice;
|
const yearlyPrice = yearlyPackage?.product.priceString ?? copy.proYearlyPlanPrice;
|
||||||
const weeklyDisplayPrice = copy.weeklyPriceFallback;
|
const trialEndDate = useMemo(() => {
|
||||||
const yearlyDisplayPrice = copy.yearlyPriceFallback;
|
const date = new Date();
|
||||||
const selectedProductId: SubscriptionProductId = selectedPaywallPlan === 'yearly' ? 'yearly_pro' : 'monthly_pro';
|
date.setDate(date.getDate() + 7);
|
||||||
const paywallCtaLabel = selectedPaywallPlan === 'yearly' ? copy.startTrial : copy.monthlyCta;
|
const locale = language === 'de' ? 'de-DE' : language === 'es' ? 'es-ES' : 'en-US';
|
||||||
const paywallFooterLabel = selectedPaywallPlan === 'yearly' ? copy.noPaymentToday : copy.monthlyFooter;
|
return date.toLocaleDateString(locale, { day: 'numeric', month: 'long' });
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
const topupLabels = useMemo(() => ({
|
const topupLabels = useMemo(() => ({
|
||||||
topup_small: topupProducts.topup_small ? `${TOPUP_CREDITS_BY_PRODUCT.topup_small} Credits - ${topupProducts.topup_small.priceString}` : copy.topupSmall,
|
topup_small: topupProducts.topup_small ? `${TOPUP_CREDITS_BY_PRODUCT.topup_small} Credits - ${topupProducts.topup_small.priceString}` : copy.topupSmall,
|
||||||
@@ -638,125 +687,89 @@ export default function BillingScreen() {
|
|||||||
imageStyle={styles.hardPaywallHeroImage}
|
imageStyle={styles.hardPaywallHeroImage}
|
||||||
resizeMode="cover"
|
resizeMode="cover"
|
||||||
>
|
>
|
||||||
<SafeAreaView
|
<SafeAreaView style={styles.hardPaywallSafe} edges={['top']}>
|
||||||
style={[styles.hardPaywallSafe, compactPaywall && styles.hardPaywallSafeCompact]}
|
|
||||||
edges={['top']}
|
|
||||||
>
|
|
||||||
<View style={styles.heroTopBar}>
|
<View style={styles.heroTopBar}>
|
||||||
<TouchableOpacity onPress={handleBack} style={styles.heroIconButton}>
|
<TouchableOpacity onPress={handleBack} style={styles.heroIconButton}>
|
||||||
<Ionicons name="close" size={24} color="#FFFFFF" />
|
<Ionicons name="close" size={24} color="#FFFFFF" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={handleRestore} disabled={isUpdating}>
|
||||||
|
<Text style={styles.heroRestoreText}>{copy.restorePurchases}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={[styles.scanFrameOverlay, compactPaywall && styles.scanFrameOverlayCompact]}>
|
<View style={[styles.paywallSheet, { backgroundColor: colors.background }]}>
|
||||||
<View style={[styles.scanCorner, styles.scanCornerTopLeft]} />
|
<ScrollView
|
||||||
<View style={[styles.scanCorner, styles.scanCornerTopRight]} />
|
contentContainerStyle={styles.paywallBody}
|
||||||
<View style={[styles.scanCorner, styles.scanCornerBottomLeft]} />
|
showsVerticalScrollIndicator={false}
|
||||||
<View style={[styles.scanCorner, styles.scanCornerBottomRight]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[styles.identifiedChip, compactPaywall && styles.identifiedChipCompact]}>
|
|
||||||
<Ionicons name="leaf-outline" size={17} color="#8FD19E" />
|
|
||||||
<Text style={styles.identifiedChipText}>{copy.identifiedChip}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[styles.hardPaywallSheet, compactPaywall && styles.hardPaywallSheetCompact]}>
|
|
||||||
<View style={[styles.sheetHandle, compactPaywall && styles.sheetHandleCompact]} />
|
|
||||||
<Text style={[styles.hardPaywallTitle, compactPaywall && styles.hardPaywallTitleCompact]}>
|
|
||||||
{copy.hardPaywallTitle}
|
|
||||||
</Text>
|
|
||||||
<Text style={[styles.hardPaywallHint, compactPaywall && styles.hardPaywallHintCompact]}>
|
|
||||||
{copy.hardPaywallHint}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={[styles.hardBenefits, compactPaywall && styles.hardBenefitsCompact]}>
|
|
||||||
{[
|
|
||||||
{ icon: 'infinite-outline' as const, title: copy.benefitUnlimitedScans },
|
|
||||||
{ icon: 'calendar-outline' as const, title: copy.benefitCarePlan },
|
|
||||||
].map((benefit) => (
|
|
||||||
<View key={benefit.title} style={[styles.hardBenefitRow, compactPaywall && styles.hardBenefitRowCompact]}>
|
|
||||||
<View style={[styles.hardBenefitIcon, compactPaywall && styles.hardBenefitIconCompact]}>
|
|
||||||
<Ionicons name={benefit.icon} size={compactPaywall ? 18 : 20} color="#1F5B34" />
|
|
||||||
</View>
|
|
||||||
<Text style={[styles.hardBenefitText, compactPaywall && styles.hardBenefitTextCompact]}>
|
|
||||||
{benefit.title}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.hardPlanRow}>
|
|
||||||
{[
|
|
||||||
{ id: 'weekly' as PaywallPlanId, name: copy.weeklyPlanName, price: weeklyDisplayPrice },
|
|
||||||
{ id: 'yearly' as PaywallPlanId, name: copy.yearlyPlanName, price: yearlyDisplayPrice, badge: copy.bestOffer },
|
|
||||||
].map((plan) => {
|
|
||||||
const selected = selectedPaywallPlan === plan.id;
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={plan.id}
|
|
||||||
style={[
|
|
||||||
styles.hardPlanCard,
|
|
||||||
compactPaywall && styles.hardPlanCardCompact,
|
|
||||||
selected && styles.hardPlanCardSelected,
|
|
||||||
]}
|
|
||||||
onPress={() => setSelectedPaywallPlan(plan.id)}
|
|
||||||
activeOpacity={0.88}
|
|
||||||
>
|
|
||||||
<View style={[styles.planRadio, selected && styles.planRadioSelected]}>
|
|
||||||
{selected ? <Ionicons name="checkmark" size={15} color="#FFFFFF" /> : null}
|
|
||||||
</View>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Text style={[styles.hardPlanName, compactPaywall && styles.hardPlanNameCompact]}>{plan.name}</Text>
|
|
||||||
<Text style={[styles.hardPlanPrice, compactPaywall && styles.hardPlanPriceCompact]}>{plan.price}</Text>
|
|
||||||
{plan.badge ? (
|
|
||||||
<View style={styles.bestOfferBadge}>
|
|
||||||
<Text style={styles.bestOfferText}>{plan.badge}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[
|
|
||||||
styles.hardPaywallCta,
|
|
||||||
compactPaywall && styles.hardPaywallCtaCompact,
|
|
||||||
(!storeReady || isUpdating || Boolean(storeError)) && styles.disabledPlanCard,
|
|
||||||
]}
|
|
||||||
onPress={() => handlePurchase(selectedProductId)}
|
|
||||||
disabled={isUpdating || !storeReady || Boolean(storeError)}
|
|
||||||
activeOpacity={0.9}
|
|
||||||
>
|
>
|
||||||
{isUpdating || !storeReady ? (
|
<View style={[styles.sheetHandle, { backgroundColor: colors.border }]} />
|
||||||
<ActivityIndicator color="#FFFFFF" />
|
<Text style={[styles.paywallEyebrow, { color: colors.primary }]}>{copy.paywallEyebrow.toUpperCase()}</Text>
|
||||||
) : (
|
<Text style={[styles.paywallHeadline, { color: colors.text }]}>{copy.paywallHeadline}</Text>
|
||||||
<>
|
<Text style={[styles.paywallSub, { color: colors.textSecondary }]}>{copy.paywallSub}</Text>
|
||||||
<Text style={[styles.hardPaywallCtaText, compactPaywall && styles.hardPaywallCtaTextCompact]}>
|
|
||||||
{paywallCtaLabel}
|
|
||||||
</Text>
|
|
||||||
<Ionicons name="arrow-forward" size={23} color="#FFFFFF" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{storeError ? (
|
<View style={[styles.planCard, { backgroundColor: colors.surfaceMuted }]}>
|
||||||
<Text style={styles.hardPaywallFooter}>{storeError}</Text>
|
<Text style={[styles.planCardTitle, { color: colors.text }]}>{copy.planCardTitle}</Text>
|
||||||
) : null}
|
<Text style={[styles.planCardBody, { color: colors.textSecondary }]}>{copy.planCardBody}</Text>
|
||||||
<Text style={styles.hardPaywallFooter}>{paywallFooterLabel}</Text>
|
<View style={[styles.planCardDivider, { backgroundColor: colors.border }]} />
|
||||||
<View style={styles.legalLinksRow}>
|
<Text style={[styles.planCardPrice, { color: colors.text }]}>
|
||||||
<TouchableOpacity onPress={() => Linking.openURL('https://greenlenspro.com/privacy')}>
|
{trialEnabled ? copy.planCardPriceTrial(yearlyPrice) : copy.planCardPriceMonthly(monthlyPrice)}
|
||||||
<Text style={[styles.legalLink, { color: '#1F5B34' }]}>Privacy Policy</Text>
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[styles.trialToggleRow, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
||||||
|
<Text style={[styles.trialToggleLabel, { color: colors.text }]}>{copy.trialToggleLabel}</Text>
|
||||||
|
<Switch
|
||||||
|
value={trialEnabled}
|
||||||
|
onValueChange={(next) => setSelectedPaywallPlan(next ? 'yearly' : 'weekly')}
|
||||||
|
trackColor={{ true: colors.primary, false: colors.border }}
|
||||||
|
thumbColor="#FFFFFF"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{trialEnabled ? (
|
||||||
|
<View style={styles.dueTimeline}>
|
||||||
|
<View style={styles.dueRow}>
|
||||||
|
<View style={[styles.dueDot, { backgroundColor: colors.primary }]} />
|
||||||
|
<Text style={[styles.dueLabel, { color: colors.primary }]}>{copy.dueTodayTrial}</Text>
|
||||||
|
<Text style={[styles.dueAmount, { color: colors.text }]}>{copy.dueTodayAmount}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.dueLine, { backgroundColor: colors.border }]} />
|
||||||
|
<View style={styles.dueRow}>
|
||||||
|
<View style={[styles.dueDot, { backgroundColor: colors.border }]} />
|
||||||
|
<Text style={[styles.dueLabel, { color: colors.textSecondary }]}>{copy.dueLater(trialEndDate)}</Text>
|
||||||
|
<Text style={[styles.dueAmount, { color: colors.text }]}>{yearlyPrice}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{storeError ? (
|
||||||
|
<Text style={[styles.paywallFooterText, { color: colors.danger, marginBottom: 8 }]}>{storeError}</Text>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.paywallCta,
|
||||||
|
{ backgroundColor: colors.primary },
|
||||||
|
(!storeReady || isUpdating || Boolean(storeError)) && styles.disabledPlanCard,
|
||||||
|
]}
|
||||||
|
onPress={() => handlePurchase(trialEnabled ? 'yearly_pro' : 'monthly_pro')}
|
||||||
|
disabled={isUpdating || !storeReady || Boolean(storeError)}
|
||||||
|
activeOpacity={0.86}
|
||||||
|
>
|
||||||
|
{isUpdating || !storeReady ? (
|
||||||
|
<ActivityIndicator color={colors.onPrimary} />
|
||||||
|
) : (
|
||||||
|
<Text style={[styles.paywallCtaText, { color: colors.onPrimary }]}>
|
||||||
|
{trialEnabled ? copy.ctaTrial : copy.ctaMonthly}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={[styles.legalSep, { color: '#7A8079' }]}> · </Text>
|
|
||||||
<TouchableOpacity onPress={() => Linking.openURL('https://greenlenspro.com/terms')}>
|
<View style={styles.paywallFooter}>
|
||||||
<Text style={[styles.legalLink, { color: '#1F5B34' }]}>Terms of Use</Text>
|
<Text style={[styles.paywallFooterText, { color: colors.textMuted }]}>Privacy | Terms</Text>
|
||||||
</TouchableOpacity>
|
<Text style={[styles.paywallFooterText, { color: colors.textMuted }]}>{copy.cancelAnytime}</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity style={styles.hardRestoreBtn} onPress={handleRestore} disabled={isUpdating}>
|
</ScrollView>
|
||||||
<Text style={styles.hardRestoreText}>{copy.restorePurchases}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ImageBackground>
|
</ImageBackground>
|
||||||
@@ -1142,10 +1155,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
hardPaywallSafe: {
|
hardPaywallSafe: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
hardPaywallSafeCompact: {
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
},
|
||||||
heroTopBar: {
|
heroTopBar: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -1170,256 +1179,42 @@ const styles = StyleSheet.create({
|
|||||||
textShadowOffset: { width: 0, height: 1 },
|
textShadowOffset: { width: 0, height: 1 },
|
||||||
textShadowRadius: 3,
|
textShadowRadius: 3,
|
||||||
},
|
},
|
||||||
scanFrameOverlay: {
|
paywallSheet: {
|
||||||
position: 'absolute',
|
flex: 1,
|
||||||
top: '14%',
|
|
||||||
left: 54,
|
|
||||||
right: 54,
|
|
||||||
height: '28%',
|
|
||||||
},
|
|
||||||
scanFrameOverlayCompact: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
scanCorner: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: 44,
|
|
||||||
height: 44,
|
|
||||||
borderColor: '#FFFFFF',
|
|
||||||
},
|
|
||||||
scanCornerTopLeft: {
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
borderTopWidth: 4,
|
|
||||||
borderLeftWidth: 4,
|
|
||||||
borderTopLeftRadius: 18,
|
|
||||||
},
|
|
||||||
scanCornerTopRight: {
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
borderTopWidth: 4,
|
|
||||||
borderRightWidth: 4,
|
|
||||||
borderTopRightRadius: 18,
|
|
||||||
},
|
|
||||||
scanCornerBottomLeft: {
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
borderBottomWidth: 4,
|
|
||||||
borderLeftWidth: 4,
|
|
||||||
borderBottomLeftRadius: 18,
|
|
||||||
},
|
|
||||||
scanCornerBottomRight: {
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
borderBottomWidth: 4,
|
|
||||||
borderRightWidth: 4,
|
|
||||||
borderBottomRightRadius: 18,
|
|
||||||
},
|
|
||||||
identifiedChip: {
|
|
||||||
position: 'absolute',
|
|
||||||
alignSelf: 'center',
|
|
||||||
top: '43%',
|
|
||||||
zIndex: 4,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
borderRadius: 18,
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingVertical: 8,
|
|
||||||
backgroundColor: '#16351FCC',
|
|
||||||
},
|
|
||||||
identifiedChipCompact: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
identifiedChipText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: '800',
|
|
||||||
},
|
|
||||||
hardPaywallSheet: {
|
|
||||||
backgroundColor: '#FAFAF5',
|
|
||||||
borderTopLeftRadius: 30,
|
borderTopLeftRadius: 30,
|
||||||
borderTopRightRadius: 30,
|
borderTopRightRadius: 30,
|
||||||
paddingHorizontal: 20,
|
overflow: 'hidden',
|
||||||
paddingTop: 9,
|
|
||||||
paddingBottom: 18,
|
|
||||||
zIndex: 5,
|
zIndex: 5,
|
||||||
},
|
},
|
||||||
hardPaywallSheetCompact: {
|
|
||||||
paddingHorizontal: 18,
|
|
||||||
paddingTop: 8,
|
|
||||||
paddingBottom: 16,
|
|
||||||
},
|
|
||||||
sheetHandle: {
|
sheetHandle: {
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
width: 42,
|
width: 42,
|
||||||
height: 5,
|
height: 5,
|
||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
backgroundColor: '#C7C8C3',
|
marginTop: 10,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
sheetHandleCompact: {
|
paywallBody: { paddingHorizontal: 22, paddingTop: 10, paddingBottom: 24 },
|
||||||
marginBottom: 8,
|
paywallEyebrow: { fontSize: 12, fontWeight: '900', letterSpacing: 1.4, textAlign: 'center', marginBottom: 6 },
|
||||||
},
|
paywallHeadline: { fontSize: 32, fontWeight: '900', textAlign: 'center', marginBottom: 6 },
|
||||||
hardPaywallTitle: {
|
paywallSub: { fontSize: 15, lineHeight: 21, textAlign: 'center', marginBottom: 18 },
|
||||||
color: '#101411',
|
planCard: { borderRadius: 16, padding: 18, marginBottom: 14 },
|
||||||
fontSize: 23,
|
planCardTitle: { fontSize: 19, fontWeight: '800', marginBottom: 6 },
|
||||||
lineHeight: 28,
|
planCardBody: { fontSize: 14, lineHeight: 20 },
|
||||||
fontWeight: '900',
|
planCardDivider: { height: StyleSheet.hairlineWidth, marginVertical: 12 },
|
||||||
textAlign: 'center',
|
planCardPrice: { fontSize: 15, fontWeight: '800' },
|
||||||
marginBottom: 5,
|
trialToggleRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderRadius: 14, borderWidth: 1, paddingHorizontal: 16, paddingVertical: 12, marginBottom: 16 },
|
||||||
},
|
trialToggleLabel: { fontSize: 15, fontWeight: '800' },
|
||||||
hardPaywallTitleCompact: {
|
dueTimeline: { marginBottom: 18, paddingHorizontal: 4 },
|
||||||
fontSize: 22,
|
dueRow: { flexDirection: 'row', alignItems: 'center', gap: 10 },
|
||||||
lineHeight: 26,
|
dueDot: { width: 10, height: 10, borderRadius: 5 },
|
||||||
},
|
dueLine: { width: 2, height: 18, marginLeft: 4, marginVertical: 2 },
|
||||||
hardPaywallHint: {
|
dueLabel: { flex: 1, fontSize: 14, fontWeight: '700' },
|
||||||
color: '#676C66',
|
dueAmount: { fontSize: 14, fontWeight: '800' },
|
||||||
fontSize: 12,
|
paywallCta: { height: 58, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginBottom: 12 },
|
||||||
lineHeight: 17,
|
paywallCtaText: { fontSize: 18, fontWeight: '800' },
|
||||||
textAlign: 'center',
|
paywallFooter: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||||
marginBottom: 10,
|
paywallFooterText: { fontSize: 12, fontWeight: '600' },
|
||||||
},
|
|
||||||
hardPaywallHintCompact: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
hardBenefits: {
|
|
||||||
gap: 5,
|
|
||||||
marginBottom: 7,
|
|
||||||
},
|
|
||||||
hardBenefitsCompact: {
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
hardBenefitRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 12,
|
|
||||||
},
|
|
||||||
hardBenefitRowCompact: {
|
|
||||||
gap: 9,
|
|
||||||
},
|
|
||||||
hardBenefitIcon: {
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
borderRadius: 17,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: '#E3EFD9',
|
|
||||||
},
|
|
||||||
hardBenefitIconCompact: {
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
borderRadius: 16,
|
|
||||||
},
|
|
||||||
hardBenefitText: {
|
|
||||||
color: '#101411',
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: '800',
|
|
||||||
},
|
|
||||||
hardBenefitTextCompact: {
|
|
||||||
fontSize: 13,
|
|
||||||
},
|
|
||||||
hardPlanRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 10,
|
|
||||||
marginBottom: 7,
|
|
||||||
},
|
|
||||||
hardPlanCard: {
|
|
||||||
flex: 1,
|
|
||||||
minHeight: 76,
|
|
||||||
borderRadius: 14,
|
|
||||||
borderWidth: 1.5,
|
|
||||||
borderColor: '#D8DAD3',
|
|
||||||
padding: 9,
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 10,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
},
|
|
||||||
hardPlanCardCompact: {
|
|
||||||
minHeight: 76,
|
|
||||||
padding: 9,
|
|
||||||
gap: 8,
|
|
||||||
},
|
|
||||||
hardPlanCardSelected: {
|
|
||||||
borderColor: '#1F5B34',
|
|
||||||
borderWidth: 2,
|
|
||||||
backgroundColor: '#FBFFF8',
|
|
||||||
},
|
|
||||||
planRadio: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
borderRadius: 11,
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#8C918A',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginTop: 2,
|
|
||||||
},
|
|
||||||
planRadioSelected: {
|
|
||||||
backgroundColor: '#1F5B34',
|
|
||||||
borderColor: '#1F5B34',
|
|
||||||
},
|
|
||||||
hardPlanName: {
|
|
||||||
color: '#101411',
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '900',
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
hardPlanNameCompact: {
|
|
||||||
fontSize: 13,
|
|
||||||
marginBottom: 2,
|
|
||||||
},
|
|
||||||
hardPlanPrice: {
|
|
||||||
color: '#626862',
|
|
||||||
fontSize: 11,
|
|
||||||
fontWeight: '700',
|
|
||||||
lineHeight: 18,
|
|
||||||
},
|
|
||||||
hardPlanPriceCompact: {
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 15,
|
|
||||||
},
|
|
||||||
bestOfferBadge: {
|
|
||||||
alignSelf: 'flex-start',
|
|
||||||
backgroundColor: '#DDEBCF',
|
|
||||||
borderRadius: 999,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
paddingVertical: 3,
|
|
||||||
marginTop: 5,
|
|
||||||
},
|
|
||||||
bestOfferText: {
|
|
||||||
color: '#1F5B34',
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: '900',
|
|
||||||
},
|
|
||||||
hardPaywallCta: {
|
|
||||||
minHeight: 50,
|
|
||||||
borderRadius: 14,
|
|
||||||
backgroundColor: '#1F5B34',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 12,
|
|
||||||
marginBottom: 5,
|
|
||||||
},
|
|
||||||
hardPaywallCtaCompact: {
|
|
||||||
minHeight: 48,
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
hardPaywallCtaText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: '900',
|
|
||||||
},
|
|
||||||
hardPaywallCtaTextCompact: {
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
hardPaywallFooter: {
|
|
||||||
color: '#777C75',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '600',
|
|
||||||
textAlign: 'center',
|
|
||||||
marginBottom: 5,
|
|
||||||
},
|
|
||||||
safeArea: { flex: 1 },
|
safeArea: { flex: 1 },
|
||||||
header: { flexDirection: 'row', alignItems: 'center', padding: 16 },
|
header: { flexDirection: 'row', alignItems: 'center', padding: 16 },
|
||||||
backButton: { width: 40, height: 40, justifyContent: 'center' },
|
backButton: { width: 40, height: 40, justifyContent: 'center' },
|
||||||
@@ -1768,17 +1563,6 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
},
|
},
|
||||||
hardRestoreBtn: {
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 6,
|
|
||||||
marginTop: 4,
|
|
||||||
},
|
|
||||||
hardRestoreText: {
|
|
||||||
color: '#7A8079',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '500',
|
|
||||||
textDecorationLine: 'underline',
|
|
||||||
},
|
|
||||||
autoRenewText: {
|
autoRenewText: {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
|
|||||||
Reference in New Issue
Block a user