Sprint 2 ‐ Deliverable - jdgutierrezr/ISIS3510-Moviles-Group32 GitHub Wiki

Sprint 2 - App Implementation

1. Business Questions (BQs)

# Business Question Type Rationale Implemented By
BQ3 What activities near me match my interests based on my selected energy level right now? 2 Delivers personalized recommendations directly in the app by using explicit user input (Likert scale 1-5) combined with location data. Sharik Galviz
BQ4 How is my activity streak evolving compared to previous weeks? 2 Wandr's value proposition is to help users break out of their routine. Showing users how many quests they completed each week, compared to previous weeks, gives them direct feedback on their progress and reinforces the gamification and reward loop, motivating them to keep exploring. Catalina Villacris
BQ5 What quest should be recommended to me next, based on my past activity history? 2 Provides personalized quest suggestions based on user behavior patterns from completed activities. Manuela Ragua
BQ6 Which of my favorite places or activities are near me right now? 2 Displays relevant locations in real-time using explicit distance threshold and user-saved preferences to improve discovery and engagement. Juan David
BQ8 At which step do users most often abandon a quest without finishing it? 3 A step is a phase in the quest flow. Identifying drop-off points helps optimize UX and reduce friction. Manuela Galarza
BQ11 Which neighborhoods are the most active, so we can offer partnerships to local businesses there? 4 Identifies high-activity areas for possible business partnerships and monetization strategies. Daniel Reales

2. Analytics Pipeline

Analytics pipeline diagram drawio

BQ3 data flow (Home / Discovery)

Phase Details
Collection During onboarding the user picks their interests and energy level. The app saves the interests in user_interests and the energy level in users.
Storage Supabase tables users, user_interests, tags, quests, quest_tags and places.
Processing The nearby_quests(p_lat, p_lng, p_radius_km) RPC returns the quests inside the radius, closest first. The result is then filtered to the quests whose tags match the user's interests and whose difficulty fits the selected energy level.
Exposure The Home / Discovery view shows the matching quests with their distance to answer BQ3.

BQ4 data flow (Profile / Streak)

Phase Details
Collection When a user finishes a quest, the complete_objective RPC marks the row in quest_completions as completed with its completed_at time, and updates current_streak and current_xp in users.
Storage Supabase tables users, quest_completions, badges and user_badges.
Processing The get_streak_summary() RPC groups the logged-in user's completed quests by week, checks which days of the current week had activity, reads the user's XP and level, and returns every badge with its description and the day it was unlocked.
Exposure The Profile / Streak view (iOS) shows the weekly chart and compares the current week with the previous one to answer BQ4.

BQ5 data flow (Home "Recommended")

Phase Details
Collection When a user finishes a quest, the complete_objective RPC marks the row in quest_completions as completed. This builds the user's activity history.
Storage Supabase tables quest_completions, quests, quest_tags and tags.
Processing The recommend_next_quest() RPC finds the tags that appear most in the logged-in user's completed quests and returns a quest with those tags that the user has not started yet.
Exposure The "Recommended" section of the Home view shows that quest with the reason ("Because you liked …") to answer BQ5.

BQ6 data flow (Map / Nearby)

Phase Details
Collection The user saves a place or activity as a favorite. The phone's GPS gives the current position when the map opens; it is sent as a parameter and is not stored.
Storage Supabase tables favorite_places and places.
Processing The nearby_favorites(p_lat, p_lng, p_radius_km) RPC computes the distance from the user to each of their favorite places, keeps the ones inside the radius and sorts them by distance.
Exposure The Map / Nearby view shows those favorites as pins and in a list with their distance, and lets the user change the radius, to answer BQ6.

BQ8 data flow (Abandonment funnel)

