Drop the in-app unlock key: only the PRO apk opens PRO

The set is two apks and the unlock key was a second, weaker lock on the
same door. A verifier that runs offline has to carry its secret inside
the lite apk, so anyone who unpacked it could mint a key. Nothing in the
lite build can flip the gate now.

- entitlement.ts keeps the gate (which look is PRO, what an export
  carries in lite) and exports IS_PRO straight from the build flag.
- SETTINGS loses the key field; the plan chip still says LITE / PRO
  ACTIVE and the helper text says what lite cannot export.
- The upsell alerts just name the build that has the feature.
- tools/keygen.mjs goes with the verifier it fed.
This commit is contained in:
2026-09-14 20:09:12 +07:00
parent 97949e449d
commit 1b60ca2f22
6 changed files with 35 additions and 210 deletions
+9 -35
View File
@@ -26,7 +26,7 @@ import { processAndExportPhoto } from './src/utils/exportEngine';
import { processAndExportPhotoNative } from './src/utils/nativeExport';
import { exportRecipeXml, importRecipeXml } from './src/utils/recipeShare';
import { PARAM_DEFS } from './src/utils/paramDefs';
import { LITE_MARK, LITE_RECIPE_LIMIT, activatePro, loadPro, proLookInUse } from './src/utils/entitlement';
import { IS_PRO, LITE_MARK, LITE_RECIPE_LIMIT, proLookInUse } from './src/utils/entitlement';
import UltraWide from './modules/recipescam-ultrawide';
// @ts-ignore
@@ -97,12 +97,8 @@ export default function App() {
};
}, []);
const [recipes, setRecipes] = useState<Recipe[]>([]);
// Lite (free) vs PRO (unlocked key). Read once at startup; the unlock key
// flips it in place, so every later export drops the mark and the look gate.
const [pro, setPro] = useState(false);
useEffect(() => {
loadPro().then(setPro);
}, []);
// LITE (free) vs PRO (the paid build): fixed for the life of the install.
const pro = IS_PRO;
// Lite stores up to LITE_RECIPE_LIMIT recipes of its own; past that the save
// paths ask for PRO. Deleting one is PRO as well, so a Lite list only grows.
const customRecipeCount = recipes.filter((r) => r.isCustom).length;
@@ -1035,41 +1031,20 @@ export default function App() {
// The native Kotlin exporter has no text-watermark draw, so a Lite export
// (which always carries the mark) must take the Skia engine.
const nativeExport = NATIVE_EXPORT && pro;
// Shutter/export with a PRO look: nothing is rendered — name the feature and
// point at the unlock.
// Shutter/export with a PRO look: nothing is rendered — name the feature so
// the user knows which PRO build would render it.
const promptPro = (feature: string) => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
Alert.alert(
'PRO LOOK',
`${feature} can be previewed and edited for free, but exporting it needs PRO. Your own key unlocks it in SETTINGS.`,
[
{ text: 'LATER', style: 'cancel' },
{ text: 'SETTINGS', onPress: () => setSettingsVisible(true) },
]
`${feature} can be previewed and edited for free, but exporting it needs the PRO build.`
);
};
// Saving, starring, spawning: the action IS the feature, so there is no
// look to preview first — a Lite tap names it and points at the unlock,
// and nothing else happens.
// Saving, starring, spawning: the action IS the feature, so there is no look
// to preview first — a LITE tap names it and nothing else happens.
const promptProFeature = (feature: string) => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
Alert.alert(
'PRO FEATURE',
`${feature} needs PRO. Your own key unlocks it in SETTINGS.`,
[
{ text: 'LATER', style: 'cancel' },
{ text: 'SETTINGS', onPress: () => setSettingsVisible(true) },
]
);
};
// Settings unlock: a valid key persists PRO and returns true.
const handleUnlock = async (key: string): Promise<boolean> => {
const ok = await activatePro(key);
if (ok) {
setPro(true);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
}
return ok;
Alert.alert('PRO FEATURE', `${feature} needs the PRO build.`);
};
// Watermark handed to the export engines (Skia + native): the ACCEPTED mark
@@ -1712,7 +1687,6 @@ export default function App() {
startupMode={startupMode}
onStartupMode={handleStartupMode}
pro={pro}
onUnlock={handleUnlock}
/>
{/* Session photo viewer (swipe across captured/exported photos) */}