Skip to content

Repository files navigation

Meowtropolis

A SwiftUI + Firebase iOS app that brings every everyday pet-care workflow — profiles, grooming, vet requests, and a marketplace — into one place.

Platform Swift Build License

Table of Contents

Overview

Meowtropolis is a native iOS companion app for pet owners, built entirely in SwiftUI on top of Firebase. The goal is to consolidate the scattered parts of caring for a pet — keeping track of who they are, booking grooming and vet visits, and shopping for supplies — into a single, coherent mobile experience instead of juggling multiple apps or paper records.

The app is designed as a full-stack demo/reference implementation: it pairs a polished, animated SwiftUI front end with a real Firebase backend (Authentication + Cloud Firestore), a protocol-oriented service layer that keeps business logic testable and swappable, and a lightweight state layer that drives navigation and screen data. It ships with:

  • Bilingual UI — every user-facing string is available in English (US) and Bangla, switchable at runtime and persisted per-user (see AppLanguage in AppDesign.swift).
  • Two navigation modes — a standard customer experience (DashboardView) and a separate admin experience (AdminDashboardView) gated by an admin-email allowlist in AppState.
  • Offline-friendly sample data — a bundled JSON catalog that auto-seeds Firestore on first run, so the marketplace is populated without any manual data entry.
  • A dedicated UI kit — the images/ and Pet Care - Pet Services App UI Kit (No Image)/ directories contain the full set of reference screen designs the app was built from.

This repository contains the iOS app source, both test targets, Firebase data-contract references, and the design/demo assets used to build and validate the app.

Features

Authentication and Session

  • Sign up, log in, log out
  • Password reset
  • Session restore on app start
  • User-friendly auth error handling

In depth: Authentication is abstracted behind an AuthService protocol (Services/AuthService.swift), implemented concretely by FirebaseAuthService on top of Firebase Auth. AppState (State/AppState.swift) owns the single source of truth for session state — it listens to Firebase's auth-state-changed stream, resolves the authenticated user's Firestore profile, and exposes isLoggedIn, currentUser, isProfileLoading, and profileErrorMessage to the rest of the app via @Published properties. Auth errors from Firebase (wrong password, user not found, invalid email, weak password, network errors, etc.) are translated into friendly, localized (English/Bangla) messages instead of raw SDK error text.

Profile and Account

  • Personal information update
  • Profile image upload with optimization/compression
  • Password change and account delete with reauthentication safeguards
  • Local activity history with noise filtering for meaningful events

In depth: Profile photo uploads are compressed client-side before being persisted as Base64 (profileImageBase64 on the User model) to keep Firestore document sizes reasonable. Sensitive account actions — changing the account email and deleting the account — require the user to re-enter their current password, satisfying Firebase's reauthentication requirement for security-sensitive operations. UserHistoryService records a locally-scoped activity trail (e.g. "Logged in", "Updated personal information", "Changed password") per user, filtering out noisy/duplicate events so the account screen shows a meaningful history rather than a raw event log.

Pet Management

  • Create, edit, list, and delete pet profiles

In depth: Each Pet (Models/DataModels.swift) is linked to its owner via userId, and stores name, breed, and an optional age. PetService handles all Firestore CRUD against the pets collection, keeping the view layer free of direct Firestore calls.

Grooming and Vet

  • Grooming booking flow
  • Vet request flow

In depth: Booking records capture serviceType, an ISO-8601 date, and a status (pending, confirmed, completed, cancelled) via the BookingStatus enum. BookingService writes these to the bookings collection. Vet requests use a dedicated, simpler VetRequest model and VetService, since vet workflows don't need the full booking lifecycle. ReminderService schedules local notifications for upcoming bookings so users get a reminder without any server-side push infrastructure.