Phase Details
Collection start_quest creates a pending row in quest_completions. Each complete_objective call adds a row to quest_objective_completions. abandon_quest changes the status to abandoned. The app also saves the funnel events (QUEST_VIEWED, QUEST_ACCEPTED, NAVIGATION_STARTED, QUEST_COMPLETED, QUEST_ABANDONED) in telemetry_events.
Storage Supabase tables quest_completions, quest_objective_completions, quest_objectives and telemetry_events.
Processing The get_quest_dropoff() RPC takes every abandoned quest, finds the last step the user completed and counts the abandonments per quest and step, for all users. It only returns counts, never who abandoned. It runs on the server because row-level security only lets each user read their own rows.
Exposure The Abandonment Funnel view shows the step where most users quit and the abandonments per quest to answer BQ8. It is an internal team view, only available in debug builds from Profile. Users see the same data as a hint in Active Quest, on the step where most people give up.

BQ11 data flow (Activity map by neighborhood)

Phase Details
Collection When a user finishes a quest, the complete_objective RPC marks the row in quest_completions as completed. The places and the neighborhoods of Bogota are reference data loaded by the team, not collected from users.
Storage Supabase tables quest_completions, quests, places and neighborhoods.
Processing The quest_activity_by_neighborhood SQL view links every completed quest to its place and the place to its neighborhood, and counts the completed quests per neighborhood, for all users.
Exposure The team's dashboard shows a map of Bogota with the activity per neighborhood to answer BQ11. It is not shown in the app.

3. Architectural Design

Architecture - Overall System Structure and Components

Overall architecure diagram

Rationale: The app follows the MVVM pattern, organized in three layers with one-way dependencies: View → ViewModel → Model. Each layer only knows the one below it.

  • View: the screens only draw the state they observe from their ViewModel and report what the user does. They never access data directly. The design system keeps colors, typography and components in one place, so all screens look consistent.
  • ViewModel: there is one per screen. It holds the screen's state and survives configuration changes such as rotation, so the View can be rebuilt at any time without losing data.
  • Model: repositories are the only entry point to the data. They hide whether it comes from Supabase (remote data sources) or from the local Room database. Mappers convert the backend's format into domain models, so a change in the backend does not reach the ViewModels or the screens.
  • Local database (Room / SQLite): the app must keep working without a connection and keep the user's quest progress. Saving the data on the device makes that possible.
  • Supabase as backend: it provides authentication, a REST API over PostgreSQL and file storage, so the team does not need to build and host its own server. All communication goes over HTTPS.
  • Core: connectivity, location, fetch strategies, the event bus, dependency injection and telemetry are needed by several features. Keeping them in one module avoids repeating them and keeps the device's sensors (GPS, network state) behind a single access point.

This separation lets the team work on screens and data access in parallel, test the logic without a device, and replace a part (for example, the map provider or the backend SDK) without changing the rest.

Architecture Design - Component Interaction and Communication

BQ4 – Profile / Streak (iOS)

flowchart LR
    User((User))

    subgraph iOS["iOS app – MVVM"]
        View[ProfileView]
        VM[ProfileViewModel]
        Service[StreakService]
        DTO[(StreakDTO)]
    end

    subgraph Supabase["Supabase backend"]
        RPC[get_streak_summary RPC]
        subgraph DB["PostgreSQL"]
            Users[(users)]
            QC[(quest_completions)]
            Badges[(badges / user_badges)]
        end
    end

    User -- opens Profile --> View
    View -- observes --> VM
    VM -- asks for data --> Service
    Service -- calls via HTTPS --> RPC
    RPC -- reads --> Users
    RPC -- reads --> QC
    RPC -- reads --> Badges
    RPC -- JSON --> Service
    Service -- decodes into --> DTO
    VM -- holds --> DTO
Loading

Rationale: The iOS side follows MVVM. The view only observes the ViewModel, the ViewModel holds the screen state and the BQ4 comparison logic, and the Service is the only component that knows about Supabase. On the backend, a single RPC function reads the three tables and returns the summary already aggregated, so the phone makes one request instead of several and the Android app can reuse exactly the same calculation.

BQ5 – Personalized Quest Recommendations (iOS)

