Files
RecipesCam/src/components/ProModal.tsx
T
admin 76cf4c6d7e Cut 1.2.3 with a shutter that never waits on GPS
Bound the geotag fix with a 2 s deadline and prewarm it at launch, queue a
shot pressed before the camera session binds and replay it, pass the options
argument capturePhotoToFile requires, save through the native path so the
media permission is not needed, and release the Skia surface and bitmap after
every export. Store the JS bundle uncompressed and load the export pipeline on
demand. Note in RELEASE_NOTES.md that none of this moved the tap threshold.
2026-09-17 10:02:35 +07:00

118 lines
4.9 KiB
TypeScript

import React from 'react';
import { View, Text, Modal, ScrollView, TouchableOpacity } from 'react-native';
import { X, Zap, RefreshCw } from 'lucide-react-native';
import { buyPro, restorePro, useProUnlock } from '../utils/proUnlock';
import { PRO_FRAMES } from '../utils/entitlement';
// The paywall. LITE keeps every look previewable and editable; this sheet is
// the only place the tier is sold, and Google Play does the selling — the price
// shown is the one Play returned for the product.
//
// Play requires a way back to an existing purchase on any screen that sells
// one, so RESTORE is a peer of BUY, not a footnote.
const PERKS = [
'No RECIPESCAM mark on your exports',
'HDF effect',
`${PRO_FRAMES.length} printed frames`,
'GPS location watermark',
'Every platform font for your watermark',
'Unlimited recipes and favorites',
];
export default function ProModal({ visible, onClose }: { visible: boolean; onClose: () => void }) {
const { owned, price, busy, error } = useProUnlock();
if (!visible) return null; // see PhotoViewerModal
return (
<Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
<View className="flex-1 bg-black/70 items-center justify-center px-4">
<TouchableOpacity className="absolute inset-0" activeOpacity={1} onPress={onClose} />
<View
className="w-full max-w-md rounded-2xl border border-zinc-800/70 p-5"
style={{ backgroundColor: 'rgba(8,8,10,0.97)', maxHeight: '85%' }}
>
<View className="flex-row items-center justify-between mb-3">
<View className="flex-row items-center">
<Zap size={13} color="#f59e0b" strokeWidth={1.75} />
<Text className="text-zinc-300 font-mono text-xs font-bold tracking-widest ml-2">
RECIPESCAM PRO
</Text>
</View>
<TouchableOpacity onPress={onClose} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
<X size={15} color="#71717a" />
</TouchableOpacity>
</View>
<ScrollView showsVerticalScrollIndicator={false}>
<Text className="text-zinc-500 font-mono text-[10px] leading-4">
One payment, kept on your Google account. Nothing to type in, and it comes back on
every phone that has the app.
</Text>
<View className="mt-3 space-y-1.5">
{PERKS.map((p) => (
<View key={p} className="flex-row items-start">
<Text className="text-amber-500 font-mono text-[11px] mr-2">+</Text>
<Text className="text-zinc-300 font-mono text-[11px] flex-1">{p}</Text>
</View>
))}
</View>
{owned ? (
<View className="mt-4 rounded-xl border border-amber-500/40 bg-amber-500/10 px-3 py-2.5">
<Text className="text-amber-500 font-mono text-[11px] font-bold tracking-widest">
PRO ACTIVE
</Text>
<Text className="text-zinc-500 font-mono text-[10px] mt-1">
Every look exports clean: no mark, no limits.
</Text>
</View>
) : (
<TouchableOpacity
onPress={buyPro}
disabled={busy}
activeOpacity={0.7}
className={`mt-4 rounded-xl border px-3 py-3 items-center ${
busy ? 'border-zinc-800/70 bg-black/30' : 'border-amber-500/70 bg-amber-500/15'
}`}
>
<Text
className={`font-mono text-[11px] font-bold tracking-widest ${
busy ? 'text-zinc-500' : 'text-amber-500'
}`}
>
{busy ? 'CONTACTING GOOGLE PLAY…' : price ? `UNLOCK PRO — ${price}` : 'UNLOCK PRO'}
</Text>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={restorePro}
disabled={busy}
activeOpacity={0.7}
className="mt-2 flex-row items-center justify-center rounded-xl border border-zinc-800/70 bg-black/30 px-3 py-2.5"
>
<RefreshCw size={11} color="#a1a1aa" strokeWidth={1.75} />
<Text className="text-zinc-400 font-mono text-[10px] font-bold tracking-widest ml-2">
RESTORE PURCHASES
</Text>
</TouchableOpacity>
{error ? (
<Text className="text-zinc-500 font-mono text-[10px] mt-2.5 leading-4">{error}</Text>
) : null}
<Text className="text-zinc-600 font-mono text-[10px] mt-3 leading-4">
Bought already but the app still says LITE? Restore, or check that you are signed in
to the Google account that paid.
</Text>
</ScrollView>
</View>
</View>
</Modal>
);
}