Sell PRO as an in-app unlock, take shared photos, and let FX run negative
- One free Play app (com.locphamtran.recipescamera) with an expo-iap PRO unlock, replacing the paid apk for the store channel - Android SEND/SEND_MULTIPLE share target feeds photos into the editor, on cold start and on resume - BACK button in the TopBar and press-and-hold PEEK replace the chip row - NOISE REDUCTION and SHARPENING accept -10: a negative SHARPENING softens the frame before the grain pass, a negative NOISE REDUCTION re-grains it, in the Skia engine, the live preview and the Kotlin export path alike
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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();
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user