%%{init: {"flowchart": {"curve": "linear"}}}%%
flowchart LR
    User((User))

    subgraph iOS["iOS app – MVVM"]
        HV[HomeView]
        RV[RecommendedQuestsView]
        VM[DiscoveryViewModel]
        QR[QuestRepository]
        SR[SupabaseQuestRepository]
        Strategy[HistoryRecommendationStrategy]
    end

    subgraph Supabase["Supabase backend"]
        subgraph DB["PostgreSQL"]
            Q[(quests)]
            QC[(quest_completions)]
            P[(places)]
        end
    end

    User -->|opens Home| HV
    HV -->|observes| VM
    VM -->|requests recommendations| QR
    QR -->|implemented by| SR

    SR -->|reads| Q
    SR -->|reads| QC
    SR -->|reads| P

    SR -->|returns quests, history and ratings| VM
    VM -->|applies| Strategy
    Strategy -->|filters and orders completed quests| VM

    VM -->|updates recommendation state| HV
    HV -->|shows recommended quests| RV
    RV -->|observes| VM
Loading

Rationale: The iOS side follows MVVM. HomeView and RecommendedQuestsView display the recommendation state, while DiscoveryViewModel manages the loading and screen state. The SupabaseQuestRepository retrieves the available quests, the user's completed quests, and the corresponding place ratings from Supabase. The HistoryRecommendationStrategy contains the recommendation logic, excluding completed quests and prioritizing quests whose categories match the user's previous activity, with ratings used to further order the results. This separation keeps the recommendation logic independent from the views and repository and allows the strategy to be changed without modifying the UI.

BQ6 – Map / Nearby (Android)

sequenceDiagram
    box View
        participant UI as HomeScreen
    end
    box ViewModel
        participant VM as HomeViewModel
    end
    box Model
        participant D as TelemetryQuestRepository
        participant R as QuestRepositoryImpl
        participant S as FetchStrategy
        participant DB as Room
    end
    participant API as Supabase
    participant Bus as AppEventBus

    UI->>VM: observes uiState
    VM->>D: nearbyQuests(location)
    D->>R: nearbyQuests(location)
    R->>S: select(policy).fetch(local, refresh)
    alt online (NetworkFirst)
        S->>API: nearby_quests RPC + quest_tags
        API-->>S: DTOs (JSON)
        S->>DB: mappers → entities, replaceNearby()
    else offline (CacheOnly)
        Note over S: skips the network
    end
    DB-->>VM: Flow of quests (isStale = true when offline)
    VM-->>UI: new UiState
    D->>Bus: RequestTimed(quest_recommendations_load)
    Bus->>Bus: TelemetryCollector saves it, uploads a batch later
Loading

Rationale: The app uses Supabase to store user interests, favorite places, and quest history. When a user opens the app, it uses their phone's GPS to show nearby quests that match their chosen energy level and interests. As users complete quests, the app learns their preferences and recommends new activities they might like. Users can also save places as favorites and see them on a nearby map based on their current location.

BQ8 – Quest / Progress (Android)

flowchart LR
    User((User))

    subgraph Android["Android app – MVVM"]
        QS[QuestDetail / ActiveQuest screens]
        QVM[Quest ViewModels]
        FS[QuestDropoffScreen]
        FVM[QuestDropoffViewModel]
        QR[QuestRepository]
        AR[AnalyticsRepository]
        Bus[AppEventBus]
        Tracker[QuestFunnelTracker]
        Tel[TelemetryCollector]
    end

    subgraph Supabase["Supabase backend"]
        QRPC[start_quest / complete_objective / abandon_quest]
        DRPC[get_quest_dropoff RPC]
        subgraph DB["PostgreSQL"]
            QC[(quest_completions)]
            QOC[(quest_objective_completions)]
            TE[(telemetry_events)]
        end
    end

    User -- views, accepts, checks steps --> QS
    QS -- observes --> QVM
    QVM -- calls --> QR
    QR -- HTTPS --> QRPC
    QRPC -- writes --> QC
    QRPC -- writes --> QOC
    QVM -- publishes quest events --> Bus
    Bus -- notifies --> Tracker
    Tracker -- funnel events --> Tel
    Tel -- batch upload --> TE
    FS -- observes --> FVM
    FVM -- asks for report --> AR
    QVM -- asks for drop-off hint --> AR
    AR -- HTTPS --> DRPC
    DRPC -- reads --> QC
    DRPC -- reads --> QOC
Loading

Rationale: The screens never call the analytics code. The ViewModels only publish quest events to the event bus, and the QuestFunnelTracker (Observer) listens to them and saves them as telemetry, so new metrics can be added without changing the screens. The abandonment count is calculated on the server by one RPC, because row-level security only lets each user read their own rows, and that way the phone makes a single request. The same report feeds the internal funnel view and the drop-off hint in Active Quest; if it fails, the quest still works without the hint.

BQ 11 - Activity Hotspots (Location)

flowchart LR
    U((User))

    subgraph IOS["iOS app · MVVM"]
        V["DiscoveryMapView<br/>SwiftUI + MapKit"]
        VM["DiscoveryMapViewModel"]
        LOC["MapLocationService<br/>CoreLocation"]
        F["MapDataFacade"]
        R["SupabaseMapRemote"]
        M["MapQuest / NeighborhoodActivityReport"]

        V -->|user actions| VM
        VM -->|publishes screen state| V
        LOC -->|GPS and permission state| VM
        VM -->|requests geography and analytics| F
        F -->|requests backend data| R
        R -->|decoded responses| F
        F -->|joins coordinates and sorts results| M
        M -->|map-ready data| VM
    end

    subgraph BACK["Supabase backend"]
        N["nearby_quests / nearby_places RPCs"]
        A["neighborhood_quest_activity RPC"]
        Q[("quests / places")]
        C[("quest_completions")]
        B[("neighborhoods")]

        N -->|reads| Q
        A -->|counts completed quests<br/>and distinct participants| C
        A -->|groups activity by place| Q
        A -->|reads neighborhood assignments| B
    end

    U -->|opens Map| V
    R -->|authenticated HTTPS| N
    R -->|authenticated HTTPS| A
Loading

Rationale: The iOS Map follows MVVM. DiscoveryMapView renders nearby quests, distances, and neighborhood activity, while DiscoveryMapViewModel manages screen state, filtering, loading, and retries. MapLocationService provides GPS and permission updates; when location is denied or unavailable, the screen explains the situation and uses Bogotá as a fallback.

The Facade pattern is implemented in MapDataFacade. It combines nearby quest data with place coordinates and returns sorted, map-ready models. SupabaseMapRemote handles authentication, RPC calls, and response decoding, keeping backend details outside the View and ViewModel.

The backend aggregates completed quests per neighborhood over the last 30 days, including distinct participants and active places. The screen ranks neighborhoods and highlights those with the most activity to answer BQ11: where should we prioritize local business partnerships?

Design Patterns and Tactics

Design patterns

Pattern Problem it solves
MVVM Screens only render a UiState and call functions. All the logic is testable without an UI.
Repository One interface per domain. ViewModels never depend on Supabase or Room.
DTO Each class mirrors a Supabase response exactly. Postgres enums stay as String, so a new backend value cannot break decoding.
DAO All local SQL lives in Room DAOs. Queries return Flow, and multi-step writes use @Transaction.
Mapper (Adapter) Converts DTO → Entity → domain model. If a column changes, only the mapper changes.
Adapter WandrMap and LocationProvider are the interfaces the app expects. GoogleMapAdapter and FusedLocationProvider translate them to the Google SDKs, so changing the map provider does not touch any screen.
Facade Hides the supabase query builder behind simple methods such as nearbyQuests(lat, lng, radius).
Strategy NetworkFirst, CacheFirst and CacheOnly are interchangeable. StrategySelector picks one for each request.
Decorator Adds measurement around the real repository without changing it.
Observer When data changes, every screen that shows it updates automatically. The QuestFunnelTracker subscribes to the AppEventBus and turns quest events into telemetry for BQ8, without the screens knowing about it.
Singleton + Dependency Injection One SupabaseClient (one session) and one database for the whole app.

Swift

iOS (SwiftUI)