Marketplace

  • Product listing and detail views
  • Search and sort/filter controls
  • Add-to-cart and cart quantity management
  • Checkout flow with stock-safe order placement (no real payments)
  • Cart recommendations and savings summary
  • One-time Firestore auto-seeding from bundled product JSON when products collection is empty

In depth: ProductService follows a Firestore-first strategy with a local JSON fallback (LocalProductService reads SampleData/products.json from the app bundle) — so the marketplace still works if Firestore is temporarily unreachable. ProductSeedService checks once whether the products collection is empty and, if so, uploads the bundled catalog (17 sample products spanning categories like cat litter, food, and accessories) so a brand-new Firebase project has real data to browse immediately. A local one-time seed flag prevents re-uploading on every launch. OrderService places orders inside a Firestore transaction that atomically checks and decrements product stock, so two users can't oversell the same limited-stock item. CartState (an ObservableObject injected app-wide) owns cart contents, quantities, and derived totals/savings shown in CartRecommendationsView.

Blogs

  • Built-in pet blog section with featured cards, list view, and detail pages

Maps and Nearby Places

  • Google Places-backed nearby place search flow (loading, empty, error, retry states)

In depth: MapsService performs one-time setup of the Google Maps and Google Places SDKs using an API key read from Info.plist. PlacesService and LocationService handle place search requests/responses (Models/PlaceSearchRequest.swift, Models/PlaceSearchResponse.swift, Models/Place.swift) and device location, while MapState drives the loading/empty/error/retry UI states shown in MapView.

Admin Dashboard

  • Separate admin-only home screen (AdminDashboardView), gated by an email allowlist in AppState
  • A demo admin account is auto-provisioned on first login attempt for convenience during evaluation/demo (see Usage)

Localization

  • Full English (US) / Bangla bilingual support via the AppLanguage enum, with the preference persisted both locally (UserDefaults) and on the user's Firestore profile (preferredLanguageCode)

Tech Stack

Layer Technology
UI Swift, SwiftUI
Backend Firebase Authentication, Cloud Firestore
Location Google Maps SDK, Google Places SDK
Local persistence UserDefaults (language preference), on-device local notifications (booking reminders)
Reactive state Combine / ObservableObject (AppState, CartState, MapState, MarketplaceState)
Testing XCTest (unit + UI)

Why this stack: SwiftUI + Firebase keeps the project fully native with no custom backend to host or operate — Authentication and Firestore cover the app's identity and data needs, while Google Maps/Places covers location discovery. The protocol-oriented service layer (AuthService, and concrete FirebaseAuthService) means Firebase could be swapped for another backend without touching the UI or state layers.

Architecture: a clean, layered structure — Models (Codable domain types), Services (Firebase/Maps/data access), State (observable orchestration), and Views (SwiftUI screens). UI stays in Views, side effects stay in Services, and flow orchestration stays in State.

Architecture

Meowtropolis follows a straightforward four-layer separation, enforced by directory structure rather than a heavyweight framework:

Views  →  State  →  Services  →  Firebase / Google APIs
 (UI)     (orchestration)  (side effects)   (external systems)
                 ↓
              Models
        (shared data shapes)
  • Models/ — plain Codable, Sendable structs (User, Pet, Booking, Product, Order, OrderItem, VetRequest, Place, PlaceSearchRequest, PlaceSearchResponse, CartItem) that define the data contracts shared between Firestore, services, and the UI. Firestore/ModelCoding.swift centralizes Firestore-specific encode/decode helpers, and Firestore/FirestoreCollectionLegacy.swift retains legacy collection-name handling.
  • Services/ — one service per responsibility, each documented in-file:
    • AuthService / FirebaseAuthService — authentication interface + Firebase implementation
    • UserService — Firestore CRUD for user profiles
    • PetService — Firestore CRUD for pet profiles
    • BookingService — Firestore CRUD for grooming/service bookings, plus a manual smoke-test helper
    • VetService — minimal Firestore CRUD for vet requests
    • ProductService — Firestore-first product loading with local JSON fallback, plus manual validation helpers
    • LocalProductService — loads the bundled product catalog from SampleData/products.json
    • FirestoreProductService — loads product data from Firestore
    • ProductSeedService — one-time seeding of bundled products into Firestore
    • OrderService — stock-safe order creation via Firestore transactions
    • MapsService — one-time Google Maps/Places SDK setup
    • PlacesService / LocationService — nearby-place search and device location
    • ReminderService — local-notification scheduling for booking reminders
    • UserHistoryService — local, filtered activity history per user
    • FirestoreCollections — a single enum of Firestore collection name constants, used everywhere instead of hardcoded strings
    • BackendSmokeTests — simple, manual smoke tests for backend services aimed at beginner-friendly debugging
  • State/ObservableObject classes that orchestrate screen-level data flow and are injected via @EnvironmentObject/@StateObject: AppState (session, profile, admin routing), CartState (cart contents/totals), MapState (nearby-places loading/empty/error/retry), MarketplaceState (product listing/search/filter state).
  • Views/ — SwiftUI screens grouped by area: Auth/ (landing, login, signup, forgot password, OTP verification), Main/ (dashboard, admin dashboard, pet profile, grooming, vet, marketplace, product detail, related products, cart, cart recommendations, checkout, account, blog, map, services catalog), Common/ (design system in AppDesign.swift, onboarding, splash), Shared/ (reusable primitives: cards, empty/error/loading states, placeholder images, spacing and text style constants).

Design principle: keep UI concerns in Views, side effects (network/Firebase/notifications) in Services, and cross-screen flow/state orchestration in State. Views should be able to render from State alone, and State should never talk to Firebase directly — it always goes through a Service.

Project Structure

Meowtropolis/
├── Meowtropolis/                     # App target source
│   ├── MeowtropolisApp.swift         # @main entry point, Firebase bootstrap
│   ├── ContentView.swift
│   ├── Firestore/
│   │   ├── ModelCoding.swift
│   │   └── FirestoreCollectionLegacy.swift
│   ├── Models/
│   │   ├── DataModels.swift          # User, Pet, Booking, Product, Order, OrderItem
│   │   ├── CartItem.swift
│   │   ├── VetRequest.swift
│   │   ├── Place.swift
│   │   ├── PlaceSearchRequest.swift
│   │   └── PlaceSearchResponse.swift
│   ├── SampleData/
│   │   └── products.json             # 17 bundled sample products
│   ├── Services/                     # One file per backend/system responsibility
│   ├── State/                        # AppState, CartState, MapState, MarketplaceState
│   └── Views/
│       ├── Auth/
│       ├── Common/
│       ├── Main/
│       └── Shared/
├── MeowtropolisTests/
│   └── MeowtropolisTests.swift
├── MeowtropolisUITests/
│   ├── MeowtropolisUITests.swift
│   └── MeowtropolisUITestsLaunchTests.swift
├── docs/
│   ├── erdiagram.xml                 # Entity-relationship diagram source
│   └── validate_asset_catalog.ps1    # Asset catalog validation script
├── images/                            # 14 reference screen screenshots
├── Pet Care - Pet Services App UI Kit (No Image)/  # Source UI kit assets
├── firestore.rules                   # Development-baseline security rules
├── GoogleService-Info.plist.example  # Sanitized Firebase config template
├── SECURITY.md
├── LICENSE
└── README.md

Data Model

Firestore collection names are centralized in FirestoreCollections and used everywhere instead of hardcoded strings:

Collection Model Key Fields
users User id, name, email, preferredLanguageCode?, profileImageBase64?, role?
pets Pet id, userId, name, breed, age?
bookings Booking id, userId, petId, serviceType, date (ISO-8601), status (pending | confirmed | completed | cancelled)
products Product id, name, price, category, imageURL, stock (defaults to 50 if absent)
orders Order id, userId, items: [OrderItem], totalAmount, currencyCode, status, createdAt
vetRequests VetRequest see Models/VetRequest.swift

