Inspiration

Vision boards are a proven way to keep your goals in front of you — but a cork board on the wall can't follow you around, and most "mood board" apps stop at making something pretty. I wanted a board that is playful to build (like cutting out photos and pasting stickers as a kid) and that actually tracks whether your wishes come true. That became Petame: Vision Board — "picture your dreams, then make them real."

What it does

Petame turns your photos into a living vision board:

  • 📸 Snap or pick a photo → instant sticker. On-device background removal (VisionKit) cuts out the subject automatically, so every photo becomes a paste-able sticker.
  • ✍️ Attach a wish. Each sticker carries a short intention ("run a full marathon", "move to the coast") plus a category tag.
  • Track it from "manifesting" to "achieved." Not just a mood board — when a wish comes true you check it off and the board celebrates it.
  • 🎨 Make it yours. Free-form drag / pinch / rotate collage, 28 background designs (watercolor, cork, washi, sakura…), handwriting stickers drawn with Skia, and text stickers in 7 hand-picked fonts.
  • 📱 Home Screen widget. Your board lives on your Home Screen (small & large families), so your dreams stay in sight every day.
  • 📤 Share. Export a square image or a 9:16 story card with a motivational quote, localized per language.
  • 🌏 4 in-app languages (EN / JA / KO / zh-Hans) and 8 App Store locales.

How I built it

  • Expo SDK 56 + React Native 0.85 on the New Architecture, with the React Compiler enabled and expo-router typed routes.
  • @shopify/react-native-skia + Reanimated 4 + Gesture Handler power the collage canvas: normalized [0,1] transforms so a board lays out identically on any device, a 25-step undo stack, and a handwriting tool with a native SwiftUI color picker.
  • zustand + SQLite (expo-sqlite) as the local source of truth — no backend server, everything stays on device.
  • expo-widgets + App Groups render the board into an iOS widget; a custom config plugin injects a live AppEntity picker so the widget's settings show your real board names.
  • OneSignal for push (permission asked at the value moment — right after creating a board, never at launch), PostHog for product analytics.

How I use RevenueCat

RevenueCat is the entire monetization layer of Petame Pro:

  • Custom paywall designed as a tear-off "Pro Pass" ticket (perforated edge, shimmer CTA, staggered entry animations), rendered generically from getOfferings() — Monthly / Annual ("Best value") / Lifetime ("Yours forever").
  • Honest trial disclosure: intro-price data is normalized to days and checkTrialOrIntroductoryPriceEligibility makes sure "7 days free" is only shown to users who actually qualify.
  • Entitlement-driven gating: a single Petame Pro entitlement unlocks 50 boards, 100 stickers per board, premium backgrounds and the large widget; the free tier stays genuinely useful (1 board, 20 stickers).
  • Offer code redemption via presentCodeRedemptionSheet() right from Settings.
  • Analytics joined end-to-end: the RevenueCat app user ID and pro status are PostHog super-properties, and every paywall view carries a typed source (board_limit, sticker_limit, premium_background, settings) so I can see exactly which limit converts.
  • Purchase listener keeps entitlements in sync across purchase / restore / expiry, with localized error handling per RevenueCat error code.

Challenges I ran into

  • Config-plugin ordering: OneSignal's plugin must run before expo-widgets or the widget extension loses its aps-environment entitlement — the reason app.config.ts is TypeScript with carefully ordered plugins.
  • Widget memory limits: synchronous full-size image decode crashed the widget extension; boards are now auto-captured and downscaled to 720px JPEG before hitting the App Group.
  • A 36.5 MB share image 😅 — the story export pipeline now produces ~660 KB.
  • The iOS Simulator can't do background removal, so the core feature could only be tested on real devices.
  • RevenueCat Test Store keys don't support Customer Center, so it's temporarily replaced with restore + showManageSubscriptions() until production keys land.

Accomplishments I'm proud of

  • Shipped v1.0.1 solo in ~7.5 weeks — 172 commits from empty repo to App Store submission.
  • An onboarding that isn't a slideshow: a 6-step interactive tour where you build a real mini-collage and paste your first photo before you ever see the main screen.
  • A data-driven ASO baseline: keyword research across 14 storefronts and 250+ keywords before writing a single line of store metadata.
  • Thoughtful defaults everywhere: review prompts gated by real engagement (3 days / 3 launches / 3 stickers), pro-gating that never deletes user data, and graceful degradation when native modules are missing.

What I learned

  • Monetization is a product feature, not an afterthought — typed paywall sources and trial-eligibility checks took hours to build and completely change what you can learn from week one.
  • On-device ML (VisionKit subject segmentation) is finally good enough to build a whole UX around — as long as you design a fallback for older devices.
  • Widgets are a retention feature disguised as a rendering problem.

What's next

  • RevenueCat Customer Center once production API keys replace the Test Store.
  • Android launch — the code ships dark (API key deliberately null so Test Store purchases can never leak into production).
  • Board sharing between friends, more background designs, and richer "achieved" celebrations.

Built With

Share this project:

Updates