Pattern Problem it solves
MVVM Views only draw the state they observe from their ViewModel. The logic (BQ4 comparison, level progress, quest progress) lives in the ViewModel and is tested without the UI.
Observer ViewModels are ObservableObject with @Published values. When a value changes (for example, after checking a step), every view that observes it redraws automatically.
DTO Each struct mirrors a Supabase response. convertFromSnakeCase maps the backend's snake_case names to Swift names, so the backend format does not leak into the views.
Facade Services hide Supabase behind simple methods such as fetchActiveQuest() or the neighborhood activity request for the map. ViewModels never build queries or RPC calls themselves.
Strategy Recommendation strategies are interchangeable. The Home screen asks for "the next quest" and the selected strategy decides how to pick it, without changing the screen.
Singleton One Supabase client (one session) shared by the whole app.

Tactics

Quality attribute Tactic Where Scenario
Availability Local cache with fallback: if the network fails, show saved data marked as stale FetchStrategy, Room QS1
Availability Detect the fault: observe connectivity and switch strategy before calling the network ConnectivityObserver, StrategySelector QS1
Availability Degraded mode for location: current fix → last known → center of Bogotá LocationProvider QS11
Reliability Persist state: quest progress is saved in Room after every step QuestDao QS8
Performance Batch requests: telemetry is sent in groups of 20, or when the app goes to the background TelemetryCollector BQ1, BQ2
Performance Reduce requests: tags use CacheFirst, and the user search waits 300 ms after the last keystroke TagRepository, FriendsViewModel QS2, QS7
Energy Location updates only after moving 50 m LocationProvider QS5
Energy Poll friends' positions only while the screen is visible FriendsMapViewModel QS5
Energy GPS distance updates only while the Active Quest screen is visible ActiveQuestViewModel QS5
Availability Wait for the saved session to load before uploading a photo after the camera closes; a failed token refresh keeps the user in the app awaitUserId, SessionViewModel QS1
Security The analytics RPC returns only aggregated counts, without user ids, and anonymous users cannot call it get_quest_dropoff QS6
Security Authorize on the server: row-level security and RPC functions; the app only holds a publishable key Supabase QS6
Security Limit exposure: the cache is cleared on sign out; photos go to a private bucket, in a per-user folder CacheInvalidator, storage.sql QS6
Testability Depend on interfaces and inject fakes Repositories, Hilt -
Modifiability Isolate the backend SDK behind facades and mappers data/remote, data/mapper -

Rationale: To make sure the app works offline, we use a local database called Room as our main source of truth, the Strategy pattern to switch between the internet and local storage, and the Observer pattern to update the screen automatically. To measure how fast the app responds without draining the battery or slowing down the user, we use a Decorator to time actions and group the tracking data in the background. Since the server code is still changing, we use DTOs, mappers, and facades to protect the main app code from breaking when those updates happen. Finally, we handle all security on the server side, meaning the app is treated as untrusted and can only do exactly what the database rules allow.


4. List of Implemented Features

List all features implemented in this sprint and who is responsible for each, per app/platform.