OrderItem snapshots the product data at time of purchase (productId, name, category, imageURL, unitPrice, quantity, lineTotal) so historical orders remain accurate even if a product's price or listing changes later.

Sample product record (from SampleData/products.json, used for local fallback and one-time Firestore seeding):

{
  "id": "product_cat_001",
  "name": "Black Sand Bentonite Cat Litter 10L",
  "price": 620,
  "category": "cat",
  "imageURL": "img_black_sand_carbon_bentonite_cat_litter_10l_600x588",
  "stock": 15
}

An entity-relationship diagram source is available at docs/erdiagram.xml.

Getting Started

Prerequisites

  • macOS with a recent stable Xcode
  • iOS Simulator or a physical iPhone
  • A Firebase project with Authentication and Cloud Firestore enabled
  • A Google Cloud project with Maps SDK for iOS and Places API enabled

Installation

  1. Clone the repository
    git clone https://github.com/mayer-doa-coder/Meowtropolis.git
    cd Meowtropolis
  2. Open the project in Xcode
    open Meowtropolis.xcodeproj
  3. Add your Firebase config — download GoogleService-Info.plist from your Firebase project's console (Project settings → General → Your apps) and drag it into the Meowtropolis app target in Xcode. A template describing the expected fields is at GoogleService-Info.plist.example.
    • In Firebase Console, enable Authentication → Sign-in method → Email/Password.
    • Enable Cloud Firestore and create a database (start in the default/native mode).
  4. Add your Google Maps/Places key — add a GOOGLE_MAPS_API_KEY entry to the app target's Info.plist with your key value (see Configuration). In Google Cloud Console, make sure both Maps SDK for iOS and Places API are enabled for that key's project, and restrict the key to your iOS bundle identifier once things work.
  5. Set your signing team in Xcode (Signing & Capabilities) if you plan to run on a physical device.
  6. Build and run on a simulator or device (⌘R).

Usage

Once configured, launch the app to land on the splash/onboarding flow, then sign up or log in. From the dashboard you can:

Dashboard → Pet Profile      → add/edit/remove pets
         → Grooming / Vet    → book appointments or submit vet requests
         → Marketplace       → browse, add to cart, checkout
         → Pet Blog          → read featured articles
         → Map               → find nearby pet-friendly places

The marketplace auto-seeds sample products into Firestore the first time you open it on a fresh project (see Services/ProductSeedService.swift), so there's no manual data entry needed to try it out.

Demo Admin Access

For quick evaluation without hand-provisioning an admin account, AppState recognizes a built-in demo admin login (admin@meowtropolis.com). Logging in with those demo credentials on a fresh Firebase project auto-provisions the account and its role: "admin" Firestore profile on first attempt, then routes into AdminDashboardView instead of the regular customer DashboardView. For any real deployment, remove or replace this demo shortcut and manage admin access exclusively through your own allowlist/role system.

Switching Language

