Local road incident reporting & traffic advisory management for Valenzuela City, Philippines.
VCRoad is a cross-platform mobile and web application that empowers citizens to report road incidents (accidents, potholes, floods, debris) and stay informed with real-time traffic advisories. Built for Valenzuela City, the app replaces paper-based reporting with a digital, map-driven experience β integrating incident reports, traffic advisories, road safety education, and role-based administration into a single platform.
πΊοΈ Map Dashboard
- Real-time map view of all reported incidents and active advisories using OpenStreetMap
- Interactive markers with category-based icons and color coding
- Barangay-level filtering and statistics
- "Center on me" GPS location with permission-gated access
- Marker clustering with responsive sizing across devices
π Incident Reporting
- Multi-step wizard: category selection β location pinning β media capture β review & submit
- Image upload with client-side validation (size, resolution, content heuristics)
- GPS-assisted barangay auto-detection
- Report history with status tracking
- Email verification required before first report
π¦ Traffic Advisories
- Create and publish advisories (road closures, stop-and-go, one-way, construction, etc.)
- Map-based route plotting with OSRM road snapping
- Configurable severity levels and affected areas
- Real-time updates pushed to all users
- Enhanced data model β advisories carry a stable
barangayId(from the GeoJSONid), a FirestoreGeoPointcenterplusboundsNE/boundsSWfor geo-queryability, denormalized lowercasesearchKeywordsfor indexedarray-containssearch,createdByUid/updatedByUidaudit fields, a cappedversionHistoryaudit trail, andstatusUpdatedAt/nextStatusAttimestamps - Optimistic locking β edits increment
versioninside a Firestore transaction and fail with a refresh prompt on conflict; security rules enforce theversion + 1bump and protect immutable fields (advisoryId,createdAt,createdBy,createdByUid) - Single consolidated stream β regular users share one realtime stream (active + scheduled, all barangays) that powers the home map, the advisory list, and the new-advisory alert popups (derived from new additions after the session baseline, with mute/dedupe/self-notification guards); the map always plots only active advisories and barangay selection on the advisory screen is a client-side filter. The admin/sysadmin all-statuses stream is capped at the 200 latest advisories for performance.
- Shared advisory UI β status badges render through one
AdvisoryStatusBadgewidget and category icons throughAdvisoryCategory.iconFor(labels/colors/icons come from the model, so cards, detail dialogs, and the wizard never drift apart). Desktop/wide screens show the advisory list as a 2-column card grid, and empty states offer an admin Create CTA. The advisory screen is split into testable widgets (AdvisoryCard,AdvisoryListView) so the list body can be exercised without Firebase. - Quick status toggle β admins can Activate/Deactivate any advisory directly from its card (re-saves with the persisted status and a bumped
version; no manual re-save needed). The advisory list also supports Newest / Oldest / Recently-updated sorting. - UI/UX & layout (ISO 25010-aligned) β the advisory screen surfaces the active sort as a labeled control, the top stat cards (admin/sysadmin only) are a read-only summary (filtering is done through the single chip row below), and pull-to-refresh (mobile) or a refresh action (desktop) re-subscribe the stream. Contextual empty states distinguish "no data yet" from "no results for your search/filters" (with a Clear search & filters action). The whole surface is theme-aware β stat cards, filter chips, the card grid, and the details dialog all use
Theme.colorScheme+AppColors.primaryAdaptivetokens (darker accents brightened in dark mode for ~3:1 contrast) while the brand-navy AppBar/FAB stay fixed; category icons pick dark or light ink from the chip color's luminance (e.g. the yellow partial-lane chip gets a dark icon) to keep WCAG contrast. Desktop uses a lazy 2-column masonry grid with each card isolated in aRepaintBoundaryso scrolling doesn't repaint off-screen cards, and card action rows wrap instead of overflowing on narrow screens. The advisory details dialog shows a read-only mini map that auto-fits the entire affected + alternate route plot (padded, zoomed out one level, with a graceful fallback for single-point routes), and its image preview uses a stable hero tag. Screen-reader users getselected/button semantics on filters, a labeled sort control, and a semantically labeled card. - Inline wizard validation & save safety β validation errors in the create/edit wizard surface as a persistent inline banner above the navigation bar (not just a transient snackbar), the Details step shows live inline hints on the reason and contractor fields as you type, and an advisory photo (validated client-side: 5 MB cap, JPG/PNG) uploads to Supabase Storage at
advisories/{id}/image.jpgwith the superseded image cleaned up on edit. Save failures surface in the same inline banner with the real error, and leaving the wizard with unsaved changes triggers a discard confirmation viaPopScope(withmountedguards on async saves). - Status lifecycle (deferred) β statuses (
active/inactive/expired/scheduled) are currently set on save (client-side).nextStatusAtrecords when each status should next change (one-timestartDate/endDateor the next recurring window boundary), so a future scheduled job or client evaluator can auto-transition by queryingstatus+nextStatusAt. Until such a job exists, a status only changes when an admin re-saves the advisory.
π Road Safety Education
- Interactive lessons with 4 question types: multiple choice, true/false, identification, matching type
- Question images supported across all types (e.g., road-signage photos): question-level images for identification/true-false, per-option images for multiple choice, and imageβmeaning pairs for matching type
- Admin question editor lets you attach images to any question or option (uploaded and compressed on save) β ideal for signage identification drills
- Learn dashboard β stats header with level badge & title (Student Driver β Road Master), XP progress bar, day streak, lessons completed X/Y, overall completion %, and a tappable due-review counter that jumps to the first lesson scheduled for review
- Per-chapter progress β expandable chapter headers show completed/total lesson counts with green progress bars
- Progressive unlocking β only the first lesson and each chapter's opener start unlocked (locked cards are greyed out); scoring 70% or higher completes a lesson and unlocks the next one
- Retake flow β scores below 70% show a "Keep Going!" result with a pass-hint card and a Retake Lesson button that resets the lesson's progress for another attempt
- Spaced-repetition reviews β completed lessons open a dedicated review screen: due questions by default (intervals double on correct answers, reset on wrong), an "All caught up!" state with a next-review countdown and a Review all questions anyway option, +5 XP per completed review, and a result screen showing the next review countdown
- Preview mode β admin/sysadmin-only toggle on the Learn page that unlocks every lesson card for browsing; regular users always follow the locked progression. Opening an unlocked-but-not-completed lesson in preview is a placeholder ("coming soon"), while completed lessons open normally.
- XP system with 5 levels (Student Driver β Road Master), streak tracking, and streak bonuses
- 8 achievement badges (First Steps, Perfect Score, On a Roll, Week Warrior, Chapter Master, Quick Learner, See It β Report It, Review Master) β 7 are earnable today; "See It, Report It" awaits the report-flow link-up
- Animated XP preview as final onboarding slide β shows level progression before entering the app
- Rich lesson result screen with animated score circle, XP counter, level-up indicator, and badge awards (plus a dedicated retake state when the pass threshold isn't met)
- "Report It" prompt after lesson completion β encourages filing a road report tied to what was learned (placeholder: the report link-up is not yet wired)
- Admin/sysadmin lesson management: create, edit, delete, publish/unpublish, and per-lesson question editor (lessons are auto-numbered; chapters and questions are drag-reorderable) β the lesson list adds search, summary chips (N Chapters / N Lessons / N Published), and pull-to-refresh, and the question editor guards against losing unsaved changes. Lessons auto-sum per-question points into their total, and per-answer analytics (
timesAnswered/timesCorrect) are tracked in Firestore. - Chapter manager with drag-to-reorder chapters
- Role-aware tutorial with animated widget previews (users: 5 slides including XP preview; admins: 3 slides with dashboard overview)
- Location permission requested contextually at point of need (center-on-map or first report), not during onboarding
π€ User Roles & Administration
- Three roles:
user,admin,sysadminenforced at both UI and Firestore rules level - Admin panel for user management, bans, and role elevation
- Account search scans the whole
userscollection (bounded scan loop over createdAt-ordered pages) so matches are found regardless of where they sit β no paid search service (Algolia) or Cloud Functions involved; pagination is cursor-based and compositely indexed infirestore.indexes.json - Registration flow with identity verification (valid ID + selfie capture)
- Reusable searchable Barangay dropdown (register, profile details, and create-admin) with instant cached names, normalized/prefixed search, load error + Retry, and a no-results state
- Profile details page: view/edit contact & address (phone, street, house number, barangay dropdown), read-only name & email, role/verification badges, selfie display, and an unsaved-changes guard when leaving while editing
- Profile management with appearance settings (theme toggle)
π Security & Session Management
- Firestore-based role verification in security rules (no self-elevation)
- Brute force protection: Firestore-based per-email lockout (5 failed attempts β 15-min block) + local SharedPreferences cache
- Single-device session enforcement: on login from a new device, the old device is notified and signed out
- Persistent login via Firebase Auth local persistence
- Password reset via email link with rate-limited resends (45s cooldown) and anti-enumeration messaging
π Theme System
- Light and dark themes with a navy-based dark palette
- Persistent theme toggle via SharedPreferences (Light / Dark / System)
- Accessible from Profile β Appearance β Theme
- Full dark-mode coverage, including the profile details page β surface cards, text, dividers, and outlined buttons adapt to the active theme while brand-blue header and input fields stay consistent
π± Framework & Language
| Component | Technology |
|---|---|
| Framework | Flutter ^3.12.2 |
| Language | Dart ^3.12.2 |
| Platforms | Android, iOS, Web, Windows, macOS, Linux |
ποΈ Backend & Database
| Service | Status | Details |
|---|---|---|
| Firebase Authentication | β Active | Email/password, Spark free tier (unlimited) |
| Cloud Firestore | β Active | Primary database, Spark free tier (50K reads/day) |
| Supabase Storage | β Active | Image uploads for advisories, reports & lessons (1 GB free tier) |
| Firebase Storage | βΈοΈ Parked | Not used β Supabase Storage is the live backend |
| Cloud Functions | βΈοΈ Parked | Login tracking migrated to Firestore directly |
πΊοΈ Maps & Location
| Service | Replacement For | Status |
|---|---|---|
OpenStreetMap via flutter_map |
Google Maps SDK | β Active |
| Nominatim (OSM geocoding) | Google Places API | β Active |
| OSRM Nearest API (road snapping) | Google Roads API | β Active |
| Client-side image validation | Google Cloud Vision | β Active |
| OSM tile rendering via Canvas | Static map generation | β Active |
π¦ Key Packages
| Category | Packages |
|---|---|
| State Management | provider |
| Routing | go_router, url_strategy |
| Firebase | firebase_core, firebase_auth, cloud_firestore |
| Maps & Location | flutter_map, latlong2, geolocator, permission_handler |
| HTTP & APIs | http |
| UI Components | lottie, carousel_slider, introduction_screen, cached_network_image |
| Media | image_picker, file_picker, image, flutter_image_compress, video_player, video_thumbnail |
| Utilities | shared_preferences, uuid, device_info_plus, intl, url_launcher |
| Fonts | Poppins (Regular + Bold) |
VCRoad follows Clean Architecture with feature-first organization, aligned with ISO 25010 quality standards.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β Screens β Providers (ChangeNotifier) β Shared Widgets β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β calls
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β Repository Layer β
β AuthRepo β ReportRepo β AdvisoryRepo β LessonRepo ... β
ββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β β
ββββββββΌβββββββββββ ββββββββΌβββββββββββββββββββββββββββ
β Firebase β β Free APIs / Client-Side β
β (Spark tier) β β ββββββββββββββββββββββββββββ β
β ββββββββββββ β β β Nominatim (geocoding) β
β β
β β Auth β
β β β β OSRM (road snap) β
β β
β β Firestoreβ
β β β β Client vision heuristicsβ
β β
β ββββββββββββ β β β OSM tiles (static map) β
β β
β β β β CSV export β
β β
β β β ββββββββββββββββββββββββββββ β
ββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ
| Quality | How the Structure Addresses It |
|---|---|
| Maintainability | Feature modules encapsulate related UI, state, and logic. Clear dependency direction (β presentation β data β core). |
| Portability | Repository pattern abstracts data sources. Platform-specific code isolated via conditional exports. |
| Modularity | Feature-first grouping makes it easy to add/remove/modify features without affecting others. |
| Analyzability | File names reflect their purpose (e.g., login_screen.dart, auth_service.dart). |
| Testability | Repositories can be mocked independently. Providers decoupled from UI. Domain logic testable without Flutter dependencies. |
| Reusability | Shared widgets used across features. Utility functions are pure Dart with no UI coupling. |
| Replaceability | External services isolated behind repository interfaces, swappable without touching UI code. |
Prerequisites
- Flutter SDK ^3.12.2
- Dart SDK ^3.12.2
- Firebase project (configured with Authentication and Firestore)
- A code editor (VS Code, Android Studio, or IntelliJ)
Setup
# Clone the repository
git clone https://github.com/yo-gerr/vcroad.git
cd vcroad
# Configure environment variables
cp .env.example .env
# -> Edit .env and fill in your Supabase project URL and anon/publishable key.
# Install dependencies
flutter pub get
# Run on your preferred platform
flutter run -d chrome # Web
flutter run -d android # Android
flutter run -d ios # iOS (macOS only)
# Generate launcher icons (if needed)
flutter pub run flutter_launcher_iconsNote:
.envis gitignored and holds the Supabase credentials. The anon/publishable key is public by design (it ships in the client binary), so production data security relies on Supabase RLS / storage-bucket policies, not on keeping the key secret.
Firebase Configuration
The app uses Firebase for authentication and data storage. Configuration files are included:
lib/firebase_options.dartβ auto-generated by FlutterFire CLIandroid/app/google-services.jsonβ Android Firebase configios/Runner/GoogleService-Info.plistβ iOS Firebase config (if present)
To use your own Firebase project, run:
flutter pub add firebase_core
flutterfire configureThis regenerates firebase_options.dart and the platform-specific config files.
Lint & Analyze
flutter analyzelib/
βββ main.dart # Entry point, provider setup, GoRouter
βββ core/ # Cross-cutting concerns
β βββ constants/ # App-wide constants (config, password policy)
β βββ errors/ # Error types and handling
β βββ theme/ # AppColors, AppTextStyles, AppTheme
β βββ utils/ # Pure utility modules (no UI)
β βββ debouncer/ # Debounce utility
β βββ exception/ # try/catch helpers
β βββ format/ # Date/text formatting
β βββ input/ # Input validation, styling
β βββ map/ # Map configuration
β βββ responsive/ # Responsive breakpoint helpers
β βββ routing/ # Role-based routing config
β βββ web/ # Web-specific stubs
βββ data/ # Data layer
β βββ models/ # Data models (DTOs, JSON serde)
β βββ repositories/ # Repository/service implementations
βββ presentation/ # UI layer
βββ app/ # App shell, splash screen
βββ providers/ # 9 ChangeNotifier state managers
βββ shared/ # Shared dialogs, snackbar, widgets (banner, location prompt, coach marks)
βββ features/ # Feature modules
βββ auth/ # Login, Register, Reset Password
βββ onboarding/ # Role-aware tutorial with animated slides
βββ home/ # Map dashboard
βββ reports/ # Incident reporting wizard
βββ advisories/ # Advisory management wizard
βββ lesson/ # Quiz/lesson system
βββ admin/ # User/account administration
βββ profile/ # User profile & settings
assets/
βββ fonts/ # Poppins (Regular, Bold)
βββ icons/ # Map markers, app icons (.webp)
βββ images/ # Feature illustrations, user content
βββ lottie/ # Lottie animation files
βββ json/ # Static configuration
βββ texts/ # Agreement text (EN + Tagalog)
βββ barangays/ # Barangay boundary GeoJSON
βββ database/ # Archived Firestore snapshots (reference only)
βββ downloads/ # Downloadable content
Role-based access control is enforced at the database level via firestore.rules, not just the client UI.
| Role | Read | Write | Elevation |
|---|---|---|---|
user |
Own data, public reports, advisories | Own profile, own reports (if verified, not banned) | Self-registration sets role: user only |
admin |
All users, reports, advisories, settings | User profiles (non-admin), reports, advisories, settings | Cannot self-promote to sysadmin |
sysadmin |
Everything | Everything | Full access |
- No self-elevation β role changes denied unless performed by admin/sysadmin on another user
- Report integrity β
userIdandreportedBymust match the authenticated user - Verification gating β only verified users can create reports
- Deny-all fallback β
match /{document=**}at the bottom rejects anything not explicitly allowed
firebase deploy --only firestore:rules,firestore:indexes
β οΈ Firestore may prompt you to accept the composite indexes added infirestore.indexes.json(advisoriesstatus/barangay/barangayId
createdAt, andstatus+nextStatusAt) before queries using them work.
After deploying rules + indexes, backfill existing advisory documents with the new schema (idempotent, dry-run by default):
node scripts/migrate_advisories.js # preview what will change
node scripts/migrate_advisories.js --run # apply the backfillPure-Dart and widget tests (no Firebase required):
flutter testtest/advisory_model_test.dartβ model units: status labels/colors, category lookup + icon fallback,buildSearchKeywords,computeCenter/computeBounds,computeNextStatusAt(one-time + recurring + wrap-around), and tolerantfromJson/toJsonround-trips.test/widget_test.dartβ widget tests for the shared advisory UI:AdvisoryStatusBadgelabels/colors andAdvisoryCardcontent, admin-action visibility, callback wiring, recurring-schedule rendering, the lazy desktop masonry grid, and the filtered-empty state's clear-filters action.
Planned Enhancements
| Priority | Feature | Notes |
|---|---|---|
| π΄ High | Add Firebase App Check | Protects API keys from unauthorized use |
| π‘ Medium | Storage quota & image optimization | Uploads already run on Supabase Storage (1 GB free tier); add a compression/cleanup pipeline for larger media |
| π‘ Medium | Cloud Functions integration | Login tracking migrated; remaining functions pending Blaze |
| π‘ Medium | End-to-end testing suite | Provider-based architecture primed for integration tests |
| π‘ Medium | CI/CD pipeline | GitHub Actions for lint β test β build |
| π’ Low | Push notifications | Real-time alerting for new advisories |
| π’ Low | Offline-first support | Firestore persistence + local sync |
| π’ Low | Accessibility (a11y) audit | Screen reader labels, contrast, keyboard navigation |
| π’ Low | Localization expansion | Beyond English/Tagalog |
Contributions are welcome. This project does not yet have a formal CONTRIBUTING.md β for now, please open an issue or pull request on GitHub. Basic guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -m 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.
MIT is recommended for open-source Flutter projects β it's permissive, allows commercial use, and is the most widely adopted license in the Flutter ecosystem.
- Website: https://vcroad-a76a1.web.app
- Repository: https://github.com/yo-gerr/vcroad
- Issue Tracker: GitHub Issues
Built with Flutter. Backend on Firebase Spark free tier. Maps powered by OpenStreetMap and contributors. Architecture restructured July 2026.