Feature Description Platform Responsible
Home / Discovery view Implemented the Android Home screen with nearby quests, location information, Mystery Quest recommendations, energy-level and radius filters, loading/error/empty states, and quest listings based on the user's preferences and location. Android Sharik Galviz
Mystery Quest filters Added interactive filters for energy level (Low, Medium, High) and maximum radius (1 km, 3 km, 5 km), connected to the HomeViewModel so the selected preferences update the displayed recommendations. Android Sharik Galviz
Explore / Discover view Implemented the Explore screen with search, category filters, featured experiences and featured quests. The screen displays events and quests retrieved from the existing data layer and provides empty and loading states. Android Sharik Galviz
Bottom tab navigation Implemented the shared bottom navigation for Home, Explore, Map, Saved and Profile, and connected the navigation actions so users can move between the main sections of the Android application. Android Sharik Galviz
Main navigation integration Connected Home and Explore with the application's navigation host and integrated the existing Map and Profile screens into the main navigation flow. Android Sharik Galviz
Quest Details view Added the QUEST_VIEWED event and the start action. Android Manuela Galarza
Active Quest view Shows the quest in progress with its progress bar, destination and steps. Steps are checked through the complete_objective RPC; photo steps open the camera and upload the photo to the private quest-photos bucket. Navigate opens the maps app, and Give up asks for confirmation and calls abandon_quest. Includes loading, error and empty states. Android Manuela Galarza
Active Quests list Lists every quest the user has in progress, with its progress. Android Manuela Galarza
Live distance Shows the distance from the user's GPS position to the quest place, updated while walking, and "You're here!" within 100 m. Shows a message while searching for a signal or without permission. Android Manuela Galarza
Drop-off hint On the step where most people give up that quest, shows how many stopped there to encourage the user. Android Manuela Galarza
Abandonment Funnel view Internal view (debug builds only) with the step where most users quit and the abandonments per quest (BQ8). Android Manuela Galarza
Session gate Splash screen that restores the saved session and opens Home or Log in. If the session ends, it goes back to Log in and clears the cache. Android Manuela Galarza
Profile / Streak view Same information as the iOS version: streak, XP and level, active days of the week and badges. Android Manuela Galarza
Log in Email and password login with Supabase Auth. Shows loading and error states, and checks whether the user still needs onboarding. Android Juan David
Sign up Creates the account and the user's profile in the backend. Validates name, email and password before sending. Android Juan David
Map / Nearby view Google Map with the user's current position and one pin per nearby quest, plus a list of those quests with their distance, closest first. Android Juan David
Profile / Streak view Shows the current streak, progress to the next level (XP), active days of the current week (today highlighted) and badges in a grid, with the day each one was unlocked or how to unlock it. Same information as the Android version. Includes loading, error and empty states. iOS Catalina Villacris
Active Quest view Shows the user's quest in progress: title, progress bar, destination and the list of steps in order. Includes loading, error and empty states. iOS Catalina Villacris
Active Quest Review after finish Shows the user's quest in progress: title, progress bar, destination and the list of steps in order. Includes loading, error and empty states. iOS Daniel Reales
Active Quest Review Backend Shows the user's quest in progress: title, progress bar, destination and the list of steps in order. Includes loading, error and empty states. iOS Daniel Reales
Check quest steps Tapping a step checks it through the complete_objective RPC. Steps that need a photo open the photo picker, upload the photo to the private quest-photos bucket and send its path as proof. When the last step is checked, the app shows the XP earned. iOS Catalina Villacris
Navigate and give up Navigate opens Apple Maps with directions to the quest place. Give up asks for confirmation and calls the abandon_quest RPC. iOS Catalina Villacris
Tab navigation Bottom tab bar that connects the iOS screens, other screens are added as new tabs. iOS Catalina Villacris & Manuela Ragua & Daniel Reales
Home view Displays the main iOS experience with personalized quest recommendations and navigation to the different app sections. iOS Manuela Ragua
Explore view Displays available quests and events with category filters. Completed quests are excluded from the quest list. iOS Manuela Ragua
Personalized Quest Recommendations Recommends quests based on the user's activity history. Completed quests are excluded, quests categories from completed quests and ratings are considered. iOS Manuela Ragua
Recommended Quests view Displays personalized quest recommendations, allowing users to browse the recommended quests based on their activity history. iOS Manuela Ragua
Loading and Error States Provides loading and error states when retrieving recommendations from the repository iOS Manuela Ragua
Map View with Filters Shows the user's quest in progress: title, progress bar, destination and the list of steps in order. Includes loading, error and empty states. iOS Daniel Reales

Coverage check at minimum, one functionality per item below must be present:

Requirement Feature That Covers It Responsible
Uses at least one phone sensor Map / Nearby view (GPS) - Android Juan David & Daniel Reales
Answers a type 2 business question Streak trend - iOS Catalina Villacris
Is context-aware Live distance to the quest place (GPS) - Android Manuela Galarza
Is a smart feature Drop-off hint in Active Quest - Android Manuela Galarza
Uses an external service (non-auth, connected to backend) Photo proof upload to Supabase Storage - Android Manuela Galarza
Allows user authentication Log in and Sign up - Android Juan David
Smart Feature Personalized Quest Recommendations - iOS Manuela Ragua

Ethics Video: https://youtu.be/Iug1vyR6aBw Sprint Board: https://github.com/users/jdgutierrezr/projects/3

⬅️ Go to Sprint 2

⚠️ **GitHub.com Fallback** ⚠️