Use the language selector (persisted via AppLanguage/UserDefaults and mirrored to the user's Firestore profile) to switch the entire UI between English (US) and Bangla at runtime — no restart required.

Testing

Two test targets are included:

  • MeowtropolisTests — unit tests (MeowtropolisTests.swift)
  • MeowtropolisUITests — UI tests, including a dedicated launch-performance test (MeowtropolisUITestsLaunchTests.swift)

Run either from Xcode's Test navigator, or from the command line:

xcodebuild test \
  -project Meowtropolis.xcodeproj \
  -scheme Meowtropolis \
  -destination 'platform=iOS Simulator,name=iPhone 15'

For quick, beginner-friendly manual verification of backend wiring (without a full XCTest run), see Services/BackendSmokeTests.swift and the smoke-test helper in Services/BookingService.swift.

Configuration

Key Where Purpose
GoogleService-Info.plist App target root Firebase project credentials (Auth + Firestore)
GOOGLE_MAPS_API_KEY Info.plist Enables Google Maps SDK and Places API lookups
appLanguageCode UserDefaults (key defined by AppLanguage.storageKey) Persists the selected UI language (en-US or bn-BD) across launches

Firestore's development-baseline rule (firestore.rules) allows read/write to any authenticated user — tighten this before shipping to production.

Admin access is controlled by a hardcoded email allowlist (adminEmails) inside State/AppState.swift. For production use, replace this with a proper role/claims-based authorization mechanism instead of an in-app allowlist.

Firestore Rules & Security

The repository ships with a development-baseline Firestore rule set (firestore.rules):

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

This means any authenticated user can read or write any document — convenient for local development and demos, but not suitable for production. Before shipping, scope rules per-collection (e.g. users can only write their own users/{uid} document, orders are only readable by their owner, products are admin-write / public-read, etc.).

A few practical security notes for contributors:

  • Never commit a real GoogleService-Info.plist — it's already excluded via .gitignore; use GoogleService-Info.plist.example as the template for the fields Xcode/Firebase expect.
  • Treat any Google/Firebase API key that ends up in git history as compromised — rotate or restrict it (by bundle ID / API scope) in Google Cloud Console rather than assuming deletion from a later commit is sufficient.
  • See SECURITY.md for how to report a vulnerability.

Troubleshooting

Profile image save fails

  • Ensure user is still authenticated.
  • Large images are automatically resized/compressed before save.
  • If Firestore errors still occur, verify network and Firestore permissions.

Marketplace has no products in Firestore

  • Open marketplace once while signed in to trigger one-time auto-seeding.
  • If one manual product already exists, seeding is skipped by design.

Maps/Places returns configuration errors

  • Confirm API key exists and required APIs are enabled.
  • Confirm key restrictions allow your iOS bundle/app setup.

Firebase auth/profile inconsistencies

  • Verify GoogleService-Info.plist belongs to the same Firebase project you are inspecting in console.

Admin dashboard doesn't appear after logging in as admin

  • Admin routing requires both isAdmin (email present in the adminEmails allowlist in AppState) and prefersAdminHome (set when logging in through the demo-admin path) to be true — logging in with a non-allowlisted email will always route to the regular customer dashboard.

Booking reminders never fire

  • Confirm the app has notification permission granted on the device/simulator; ReminderService schedules local notifications but cannot deliver them without OS-level permission.

Known Limitations & Roadmap

  • Checkout does not process real paymentsOrderService places stock-safe orders in Firestore, but there is no payment gateway integration; this is a demo/prototype checkout flow.
  • Firestore rules are a development baseline, not production-hardened (see Firestore Rules & Security).
  • Admin access uses a hardcoded email allowlist rather than custom claims/roles — fine for a demo, not for multi-admin production use.
  • Demo admin auto-provisioning exists for convenience during evaluation and should be removed before a public production release.

Contributing

Contributions are welcome. To propose a change:

  1. Fork the repo and create a feature branch
  2. Keep changes scoped — UI in Views, side effects in Services, orchestration in State
  3. Add or update tests under MeowtropolisTests / MeowtropolisUITests where relevant
  4. Open a pull request with a clear description of what changed and why

Please don't commit real Firebase credentials or API keys — use the .example template and your own local config.

Acknowledgments

  • UI reference designs sourced from the "Pet Care - Pet Services App UI Kit" (see images/ and Pet Care - Pet Services App UI Kit (No Image)/).
  • Built with Firebase (Authentication, Cloud Firestore) and Google Maps Platform (Maps SDK, Places API).

License

Released under the MIT License.

About

Meowtropolis is your all-in-one pet care companion—offering grooming services, veterinary support, pet supplies, and trusted care solutions to keep your furry friends happy and healthy.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages