Sprint 2 ‐ Deliverable - jdgutierrezr/ISIS3510-Moviles-Group32 GitHub Wiki
| # | 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 |

| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |

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.
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
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.
%%{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
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.
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
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.
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
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.
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
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?
| 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. |
| 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. |
| 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.
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