A comprehensive Flutter application designed to help users manage and overcome sugar cravings through behavioral science, community support, and personalized tracking.
MIT License
Copyright (c) 2025 Stoppr
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
⚠️ IMPORTANT LEGAL NOTICE- Overview
- Features
- Architecture
- Project Structure
- Prerequisites
- Setup Instructions
- Configuration Guide
- Config Files Generation
- Development Guide
- Building & Running
- Contributing
This app was banned from the Apple App Store. This repository is provided for educational and inspirational purposes only.
DO NOT use this codebase as-is if you plan to submit to the App Store. Use this repository as a learning resource and inspiration, not as a template to copy.
Stoppr is a mobile application built with Flutter that helps users break free from sugar addiction through:
- Personalized Onboarding: Comprehensive questionnaire and analysis to understand user's relationship with sugar
- Streak Tracking: Real-time tracking of sugar-free days with motivational widgets
- Community Support: Social features including posts, comments, and accountability partners
- Educational Content: Video lessons and articles about sugar addiction and health
- Crisis Intervention: Panic button feature for moments of temptation
- Nutrition Tracking: Food scanning, calorie tracking, and meal rating
- Meditation & Mindfulness: Breathing exercises and meditation sessions
- Recipe Database: Sugar-free recipes and meal alternatives
Important: Stoppr follows an onboarding-first approach. This means:
- Users complete the full onboarding experience FIRST - They go through a comprehensive 13-question questionnaire, see personalized benefits, set goals, and understand the value of the app
- Paywalls appear AFTER onboarding - Only after users have invested time and seen the value do they encounter subscription options
- Multiple paywall opportunities - Paywalls can appear at strategic points:
- Pre-Paywall Screen (
pre_paywall.dart): After completing the questionnaire and seeing their personalized analysis, users see a "Become a STOPPR" button that triggers the main paywall - Soft Paywall (
give_us_ratings_screen.dart): A lighter paywall that may appear during onboarding if users try to skip certain steps - Feature Paywalls: Throughout the app, certain premium features trigger paywalls when free users try to access them (e.g., food scanning, advanced panic button features, unlimited learn videos)
- Pre-Paywall Screen (
Why this approach? Users are more likely to subscribe after they've experienced the app's value and personalized content, rather than being asked to pay immediately upon opening the app.
Flow Overview: Users go through a comprehensive onboarding experience BEFORE seeing any paywalls. This builds trust and demonstrates value. The onboarding is designed to be engaging, personalized, and educational - helping users understand their relationship with sugar before asking them to commit financially.
Complete Step-by-Step Flow (Screen by Screen):
-
Welcome & Quiz Introduction (
onboarding_screen2.dart)- What happens: Users see an introduction to Stoppr with a call-to-action to start a quiz
- Purpose: Creates initial engagement and sets expectations
- User action: Taps "Start Quiz" button
-
FOMO Stats Screen (
onboarding_fomo_stats_screen.dart)- What happens: Displays shocking statistics about sugar consumption (e.g., "The average person consumes X pounds of sugar per year")
- Purpose: Creates awareness and urgency (Fear Of Missing Out on better health)
- User action: Taps continue
-
Authentication Screen (
onboarding_screen3.dart)- What happens: Users can sign in with:
- Apple Sign In (iOS)
- Google Sign In
- Email/Password
- Skip (continue as anonymous/guest)
- Purpose: Creates user account for data persistence and cross-device sync
- User action: Chooses authentication method or skips
- What happens: Users can sign in with:
-
13-Question Questionnaire (
questionnaire_screen.dart)- What happens: Users answer 13 comprehensive questions covering:
- Question 1-5: Current sugar consumption habits (how much, how often, when)
- Question 6-8: Symptoms and health concerns (energy levels, mood, physical symptoms)
- Question 9-11: Goals and motivations (why they want to quit sugar)
- Question 12-13: Challenges and pain points (what makes it hard)
- Purpose: Collects data to personalize the entire experience
- User action: Answers each question (multiple choice, sliders, or text input)
- Data stored: Answers saved to Firestore for analysis
- What happens: Users answer 13 comprehensive questions covering:
-
Profile Setup (
profile_info_screen.dart)- What happens: Collects basic demographic information:
- Age
- Gender
- Name (optional)
- Purpose: Personalizes messaging and recommendations
- User action: Fills in profile information
- What happens: Collects basic demographic information:
-
Symptoms Screen (
symptoms_screen.dart)- What happens: Users select from a list of symptoms they experience related to sugar consumption:
- Low energy
- Mood swings
- Brain fog
- Weight gain
- Sleep issues
- etc.
- Purpose: Identifies specific health concerns to address
- User action: Selects multiple symptoms
- What happens: Users select from a list of symptoms they experience related to sugar consumption:
-
Sugar Pain Points (
onboarding_sugar_painpoints_page_view.dart)- What happens: Users swipe through pages and select their biggest challenges:
- Cravings at specific times
- Emotional eating
- Social pressure
- Hidden sugars in foods
- etc.
- Purpose: Identifies specific pain points to address in the app experience
- User action: Swipes through pages and selects pain points
- What happens: Users swipe through pages and select their biggest challenges:
-
Benefits Visualization (
benefits_page_view.dart)- What happens: Shows personalized benefits based on questionnaire answers:
- Multiple benefit screens (rewire brain, level up life, conquer yourself, etc.)
- Each benefit is visually presented with animations
- Purpose: Shows users what they'll gain by quitting sugar (personalized to their goals)
- User action: Swipes through benefit screens
- What happens: Shows personalized benefits based on questionnaire answers:
-
Science-Backed Plan (
stoppr_science_backed_plan.dart)- What happens: Explains Stoppr's methodology:
- Based on behavioral science
- Neuroscience of addiction
- Habit formation principles
- Purpose: Builds credibility and trust in the approach
- User action: Reads and continues
- What happens: Explains Stoppr's methodology:
-
Goal Selection (
choose_goals_onboarding.dart)- What happens: Users select their primary goals from options like:
- Weight loss
- More energy
- Better mental clarity
- Improved health
- Better self-control
- etc.
- Purpose: Sets user's primary motivation (used throughout app for reminders)
- User action: Selects one or more goals
- What happens: Users select their primary goals from options like:
-
Current vs Potential Rating (
current_6_blocks_rating_screen.dart,potential_rating_screen.dart)- What happens:
- First screen: Users rate themselves on 6 life areas (energy, mood, focus, etc.) - "Where are you now?"
- Second screen: Shows potential ratings - "Where could you be?"
- Purpose: Creates visual contrast between current state and potential future
- User action: Rates themselves on multiple dimensions
- What happens:
-
Weeks Progression (
weeks_progression_screen.dart)- What happens: Shows a visual timeline of expected progress:
- Week 1: What to expect
- Week 2-4: Milestones
- Month 2-3: Long-term benefits
- Purpose: Sets realistic expectations and shows the journey ahead
- User action: Views timeline and continues
- What happens: Shows a visual timeline of expected progress:
-
Analysis Results (
analysis_result_screen.dart)- What happens: Displays personalized insights based on all questionnaire answers:
- Sugar consumption analysis
- Risk factors identified
- Personalized recommendations
- Expected benefits specific to their answers
- Purpose: Shows users their personalized analysis (the "aha moment" that demonstrates value)
- User action: Reviews their personalized results
- What happens: Displays personalized insights based on all questionnaire answers:
-
Give Us Ratings (
give_us_ratings_screen.dart)- What happens: Asks users to rate the app (optional)
- May show: Soft paywall if user tries to skip
- Purpose: Collects feedback and may show lighter paywall
- User action: Rates app or continues
-
Benefits Impact (
benefits_impact_screen.dart)- What happens: More detailed visualization of how quitting sugar will impact their specific life areas
- Purpose: Reinforces the value proposition
- User action: Views benefits and continues
-
Letter from Future Self (
letter_from_future_screen.dart)- What happens: Users write a letter to their future self describing how their life will be better after quitting sugar
- Purpose: Creates emotional connection and commitment (future self visualization)
- User action: Writes a personal letter
-
The Vow (
read_the_vow_screen.dart)- What happens: Users read and commit to "The Vow" - a commitment statement to quit sugar
- Purpose: Creates a formal commitment ceremony (psychological commitment)
- User action: Reads vow and commits
-
Pre-Paywall Screen (
pre_paywall.dart) - FIRST MAIN PAYWALL- What happens:
- Shows "Become a STOPPR" button
- Displays subscription benefits
- May show discount offers
- When user taps button, triggers Superwall paywall with placement
"standard_paywall"or"gift_step_1"
- Purpose: This is where users are asked to subscribe AFTER seeing all the value
- User action:
- Subscribes → Goes to congratulations screens
- Skips/Closes → Continues as free user with limited features
- What happens:
-
Congratulations Screens (
congratulations_screen_1.dartthroughcongratulations_screen_8.dart)- What happens: 8 sequential celebration screens:
- Screen 1: Initial congratulations
- Screen 2-7: Various motivational messages and next steps
- Screen 8: Final welcome to the app
- Purpose: Celebrates their commitment and transitions them into the main app
- User action: Views celebration screens, then enters main app
- What happens: 8 sequential celebration screens:
Paywall Locations in Onboarding:
-
Main Paywall (
pre_paywall.dart):- When: After completing entire questionnaire and seeing personalized analysis
- Trigger: User taps "Become a STOPPR" button
- Superwall Placement:
INSERT_YOUR_STANDARD_PAYWALL_PLACEMENT_ID_HEREorINSERT_YOUR_GIFT_STEP_1_PLACEMENT_ID_HERE(⚠️ Replace with your own placement IDs) - Why here: Users have invested 10-15 minutes and seen personalized value
-
Soft Paywall (
give_us_ratings_screen.dart):- When: If user tries to skip rating step
- Trigger: User attempts to skip
- Superwall Placement:
INSERT_YOUR_SOFT_PAYWALL_PLACEMENT_ID_HERE(⚠️ Replace with your own placement ID) - Why here: Lighter ask, doesn't interrupt main flow
-
Feature Paywalls (Post-Onboarding):
- Triggered when free users try to access premium features
- Examples: Unlimited food scans, full panic button flow, all learn videos
Overview: The main screen users see after onboarding. Displays streak, widgets, and provides access to all app features.
Main Components:
Top Section:
-
Streak Counter Widget (
streak_counter_widget.dart):- Real-time display of sugar-free days (updates every second)
- Shows format like "2 DAYS 15 HRS 30 MIN" or "3 WEEKS 2 DAYS 4 HRS"
- Large, prominent display
- Tappable to edit streak or view details
-
Daily Check-In Widget (
daily_check_in_widget.dart):- Mood tracking with emoji selection
- Users select how they're feeling (happy, neutral, sad, etc.)
- Stores mood data for tracking patterns
- May trigger motivational messages based on mood
-
Brain Rewiring Widget (
brain_rewiring_widget.dart):- Visual progress indicator showing "brain rewiring" progress
- Based on days sugar-free and activities completed
- Motivational visualization
-
Challenge Progress Widget (
challenge_progress_widget.dart):- 28-day challenge tracking
- Shows completed days and tasks
- Displays current challenge day
-
Goal Date Widget (
goal_date_widget.dart):- Countdown to goal achievement date
- Based on user's selected goal from onboarding
- Motivational countdown
-
Temptation Status Widget (
temptation_status_widget.dart):- Shows current state (tempted, strong, etc.)
- Quick status indicator
-
Reason to Quit Widget (
reason_to_quit_widget.dart):- Displays user's primary reason from onboarding
- Personalized motivation reminder
-
Weekly Tracker Widget (
weekly_tracker_widget.dart):- Visual progress chart for the week
- Shows daily progress
-
Pledge Check-In Widget (
pledge_check_in_widget.dart):- Daily commitment verification
- Users confirm they're staying committed
- May show notifications if not checked in
-
Todo Challenge Widget (
todo_challenge_widget.dart):- Task-based challenges for the day
- Examples: Journal entry, breathing exercise, meditation, food scan, etc.
- Completing tasks unlocks rewards
Bottom Navigation (main_scaffold.dart):
The app has 5 main tabs accessible via bottom navigation:
- Home Tab (Index 0):
HomeScreen- Main dashboard with streak and widgets - Learn Tab (Index 1):
LearnVideoListScreen- Educational videos - Rewire Brain Tab (Index 2):
HomeRewireBrainScreen- Brain rewiring activities - Community Tab (Index 3):
CommunityScreen- Social feed and chat - Profile Tab (Index 4):
UserProfileScreen- User settings and profile
Home Screen Menu Options (Accessible via hamburger menu):
Main Section:
- Add Widgets to Home: Instructions for adding iOS/Android widgets to home screen
- Accountability Partner: Navigate to accountability partner feature
- Calorie Tracker: Opens calorie tracking dashboard
- Food Scan: Quick access to food scanning feature
- Fasting: Opens fasting tracking dashboard
- Healthy Recipes: Browse sugar-free recipes
- Rate My Plate: Meal analysis feature
- Self Reflection: Positive affirmations and reflection exercises
- Talk to Jarvis: AI chatbot for support
- Chat: Join community group chat
- Leaderboard: View community rankings
- Achievements: View unlocked achievements and milestones
Mindfulness Section:
- Breathing Exercise: Guided breathing animations
- Success Stories: User testimonials and success stories
- Meditation Session: Guided meditation sessions
- Podcast: Listen to health and wellness podcasts
- Audio Library: Collection of meditation and relaxation audio files
- Articles: Read educational articles about sugar addiction
Misc Section:
- Games: Links to distraction games (Gamezop) - helps users distract themselves during cravings
- Tree of Life: Visual progress tree that grows with streaks - motivational visualization
- 28-Day Challenge: Full challenge screen with all tasks (see details below)
- Journal: Personal reflection journal entries
Overview: Visual progress tracking showing "brain rewiring" progress over time. Based on neuroscience that it takes time to rewire neural pathways.
Features:
-
Two View Modes (toggleable):
- Ring View: Circular progress ring showing completion percentage
- Radar View: Radar chart showing progress across multiple dimensions
-
Progress Calculation:
- Based on days sugar-free (from streak)
- Target: 90 days for full "brain rewiring"
- Shows percentage complete (e.g., "45% Complete")
- Calculates completion date based on current progress
-
Weekly Check-In Tracker:
- Shows 7 days of the week
- Tracks daily check-ins
- Visual indicators for completed check-ins
- Encourages daily engagement
-
Visual Elements:
- Animated progress rings/charts
- Color changes as progress increases
- Smooth animations when toggling views
- Motivational messages based on progress
-
Integration:
- Links to pledge check-in
- Connects to streak system
- Shows target date for full rewiring
Overview: Structured 28-day challenge with daily tasks to help users build healthy habits and stay engaged.
How It Works:
Challenge Structure:
- 28 Days: One task per day for 28 consecutive days
- Random Task Distribution: Tasks are randomly assigned to days (different for each user)
- Daily Tasks: Each day has one specific task to complete
Available Task Types (challenge_service.dart):
- Journal (
taskTypeJournal): Write a journal entry - Breathing (
taskTypeBreathing): Complete a breathing exercise - Pledge (
taskTypePledge): Make a daily pledge commitment - Meditation (
taskTypeMeditation): Complete a meditation session - Podcast (
taskTypePodcast): Listen to a podcast episode - Articles (
taskTypeArticles): Read an educational article - Food Scan (
taskTypeFoodScan): Scan a food item - Rate My Plate (
taskTypeRateMyPlate): Rate a meal plate - Chatbot (
taskTypeChatbot): Talk to Jarvis chatbot - Community Post (
taskTypeCommunityPost): Create a community post - Self Reflection (
taskTypeSelfReflection): Complete self-reflection exercise
Challenge Screen Features:
- Day Display: Shows current challenge day (1-28)
- Task Card: Displays today's task with description
- Completion Status: Visual indicator when task is completed
- Progress Bar: Shows overall challenge progress (X/28 days)
- Calendar View: Shows all 28 days with completion status
- Task Navigation: Tapping task navigates to relevant feature
Starting the Challenge:
- User can start challenge from home screen
- Challenge starts from day 1
- Tasks are randomly assigned at start
- Progress is saved locally and synced to Firestore
Completing Tasks:
- User completes task in relevant feature (e.g., completes breathing exercise)
- Challenge service detects completion
- Day is marked as complete
- Progress updates automatically
- Next day unlocks at midnight
Rewards:
- Achievement unlocked when challenge completed
- Visual celebration when finishing all 28 days
- May unlock special features or badges
Overview: AI-powered meal analysis that rates how healthy a plate is for someone quitting sugar.
How It Works:
Step 1: Photo Capture (rate_my_plate_scan_screen.dart):
- User takes photo of their meal plate
- Camera interface with capture button
- Can retake photo if needed
Step 2: AI Analysis:
-
Groq Vision API analyzes the image:
- Identifies food items on plate
- Estimates nutritional content
- Evaluates sugar and carb content
- Assesses protein, fats, vegetables
-
Analysis Criteria:
- High Score (8-10): Plates with protein, healthy fats, vegetables, low carbs/sugar
- Mid Score (4-7): Balanced plates with some carbs
- Low Score (1-3): Plates dominated by high-carb or sugary foods
Step 3: Results Display (rate_my_plate_results_screen.dart):
Rating Components:
- Overall Score: 0-10 rating (higher = better for quitting sugar)
- Title: Summary of rating (e.g., "Excellent Low-Carb Plate!" or "Too Many Simple Carbs")
- Description: Detailed explanation of the rating
- Strengths: List of what's good about the plate
- Improvements: Suggestions for making it better
- Nutritional Estimates:
- Estimated calories
- Protein (grams)
- Carbs (grams)
- Fat (grams)
- Sugar content (highlighted)
- Carb Impact: Description of how carbs affect blood sugar
- Sugar Content: Detailed sugar analysis
- Protein Content: Protein analysis
Features:
- Share Results: Share rating to social media
- Save for Later: Save plate rating to review
- Try Again: Rate another plate
- Educational: Helps users learn what makes a healthy plate
Free vs Premium:
- Free users: Limited ratings (e.g., 1 per day)
- Premium users: Unlimited ratings
- Paywall appears after free limit
Overview: Camera-based food recognition using AI to identify foods and provide nutritional information.
How It Works:
Step 1: Photo Capture:
- User takes photo of food item
- Camera interface with capture button
- Can select from photo library
Step 2: AI Recognition:
- OpenAI Vision API analyzes image:
- Identifies food items
- Recognizes multiple foods in one image
- Provides food names
Step 3: Nutrition Lookup:
- Searches Edamam Food Database for nutritional info
- Fallback: If Edamam fails, uses Spoonacular API
- Returns detailed nutrition:
- Calories
- Protein, carbs, fats
- Sugar content (highlighted)
- Fiber, sodium, etc.
Step 4: Add to Log:
- User confirms food identification
- Adjusts serving size if needed
- Adds to calorie tracker log
- Appears in daily food log
Features:
- Quick Add: Fast way to log foods without searching
- Visual Recognition: No need to type food names
- Multiple Foods: Can identify multiple items in one photo
- Serving Size: Adjustable portions
Free vs Premium:
- Free users: Limited scans (e.g., 1 scan per day)
- Premium users: Unlimited scans
- Paywall appears after free limit
Overview: Positive affirmations and reflection exercises to help users maintain motivation and self-awareness.
Features:
- Daily Affirmations: Positive messages tailored to user's goals
- Reflection Prompts: Questions to help users reflect on their journey
- Emotional Tracking: Track feelings and emotions
- Progress Reflection: Review progress and celebrate wins
- Motivational Content: Encouraging messages and quotes
Overview: AI-powered chatbot assistant for support and guidance.
Features:
- AI Chatbot: Powered by OpenAI (or Groq as fallback)
- 24/7 Support: Always available for questions
- Personalized Responses: Tailored to user's journey
- Sugar Quitting Guidance: Answers questions about quitting sugar
- Motivational Support: Provides encouragement when needed
- Feature Information: Explains how to use app features
Use Cases:
- Questions about sugar addiction
- Need for motivation
- Understanding app features
- General health questions
- Crisis support (complements panic button)
Overview: Comprehensive calorie and nutrition tracking system to help users monitor their food intake and stay within sugar limits.
How It Works:
Initial Setup (nutrition/presentation/onboarding/):
- Goal Selection Screen (
goal_selection_screen.dart): User selects nutrition goals (weight loss, maintenance, muscle gain) - Height & Weight Screen (
height_weight_screen.dart): Collects body measurements - Workouts Per Week Screen (
workouts_per_week_screen.dart): Asks about exercise frequency - Results Screen (
results_screen.dart): Calculates and displays:- Daily calorie goal (BMR + activity level)
- Macronutrient breakdown (protein, carbs, fats)
- Sugar limit recommendation (typically 50g or less)
Main Dashboard (calorie_tracker_dashboard.dart):
Features:
-
Day Selector: Swipe through days to view past or future days
-
Daily Summary: Shows:
- Total calories consumed
- Calories remaining
- Macronutrients (protein, carbs, fats)
- Sugar intake (with warning if over limit)
- Water intake tracking
- Net calories (after exercise)
-
Food Logs: List of all foods logged for the day:
- Each food shows calories, macros, and sugar content
- Can edit or delete entries
- Shows meal timing (breakfast, lunch, dinner, snack)
-
Add Meal Button: Opens food entry screen
-
Progress Rings: Visual indicators for:
- Calorie progress
- Protein, carbs, fats progress
- Sugar limit (red warning if exceeded)
-
Water Tracker:
- Track water intake by glasses/cups
- Visual progress indicator
- Customizable serving size
Adding Foods (food_scanner_screen.dart, food_database_entry_screen.dart):
Method 1: Food Scanner (Premium Feature):
- User takes photo of food
- OpenAI Vision API analyzes image
- Identifies food items
- Provides nutritional information
- User confirms and adds to log
- Free users: Limited scans (1 scan), paywall appears after limit
Method 2: Food Database Search:
- User searches food database (Edamam API)
- Selects food from results
- Adjusts serving size
- Adds to log
- Fallback: If Edamam fails, automatically uses Spoonacular API
Method 3: Manual Entry:
- User manually enters food name
- Searches database
- Selects and adds
Additional Screens:
- Daily Breakdown (
daily_breakdown_screen.dart): Detailed view of daily nutrition by meal - Calorie Progress (
calorie_progress_screen.dart):- Charts showing progress over time (90 days, 6 months, 1 year)
- Weight tracking
- BMI calculation
- Goal progress visualization
- Calorie Streak (
calorie_streak_screen.dart): Tracks consecutive days staying within calorie goals - Nutrition Goals (
nutrition_goals_screen.dart): Edit calorie and macro goals - Edit Weight/Height (
edit_weight_screen.dart,edit_height_screen.dart): Update body measurements - Exercise Logging (
log_exercice/):- Run Exercise (
run_exercise_setup_screen.dart): Log running workouts - Weight Lifting (
weight_lifting_setup_screen.dart): Log strength training - Manual Exercise (
manual_exercise_setup_screen.dart): Log other exercises - Exercise Type (
exercise_type_screen.dart): Choose exercise category - Calorie Burned Result (
calorie_burned_result_screen.dart): Shows calories burned
- Run Exercise (
Sugar Tracking:
- Tracks refined sugar intake separately
- Warns when approaching daily limit (default 50g)
- Shows popup if sugar limit exceeded
- Links to streak system (exceeding sugar may break streak)
Data Storage:
- Food logs stored in Firestore (
food_logscollection) - Daily summaries calculated and cached
- Body profile stored separately for calculations
Overview: Social features that create a supportive community for users quitting sugar together.
Main Screens:
Community Screen (community_screen.dart):
-
Posts Feed:
- Scrollable feed of user-generated posts
- Posts can include text, images
- Shows author, timestamp, upvote count
- Users can upvote posts they find helpful
- Posts sorted by upvotes and recency
-
Add Post Button:
- Opens
add_post_screen.dart - Users can create new posts
- Can include text and images
- Posts are moderated
- Opens
-
Post Details (
post_detail_screen.dart):- Full post view
- Comments section
- Users can comment on posts
- View all comments and replies
Chat Features:
Official Chat (official_chat_screen.dart):
- What it is: AI-generated supportive messages
- How it works:
- Shows messages that appear to be from community members
- Actually generated by AI (OpenAI) to provide constant support
- Messages are encouraging and relevant to sugar quitting journey
- Creates sense of active community even when real users aren't posting
Language Chat (language_chat_screen.dart):
- Chat filtered by user's language preference
- Ensures users see content in their language
Community Rules (community_rules_screen.dart):
- Guidelines for community participation
- Code of conduct
- What's allowed/not allowed
Blocked Users (blocked_users_screen.dart):
- Users can block other users
- Manage blocked list here
Data Models:
- Post Model (
post_model.dart): Stores post data (text, images, author, timestamp, upvotes) - Comment Model (
comment_model.dart): Stores comments on posts - Chat Message Model (
chat_message_model.dart): Stores chat messages
Repositories:
- Community Repository (
community_repository.dart): Handles post creation, fetching, upvoting - Chat Repository (
chat_repository.dart): Handles chat message operations
Overview: Educational content to help users understand sugar addiction and build healthy habits.
Video Lessons (learn_video_list_screen.dart):
Features:
-
Video List: Browseable collection of educational videos
-
Video Topics:
- Welcome to Stoppr
- Where is sugar hiding
- Understanding cravings
- Building healthy habits
- Sugar is not a feelings fixer
- Sport to fix your sugar life
- Kill sugar peer pressure
- And more...
-
Video Player (
full_screen_video_player_screen.dart):- Full-screen video playback
- Play/pause controls
- Progress bar
- Subtitles support (multiple languages)
- Volume controls
- Can toggle subtitles on/off
-
Progress Tracking:
- Tracks which videos user has watched
- Shows completion status
- Unlocks next videos as user progresses
-
Free vs Premium:
- Free users: Limited to first video only
- Premium users: Access to all videos
- Paywall appears when free user tries to watch premium videos
Articles (articles_list_screen.dart):
Features:
-
Article List: Browseable collection of educational articles
-
Article Topics (20+ articles available):
- Neuroscience of sugar addiction
- Physical health consequences
- Psychological effects
- Natural vs added sugar
- Sugar and gut health
- Sugar and skin aging
- Managing cravings triggers
- Healthy coping mechanisms
- And more...
-
Article Reader (
article_detail_screen.dart):- Markdown-based article viewing
- Formatted text with headings, lists, etc.
- Scrollable content
- Can bookmark articles
- Tracks reading progress
-
Multi-Language Support:
- Articles available in: English, French, Spanish, German, Italian, Russian, Czech, Slovak
- Stored in
assets/articles/{language}/directories - User sees articles in their app language
Progress Tracking:
- Tracks which articles user has read
- Shows reading progress
- May unlock achievements for reading all articles
Overview: Crisis intervention feature that provides immediate support when users feel tempted to consume sugar. The panic button guides users through a series of distraction techniques and coping strategies.
How to Access:
- Large red "PANIC" button on home screen
- Quick action from app icon (iOS/Android)
- Accessible from anywhere in the app
Complete Flow Step-by-Step:
Step 1: What's Happening Screen (what_happening_screen.dart)
- What happens: User taps panic button, sees screen asking "What's happening, [Name]?"
- Purpose: Acknowledges their struggle and creates pause
- User action: Taps "I need help" button
- Next: Goes to Tricks Intro Screen
Step 2: Tricks Intro Screen (tricks_intro_screen.dart)
- What happens: Explains that the app will show various "tricks" to help them resist
- Purpose: Sets expectations for the flow
- User action: Taps continue
- Next: Goes to first randomized trick
Step 3-20: Randomized Trick Flow (panic_flow_manager.dart)
How Randomization Works:
PanicFlowManagercreates a random order of 18 tricks- First 17 tricks are shuffled randomly
- 18th trick (Sugary Treat) is always last
- Each user gets a different order each time they use panic button
Available Tricks (in random order):
- Drink Water (
drink_glasses_water.dart): Encourages drinking water to reduce cravings - Eat Salty Food (
eat_salty_screen.dart): Suggests salty snacks to satisfy cravings - Brush Teeth (
brush_teeth_screen.dart): Brushing teeth changes taste and creates barrier - Drink Hot Tea (
drink_hot_tea_screen.dart): Warm beverage as distraction - Dark Chocolate (
dark_chocolate_screen.dart): Small amount of dark chocolate (better than sugary treats) - Exercise (
exercise_screen.dart): Physical activity to release endorphins - High Protein (
high_protein_screen.dart): Protein-rich snack to feel full - Fruits & Fiber (
fruits_fiber_screen.dart): Healthy fruit option - Lemon Water (
lemon_water_screen.dart): Tart flavor to reduce cravings - Cold Sparkling Water (
cold_sparkling_water_screen.dart): Carbonated water as alternative - Step Burst (
step_burst_screen.dart): Quick burst of movement - Visualization Script (
visualization_script_screen.dart): Guided visualization exercise - Burpees (
burpees_screen.dart): High-intensity exercise - Mint Gum (
mint_gum_screen.dart): Chewing gum for distraction - Quick Nap (
quick_nap_screen.dart): Rest to reset mindset - Apple Cider Vinegar (
apple_cider_vinegar_screen.dart): ACV drink suggestion - Cold/Hot Shower (
cold_hot_shower_screen.dart): Temperature change to reset - Games (
games_screen.dart): Distraction games - Sugary Treat (
sugary_treat_screen.dart): LAST RESORT - If nothing else works, guides user to make better choice
Flow Pattern (After Each Trick):
- User sees trick screen with explanation
- User taps "I did it" or "Try something else"
- After Trick 1: Goes to "Feeling Now?" → "Try Something Else" → Next trick
- After Trick 2: Goes to "Feeling Now?" → Next trick directly
- After Trick 3: Goes to "Feeling Now?" → "Another Solution" → Next trick
- After Trick 4: Goes to "Feeling Now?" → "Other Trick" → Next trick
- After Trick 5: Goes to "Feeling Now?" → "Another Way" → Next trick
- After Tricks 6-14: Cycles through connector screens ("Try Something Else", "Another Solution", "Other Trick", "Another Way")
- After Trick 18: Goes to Congratulations Screen
Connector Screens (Between Tricks):
- Feeling Now Screen (
feeling_now_screen.dart): Asks "How are you feeling now?" - checks if trick worked - Try Something Else Screen (
try_something_else_screen.dart): Encourages trying next trick - Another Solution Screen (
another_solution_screen.dart): Presents next trick as alternative solution - Other Trick Screen (
other_trick_screen.dart): Introduces next trick - Another Way Screen (
another_way_screen.dart): Suggests another approach
Final Screen:
- Congratulations Screen (
congratulations_screen.dart):- Celebrates user for getting through the panic moment
- Shows supportive messages
- Returns user to home screen
Free vs Premium:
- Free users: Limited to ~5 panic flow steps, then paywall appears
- Premium users: Full access to all 18 tricks and complete flow
Data Tracking:
- Tracks panic button usage (analytics)
- Records which tricks were shown
- May track which tricks are most effective
Integration:
- Links to breathing exercises
- May trigger notifications if user frequently uses panic button
- Connects to streak system (using panic button doesn't break streak, but giving in does)
Overview: Users can pair up with accountability partners to support each other in quitting sugar.
How It Works:
Accountability Partner Screen (accountability_partner_screen.dart):
If User Has No Partner:
-
Find Partner Button:
- Searches for available partners
- Shows list of users looking for partners
- User can send partner request
-
Invite Friend Button:
- Share app with friends via link
- Friend can join and become partner
- Uses referral system
-
Available Partners List (
available_partners_list.dart):- Shows users who are available to partner
- Displays basic info (name, streak, etc.)
- User can send request
If User Has Partner:
-
Partner Card (
partner_card_widget.dart):- Shows partner's name
- Displays partner's current streak
- Shows partner's mood (if shared)
- Last active time
-
Partner Actions:
- View partner's detailed progress
- Send messages (if enabled)
- Unpair (end partnership)
Partner Requests (partner_request_dialog.dart):
-
Pending Requests (
pending_request_card.dart):- Shows incoming partner requests
- User can accept or decline
- Shows requester's info
-
Sent Requests:
- Shows requests user has sent
- Can cancel pending requests
Partnership Features:
- Shared Streaks: See each other's streak progress
- Mutual Support: Encouragement when partner is struggling
- Notifications: Alerts when partner achieves milestones
- Accountability: Knowing someone is watching helps maintain commitment
Unpairing (unpair_confirmation_dialog.dart):
- User can end partnership
- Confirmation dialog to prevent accidental unpairing
- Both users notified when partnership ends
Data Models:
- Accountability Partner (
accountability_partner.dart): Stores partner relationship data - Partnership (
partnership.dart): Tracks partnership status and details
Overview: Core feature that tracks how many consecutive days users have been sugar-free. The streak is the primary motivation and progress indicator.
How Streaks Work:
Streak Service (core/streak/streak_service.dart):
-
Real-Time Tracking:
- Streak counter updates every second
- Calculates days, hours, minutes since last sugar consumption
- Stored in Firestore and cached locally
-
Streak Calculation:
- Starts from user's "quit date" (set during onboarding or when they start)
- Increments every day at midnight (user's timezone)
- Breaks if user logs sugar consumption or relapse
-
Streak Display:
- Format: "2 DAYS 15 HRS 30 MIN" (for shorter streaks)
- Format: "3 WEEKS 2 DAYS 4 HRS" (for longer streaks)
- Updates in real-time on home screen
Streak Features:
Edit Streak (home_edit_streak.dart):
- Users can manually adjust streak if needed
- Useful for correcting errors or setting custom start date
- Requires confirmation to prevent accidental changes
Streak Sharing (sharing_service.dart):
- Share to Social Media:
- Creates shareable image with streak count
- Includes motivational message
- Can share to Instagram, Facebook, Twitter, etc.
- Helps users celebrate milestones publicly
Achievements (achievements_service.dart):
-
Unlockable Milestones:
- 1 day streak
- 3 days streak
- 1 week streak
- 2 weeks streak
- 1 month streak
- 3 months streak
- 6 months streak
- 1 year streak
- And more...
-
Achievement Screen (
home_achievements.dart):- View all unlocked achievements
- See progress toward next achievement
- Visual badges and rewards
App Open Streak (app_open_streak_service.dart):
- Tracks consecutive days user opens the app
- Separate from sugar-free streak
- Encourages daily engagement
- May show notifications if user hasn't opened app
Streak Invites (accept_invite_page.dart):
- Users can invite friends to track streaks together
- Creates shared streak challenge
- Both users see each other's progress
Streak Widgets (iOS/Android):
- Home Screen Widgets:
- Shows current streak on device home screen
- Updates automatically
- Available for iOS and Android
- Multiple widget sizes supported
Relapse Handling:
- If user logs sugar consumption, streak breaks
- Relapse Flow (
relapsed_flow/):- Why Screen (
relapse_why_screen.dart): Asks why they relapsed - Help Worse Screen (
relapse_help_worse_screen.dart): Offers support - Target Days Screen (
relapse_target_days_screen.dart): Sets new goal - Signature Screen (
relapse_signature_screen.dart): Commits to restart
- Why Screen (
- After relapse, streak resets to 0
- User can start new streak immediately
Overview: Mindfulness features to help users manage stress, cravings, and emotional triggers through meditation and breathing exercises.
Meditation Screen (meditation_screen.dart):
Features:
-
Guided Meditation Sessions:
- Pre-recorded meditation audio
- Various lengths (5 min, 10 min, 15 min, 20 min)
- Different themes (stress relief, sleep, focus, etc.)
- Background music and guided voice
-
Meditation Library:
- Browse available meditations
- Filter by duration, theme, or type
- Track which meditations user has completed
-
Playback Controls:
- Play/pause
- Skip forward/backward
- Volume control
- Progress indicator
- Timer showing time remaining
Breathing Exercises (breathing_exercise_screen.dart, breathing_animation_screen.dart):
Features:
-
Guided Breathing Animations:
- Visual breathing guide (circle expands/contracts)
- Follows breathing pattern (inhale, hold, exhale)
- Various techniques:
- 4-7-8 breathing (4 sec inhale, 7 sec hold, 8 sec exhale)
- Box breathing (4-4-4-4)
- Deep breathing
- Calming breath
-
Panic Button Breathing (
breathing_animation_screen.dart):- Special breathing exercise accessible from panic button
- Designed for moments of high stress/craving
- Quick 2-3 minute sessions
- Calming visuals and sounds
Audio Library (audio_library_screen.dart):
Features:
-
Collection of Audio Files:
- Meditation tracks
- Nature sounds
- White noise
- Binaural beats
- Relaxation music
-
Audio Player (
audio_player_screen.dart):- Full-featured audio player
- Playlist support
- Shuffle and repeat modes
- Background playback (continues when app is minimized)
- Lock screen controls
Integration:
- Accessible from home screen menu
- Part of panic button flow (breathing exercises)
- Can be scheduled via notifications
- Tracks usage for analytics
Overview: Browseable collection of healthy, sugar-free recipes to help users find meal alternatives.
How It Works:
Recipe List Screen (recipes_list_screen.dart):
Features:
-
Browse Recipes: Scrollable list of recipes
-
Filters:
- Calorie Range: Filter by calories (e.g., 0-300, 300-600, 600+)
- Diet Type:
- Vegetarian
- Vegan
- Gluten-free
- Keto
- Low-carb
- etc.
- Meal Type: Breakfast, Lunch, Dinner, Snack, Dessert
-
Search: Search recipes by name or ingredients
-
Favorites: Users can favorite recipes for quick access
Recipe Details Screen (recipe_detail_screen.dart):
Information Displayed:
- Recipe Name: Title of the recipe
- Image: High-quality food photo
- Nutrition Info:
- Calories per serving
- Protein, carbs, fats
- Sugar content (highlighted)
- Fiber, sodium, etc.
- Ingredients List:
- All ingredients with quantities
- Can adjust serving size (ingredients scale automatically)
- Instructions:
- Step-by-step cooking instructions
- Numbered steps
- Clear directions
- Serving Size: Number of servings recipe makes
- Prep Time: How long to prepare
- Cook Time: How long to cook
- Total Time: Combined prep + cook time
Recipe Repository (recipe_repository.dart):
API Integration:
-
Primary: Edamam Recipe API
- Searches Edamam database
- Filters for sugar-free/low-sugar recipes
- Returns detailed nutrition and recipe data
-
Fallback: Spoonacular Recipe API
- Used when Edamam fails: If Edamam API call fails or times out, automatically retries with Spoonacular
- Ensures recipe search always works
- Same functionality as Edamam
Recipe Favorites (recipe_favorites_repository.dart):
- Users can save favorite recipes
- Stored in Firestore
- Quick access from favorites screen (
recipes_favorites_screen.dart) - Syncs across devices
Widgets:
- Recipe Card (
recipe_card.dart): Displays recipe preview in list - Calorie Range Card (
calorie_range_card.dart): Filter widget for calories - Diet Filter Chip (
diet_filter_chip.dart): Filter widget for diet types - Meal Type Chip (
meal_type_chip.dart): Filter widget for meal types - Nutrition Info Card (
nutrition_info_card.dart): Shows nutrition breakdown
Overview: Intermittent fasting tracker to help users manage eating windows and track fasting periods.
How It Works:
Setup Screen (fasting_setup_screen.dart):
- First-time users see setup instructions
- Explains intermittent fasting concepts
- Guides users on how to use the feature
Main Dashboard (fasting_dashboard_screen.dart):
Features:
-
Active Fast Display:
- Shows current fasting period if one is active
- Real-time timer counting up
- Progress ring showing completion percentage
- Target duration (e.g., 16 hours, 12 hours)
-
Start Fast Button:
- User taps to start a new fast
- Can set target duration (defaults to common intervals like 12h, 16h, 18h, 24h)
- Sets start time to current moment
-
Recent Fasts List:
- Shows last 7 days of fasting activity
- Each entry shows:
- Start and end times
- Duration achieved
- Status (completed, active, cancelled)
- Visual indicators for completed vs cancelled
-
Statistics:
- Current streak (consecutive days with completed fasts)
- Longest fast duration
- Fasts this week/month
- Average fast duration
Fast Management:
- Start Fast: Creates new fast log with start time
- End Fast: User manually ends fast (or it auto-ends at target time)
- Cancel Fast: User can cancel an active fast
- Edit Fast: Can adjust start/end times for past fasts
Notifications:
- Motivational notifications scheduled at:
- 4 hours before fast ends
- 2 hours before fast ends
- When fast completes
- Helps users stay motivated during fasting periods
Data Storage:
- Fast logs stored locally in SharedPreferences (not synced to Firestore)
- Each fast log contains:
- Start time
- End time (if completed)
- Target duration
- Actual duration
- Status (active, completed, cancelled)
- Milestone minutes (e.g., 12 hours = 720 minutes)
Visual Progress:
- Progress ring shows how far through current fast
- Color changes as progress increases
- Milestone markers (e.g., 12-hour mark)
Integration:
- Links to calorie tracker (fasting periods affect net calories)
- May affect streak if user breaks fast early with sugar consumption
Overview: User settings, profile management, and account features.
Profile Tab (Bottom Navigation Index 4):
Main Sections:
Profile Information (user_profile_details.dart):
- Edit Profile:
- Name
- Age
- Gender
- Profile photo (optional)
- Account Settings:
- Change password
- Delete account option
Notifications Settings (user_profile_notifications.dart):
- Push Notification Controls:
- Daily check-in reminders
- Pledge check-in notifications
- Streak milestone alerts
- Motivational messages
- Meal reminders
- Fasting notifications
- Community updates
- Notification Timing: Set preferred times for notifications
- Do Not Disturb: Quiet hours settings
Subscription Management (cancel_subscription_screen.dart):
- View Subscription Status:
- Current plan (monthly, annual)
- Renewal date
- Subscription status (active, cancelled, expired)
- Manage Subscription:
- Cancel subscription (via RevenueCat)
- Change plan
- Restore purchases
- Billing History: View past transactions
Language Selection (language_selection_screen.dart):
- Change app language
- Available languages: English, French, Spanish, German, Italian, Russian, Czech, Slovak, Chinese, Polish
- Changes take effect immediately
- Affects all app text and content
More Options (more_screen.dart):
- Help & Support (
user_profile_support.dart):- Contact support (opens Crisp chat)
- FAQ
- Help articles
- Give Feedback (
give_feedback.dart):- Rate the app
- Submit feedback
- Report bugs
- About: App version, terms, privacy policy
Progress Card (progress_card.dart):
- Statistics Display:
- Total days sugar-free (all-time)
- Current streak
- Longest streak
- Achievements unlocked
- Challenges completed
- Visual Progress: Charts and graphs showing progress over time
Journal (add_journal_entry.dart, journal_feelings.dart):
- Journal Entries:
- Users can write personal reflections
- Track emotions and feelings
- Record thoughts about their journey
- Timestamped entries
- Feelings Journal:
- Quick mood logging
- Emotional state tracking
- Patterns over time
Achievements (home_achievements.dart):
- View all unlocked achievements
- Progress toward next achievement
- Achievement categories:
- Streak milestones
- Challenge completions
- Feature usage milestones
- Community participation
Meal Notifications (meal_notifications_screen.dart):
- Configure meal reminder notifications
- Set meal times (breakfast, lunch, dinner)
- Reminders to log meals in calorie tracker
The notification system (lib/core/notifications/notification_service.dart) implements sophisticated logic to prevent spam, personalize messages, and respect user preferences. This section documents all the "smart" features that aren't immediately obvious.
Daily Notification Limit:
- Maximum: 3 notifications per day (
maxNotificationsPerDay = 3) - Minimum Spacing: 180 minutes (3 hours) between notifications (
minMinutesBetweenNotifications = 180) - Purpose: Prevents notification fatigue and ensures users aren't overwhelmed
How It Works:
- When scheduling a notification, the system checks:
- How many notifications have been scheduled today
- When the last notification was scheduled
- If the desired time is too close to the last notification, it automatically adjusts the time forward
- If the adjusted time would cross into the next day, the notification is skipped entirely
- Timezone Handling: Automatically resets counters if timezone offset changes (DST or manual changes)
Exceptions (bypass daily cap):
- Fasting Motivational Notifications: 4h, 2h, and completion reminders bypass the cap (high-priority)
- App Update Notifications: Scheduled separately at 9:12 PM, bypass cap
- Accountability Partner Notifications: Immediate push notifications (server-sent), outside daily cap
Audience Types:
- Subscribers: Get personalized, varied messages with day-based randomization
- Non-Subscribers: Get default messages encouraging subscription
- Trial Users: Get NO notifications at all during trial period
How It Works:
- Before scheduling, the system checks subscription status via RevenueCat
- Different message pools are used based on subscription status
- Subscribers get rotating messages (different each day), non-subscribers get static conversion-focused messages
RevenueCat & Superwall Integration:
- Notifications wait for RevenueCat to be "ready" before scheduling
- If RevenueCat isn't ready during onboarding, scheduling is deferred and retried once when ready
- Prevents misclassifying trial users as subscribers
One-Shot Retry Logic:
- If scheduling fails due to RevenueCat not being ready, a flag is set
- When RevenueCat becomes ready, a one-time retry is executed
- Ensures notifications aren't lost due to race conditions
Smart Caching:
- Localized notification strings are cached per language
- Cache is invalidated when language changes
- Falls back to English if translation file is missing or corrupted
Language Detection:
- Reads app language from SharedPreferences (
app_language_code) - Loads appropriate JSON file from
assets/l10n/{languageCode}.json - All notification titles and bodies are translated based on user's language preference
Text Sanitization:
- All notification strings are sanitized before display to handle special characters and encoding issues
Name Prefixing:
- If user's first name is available, notifications are prefixed with localized greeting (e.g., "Hey [Name], ...")
- Name is fetched from Firestore
userscollectionfirstNamefield, or falls back to AuthdisplayName - Falls back gracefully if name isn't available
Day-Based Randomization:
- Uses day-of-year as seed for random selection
- Ensures same message isn't shown twice on the same day
- Different users see different messages on the same day (true randomization)
Android Notification Channels (allows users to mute specific types):
checkup_reminders_channel- Daily pledge check-insstreak_goals_channel- Streak updates and achievementsmorning_motivation_channel- Morning motivational messagesapp_update_channel- App update announcementschat_notifications_channel- Community chat messagesmarketing_offers_channel- Special offers and dealstrial_offer_channel- Trial offer notificationstime_sensitive_channel- Important reminders (max importance)meal_calorie_tracking_channel- Food scan completion notificationslunch_reminder_channel- Lunch meal remindersdinner_reminder_channel- Dinner meal remindersrelapse_challenge_channel- Relapse challenge daily notifications
Notification IDs (prevents duplicates):
- Each notification type has a unique ID
- Canceling and rescheduling uses the same ID to replace existing notifications
Multi-Platform Support:
- iOS: Requests alert, badge, and sound permissions separately
- Android 13+: Uses new notification permission API
- Android <13: Permissions granted automatically
Permission State Tracking:
- Checks system-level permissions before scheduling
- If system permissions are disabled, in-app toggles are automatically disabled
- Shows warning UI if user enables in-app but system permissions are off
FCM Token Management:
- FCM tokens are saved to Firestore
users/{uid}/fcmTokenfor server-sent push notifications - Token refresh is handled automatically
- Tokens are registered after permissions are granted (onboarding, settings, accountability partner)
Configurable Times:
- Morning Motivation: User-configurable time (default: 7:35 AM)
- Checkup Reminders: User-configurable time (default: 7:23 PM)
- Meal Reminders: User-configurable breakfast, lunch, dinner times
Timezone Handling:
- Uses
flutter_timezoneto detect user's local timezone - All scheduled times are converted to user's local timezone
- Handles DST changes automatically
Recurring Notifications:
- Uses
matchDateTimeComponentsto schedule daily recurring notifications - Automatically schedules for next day if time has already passed today
Session Tracking:
- Prevents duplicate permission requests during onboarding
- Tracks if permissions were already requested in current session
- Only schedules notifications once per onboarding flow
Deferred Scheduling:
- If RevenueCat isn't ready during onboarding, scheduling is deferred
- One-shot retry executes when RevenueCat becomes ready
- Prevents trial users from receiving subscriber notifications
Payload System:
- Each notification includes a payload string identifying its type
- Payloads trigger specific navigation when tapped
- Non-paying users who tap notifications are redirected to paywall if they haven't subscribed
Navigation Handling:
- Payloads are stored in SharedPreferences for later processing
- Handles app state (foreground, background, terminated)
- Safely navigates using microtasks to avoid UI conflicts
Mixpanel Tracking:
- All notification scheduling is tracked with Mixpanel
- Tracks notification taps with type and ID
- Includes audience type, scheduled time, and title in events
- Framework: Flutter 3.7.0+
- Language: Dart 3.7.0+
- State Management: BLoC (flutter_bloc) with Freezed for immutable states
- Backend: Firebase (Auth, Firestore, Storage, Analytics, Messaging, Functions)
- Navigation: GoRouter (via app/router.dart)
- Localization: flutter_localizations with JSON-based translations
- Superwall: Paywall and subscription management
- RevenueCat: Cross-platform subscription handling
- Mixpanel: Analytics and user behavior tracking
- Crisp: Customer support chat
- OpenAI: Food recognition, chatbot, content generation
- AppsFlyer: Attribution and marketing analytics
- Facebook App Events: Meta/Facebook ad attribution and ROAS tracking (measures which Facebook ads drive subscriptions)
- Mux: Video hosting and streaming
- SendGrid: Email delivery
- Groq: AI inference (alternative to OpenAI)
- Replicate: AI model hosting
- Edamam: Nutrition database API
- Spoonacular: Recipe API
- Gemini: Google AI API
- Feature-Based Structure: Code organized by feature modules
- Clean Architecture: Separation of data, domain, and presentation layers
- Repository Pattern: Data access abstraction
- Service Layer: Business logic and external service integration
- Cubit/Bloc: State management for UI
- Freezed: Immutable data models with code generation
lib/
├── app/ # App-level configuration
│ ├── app.dart # Main app widget
│ ├── router.dart # Navigation configuration
│ └── theme/ # App theming
├── core/ # Core functionality shared across features
│ ├── accountability/ # Accountability partner services
│ ├── analytics/ # Analytics services (Mixpanel, AppsFlyer)
│ ├── api_rate_limit/ # API rate limiting
│ ├── auth/ # Authentication (Firebase Auth)
│ ├── challenge/ # 28-day challenge logic
│ ├── chat/ # Crisp chat integration
│ ├── config/ # Environment configuration
│ ├── installation/ # Installation tracking
│ ├── journal/ # Journal service
│ ├── karma/ # Karma system
│ ├── localization/ # App localization
│ ├── models/ # Shared data models
│ ├── navigation/ # Navigation utilities
│ ├── notifications/ # Push notifications
│ ├── pledges/ # Daily pledge system
│ ├── pmf_survey/ # Product-market-fit surveys
│ ├── quick_actions/ # App icon quick actions
│ ├── relapse/ # Relapse tracking
│ ├── repositories/ # Shared repositories
│ ├── services/ # Core services
│ ├── streak/ # Streak tracking
│ ├── subscription/ # Subscription management
│ ├── superwall/ # Superwall integration
│ ├── tree/ # Tree of life feature
│ ├── usage/ # Feature quota management
│ ├── user/ # User attributes
│ └── utils/ # Utility functions
├── features/ # Feature modules
│ ├── accountability/ # Accountability partners
│ ├── app/ # Main app screens (home, profile, etc.)
│ ├── auth/ # Authentication screens
│ ├── community/ # Community features
│ ├── fasting/ # Fasting tracking
│ ├── learn/ # Learn section (videos, articles)
│ ├── nutrition/ # Nutrition tracking
│ ├── onboarding/ # Onboarding flow
│ ├── panic/ # Panic button
│ ├── recipes/ # Recipe features
│ ├── streak/ # Streak features
│ └── welcome/ # Welcome screen
├── firebase_options.dart # Firebase configuration (generated)
├── main.dart # App entry point
└── permissions/ # Permission handling
assets/
├── articles/ # Markdown articles by language
├── changelog/ # App changelog files
├── fonts/ # Custom fonts (Elza Round)
├── images/ # App images and assets
├── l10n/ # Localization JSON files
├── sounds/ # Audio files
├── subtitles/ # Video subtitles
└── videos/ # Video assets
tools/ # Development and deployment tools
├── apple-store-connect/ # App Store Connect scripts
├── firebase_cloud/ # Firebase Functions and scripts
├── l10n/ # Localization tools
├── learn_articles/ # Article management scripts
├── learn_videos_subtitles/ # Subtitle processing
└── user_management/ # User management scripts
android/ # Android-specific files
├── app/
│ ├── google-services.json # Firebase config (needs generation)
│ └── build.gradle.kts # Android build configuration
└── key.properties # Signing keys (needs creation)
ios/ # iOS-specific files
├── Runner/
│ ├── GoogleService-Info.plist # Firebase config (needs generation)
│ └── Info.plist
└── exportOptions.plist # App Store export options
Quick Start Summary (for experienced developers):
- Clone repo →
flutter pub get→cd ios && pod install - Copy all
.localtemplate files and fill in your values (see step 5 below) - Generate
firebase_options.dartviaflutterfire configure - Update Team ID in Xcode (step 5i)
- Run
make reset-iosthenmake flutter-run-iphonex
Full Requirements - Before setting up the project, ensure you have:
-
Flutter SDK (3.7.0 or higher)
flutter --version
-
Dart SDK (included with Flutter)
-
Firebase CLI
npm install -g firebase-tools firebase login
-
FlutterFire CLI
dart pub global activate flutterfire_cli
-
Xcode (for iOS development on macOS)
- Xcode 14.0 or higher
- CocoaPods:
sudo gem install cocoapods
-
Android Studio (for Android development)
- Android SDK
- Android SDK Platform 35
- Android SDK Build-Tools 35.0.1
- Android Emulator: Install via Android Studio
- Open Android Studio → Tools → Device Manager → Create Device
- Recommended: Pixel 4 or Pixel 6 Pro (similar size to iPhone XS)
- Or via command line:
sdkmanager "system-images;android-35;google_apis;x86_64"then create AVD
-
Node.js (for Firebase Functions and tools)
node --version # Should be 18.x or higher -
Git for version control
git clone <repository-url>
cd stoppr_app_publicflutter pub getcd ios
pod install
cd ..The project uses code generation for Freezed models and JSON serialization:
flutter pub run build_runner build --delete-conflicting-outputs.local template files for files containing private information. You MUST copy these templates and fill in your own values before the app will work.
Quick Checklist - Copy these .local files:
.env.local→.envios/Runner/Info.plist.local→ios/Runner/Info.plistios/firebase_app_id_file.json.local→ios/firebase_app_id_file.jsonios/Runner/GoogleService-Info.plist.local→ios/Runner/GoogleService-Info.plistandroid/app/google-services.json.local→android/app/google-services.jsonandroid/app/src/main/res/values/strings.xml.local→android/app/src/main/res/values/strings.xmlios/exportOptions.plist.local→ios/exportOptions.plist(optional, only for release builds)
Detailed instructions for each file are below:
Copy the .env.local template and fill in your values:
cp .env.local .envEdit .env with your actual API keys and configuration (see Configuration Guide).
Copy the Info.plist.local template and fill in your values:
cp ios/Runner/Info.plist.local ios/Runner/Info.plistWhat to replace in Info.plist:
-
Google OAuth Client ID: Replace
YOUR_GOOGLE_CLIENT_IDwith your Google OAuth client ID- Where to find: Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client IDs
- Format:
123456789-abcdefghijklmnop.apps.googleusercontent.com - Also update: The URL scheme
com.googleusercontent.apps.YOUR_GOOGLE_CLIENT_IDinCFBundleURLTypes
-
Facebook App ID: Replace
YOUR_FACEBOOK_APP_IDwith your Facebook App ID- Where to find: Facebook Developers → Your App → Settings → Basic → App ID
- Also update: The URL scheme
fbYOUR_FACEBOOK_APP_IDinCFBundleURLTypes
-
Facebook Client Token: Replace
YOUR_FACEBOOK_CLIENT_TOKENwith your Facebook Client Token- Where to find: Facebook Developers → Your App → Settings → Advanced → Security → Client Token
⚠️ SENSITIVE: Keep this secret, never commit it to version control
-
AppsFlyer Dev Key: Replace
YOUR_APPSFLYER_DEV_KEYwith your AppsFlyer Dev Key- Where to find: AppsFlyer Dashboard → Your App → Settings → App Settings → Dev Key
⚠️ SENSITIVE: Keep this secret
-
AppsFlyer App ID: Replace
YOUR_APPSFLYER_APP_IDwith your AppsFlyer App ID- Where to find: AppsFlyer Dashboard → Your App → Settings → App Settings → App ID
Copy the Runner.entitlements.local template and fill in your values:
cp ios/Runner/Runner.entitlements.local ios/Runner/Runner.entitlementsWhat to replace in Runner.entitlements:
- Firebase Project ID in Associated Domains: Replace
YOUR_FIREBASE_PROJECT_IDwith your Firebase project ID- Location: Line 13:
applinks:YOUR_FIREBASE_PROJECT_ID.firebaseapp.com - Example: If your project ID is
my-app-12345, change it toapplinks:my-app-12345.firebaseapp.com - Where to find:
- Firebase Console → Project Settings → General → Project ID
- Or: The project ID is visible in the Firebase Console URL:
https://console.firebase.google.com/project/YOUR_PROJECT_ID
- Why needed: Required for Firebase Dynamic Links and deep linking to work properly
⚠️ IMPORTANT: This file is tracked in git, so make sure to use your own Firebase project ID
- Location: Line 13:
Note: The other values in this file need to be updated for your app:
- Replace
applinks:stoppr.appwith your app's domain (e.g.,applinks:yourapp.com) - Replace
group.com.stoppr.app.sharedwith your app group ID (e.g.,group.YOUR_BUNDLE_ID.shared)
Update the App Group identifier in ios/StreakWidgetExtension.entitlements:
- Replace
group.YOUR_BUNDLE_ID.sharedwith your actual app group ID (e.g.,group.com.yourcompany.yourapp.shared) - This must match the app group ID used in
Runner.entitlements
Copy the exportOptions.plist.local template and fill in your values:
cp ios/exportOptions.plist.local ios/exportOptions.plistWhat to replace in exportOptions.plist:
-
Apple Developer Team ID: Replace
YOUR_APPLE_DEVELOPER_TEAM_IDwith your Team ID- Where to find:
- Apple Developer Portal → Membership section → Team ID
- Or: Xcode → Preferences → Accounts → Select your Apple ID → Team ID column
⚠️ SENSITIVE: Keep this private
- Where to find:
-
Provisioning Profile Names: Replace the provisioning profile names with your own
- Main App: Replace
YOUR_MAIN_APP_PROVISIONING_PROFILE_NAMEwith your app's distribution provisioning profile name - Widget Extension: Replace
YOUR_WIDGET_PROVISIONING_PROFILE_NAMEwith your widget's distribution provisioning profile name - Where to find:
- Xcode → Preferences → Accounts → Select your Apple ID → Download Manual Profiles
- Or: Apple Developer Portal → Provisioning Profiles
- Profile names are usually like: "App Store Distribution" or "iOS Distribution - Your Company Name"
- Main App: Replace
Note: The exportOptions.plist file is only needed for building release/IPA files for App Store distribution. For development builds, you can skip this step initially.
Important: Firebase configuration files contain project-specific credentials and must be generated from your Firebase Console.
5d.1. Set Up Firebase App ID File
Copy the firebase_app_id_file.json.local template and fill in your values:
cp ios/firebase_app_id_file.json.local ios/firebase_app_id_file.jsonWhat to replace in firebase_app_id_file.json:
-
GOOGLE_APP_ID: Replace
YOUR_GOOGLE_APP_IDwith your Firebase iOS app's Google App ID- Where to find:
- Firebase Console → Your Project → Project Settings → Your iOS App → App ID
- Format:
1:PROJECT_NUMBER:ios:APP_ID(e.g.,1:123456789:ios:abcdef123456)
⚠️ SENSITIVE: Contains Firebase project-specific identifiers
- Where to find:
-
FIREBASE_PROJECT_ID: Replace
YOUR_FIREBASE_PROJECT_IDwith your Firebase project ID- Where to find:
- Firebase Console → Project Settings → General → Project ID
- Or: The project ID is visible in the Firebase Console URL:
https://console.firebase.google.com/project/YOUR_PROJECT_ID
- Where to find:
Note: This file is automatically generated by FlutterFire CLI when you run flutterfire configure. If you're using FlutterFire CLI, you can skip this manual step as it will be generated automatically.
5d.2. Set Up iOS Firebase Configuration (GoogleService-Info.plist)
Copy the GoogleService-Info.plist.local template and fill in your values:
cp ios/Runner/GoogleService-Info.plist.local ios/Runner/GoogleService-Info.plistWhat to replace in GoogleService-Info.plist:
- CLIENT_ID: Your Google OAuth Client ID (from Firebase Console → Your iOS App → OAuth clients)
- REVERSED_CLIENT_ID: Same as CLIENT_ID but reversed format
- API_KEY: Firebase API Key (from Firebase Console → Your iOS App → API Key)
- GCM_SENDER_ID: Firebase GCM Sender ID (usually your Project Number)
- BUNDLE_ID: Your app's bundle identifier (should match Xcode)
- PROJECT_ID: Your Firebase Project ID
- STORAGE_BUCKET: Your Firebase Storage Bucket (format:
PROJECT_ID.firebasestorage.app) - GOOGLE_APP_ID: Your Google App ID (format:
1:PROJECT_NUMBER:ios:APP_ID)
Where to find: All values are available in Firebase Console → Your Project → Project Settings → Your iOS App
5d.3. Set Up Android Firebase Configuration (google-services.json)
Copy the google-services.json.local template and fill in your values:
cp android/app/google-services.json.local android/app/google-services.jsonWhat to replace in google-services.json:
- project_number: Your Firebase Project Number
- project_id: Your Firebase Project ID
- storage_bucket: Your Firebase Storage Bucket
- mobilesdk_app_id: Your Android App ID (format:
1:PROJECT_NUMBER:android:APP_ID) - package_name: Your Android package name (should be
com.stoppr.sugar.app) - oauth_client: OAuth Client IDs (Android and Web)
- certificate_hash: SHA-1 certificate hash (for Android OAuth)
- api_key: Firebase API Key
Where to find: All values are available in Firebase Console → Your Project → Project Settings → Your Android App
Note: The easiest way to get these files is to download them directly from Firebase Console:
- iOS: Firebase Console → Your iOS App → Download
GoogleService-Info.plist - Android: Firebase Console → Your Android App → Download
google-services.json
The Products.storekit file is used for testing in-app purchases and subscriptions in the iOS Simulator without needing real App Store Connect products. This allows you to test subscription flows, purchases, and paywall functionality locally.
Why it's needed:
- Test in-app purchases without App Store Connect approval
- Test subscription flows (monthly, annual, trials) locally
- Test paywall functionality during development
- No need to create real products in App Store Connect for testing
How to generate it:
Option 1: Create via Xcode (Recommended):
- Open
ios/Runner.xcworkspacein Xcode - In Xcode, go to File → New → File...
- Select StoreKit Configuration File (under iOS → Other)
- Name it
Products.storekitand save it inios/Runner/directory - In the StoreKit Configuration editor:
- Click the + button to add products
- Add your subscription products:
- Product ID:
com.stoppr.app.monthly(Monthly Subscription) - Type: Auto-Renewable Subscription
- Price: $14.99
- Subscription Period: 1 Month
- Product ID:
- Add more products as needed (annual, lifetime, etc.)
- Configure subscription groups if needed
Option 2: Copy from template (if provided):
# If a Products.storekit.local template exists, copy it:
cp ios/Runner/Products.storekit.local ios/Runner/Products.storekit
# Then edit it in Xcode to match your product IDsProduct IDs used in this app (add these to your StoreKit config):
com.stoppr.app.monthly- Monthly subscription ($14.99/month)com.stoppr.app.annual- Annual subscription ($49.99/year)com.stoppr.app.annual.trial- Annual with 3-day trialcom.stoppr.app.annual80OFF- Annual with 80% discount ($19.99/year)com.stoppr.lifetime- Lifetime purchase ($79.99 one-time)com.stoppr.weekly_cheap.app- Weekly subscription (Youth pricing)com.stoppr.monthly_cheap.app- Monthly subscription (Youth pricing)com.stoppr.annual_cheap.app- Annual subscription (Youth pricing)- And more (see
ios/Runner/Products.storekitfor complete list)
How to use it:
- In Xcode, select your scheme (Runner)
- Go to Product → Scheme → Edit Scheme...
- Select Run → Options tab
- Under StoreKit Configuration, select
Products.storekit - Now when you run the app in the simulator, StoreKit will use this configuration for testing purchases
Note: The StoreKit configuration file is only used for local testing. For production builds, the app uses real products from App Store Connect configured via RevenueCat.
firebase.json contains project-specific configuration and is not committed to git. You need to create it from the template.
How to set up:
-
Copy the template file:
cp firebase.json.local firebase.json
-
Update
firebase.jsonwith your Firebase project details:- Replace
YOUR_PROJECT_IDwith your Firebase project ID - Replace
YOUR_IOS_APP_IDwith your iOS app ID from Firebase Console - Replace
YOUR_ANDROID_APP_IDwith your Android app ID from Firebase Console - Or regenerate via
firebase initif starting fresh
- Replace
Note: The .local file is a template. The actual firebase.json file is gitignored and should contain your project-specific configuration.
Several files contain the Firebase project ID (stoppr-f2d4a) which needs to be updated if you're using a different Firebase project:
Files that need updating:
-
firebase.json(see step 5f above):- Contains
projectId: "stoppr-f2d4a"and app IDs - Action: Update with your Firebase project ID and app IDs
- How: Edit the file directly or regenerate via
firebase init
- Contains
-
ios/Runner/Runner.entitlements(see step 5c above):- Contains associated domain:
applinks:YOUR_FIREBASE_PROJECT_ID.firebaseapp.com - Action: Replace
YOUR_FIREBASE_PROJECT_IDwith your Firebase project ID ⚠️ IMPORTANT: This file is tracked in git, so make sure to use your own Firebase project ID- How:
- Open
ios/Runner.xcworkspacein Xcode - Select Runner target → Signing & Capabilities → Associated Domains
- Update
applinks:YOUR_PROJECT_ID.firebaseapp.com
- Open
- Contains associated domain:
-
Makefile:- Contains
firebase use stoppr-f2d4aand cloud function URLs with project ID - Action: Replace
stoppr-f2d4awith your Firebase project ID in:- Line 543:
firebase use YOUR_PROJECT_ID - Line 553: Cloud function URL:
https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/stripeWebhook
- Line 543:
- How: Edit
Makefiledirectly and replace all occurrences ofstoppr-f2d4awith your project ID
- Contains
-
ios/Runner/GoogleService-Info.plist(if using template):- Contains
PROJECT_IDandSTORAGE_BUCKETwith project ID - Action: Already handled in step 5d (Firebase App ID File setup)
- Contains
-
android/app/google-services.json(if using template):- Contains
project_idandstorage_bucketwith project ID - Action: Already handled in step 5d (Firebase App ID File setup)
- Contains
Note: These files are configuration files that reference your Firebase project. They're less sensitive than API keys but should still be updated to match your Firebase project.
Copy the strings.xml.local template and fill in your values:
cp android/app/src/main/res/values/strings.xml.local android/app/src/main/res/values/strings.xmlWhat to replace in strings.xml:
- facebook_app_id: Replace
YOUR_FACEBOOK_APP_IDwith your Facebook App ID- Where to find: Facebook Developers → Your App → Settings → Basic → App ID
- facebook_client_token: Replace
YOUR_FACEBOOK_CLIENT_TOKENwith your Facebook Client Token- Where to find: Facebook Developers → Your App → Settings → Advanced → Security → Client Token
⚠️ SENSITIVE: Keep this secret, never commit it to version control
Note: If you have multiple language variants (values-cs, values-de, etc.), you'll need to copy the template to each directory and update the Facebook credentials in each file.
The app's localization files (assets/l10n/*.json) contain references to the App Store ID in accountability partner invitation messages. You need to replace the placeholder with your actual App Store ID:
What to replace:
- Search for
INSERT_YOUR_APP_STORE_ID_HEREin allassets/l10n/*.jsonfiles - Replace with your actual App Store ID (e.g.,
6742406521) - Where to find: Your App Store Connect → App Information → Apple ID
Files to update:
assets/l10n/en.jsonassets/l10n/fr.jsonassets/l10n/es.jsonassets/l10n/de.jsonassets/l10n/it.jsonassets/l10n/pl.jsonassets/l10n/cs.jsonassets/l10n/ru.jsonassets/l10n/sk.jsonassets/l10n/zh.json
Quick command (if you have your App Store ID):
find assets/l10n -name "*.json" -type f -exec sed -i '' 's/INSERT_YOUR_APP_STORE_ID_HERE/YOUR_ACTUAL_APP_STORE_ID/g' {} \;INSERT_YOUR_APP_STORE_ID_HERE instead of a real App Store ID. This appears in accountability partner invitation messages that include download links.
After setting up Info.plist and exportOptions.plist, you also need to update the Team ID in the Xcode project:
- Open
ios/Runner.xcworkspacein Xcode - Select the Runner project in the navigator (top item)
- Select the Runner target
- Go to the Signing & Capabilities tab
- Under Team, select your Apple Developer Team (or add your Apple ID if not already added)
- Xcode will automatically update the Team ID (
DEVELOPMENT_TEAM) inproject.pbxproj
Alternative: If you prefer to update manually, search for INSERT_YOUR_APPLE_DEVELOPER_TEAM_ID_HERE in ios/Runner.xcodeproj/project.pbxproj and replace all occurrences with your Team ID.
INSERT_YOUR_APPLE_DEVELOPER_TEAM_ID_HERE instead of a real Team ID. You must replace this with your own Apple Developer Team ID.
Note: Most Firebase configuration is handled in step 5d above. This section covers additional Firebase setup.
- Create a Firebase project at Firebase Console
- Add iOS and Android apps to your Firebase project
- Follow the Config Files Generation section to generate required Firebase files
Before running the app, ensure you have completed all required setup steps:
Required Setup:
- ✅ Copied
.env.local→.envand filled in all API keys - ✅ Copied
ios/Runner/Info.plist.local→ios/Runner/Info.plistand filled in values - ✅ Copied
ios/firebase_app_id_file.json.local→ios/firebase_app_id_file.json(or generated via FlutterFire CLI) - ✅ Copied
ios/Runner/GoogleService-Info.plist.local→ios/Runner/GoogleService-Info.plistand filled in values - ✅ Copied
android/app/google-services.json.local→android/app/google-services.jsonand filled in values - ✅ Copied
android/app/src/main/res/values/strings.xml.local→android/app/src/main/res/values/strings.xmland filled in Facebook credentials - ✅ Generated
lib/firebase_options.dartvia FlutterFire CLI - ✅ Updated Team ID in Xcode project settings (step 5i) - replaced
INSERT_YOUR_APPLE_DEVELOPER_TEAM_ID_HEREplaceholder - ✅ Updated Firebase project ID in
firebase.jsonandios/Runner/Runner.entitlements(step 5f) - ✅ Updated App Store ID in localization files (step 5h.1) - replaced
INSERT_YOUR_APP_STORE_ID_HEREplaceholder - ✅ Updated Google OAuth Client IDs in
.envorauth_service.dart(replaced placeholder fallbacks) - ✅ Installed iOS Simulator (comes with Xcode, verify in Xcode → Preferences → Components)
Optional but Recommended:
- Created
ios/Runner/Products.storekitfor testing purchases (step 5e) - Copied
ios/exportOptions.plist.local→ios/exportOptions.plist(only needed for release builds) - Set up Android Emulator (via Android Studio, for Android testing)
-
Update Team ID (if not done in step 5i):
- Open
ios/Runner.xcworkspacein Xcode - Select the Runner project → Runner target → Signing & Capabilities tab
- Select your Apple Developer Team under Team
- Xcode will automatically update the Team ID in the project
- Open
-
Verify Bundle Identifier:
- Ensure bundle identifier (
com.stoppr.app) matches your Firebase configuration - If using a different bundle ID, update it in:
- Xcode: Runner target → General → Bundle Identifier
- Firebase Console: iOS app settings
Info.plist:CFBundleIdentifier(if hardcoded)
- Ensure bundle identifier (
-
Verify Signing:
- Ensure "Automatically manage signing" is enabled (recommended for development)
- Or use manual signing with your provisioning profiles (required for distribution)
-
Set Up StoreKit Configuration (for testing purchases):
- In Xcode, select your scheme (Runner)
- Go to Product → Scheme → Edit Scheme...
- Select Run → Options tab
- Under StoreKit Configuration, select
Products.storekit(if you created it in step 5e)
iOS Simulator comes with Xcode - no separate installation needed!
To verify simulators are installed:
- Open Xcode
- Go to Xcode → Preferences → Components (or Settings → Platforms in newer Xcode)
- Check that iOS simulators are listed and downloaded
To install additional iOS versions:
- In Xcode → Preferences → Components
- Click the + button next to desired iOS version
- Wait for download to complete
Or via command line:
# List available simulators
xcrun simctl list devices
# Download a specific iOS platform (if needed)
xcodebuild -downloadPlatform iOSRecommended simulators for testing:
- iPhone XS (matches iPhone X size) - Use
make flutter-run-iphonex - iPhone 15 Pro - Use
make flutter-run-iphone15-pro - iPhone 16 - Use
make flutter-run-iphone16
Install via Android Studio:
- Open Android Studio
- Go to Tools → Device Manager
- Click Create Device
- Select a device (recommended: Pixel 4 or Pixel 6 Pro - similar size to iPhone XS)
- Select a system image (recommended: API 35 or latest)
- Click Finish to create the emulator
Or verify existing emulators:
# List available emulators (if Android SDK is installed)
$ANDROID_SDK_ROOT/emulator/emulator -list-avdsRecommended Android emulators:
- Pixel 4 (similar size to iPhone XS) - Use
make flutter-run-android-xs - Pixel 6 Pro - Use
make flutter-run-android-pixel6pro
Note: Android emulator setup is optional if you're only testing on iOS. The app works on both platforms, but iOS testing is sufficient for most development.
- Open the project in Android Studio
- Ensure
package_nameinandroid/app/build.gradle.ktsmatches Firebase configuration - Set up signing keys (see Config Files Generation)
The app uses environment variables stored in .env file (which should be in .gitignore). See .env.local for a template with all required variables.
Superwall (Paywall & Subscription Management)
SUPERWALL_IOS_API_KEY: Superwall public API key for iOSSUPERWALL_ANDROID_API_KEY: Superwall public API key for Android- Why needed: Superwall is the paywall platform that displays subscription options to users. It handles the entire paywall presentation, A/B testing, and conversion tracking.
- What the app does:
- Shows paywalls at strategic points (after onboarding, when accessing premium features)
- Tracks which paywall designs convert best
- Manages subscription status and unlocks premium features
- Handles paywall dismissals and purchases
- Where it's used:
lib/features/onboarding/presentation/screens/pre_paywall.dart(main paywall),give_us_ratings_screen.dart(soft paywall), and throughout the app for feature-gated content - Get from: Superwall Dashboard
The codebase contains placeholder strings for Superwall placement IDs (e.g., INSERT_YOUR_STANDARD_PAYWALL_PLACEMENT_ID_HERE). You MUST replace these with your own placement IDs from your Superwall dashboard before the app will work correctly.
Required Placement IDs (search the codebase for these placeholders):
INSERT_YOUR_STANDARD_PAYWALL_PLACEMENT_ID_HERE- Main paywall shown after onboarding and for premium featuresINSERT_YOUR_GIFT_STEP_1_PLACEMENT_ID_HERE- Gift paywall variant (step 1)INSERT_YOUR_GIFT_STEP_2_PLACEMENT_ID_HERE- Gift paywall variant (step 2)INSERT_YOUR_SOFT_PAYWALL_PLACEMENT_ID_HERE- Soft paywall shown during onboardingINSERT_YOUR_X_TAP_PLACEMENT_ID_HERE- Paywall triggered when user taps X to dismissINSERT_YOUR_NOTIFICATION_TRIAL_PLACEMENT_ID_HERE- Paywall triggered from notification trial payloadINSERT_YOUR_REDOWNLOAD_80_OFF_PLACEMENT_ID_HERE- Paywall for redownload feedback flowINSERT_YOUR_QUICK_ACTIONS_80OFF_PLACEMENT_ID_HERE- Paywall for quick actions
How to set up Superwall Placements:
-
Create placements in Superwall Dashboard:
- Go to Superwall Dashboard → Your Project → Placements
- Create placements with the names you want (e.g.,
standard_paywall,gift_step_1, etc.) - Configure each placement with your paywall designs and A/B tests
-
Replace placeholders in code:
- Search the codebase for
INSERT_YOUR_*_PLACEMENT_ID_HERE - Replace each placeholder with your actual placement ID from Superwall
- Example: Replace
INSERT_YOUR_STANDARD_PAYWALL_PLACEMENT_ID_HEREwithstandard_paywall(or whatever you named it in Superwall)
- Search the codebase for
-
Files that contain placement IDs (search and replace in these files):
lib/features/onboarding/presentation/screens/pre_paywall.dartlib/main.dartlib/features/app/presentation/screens/home_screen.dartlib/features/app/presentation/screens/chatbot/chatbot_screen.dartlib/features/onboarding/presentation/screens/give_us_ratings_screen.dartlib/features/onboarding/presentation/screens/redownload_feedback_screen.dartlib/core/quick_actions/quick_actions_service.dartlib/features/app/presentation/screens/food_scan/food_scan_screen.dartlib/features/app/presentation/screens/rate_my_plate/rate_my_plate_scan_screen.dartlib/features/app/presentation/screens/food_scan/food_alternatives_screen.dartlib/features/learn/presentation/screens/learn_video_list_screen.dartlib/core/analytics/superwall_utils.dart
Note: If you don't have all these placements set up yet, you can:
- Use the same placement ID for multiple locations (e.g., use
standard_paywalleverywhere) - Create minimal placements first and expand later
- Comment out placement registrations you don't need yet
Without valid placement IDs, paywalls will not display correctly and the app may crash or show errors.
RevenueCat (Cross-Platform Subscription Backend)
REVENUECAT_IOS_API_KEY: RevenueCat public API key for iOSREVENUECAT_ANDROID_API_KEY: RevenueCat public API key for AndroidREVENUECAT_PRIVATE_API_KEY: RevenueCat private API key (for server-side)- Why needed: RevenueCat manages subscriptions across iOS and Android, handles receipt validation, and provides a unified subscription status API.
- What the app does:
- Validates subscription purchases with Apple/Google
- Syncs subscription status across devices
- Handles subscription renewals and cancellations
- Provides subscription status to Superwall for paywall logic
- Where it's used:
lib/core/subscription/subscription_service.dart- checks if user has active subscription - Get from: RevenueCat Dashboard
Mixpanel (User Analytics & Behavior Tracking)
MIXPANEL_API_KEY: Mixpanel project tokenMIXPANEL_USERNAME: Mixpanel service account usernameMIXPANEL_SECRET: Mixpanel service account secret- Why needed: Mixpanel tracks user behavior, events, and funnels to understand how users interact with the app.
- What the app does:
- Tracks screen views (e.g., "Onboarding Progress Card Creation Screen: Page Viewed")
- Tracks button taps (e.g., "Onboarding Progress Card Creation Screen: Button Tap")
- Tracks user actions (food scans, panic button usage, streak milestones)
- Creates funnels to analyze onboarding completion rates
- Tracks subscription conversion events
- Where it's used:
lib/core/analytics/mixpanel_service.dart- called throughout the app for event tracking - Get from: Mixpanel Settings
Facebook App Events (Meta Analytics & Ad Attribution)
FacebookAppID: Facebook App ID (configured inInfo.plistandstrings.xml)FacebookClientToken: Facebook Client Token (configured inInfo.plistandstrings.xml)- Why needed: Facebook App Events tracks user actions and purchases for Facebook/Meta ad attribution and ROAS (Return on Ad Spend) measurement. This allows the app to measure which Facebook ads are driving subscriptions and optimize ad spend.
- What the app does:
- Purchase Tracking: Tracks
fb_mobile_purchaseevents when users subscribe (for measuring ad ROI) - Subscription Events: Tracks
Subscribeevents when users start paid subscriptions - Trial Events: Tracks
StartTrialevents when users begin free trials (via Meta CAPI/Cloud Functions) - Checkout Initiation: Tracks
fb_mobile_initiated_checkoutwhen users tap subscribe buttons (to measure funnel conversion) - Trial Conversion: Tracks trial-to-paid conversions for ROAS calculation
- Ad Attribution: Helps Facebook attribute app installs and purchases to specific ad campaigns
- ROAS Optimization: Provides data to optimize Facebook ad campaigns and improve cost per acquisition
- Purchase Tracking: Tracks
- Where it's used:
lib/core/superwall/superwall_purchase_controller.dart- tracks purchase, subscription, and trial conversion eventslib/features/onboarding/presentation/screens/pre_paywall.dart- tracks checkout initiation events- Facebook SDK is initialized in
main.dartand configured viaInfo.plist(iOS) andstrings.xml(Android)
- Note: Facebook tracking is gated by gender (skips tracking for known male users) to optimize ad targeting. This is a business optimization, not a technical requirement.
- Get from: Facebook Developers Console → Your App → Settings → Basic (App ID) and Advanced → Security (Client Token)
Crisp (Customer Support Chat)
CRISP_WEBSITE_ID: Crisp website identifier- Why needed: Provides in-app customer support chat so users can get help without leaving the app.
- What the app does:
- Displays a chat widget in the app
- Allows users to message support directly
- Shows support availability status
- Can send automated messages or FAQs
- Where it's used:
lib/core/chat/crisp_service.dart- initialized in main.dart and accessible from profile/settings screens - Get from: Crisp Dashboard
OpenAI (AI-Powered Features)
OPENAI_API_KEY: OpenAI API key for food recognition and chatbot- Why needed: Powers two main AI features - food recognition from photos and the chatbot assistant.
- What the app does:
- Food Scanner: When users take a photo of food, OpenAI Vision API analyzes the image and identifies what food it is, then provides nutritional information and sugar content
- Chatbot: Provides AI-powered support and guidance when users need help or have questions about sugar addiction
- Content Generation: May be used for generating personalized content or responses
- Where it's used:
- Food scanning:
lib/features/app/presentation/screens/food_scan/food_scan_screen.dart - Chatbot:
lib/features/app/presentation/screens/chatbot/chatbot_screen.dart
- Food scanning:
- Get from: OpenAI API Keys
Firebase Configuration
FIREBASE_PROJECT_ID: Your Firebase project IDFIREBASE_ANDROID_API_KEY: Android app API key from FirebaseFIREBASE_ANDROID_APP_ID: Android app ID from FirebaseFIREBASE_IOS_API_KEY: iOS app API key from FirebaseFIREBASE_IOS_APP_ID: iOS app ID from FirebaseFIREBASE_IOS_CLIENT_ID: iOS OAuth client IDFIREBASE_MESSAGING_SENDER_ID: Firebase Cloud Messaging sender IDFIREBASE_STORAGE_BUCKET: Firebase Storage bucket name- Get from: Firebase Console → Project Settings
AppsFlyer (Marketing Attribution & Deep Linking)
APPSFLYER_APP_ID: AppsFlyer app IDAPPSFLYER_DEV_KEY: AppsFlyer developer keyAPPSFLYER_ONELINK_TEMPLATE: AppsFlyer OneLink template ID- Why needed: Tracks which marketing campaigns, ads, or referral sources brought users to the app. Essential for measuring marketing ROI.
- What the app does:
- Tracks user acquisition sources (Facebook ads, Google ads, referrals, etc.)
- Measures which campaigns convert best
- Enables deep linking (users clicking ads go directly to specific screens)
- Tracks user lifetime value by acquisition source
- Provides attribution data for marketing optimization
- Where it's used:
lib/core/analytics/appsflyer_service.dart- initialized at app startup - Get from: AppsFlyer Dashboard
Groq (Alternative AI Provider)
GROQ_API_KEY: Groq API key- Why needed: Alternative to OpenAI for AI features. Groq offers faster inference speeds and may be cheaper for certain use cases.
- What the app does: Can be used as a fallback or alternative to OpenAI for:
- Food recognition (if OpenAI is unavailable or too slow)
- Chatbot responses
- Content generation
- Where it's used: May be used in food scanning or chatbot features as an alternative to OpenAI
- Get from: Groq Console
Replicate (AI Model Hosting)
REPLICATE_API_TOKEN: Replicate API token- Why needed: Replicate hosts specialized AI models that might be used for image processing, food analysis, or other AI tasks.
- What the app does: Can be used for advanced image processing or specialized AI tasks beyond what OpenAI/Groq provide.
- Where it's used: May be used for advanced food analysis or image processing features
- Get from: Replicate Account
Edamam (Nutrition Database API)
EDAMAM_API_KEY: Edamam API keyEDAMAM_APP_ID: Edamam application ID- Why needed: Provides comprehensive nutrition database with detailed nutritional information for thousands of foods.
- What the app does:
- When users scan food or search for foods, Edamam provides:
- Calorie counts
- Macronutrients (protein, carbs, fats)
- Micronutrients (vitamins, minerals)
- Sugar content
- Allergen information
- Used in the nutrition tracking features to give users accurate nutritional data
- When users scan food or search for foods, Edamam provides:
- Where it's used:
lib/features/nutrition/data/repositories/nutrition_repository.dart- food database queries - Get from: Edamam Developer Portal
Spoonacular (Recipe API - Backup for Edamam)
SPOONACULAR_API_KEY: Spoonacular API key- Why needed: Provides access to a large database of recipes, including sugar-free and healthy alternatives. Serves as a backup/fallback for Edamam - if Edamam API fails or is unavailable, the app automatically falls back to Spoonacular to ensure users can still search for recipes.
- What the app does:
- Primary use: Fetches recipes for users looking for sugar-free alternatives when Edamam is unavailable
- Provides recipe details (ingredients, instructions, nutrition info)
- Filters recipes by dietary preferences (sugar-free, low-carb, etc.)
- Used in the recipes feature to help users find healthy meal options
- Fallback mechanism: If Edamam API call fails, the app automatically retries with Spoonacular to ensure recipe search always works
- Where it's used:
lib/features/recipes/data/repositories/recipe_repository.dart- recipe searches and details (fallback when Edamam fails) - Get from: Spoonacular Food API
Mux (Video Hosting & Streaming)
MUX_TOKEN_ID: Mux token IDMUX_SECRET_KEY: Mux secret key- Why needed: Mux hosts and streams the educational videos in the "Learn" section of the app.
- What the app does:
- Stores educational videos about sugar addiction
- Streams videos to users with adaptive bitrate (adjusts quality based on connection)
- Provides video analytics (watch time, completion rates)
- Generates video thumbnails
- Handles video subtitles/captions
- Where it's used:
lib/features/learn/presentation/screens/- video playback in learn section - Get from: Mux Dashboard
SendGrid (Email Delivery Service)
SENDGRID_API_KEY: SendGrid API key- Why needed: Sends transactional emails to users (welcome emails, password resets, important notifications).
- What the app does:
- Sends welcome emails when users sign up
- Sends password reset emails
- Sends important notifications (subscription confirmations, account updates)
- May send weekly progress reports or motivational emails
- Where it's used: Firebase Cloud Functions (
tools/firebase_cloud/functions/) - server-side email sending - Get from: SendGrid API Keys
Stripe (Payment Processing)
STRIPE_WEBHOOK_SECRET: Stripe webhook signing secretSTRIPE_WEBHOOK_URL: Stripe webhook endpoint URL (optional, defaults to Firebase function URL)- Why needed: Stripe webhooks notify the app when payment events occur (subscription created, payment succeeded, etc.). The webhook secret is used to verify that webhook requests are actually from Stripe.
- What the app does:
- Receives webhook events from Stripe about subscription status changes
- Updates user subscription status in Firestore
- Handles payment confirmations and failures
- Processes subscription cancellations and renewals
- Where it's used:
- Firebase Cloud Functions (
tools/firebase_cloud/functions/stripeWebhook) - webhook handler - Makefile
firebase-deploytarget - for deploying webhook configuration (requiresSTRIPE_WEBHOOK_SECRETenvironment variable)
- Firebase Cloud Functions (
⚠️ SENSITIVE: The webhook secret must be kept secure. Never commit it to version control.- Get from: Stripe Dashboard → Your webhook → Signing secret
- Usage: Set environment variable before deploying:
export STRIPE_WEBHOOK_SECRET=whsec_...then runmake firebase-deploy
This section explains how to generate all required configuration files for the app.
Why needed: Required for Firebase iOS SDK to connect to your Firebase project. Contains API keys, project ID, and app configuration.
How to generate:
- Go to Firebase Console
- Select your project (or create a new one)
- Click the iOS icon to add an iOS app
- Enter your iOS bundle ID (e.g.,
com.stoppr.app) - Download the
GoogleService-Info.plistfile - Place it in
ios/Runner/GoogleService-Info.plist - The file will be automatically included in your Xcode project
What it contains:
API_KEY: Firebase API key for iOSGCM_SENDER_ID: Cloud Messaging sender IDBUNDLE_ID: Your app's bundle identifierPROJECT_ID: Firebase project IDSTORAGE_BUCKET: Firebase Storage bucketCLIENT_ID: OAuth client ID for Google Sign-In
Why needed: Required for Firebase Android SDK to connect to your Firebase project. Contains API keys, project ID, and app configuration.
How to generate:
- Go to Firebase Console
- Select your project
- Click the Android icon to add an Android app
- Enter your Android package name (e.g.,
com.stoppr.sugar.app) - Enter your app's SHA-1 certificate fingerprint (for Google Sign-In)
- Get SHA-1:
keytool -list -v -keystore android/app/release/stoppr-release-key.keystore
- Get SHA-1:
- Download the
google-services.jsonfile - Place it in
android/app/google-services.json - The file will be automatically processed by the Google Services Gradle plugin
What it contains:
project_info: Project number, ID, and storage bucketclient: App configuration including API keys and OAuth clientsservices: Additional service configurations
Why needed: FlutterFire requires platform-specific Firebase options. This file is generated by FlutterFire CLI and contains Firebase configuration for both iOS and Android.
How to generate:
-
Install FlutterFire CLI (if not already installed):
dart pub global activate flutterfire_cli
-
Make sure you're logged into Firebase:
firebase login
-
Run FlutterFire configuration:
flutterfire configure
-
Select your Firebase project
-
Select platforms (iOS and Android)
-
The CLI will:
- Download
GoogleService-Info.plistfor iOS - Download
google-services.jsonfor Android - Generate
lib/firebase_options.dartwith platform-specific options
- Download
Note: The generated firebase_options.dart uses environment variables from .env file via EnvConfig class. Make sure your .env file has the correct Firebase values.
Why needed: Firebase CLI configuration file that defines project settings, deployment targets, and file paths for Firebase services (Firestore, Functions, Storage, Hosting, etc.). Contains project-specific IDs and configuration.
How to generate:
-
Option 1: Copy from template (recommended if using existing project):
cp firebase.json.local firebase.json
Then edit
firebase.jsonto replace placeholders:- Replace
YOUR_PROJECT_IDwith your Firebase project ID - Replace
YOUR_IOS_APP_IDwith your iOS app ID from Firebase Console - Replace
YOUR_ANDROID_APP_IDwith your Android app ID from Firebase Console
- Replace
-
Option 2: Generate via Firebase CLI (if starting fresh):
firebase init
Follow the prompts to configure:
- Select your Firebase project
- Choose services to configure (Firestore, Functions, Storage, Hosting, etc.)
- Configure each service as needed
What it contains:
flutter.platforms: iOS and Android app configuration with project IDs and app IDsfirestore: Rules and indexes file pathsfunctions: Cloud Functions source directory and runtime configurationstorage: Storage rules file pathhosting: Hosting configuration (if using Firebase Hosting)extensions: Firebase Extensions configuration
firebase.json.local template or generate it via firebase init.
-
Firebase Authentication
- Enable: Email/Password, Google Sign-In, Apple Sign-In (for iOS)
- Location: Firebase Console → Authentication → Sign-in method
- Why needed: User authentication and account management
- Configuration:
- Enable "Email/Password" provider
- Enable "Google" provider (requires OAuth client IDs from Google Cloud Console)
- Enable "Apple" provider (requires Apple Developer account setup)
- Configure authorized domains for OAuth redirects
-
Cloud Firestore
- Enable: Create Firestore database
- Location: Firebase Console → Firestore Database → Create database
- Why needed: Stores user data, questionnaire answers, streaks, food logs, etc.
- Configuration:
- Choose "Start in production mode" (you'll configure rules below)
- Select a region (choose closest to your users)
⚠️ CRITICAL: Configure security rules (see Firestore Rules section below)
-
Firebase Cloud Messaging (FCM)
- Enable: Cloud Messaging API
- Location: Firebase Console → Cloud Messaging
- Why needed: Push notifications for accountability partners and server-sent notifications
- Configuration:
- FCM is automatically enabled when you create a Firebase project
- No additional setup needed, but ensure Cloud Messaging API is enabled in Google Cloud Console
- iOS: Requires APNs certificate upload (see iOS setup below)
- Android: Works automatically with
google-services.json
-
Firebase Storage
- Enable: Cloud Storage
- Location: Firebase Console → Storage → Get started
- Why needed: Stores user-uploaded images (food photos, profile pictures, etc.)
- Configuration:
- Choose "Start in production mode"
- Select same region as Firestore
⚠️ CRITICAL: Configure security rules (see Storage Rules section below)
-
Firebase Analytics
- Enable: Google Analytics for Firebase
- Location: Firebase Console → Analytics → Get started
- Why needed: User behavior tracking and app performance metrics
- Configuration:
- Link to Google Analytics property (or create new one)
- Enable data collection for iOS and Android
-
Firebase Cloud Functions (Optional but Recommended)
- Enable: Cloud Functions
- Location: Firebase Console → Functions
- Why needed: Server-side logic (Stripe webhooks, email sending, etc.)
- Configuration:
- Requires Firebase CLI:
firebase init functions - Select Node.js runtime (18.x recommended)
- Deploy functions from
tools/firebase_cloud/functions/
- Requires Firebase CLI:
APNs Certificate for FCM:
- Go to Firebase Console → Project Settings → Cloud Messaging
- Under "Apple app configuration", upload your APNs certificate:
- Option 1: Upload APNs Authentication Key (.p8 file)
- Generate in Apple Developer Portal → Certificates, Identifiers & Profiles → Keys
- Download the .p8 file and upload to Firebase
- Option 2: Upload APNs Certificate (.p12 file)
- Export from Keychain Access
- Upload to Firebase
- Option 1: Upload APNs Authentication Key (.p8 file)
- This enables push notifications on iOS devices
iOS Capabilities:
- Ensure "Push Notifications" capability is enabled in Xcode
- Ensure "Background Modes" → "Remote notifications" is enabled
- Verify
ios/Runner/Runner.entitlementsincludesaps-environment
FCM Auto-Initialization:
- FCM is automatically initialized when
google-services.jsonis properly configured - No additional setup needed for Android 12 and below
- Android 13+ requires runtime permission request (handled by app)
Create firestore.rules file in your project root:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper function to check if user is authenticated
function isAuthenticated() {
return request.auth != null;
}
// Helper function to check if user owns the document
function isOwner(userId) {
return isAuthenticated() && request.auth.uid == userId;
}
// Users collection
match /users/{userId} {
// Users can read their own document
allow read: if isOwner(userId);
// Users can create/update their own document
allow create: if isOwner(userId);
allow update: if isOwner(userId) &&
// Prevent users from modifying critical fields
!request.resource.data.diff(resource.data).affectedKeys().hasAny(['createdAt', 'isDeleted']);
// Users can write their own FCM token
allow write: if isOwner(userId) &&
request.resource.data.keys().hasOnly(['fcmToken', 'fcmTokenUpdatedAt']);
}
// User subcollections
match /users/{userId}/favorite_recipes/{recipeId} {
allow read, write: if isOwner(userId);
}
// Food logs collection
match /food_logs/{logId} {
allow read: if isAuthenticated() && resource.data.userId == request.auth.uid;
allow create: if isAuthenticated() && request.resource.data.userId == request.auth.uid;
allow update, delete: if isAuthenticated() && resource.data.userId == request.auth.uid;
}
// Questionnaire answers (stored during onboarding)
match /questionnaire_answers/{answerId} {
allow read, write: if isAuthenticated() &&
(resource == null || resource.data.userId == request.auth.uid) &&
(request.resource == null || request.resource.data.userId == request.auth.uid);
}
// Redownload feedback collection
match /redownload_feedback/{feedbackId} {
allow create: if isAuthenticated();
allow read: if false; // Only admins can read (use Firebase Admin SDK)
}
// Transaction failures (for debugging)
match /transactions_fail/{transactionId} {
allow create: if isAuthenticated();
allow read: if false; // Only admins can read
}
// Security alerts (for fraud detection)
match /security_alerts/{alertId} {
allow create: if isAuthenticated();
allow read: if false; // Only admins can read
}
// Deny all other collections by default
match /{document=**} {
allow read, write: if false;
}
}
}Deploy Firestore Rules:
firebase deploy --only firestore:rules- These rules allow users to read/write their own data only
- Critical fields like
createdAtandisDeletedcannot be modified by users - Admin-only collections (like
security_alerts) require Firebase Admin SDK to read - Test your rules using Firebase Console → Firestore → Rules → Rules Playground
Create storage.rules file in your project root:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// User profile pictures
match /profile_pictures/{userId}/{fileName} {
allow read: if request.auth != null;
allow write: if request.auth != null && request.auth.uid == userId &&
request.resource.size < 5 * 1024 * 1024 && // Max 5MB
request.resource.contentType.matches('image/.*');
}
// Food scan images
match /food_scans/{userId}/{fileName} {
allow read: if request.auth != null;
allow write: if request.auth != null && request.auth.uid == userId &&
request.resource.size < 10 * 1024 * 1024 && // Max 10MB
request.resource.contentType.matches('image/.*');
}
// Meal photos
match /meal_photos/{userId}/{fileName} {
allow read: if request.auth != null;
allow write: if request.auth != null && request.auth.uid == userId &&
request.resource.size < 10 * 1024 * 1024 && // Max 10MB
request.resource.contentType.matches('image/.*');
}
// Deny all other paths
match /{allPaths=**} {
allow read, write: if false;
}
}
}Deploy Storage Rules:
firebase deploy --only storage:rules- Users can only upload to their own user ID folder
- File size limits prevent abuse (5MB for profile pics, 10MB for food photos)
- Only image files are allowed (content type validation)
- All uploads require authentication
Why needed: Required for server-side Firebase operations, such as:
- Firebase Cloud Functions authentication
- Admin SDK operations (user management, Firestore admin operations)
- Server-side scripts that need elevated permissions
File locations (you'll need one for each tool that requires it):
tools/firebase_cloud/functions/stoppr-f2d4a-firebase-adminsdk-*.jsontools/user_management/firestore_queries/stoppr-f2d4a-firebase-adminsdk-*.jsontools/learn_articles/stoppr-f2d4a-firebase-adminsdk-*.json
How to generate:
- Go to Firebase Console
- Select your project
- Go to Project Settings (gear icon)
- Navigate to "Service accounts" tab
- Click "Generate new private key"
- A JSON file will be downloaded
- Rename it to match the expected filename pattern:
stoppr-<project-id>-firebase-adminsdk-<random>.json - Place it in the appropriate
tools/directory:- For Firebase Functions:
tools/firebase_cloud/functions/ - For user management scripts:
tools/user_management/firestore_queries/ - For article upload scripts:
tools/learn_articles/
- For Firebase Functions:
Security Note:
- Never commit this file to version control
- Add to
.gitignore:**/stoppr-*-firebase-adminsdk-*.json - This file contains private keys with admin access to your Firebase project
Usage in code:
// Example: Firebase Functions
const admin = require('firebase-admin');
const serviceAccount = require('./stoppr-f2d4a-firebase-adminsdk-*.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});Why needed: Required for automated App Store Connect operations, such as:
- Fetching app analytics
- Managing app metadata
- Automated build uploads (via CI/CD)
File location: tools/apple-store-connect/AuthKey_<KEY_ID>.p8
How to generate:
- Go to App Store Connect
- Navigate to Users and Access → Keys
- Click the "+" button to create a new key
- Enter a name for the key (e.g., "Stoppr CI/CD Key")
- Select "App Manager" or "Admin" role
- Click "Generate"
- Download the
.p8file immediately (you can only download it once) - Note the Key ID (shown in the key details)
- Rename the file to
AuthKey_<KEY_ID>.p8(replace<KEY_ID>with the actual key ID) - Place it in
tools/apple-store-connect/
Security Note:
- Never commit this file to version control
- Add to
.gitignore:**/*.p8 - Store the Key ID and Issuer ID securely (you'll need them for API calls)
Usage:
// Example: Using the key with App Store Connect API
const { AppStoreConnectApi } = require('@apple/app-store-connect-api');
// Key ID and Issuer ID are used with the .p8 file for authenticationWhy needed: Contains Android app signing credentials for release builds. Required to sign your app for Google Play Store distribution.
How to create:
-
Generate a keystore (if you don't have one):
keytool -genkey -v -keystore android/app/release/stoppr-release-key.keystore \ -alias stoppr -keyalg RSA -keysize 2048 -validity 10000
- Enter a password (remember this!)
- Fill in the certificate information
- The keystore file will be created at
android/app/release/stoppr-release-key.keystore
-
Create
android/key.properties:storePassword=YOUR_KEYSTORE_PASSWORD keyPassword=YOUR_KEY_PASSWORD keyAlias=stoppr storeFile=release/stoppr-release-key.keystore
-
Replace placeholders:
YOUR_KEYSTORE_PASSWORD: The password you used when creating the keystoreYOUR_KEY_PASSWORD: Usually the same as keystore password (unless you set a different key password)keyAlias: The alias you used when creating the keystore (default:stoppr)storeFile: Path relative toandroid/app/directory
Security Note:
- Never commit
key.propertiesto version control - Add to
.gitignore:android/key.properties - Keep your keystore file secure and backed up
- If you lose the keystore, you cannot update your app on Google Play
Usage: The android/app/build.gradle.kts file automatically reads this file for signing configuration.
Why needed: Required for exporting iOS builds for App Store distribution. Contains signing certificates, provisioning profiles, and export settings.
How to create/configure:
-
Basic template (already exists in the repo):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>destination</key> <string>export</string> <key>method</key> <string>app-store</string> <key>provisioningProfiles</key> <dict> <key>com.stoppr.app</key> <string>YOUR_PROVISIONING_PROFILE_NAME</string> <key>com.stoppr.app.StreakWidget</key> <string>YOUR_WIDGET_PROVISIONING_PROFILE_NAME</string> </dict> <key>signingCertificate</key> <string>Apple Distribution</string> <key>signingStyle</key> <string>manual</string> <key>teamID</key> <string>YOUR_TEAM_ID</string> </dict> </plist>
-
Get your Team ID:
- Go to Apple Developer
- Your Team ID is shown in the top right corner
-
Get Provisioning Profile names:
- Go to Apple Developer Portal
- Find your App Store Distribution profiles
- Use the exact name shown in the portal
-
Update the file:
- Replace
YOUR_TEAM_IDwith your actual Team ID - Replace
YOUR_PROVISIONING_PROFILE_NAMEwith your main app provisioning profile name - Replace
YOUR_WIDGET_PROVISIONING_PROFILE_NAMEwith your widget extension provisioning profile name
- Replace
Usage: This file is used when building for App Store:
flutter build ipa --export-options-plist=ios/exportOptions.plistThe project uses code generation for:
- Freezed: Immutable data classes and union types
- json_serializable: JSON serialization/deserialization
Generate code:
flutter pub run build_runner build --delete-conflicting-outputsWatch for changes (auto-regenerate on file save):
flutter pub run build_runner watch --delete-conflicting-outputsThe app supports multiple languages. Localization files are in assets/l10n/:
en.json- Englishfr.json- Frenches.json- Spanishde.json- Germanit.json- Italianru.json- Russiancs.json- Czechsk.json- Slovakzh.json- Chinesepl.json- Polish
Adding a new language:
- Create a new JSON file in
assets/l10n/ - Copy structure from
en.json - Translate all string values
- Add language to
pubspec.yamlif needed
Using localized strings:
import 'package:stoppr/core/localization/app_localizations.dart';
Text(AppLocalizations.of(context)!.yourKey)Localization Smart Features (Implementation Details):
Hot Reload Support (Development Only):
- The
AppLocalizationsclass includes debug-only methods for hot reloading translations:evictFromCache(Locale locale): Clears cache for a specific languageevictAllFromCache(): Clears all language cachesforceReload(): Forces reload of current language strings
- This allows developers to edit JSON files and see changes without full app restart
- Only works in debug mode (
kDebugMode)
Text Sanitization:
- All localized strings are sanitized via
TextSanitizer.sanitizeForDisplay()before display - Handles malformed UTF-16 characters and encoding issues
- Prevents crashes from corrupted JSON files
Fallback Strategy:
- If a translation key is missing, returns the key itself (sanitized)
- If a language file fails to load, falls back to English
- If English fails, returns empty map (prevents crashes)
Cache Management:
- Localization files are cached in production for performance
- Cache is disabled in debug mode for easier development
- Cache is automatically evicted when language changes
Run tests:
flutter testRun tests with coverage:
flutter test --coverageThe project includes a Makefile with helpful commands:
iOS Development:
make run # Run on default simulator
make flutter-run-iphone16 # Run on iPhone 16 simulator
make pod-install # Install iOS dependencies
make reset-ios # Complete iOS reset (clean everything)Android Development:
make flutter-run-android-emulator-adb # Run on Android emulator
make build-android-aap # Build Android App Bundle
make reset-android # Complete Android resetGeneral:
make format # Format Dart code
make analyze # Analyze code for issues
make delete-conflicting-outputs # Regenerate code filesNote: Some Makefile commands contain personal paths (like DerivedData paths). Update these for your system or use Flutter commands directly.
- File Naming: Use snake_case for Dart files
- Widget Structure: Export widget first, then subwidgets, helpers, static content, types
- State Management: Use Cubit for simple state, Bloc for complex event-driven state
- Error Handling: Display errors using
SelectableText.rich(not SnackBars) - Imports: Always use full package paths (e.g.,
package:stoppr/features/...) - Debugging: Use
debugPrint()instead ofprint()orlog() - Line Length: Keep lines under 80 characters
- Trailing Commas: Always use trailing commas in multi-line function calls
IMPORTANT: This project MUST be built and run using the provided Make commands. DO NOT attempt to build or run the app directly from Xcode, as it will not work correctly. The Makefile contains essential setup steps, dependency management, and build configurations that are required for the app to function properly.
The Makefile (Makefile) handles:
- Proper Flutter plugin setup and linking
- CocoaPods dependency installation and configuration
- Xcode workspace configuration
- Derived data management
- Simulator booting and configuration
- Flutter hot reload setup
- Code generation (Freezed, JSON serialization)
- Proper build paths and binary locations
Building directly from Xcode bypasses these critical setup steps and will result in build failures, missing dependencies, or runtime errors.
Before running the app for the first time, or if you encounter build issues, run the complete reset:
make reset-iosWhat reset-ios does:
- Closes Xcode if running
- Cleans Flutter build cache
- Removes iOS build artifacts (Pods, DerivedData, etc.)
- Cleans CocoaPods cache
- Reinstalls all dependencies (Flutter packages and CocoaPods)
- Regenerates all code (Freezed, JSON serialization)
- Resets iOS simulators
- Opens Xcode workspace automatically
Note: This command takes 5-10 minutes to complete. Only run it when:
- Setting up the project for the first time
- After pulling major changes
- When experiencing build errors that normal cleaning doesn't fix
After running reset-ios (or if you've already set up the project), use one of these commands to run the app:
iPhone XS (Recommended for testing):
make flutter-run-iphonexOther available simulators:
make flutter-run-iphone15 # iPhone 15
make flutter-run-iphone15-pro # iPhone 15 Pro
make flutter-run-iphone16 # iPhone 16
make flutter-run-ipad-pro-11 # iPad Pro 11-inch
make flutter-run-ipad-pro-13 # iPad Pro 13-inch
make flutter-run-ipad-air-11 # iPad Air 11-inchWhat these commands do:
- Boot the specified iOS simulator
- Open Simulator app
- Build the iOS app with Xcode
- Locate the built binary
- Run Flutter with hot reload support
- Attach Flutter debugger for hot reload
Hot Reload: Once the app is running, you can use Flutter's hot reload:
- Press
rin the terminal to hot reload - Press
Rto hot restart - Press
qto quit
If you only need to clean Flutter and regenerate code (faster than full reset):
make reset-quickWhat reset-quick does:
- Cleans Flutter build cache
- Reinstalls Flutter dependencies
- Regenerates code (Freezed, JSON serialization)
- Does NOT clean iOS/Pods (faster)
Use this when:
- Code generation files are out of sync
- You've modified Freezed models or JSON serialization
- You don't need a full iOS reset
To see all available iOS simulators:
make list-simsTo open the iOS Simulator without running the app:
make open-simSolution: Run make reset-ios to reinstall all CocoaPods dependencies.
Solution: Run make reset-ios to properly link Flutter plugins.
Solution: Run make reset-quick to regenerate all code files.
Solution:
xcrun simctl shutdown all
xcrun simctl erase all
make reset-iosSolution:
- Close Xcode completely
- Run
make reset-ios - Wait for it to complete and open Xcode automatically
- Use Make commands to run, not Xcode's Run button
For Android development, use these commands:
make flutter-run-android-xs # Run on Pixel 4 emulator
make flutter-run-android-pixel6pro # Run on Pixel 6 Pro emulator
make list-android-emulators # List available Android emulators
make reset-android # Reset Android build environmentmake build-releaseThis builds the iOS app in release mode. For TestFlight distribution:
make build-testflightmake build-android-aapThis builds an Android App Bundle (AAB) for Google Play Store.
Run make help to see all available commands:
make helpCommon commands:
make reset-ios- Complete iOS reset (use first time or when having issues)make reset-quick- Quick Flutter reset (regenerates code)make flutter-run-iphonex- Run on iPhone XS simulatormake flutter-run-iphone15- Run on iPhone 15 simulatormake flutter-run-iphone16- Run on iPhone 16 simulatormake list-sims- List available iOS simulatorsmake open-sim- Open iOS Simulatormake build-release- Build iOS releasemake build-testflight- Build for TestFlightmake pod-install- Install CocoaPods dependencies onlymake clean-xcode- Clean Xcode derived datamake format- Format Dart codemake analyze- Analyze Dart code
- Always use Make commands: Never build/run directly from Xcode
- Run
reset-iosfirst: On first setup or when encountering build issues - Use hot reload: Once app is running, use
rfor hot reload instead of rebuilding - Check Flutter doctor: Run
flutter doctor -vif you encounter environment issues - Keep dependencies updated: Run
flutter pub getafter pulling changes
If you encounter errors when running flutter analyze, here are common issues and solutions:
Issue: "The constant name 'X' isn't a lowerCamelCase identifier"
Solution: This is a lint warning, not an error. The app will still compile and run. To fix:
- Rename constants to use
lowerCamelCase(e.g.,DRY_RUN→dryRun) - Or add an ignore comment:
// ignore: constant_identifier_names
Issue: "Don't invoke 'print' in production code"
Solution: Replace print() with debugPrint():
// Bad
print('Debug message');
// Good
debugPrint('Debug message');Issue: "The value of the local variable 'X' isn't used"
Solution:
- Remove the unused variable if it's not needed
- Or prefix with underscore if intentionally unused:
_unusedVariable - Or add ignore comment:
// ignore: unused_local_variable
Issue: "Prefer const constructors"
Solution: Add const keyword to widget constructors:
// Bad
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
// Good
Widget build(BuildContext context) {
return const Container(color: Colors.red);
}Issue: "Avoid using 'as' for type casting"
Solution: Use null-safe casting or pattern matching:
// Bad
final value = someValue as String;
// Good
final value = someValue is String ? someValue : '';
// Or
final value = someValue as String? ?? '';Issue: "Prefer single quotes for string literals"
Solution: Use single quotes instead of double quotes:
// Bad
final text = "Hello";
// Good
final text = 'Hello';Issue: "Lines longer than 80 characters"
Solution: Break long lines:
// Bad
final longVariableName = SomeVeryLongClassName(parameter1: value1, parameter2: value2, parameter3: value3);
// Good
final longVariableName = SomeVeryLongClassName(
parameter1: value1,
parameter2: value2,
parameter3: value3,
);Issue: "Missing required parameter"
Solution: Add the required parameter or make it optional:
// Bad
SomeWidget(); // Missing required 'title' parameter
// Good
SomeWidget(title: 'My Title');Issue: "Unused import"
Solution: Remove unused imports or add ignore comment:
// Remove the import
// import 'package:unused/package.dart';
// Or ignore
// ignore: unused_import
import 'package:unused/package.dart';Running Flutter Analyze:
# Run analyze
flutter analyze
# Run analyze with verbose output
flutter analyze -v
# Run analyze on specific file
flutter analyze lib/path/to/file.dart
# Fix auto-fixable issues
dart fix --applyNote: Many flutter analyze warnings are style suggestions and won't prevent the app from running. Focus on errors (not warnings) if you need the app to compile.
If you encounter issues not covered above:
-
Check Flutter version: Ensure you have Flutter 3.7.0+
flutter --version
-
Check Xcode version: Ensure Xcode 14.0+ is installed
xcodebuild -version
-
Check CocoaPods: Ensure CocoaPods is installed
pod --version
-
Check iOS Simulator: Verify simulators are installed
xcrun simctl list devices
If no simulators appear, install them via Xcode → Preferences → Components
-
Check Android Emulator (if testing Android): Verify emulator is set up
$ANDROID_SDK_ROOT/emulator/emulator -list-avdsIf no emulators appear, create one via Android Studio → Tools → Device Manager
-
Clean everything: Run
make reset-iosto start fresh -
Check environment variables: Ensure
.envfile is properly configured -
Check Firebase config: Ensure
GoogleService-Info.plistandgoogle-services.jsonare present -
Check Team ID: Ensure Team ID is set in Xcode project settings (step 5i)
-
Check StoreKit config: If testing purchases, ensure
Products.storekitis configured in Xcode scheme (Product → Scheme → Edit Scheme → Run → Options → StoreKit Configuration)
When contributing to this project:
- Follow the coding conventions outlined in the Development Guide
- Ensure all code generation is up to date
- Test on both iOS and Android
- Update localization files if adding user-facing strings
- Document any new environment variables or configuration requirements
- Keep sensitive files out of version control (check
.gitignore)
For issues, questions, or contributions, please create an issue or contact the maintainers.