A modern Kotlin Multiplatform trekking/hiking application for exploring the legendary CamΓ de Cavalls trail in Menorca, Spain. This 185km coastal path is divided into 20 distinct stages, offering hikers an unforgettable journey around the island.
CamΓ de Cavalls is a production-ready mobile application designed for both Android and iOS platforms, showcasing best practices in Kotlin Multiplatform development. The app serves as both a portfolio project and a commercial application targeting local partnerships with Menorcan businesses.
Implemented:
- π GPS Tracking: Real-time location tracking with high accuracy (5s intervals, 5m minimum distance)
- π‘ Background Tracking: Reliable GPS tracking that continues when the app is in the background
- Android: Foreground service with persistent notification, wake lock, and START_STICKY restart recovery
- iOS: CoreLocation background mode with blue status bar indicator
- Process death recovery: tracking state persisted to SharedPreferences, automatic session restore on app reopen
- β±οΈ Live Duration: Real-time duration counter during tracking, independent from GPS updates
- π΄ Offline Mode: Complete offline support - all data stored locally in SQLDelight, GPS works without internet
- π Notebook: Track hiking sessions with automatic statistics calculation (distance via Haversine formula, duration, elevation, speed)
- π Session Details: View recorded sessions with gradient-colored track visualization (greenβyellowβred based on altitude)
- π€ GPX Export: Share recorded sessions as GPX files via system share sheet (Android & iOS)
- π Location Permissions: Smart permission handling with native dialogs for Android and iOS
- πΊοΈ Route Database: Complete data for all 20 CamΓ de Cavalls stages with accurate KML data (~185km, ~2,480m elevation gain, ~55 hours)
- π Session Statistics: Automatic calculation of distance, speed, elevation gain/loss during tracking
- πΊοΈ Interactive Maps: MapLibre integration with route visualization, markers, and smooth map interaction on both platforms
- π― Accurate Route Data: All 20 routes converted from official KML source with ~130 optimized points per route
- π§ GPS Toggle Button: Smart GPS following with automatic disable on map gestures (pan, zoom) - works consistently on both Android and iOS
- π Multilingual: Full support for 6 languages (Catalan, Spanish, English, French, German, Italian) with custom localization system, multilingual route descriptions, and localized settings screen
- π Elevation Charts: Interactive elevation profiles with tap/drag gestures, centered tooltips, map synchronization, and smooth curve rendering
- βοΈ Settings Hub: Dedicated settings screen with language selection, about page, and contact options
- ποΈ Points of Interest: Complete database of 190 POIs with multilingual content (6 languages) - coastal zones, natural areas, and historic sites with accurate coordinates from official map
- π§ Bottom Navigation Bar: WeWard-style dark bottom bar with 5 tabs (Map, Routes, POI, Diary, Settings) replacing the drawer menu
- πΊοΈ Complete Route: Virtual "Complete Route" (185km full loop MaΓ³βMaΓ³) combining all 20 stages with aggregated statistics, elevation profile, and combined GeoJSON track
- π Session Summary Overlay: Post-tracking session summary displayed as a dark card overlay on the map with key statistics
- π± Android Immersive Mode: Full-screen experience hiding system navigation bar with swipe-to-reveal
- π POI Proximity Notifications: Real-time local notifications when approaching nearby POIs during tracking, with tap-to-open POI detail popup (both Android and iOS), notification view analytics tracked to backend with offline queue
- π¬ Animated Splash Screen: Menorca silhouette with route tracing animation, cross-fade to logo, randomized horse/horseshoe variant
- π¦οΈ Weather Forecast: 5-day weather forecast popup via Open-Meteo API with daily temperature, precipitation, and weather icons
- π User Authentication: Email/password registration with email verification, Google OAuth (Android & iOS), Apple Sign-In (iOS only), user profile with avatar upload
- β Likes & Reviews: Route and POI like system (device-based + authenticated), user reviews with star ratings and localized timestamps
β οΈ Danger Reports: Submit trail hazard reports with photos (up to 4), GPS coordinates, and description; offline queue with automatic retry and sync; blocked user prevention on photo uploads- π¨ Polished UI: Complete Material 3 color scheme (sea-blue palette), consistent dark FAB buttons during tracking, rich card layouts with route images and gradient overlays for Notebook and Danger Reports lists, image carousel with swipe and dot indicators
- V1: Free app with essential features, user accounts, likes, reviews, and danger reports
- V2: Integration with local businesses (restaurants, hotels, shops) for commercial partnerships
The application is designed to be independent from external sources:
- Route data, GPX files, and POI information are stored locally in the app
- Optional backend integration for dynamic content updates
- No dependencies on third-party websites for core functionality
- Data sourced from official CamΓ de Cavalls resources (camidecavalls.com) but stored and managed independently
The project follows Clean Architecture principles with clear separation of concerns:
composeApp/
βββ src/
β βββ commonMain/
β β βββ kotlin/
β β β βββ domain/ # Business logic layer
β β β β βββ model/ # Domain entities
β β β β β βββ Route.kt
β β β β β βββ PointOfInterest.kt
β β β β β βββ TrackingSession.kt
β β β β β βββ TrackPoint.kt
β β β β βββ repository/ # Repository interfaces
β β β βββ data/ # Data layer
β β β β βββ local/ # Database (SQLDelight)
β β β β βββ repository/ # Repository implementations
β β β βββ di/ # Dependency injection (Koin)
β β β βββ presentation/ # UI layer
β β βββ sqldelight/ # Database schema
β β βββ com/followmemobile/camidecavalls/database/
β β βββ Route.sq
β β βββ PointOfInterest.sq
β β βββ TrackingSession.sq
β β βββ TrackPoint.sq
β βββ androidMain/ # Android-specific code
β β βββ kotlin/
β β βββ data/local/ # Android SQLite driver
β β βββ di/ # Android DI module
β βββ iosMain/ # iOS-specific code
β βββ kotlin/
β βββ data/local/ # iOS SQLite driver
β βββ di/ # iOS DI module
βββ iosApp/ # iOS application wrapper
-
Domain Layer (Pure Kotlin)
- Business entities and models (Route, POI, TrackingSession, TrackPoint)
- Repository interfaces
- Use cases (18 implemented):
- Route management: GetAllRoutes, GetRouteById, GetRouteByNumber, SaveRoutes, InitializeDatabase
- POI management: GetAllPOIs, GetPOIsByType, GetPOIsNearLocation, GetPOIsByRoute, SavePOIs
- Tracking: StartSession, StopSession, AddTrackPoint, CalculateStats, GetActiveSession, GetAllSessions, GetSessionById, DeleteSession
- Services: LocationService, PermissionHandler
-
Data Layer
- Repository implementations with Flow-based reactive queries
- Local database (SQLDelight) with 4 tables and optimized indexes
- LocationService implementations (Android: FusedLocationProvider, iOS: CoreLocation)
- PermissionHandler implementations (platform-specific dialogs)
- Platform-specific implementations (expect/actual)
-
Presentation Layer
- MainScreen with bottom navigation bar (5 tabs: Map, Routes, POI, Notebook, Settings)
- ScreenModels (Voyager): RoutesScreenModel, RouteDetailScreenModel, TrackingScreenModel, NotebookScreenModel, POIsScreenModel, SettingsScreenModel, AboutScreenModel
- Tab content: MapTabContent (GPS + routes), RoutesTabContent (route list + complete route), POIsTabContent (POI map + filters), NotebookTabContent (session diary), SettingsHubContent (settings hub)
- Detail screens: RouteDetailScreen, SessionDetailScreen, POIDetailScreen, LanguageSettingsScreen, AboutCamiScreen
- Auth screens: LoginScreen, AccountScreen with Google OAuth integration
- Interactive components: ElevationChart with Canvas API, WeWardBottomBar, CompletedOverlay, ImageCarousel (HorizontalPager)
- Sheets: DangerReportSheet, ReviewSheet, ReviewsListSheet
- Settings: DangerReportsQueueScreen, DevDebugScreen
- RouteSelectionManager for cross-tab route communication
- Navigation with Voyager (detail screens pushed on top of MainScreen)
- Material 3 UI with complete sea-blue color scheme
- Kotlin 2.2.0: Programming language
- Kotlin Multiplatform: Code sharing across platforms
- Compose Multiplatform 1.9.1: Declarative UI framework
- Clean Architecture: Separation of concerns
- MVVM Pattern: Model-View-ViewModel
- Repository Pattern: Data access abstraction
- Expect/Actual: Platform-specific implementations
- Koin 4.0.0: Lightweight DI framework
- SQLDelight 2.0.2: Type-safe SQL database
- 4 tables: Routes, POIs, Tracking Sessions, Track Points
- Optimized indexes for location queries
- Foreign keys with cascade deletes
- Ktor 3.0.1: HTTP client (backend API, Open-Meteo weather API)
- Coil 3: Multiplatform image loading (SubcomposeAsyncImage for route photos, avatars, danger report images)
- Voyager 1.1.0-beta02: Type-safe navigation
- Kotlinx Coroutines 1.9.0: Asynchronous programming
- Kotlinx Flow: Reactive streams
- Kotlinx Serialization 1.7.3: JSON serialization
- Kotlinx DateTime 0.6.1: Date/time handling
- Napier 2.7.1: Multiplatform logging
- MapLibre Compose 0.0.7: Interactive maps with native rendering
- Android: MapLibre Native Android with AndroidView integration
- iOS: MapLibre Native iOS with UIKitView integration
- OpenFreeMap tiles for offline-capable map rendering
- Route path rendering with LineLayer
- Marker support with CircleLayer
- Platform-specific MapLayerController with expect/actual pattern
- Google Play Services Location 21.3.0: GPS tracking (Android)
- Core Location: GPS tracking (iOS)
- Multiplatform Settings 1.2.0: Key-value storage
Stores the 20 stages of CamΓ de Cavalls.
- id: INTEGER PRIMARY KEY
- number: INTEGER UNIQUE (1-20)
- name: TEXT
- startPoint: TEXT
- endPoint: TEXT
- distanceKm: REAL
- elevationGainMeters: INTEGER
- elevationLossMeters: INTEGER
- maxAltitudeMeters: INTEGER
- minAltitudeMeters: INTEGER
- asphaltPercentage: INTEGER
- difficulty: TEXT (LOW, MEDIUM, HIGH)
- estimatedDurationMinutes: INTEGER
- description: TEXT
- gpxData: TEXT (nullable)
- imageUrl: TEXT (nullable)Stores natural, historic, and commercial POIs.
- id: INTEGER PRIMARY KEY
- name: TEXT
- type: TEXT (NATURAL, HISTORIC, BEACH, VIEWPOINT, etc.)
- latitude: REAL
- longitude: REAL
- description: TEXT
- images: TEXT (JSON array)
- routeId: INTEGER (nullable, FK to Route)
- isAdvertisement: INTEGER (0/1)Indexes:
poi_location_idxon (latitude, longitude)poi_type_idxon (type)
Stores user hiking sessions (Notebook feature).
- id: TEXT PRIMARY KEY
- routeId: INTEGER (nullable, FK to Route)
- startTime: INTEGER (epoch milliseconds)
- endTime: INTEGER (nullable)
- distanceMeters: REAL
- durationSeconds: INTEGER
- averageSpeedKmh: REAL
- maxSpeedKmh: REAL
- elevationGainMeters: INTEGER
- elevationLossMeters: INTEGER
- isCompleted: INTEGER (0/1)
- notes: TEXTStores GPS points for each tracking session.
- id: INTEGER PRIMARY KEY AUTOINCREMENT
- sessionId: TEXT (FK to TrackingSession, CASCADE DELETE)
- latitude: REAL
- longitude: REAL
- altitude: REAL (nullable)
- timestamp: INTEGER (epoch milliseconds)
- speedKmh: REAL (nullable)Indexes:
track_point_session_idxon (sessionId)track_point_timestamp_idxon (timestamp)
Stores POIs synced from the backend.
Offline queue for view analytics (deduplication via composite primary key).
- targetType: TEXT (POI, DANGER_REPORT)
- targetId: TEXT
- viewType: TEXT (PREVIEW, DETAIL, NOTIFICATION)
- createdAt: TEXT
- PRIMARY KEY (targetType, targetId, viewType)- Android Studio Ladybug (2024.2.1) or later
- Xcode 15.0 or later (for iOS development)
- JDK 11 or later
- Gradle 8.10.2 (included via wrapper)
-
Clone the repository
git clone <repository-url> cd CamΓDeCavalls
-
Open in Android Studio
- File β Open β Select project directory
- Wait for Gradle sync to complete
-
For iOS development (macOS only)
- Ensure Xcode is installed
- Open
iosApp/iosApp.xcodeprojin Xcode - Important: Add
libsqlite3.tbdto "Link Binary With Libraries" in Build Phases - Build the project
The app uses product flavors (dev / production) Γ build types (debug / release):
| Variant | API URL | App ID |
|---|---|---|
devDebug |
http://192.168.8.106:3002 |
com.followmemobile.camidecavalls.dev |
productionDebug |
https://camidecavalls.followtheflowai.com |
com.followmemobile.camidecavalls |
productionRelease |
https://camidecavalls.followtheflowai.com |
com.followmemobile.camidecavalls |
- Open project in Android Studio
- Select the desired build variant in Build β Select Build Variant
- Click Run (or press βR)
To change the dev API URL (e.g., your LAN IP), edit the dev flavor in composeApp/build.gradle.kts:
buildConfigField("String", "API_BASE_URL", "\"http://YOUR_IP:3002\"")Two shared schemes with separate bundle IDs (installed as separate apps):
| Scheme | Configuration | API URL | Bundle ID |
|---|---|---|---|
| iosApp Dev | Debug | http://192.168.8.106:3002 |
com.followmemobile.camidecavalls.dev |
| iosApp Production | ProductionDebug | https://camidecavalls.followtheflowai.com |
com.followmemobile.camidecavalls |
- Open
iosApp/iosApp.xcodeprojin Xcode - Select the desired scheme (iosApp Dev or iosApp Production)
- Click Run (or press βR)
iOS Configurations:
- Debug: Fast compilation, dev settings (used by iosApp Dev scheme)
- ProductionDebug: Fast compilation, production settings (used by iosApp Production scheme)
- Release: Optimized compilation for App Store distribution
Dev builds include visual indicators on both platforms:
- App Icon: Red banner with white "DEV" text at the bottom of the icon
- In-App Ribbon: Diagonal red ribbon with "DEV" text in the top-left corner (only visible when
AppConfig.isDebugis true)
./gradlew :composeApp:assembleProductionReleaseAPK location: composeApp/build/outputs/apk/production/release/
Build in Xcode with the Release configuration for App Store distribution.
- Project setup and configuration
- Clean Architecture structure
- Domain models (Route, POI, TrackingSession, TrackPoint)
- SQLDelight database implementation with 4 tables
- Repository pattern implementation with Flow
- Dependency injection with Koin 4.0.0
- Android build working
- iOS build working
- Use Cases implementation (18 total)
- Route management: GetAll, GetById, GetByNumber, Save, InitializeDatabase
- POI queries: GetAll, ByType, NearLocation, ByRoute, Save
- Tracking: Start, Stop, AddPoint, CalculateStats, GetActive, GetAll, GetById, Delete
- Route data preparation
- All 20 CamΓ de Cavalls stages with realistic data
- Database initialization on first app launch
- Offline-first data strategy
- TrackingManager with battery optimization
- Navigation setup with Voyager
- HomeScreen with route list (Material 3 cards)
- RouteDetailScreen with complete route information
- TrackingScreen (Notebook) with real-time GPS
- Location permission handling (Android & iOS)
- GPS tracking implementation
- Android LocationService (FusedLocationProvider)
- iOS LocationService (CoreLocation)
- Battery optimization (5s intervals, 5m min distance, balanced accuracy)
- Offline tracking (no internet required)
- Permission handling
- Android runtime permissions with ActivityResultContracts
- iOS authorization dialogs
- Smart permission flow in UI
- TrackingManager
- Automatic track point recording
- Real-time statistics calculation (Haversine formula)
- Session state management (Idle, Tracking, Completed, Error)
- MapLibre integration
- MapLibre Compose integration on both platforms
- Platform-specific implementations (AndroidView, UIKitView)
- OpenFreeMap tile provider for offline-capable maps
- MapLayerController with expect/actual pattern
- Route visualization
- GeoJSON LineString rendering with LineLayer
- Route path with colored lines and white casing
- Start/end markers with CircleLayer (green for start, red for end)
- Smart camera positioning with automatic zoom calculation
- Bounding box calculation to fit entire route
- Aspect-ratio aware zoom for balanced framing
- Accurate route data
- All 20 routes converted from official KML source
- Coordinate simplification (~130 points per route)
- Fixed Route 11 (Ciutadella - Cap d'Artrutx) with correct segment order
- Database versioning with AppPreferences for automatic data updates
- Map interactions
- Pan, zoom, and rotate gestures on both platforms
- Fixed Android scroll conflict with requestDisallowInterceptTouchEvent
- Smooth map interaction without parent scroll interference
- Map preview in RouteDetailScreen (350dp height, rounded corners)
- Equal margins for both vertical (N-S) and horizontal (E-W) routes
- Interactive elevation charts
- ElevationChart component with Canvas API
- GeoJSON parsing and elevation data extraction
- Haversine distance calculation for cumulative distance
- Smooth curve rendering with quadratic Bezier curves
- Interactive tap and drag gestures for point selection
- Centered tooltip with elevation and distance info
- Transparent overlay for chart visibility
- Map marker synchronization
- Settings screen
- In-app language selection (6 languages)
- Settings localization with custom LocalizedStrings
- Language persistence across app launches
- POI data collection (190 POIs from official website)
- Multilingual scraping (6 languages: CA, ES, EN, DE, FR, IT)
- Coordinate correction (UTM to WGS84 conversion)
- POI database with versioning system
- POI categories: Coastal zones, Natural areas, Historic sites
- Bottom navigation bar (WeWard-style dark theme)
- 5 tabs: Map, Routes, POI, Diary, Settings
- Dark background (#1C1C2E) with rounded top corners
- Blue accent for selected tab, gray for unselected
- Variable weight layout for proportional label sizing
- Auto-hide during active GPS tracking
- MainScreen as root with tab-based navigation
- RouteSelectionManager for cross-tab route communication
- Detail screens pushed on top (bottom bar hidden)
- Complete Route feature
- Virtual 185km full loop (MaΓ³βMaΓ³) combining all 20 stages
- Aggregated statistics (elevation, duration, difficulty)
- Combined GeoJSON track from all route segments
- Multilingual descriptions in 6 languages
- Settings hub with language, about, and contact options
- Session summary overlay (dark card on map after tracking)
- Android immersive mode (hidden system navigation bar)
- Drawer menu fully replaced and removed
- POI proximity notifications
- Real-time detection of nearby POIs during tracking
- Local notifications with tap-to-open POI detail popup
- Android: PendingIntent with foreground service integration
- iOS: UNUserNotificationCenter with delegate-based tap handling
- PoiNavigationManager for cross-component notification navigation
- Animated splash screen
- Menorca silhouette with route tracing animation (Canvas API)
- Cross-fade to logo with parallel scale animation
- Randomized horse/horseshoe logo variant per launch
- Always-composed phases with graphicsLayer for iOS performance
- Weather forecast
- Open-Meteo API integration via Ktor HTTP client
- 5-day forecast with daily temperature, precipitation, and weather codes
- Weather popup accessible from MAP tab FAB
- CRM Backend (Next.js 15 + PostgreSQL + Prisma)
- POI management with multilingual translations (6 languages)
- Dashboard tables with multi-column filters and sortable headers
- Hardcoded POI seeding: all 190 app POIs in the database (1,140 translations) for view/like tracking
- Image upload with Sharp-based optimization
- User management with role-based access (ADMIN, EDITOR, VIEWER)
- Danger reports moderation with view/like/notification analytics
- REST API for mobile app sync (
/api/v1/pois,/api/v1/sync-status,/api/v1/views) - Public pages: privacy policy, support, account deletion (6 languages, required for Google Play)
- Docker deployment on EasyPanel (Contabo)
- Remote POI sync in KMP app
- Incremental sync via
?since=parameter (daily, if connected) - Sync version mechanism for forced cache migration
- Remote POIs merged with hardcoded POIs via Flow combine
- Environment-aware endpoint switching with automatic cache clear
- Incremental sync via
- Build variants / schemes
- Android: product flavors (dev/production) with separate app IDs
- iOS: Xcode schemes with ProductionDebug config for fast compilation
- DEV indicators: badged icons + in-app diagonal ribbon
- See
web/README.mdfor full CRM documentation
- User authentication system
- Email/password registration with server-side validation
- Email verification with multilingual templates (6 languages)
- Google OAuth sign-in (Android: CredentialManager, iOS: GoogleSignIn SDK)
- Apple Sign-In (iOS only, ASAuthorizationController with expect/actual pattern)
- JWT-based session management with token refresh
- User profile with avatar upload (base64 encoding + server-side Sharp optimization)
- Likes system
- Device-based + authenticated likes for routes and POIs
- Optimistic UI updates with server sync
- Like counts displayed on route and POI cards
- Reviews system
- Star rating (1-5) with text review for routes and POIs
- User's own review management (edit/delete)
- Reviews list with paginated display
- Danger reports
- Trail hazard reporting with GPS coordinates, description, and up to 4 photos
- Offline queue manager with automatic retry on connectivity restore
- Photo upload with compression (ImageCompressor expect/actual)
- Daily submission limit per device
- Danger reports queue screen with image carousel (swipe + dot indicators)
- Status tracking: PENDING β SENDING β SENT / FAILED
- UI polish
- Complete Material 3 sea-blue color scheme (no default purple)
- Consistent dark FAB buttons on tracking screen
- Rich card layouts: Notebook sessions with route images, Danger reports with photo backgrounds
- Image carousel (HorizontalPager) with animated dot indicators for multi-photo danger reports
- hero_cami.jpg fallback for sessions without specific route and danger reports without photos
- Dashboard admin features
- App users management with status actions (activate, suspend, ban)
- Admin can activate PENDING_VERIFICATION users without email verification
- Danger reports moderation with blocked user protection on photo uploads
- Multilingual API responses based on user language
- Notification analytics: proximity notification views tracked per POI/danger report with offline queue and deduplication
- Route navigation with turn-by-turn
- Statistics and achievements
- Photo gallery for routes
- Social features
- Analytics
Milestones 1-11 Completed β
The app is now fully functional with core trekking features, interactive maps, elevation charts, settings, complete POI database, bottom navigation bar, user authentication, social features (likes, reviews), and danger reporting:
Architecture & Foundation:
- Clean Architecture fully implemented across 3 layers
- SQLDelight database with 4 tables and optimized indexes
- Repository pattern with Flow-based reactive queries
- Koin 4.0.0 dependency injection (platform-specific modules)
- Cross-platform compilation verified (Android + iOS)
- AppPreferences system for database versioning
Business Logic:
- 18 use cases implemented and tested
- Accurate route data for all 20 CamΓ de Cavalls stages (~185km)
- All routes converted from official KML source
- TrackingManager with battery-optimized GPS tracking
- Haversine formula for accurate distance calculation
- Automatic statistics calculation (distance, speed, elevation)
User Interface:
- Material 3 design system
- Voyager navigation (type-safe, without voyager-koin)
- Bottom navigation bar with 5 tabs (Map, Routes, POI, Diary, Settings)
- WeWard-style dark theme with blue accent for selected tab
- Variable weight layout for proportional label sizing
- Auto-hides during active GPS tracking for maximum map space
- MainScreen as root with tab-based content switching
- RoutesTabContent with route list cards + complete route (185km MaΓ³βMaΓ³)
- RouteDetailScreen with complete stage information, map preview, and elevation chart
- MapTabContent (formerly TrackingScreen) with real-time GPS display
- Settings hub with language selection, about page, and contact options
- Session summary overlay: dark card on map after tracking with key statistics
- ElevationChart component with interactive visualization
- RouteSelectionManager for cross-tab route communication
- Android immersive mode hiding system navigation bar
- Smart location permission handling
GPS Tracking:
- Android: FusedLocationProviderClient with high accuracy in foreground service
- iOS: CoreLocation with CLActivityTypeFitness and background mode
- Background tracking: Foreground service (Android) / background location updates (iOS)
- Process death recovery: SharedPreferences persistence + START_STICKY + automatic session restore
- GPS accuracy filtering: first-fix (30m threshold) + ongoing (50m threshold)
- Elevation dead band: 3m threshold to filter GPS altitude noise in statistics
- Offline-first: All data saved locally in SQLDelight
- Configurable intervals (5s default) and accuracy (high)
- Pause detection and session management
- GPS toggle button: Automatic disable on map gestures (drag, zoom), manual toggle support
- Platform-specific gesture detection (MLNMapViewDelegate on iOS, CameraMoveListener on Android)
- Emulator/simulator support with relaxed filters for development testing
Interactive Maps:
- MapLibre integration with native rendering on both platforms
- OpenFreeMap tiles for offline-capable map display
- Route visualization with colored LineLayer and white casing
- Start/end markers with CircleLayer (green/red)
- Smart camera positioning with bounding box calculation
- Aspect-ratio aware zoom for balanced route framing
- Equal margins for vertical (N-S) and horizontal (E-W) routes
- Smooth pan, zoom, and rotate gestures
- Fixed scroll conflicts on Android with requestDisallowInterceptTouchEvent
- Map preview in RouteDetailScreen (350dp height, rounded corners)
- Platform-specific implementations (AndroidView, UIKitView)
Elevation Charts:
- Interactive elevation profile component with Compose Canvas API
- GeoJSON parsing for elevation data extraction
- Haversine formula for accurate cumulative distance calculation
- Smooth curve rendering using quadratic Bezier curves
- Interactive tap and drag gestures for point selection
- Centered tooltip with elevation and distance information
- Transparent overlay (70% opacity) for chart visibility
- Real-time map marker synchronization on point selection
- Positioned below map for optimal user experience
Settings & Localization:
- Settings screen with Material 3 design
- In-app language selection (6 languages: CA, ES, EN, DE, FR, IT)
- Custom LocalizedStrings system for app-controlled localization
- Language persistence across app launches
- Full settings localization (titles, language names)
- Independent from system language settings
Points of Interest (POI):
- Complete database of 190 POIs from official CamΓ de Cavalls website
- Multilingual content in 6 languages (CA, ES, EN, DE, FR, IT)
- Three POI categories with color-coded badges:
- Coastal zones (blue): Beaches, coves, coastal areas
- Natural areas (green): Protected spaces, flora, fauna
- Historic sites (red): Towers, lighthouses, prehistoric sites
- Accurate coordinates corrected from official map (UTM Zone 31N β WGS84)
- POI versioning system for automatic updates
- Custom scraping tools for data collection and coordinate validation
- Fixed 16 POIs with coordinate errors > 800m (up to 8.3 km corrections)
POI Proximity Notifications:
- Real-time detection of nearby POIs during active tracking
- Local notifications with POI name and distance
- Tap-to-open: notification tap navigates to MAP tab and shows POI detail popup
- Android: PendingIntent with singleTop launch mode, foreground service integration
- iOS: UNUserNotificationCenter with delegate-based tap handling
- PoiNavigationManager singleton for cross-component event coordination
- Notification analytics: each notification event tracked to backend via ViewTracker (targetType=POI/DANGER_REPORT, viewType=NOTIFICATION)
- Offline queue with composite PK deduplication (PendingViewEntity: targetType+targetId+viewType)
- Automatic flush on connectivity restore
Animated Splash Screen:
- Menorca silhouette drawn with Canvas API SVG path rendering
- Route tracing animation with color-coded segments
- Cross-fade to logo with parallel scale-in animation
- Randomized horse/horseshoe logo variant per launch
- Always-composed phases with graphicsLayer alpha for iOS performance
- Smooth screen fade-out transition to main content
Weather Forecast:
- Open-Meteo API integration via Ktor HTTP client
- 5-day forecast with daily min/max temperature, precipitation, and WMO weather codes
- Custom weather icons mapped to WMO code ranges
- Weather popup accessible from MAP tab floating action button
- Loading and error states with localized messages
User Authentication & Social Features:
- Email/password registration with email verification (multilingual templates)
- Google OAuth sign-in (Android CredentialManager + iOS GoogleSignIn SDK)
- Apple Sign-In (iOS only, ASAuthorizationController with NSUserDefaults email caching)
- JWT session management with secure token storage
- User profile with avatar upload
- Likes system for routes and POIs (device-based + authenticated)
- Reviews with star ratings for routes and POIs
- Danger reports with photo upload, offline queue, and automatic retry
- Complete Material 3 sea-blue color scheme replacing default purple
- Rich card layouts for Notebook (route image backgrounds) and Danger Reports (photo carousel with swipe)
- Consistent dark FAB styling across tracking screen
- Dashboard: admin user activation, danger report moderation, multilingual API responses
- Dashboard tables: multi-column filters (type, source, status) and sortable column headers (asc/desc) with top-aligned headers
- Dashboard analytics: view counts (preview/detail/notification) and likes displayed horizontally per POI and danger report
- Hardcoded POI seeding: 190 POIs with 1,140 translations in the backend for view/like/notification analytics
- Blocked user protection: photo upload endpoint rejects submissions from blocked accounts with AccountBlockedException propagation
- Public pages: privacy policy, support, account deletion with i18n (6 languages, Google Play compliance)
Ready for: Route navigation, statistics, and advanced features
This is currently a portfolio/commercial project. Contributions are not being accepted at this time.
Copyright Β© 2024-2026 Stefano Russello. All rights reserved.
This project is private and proprietary. No license is granted for use, modification, or distribution.
Stefano Russello
- Portfolio Project
- Target: Production app + Commercial partnerships in Menorca
- CamΓ de Cavalls Official Website
- Kotlin Multiplatform Documentation
- Compose Multiplatform
- SQLDelight
- Koin Documentation
- MapLibre - Open-source mapping platform
- MapLibre Compose - MapLibre integration for Compose Multiplatform
- OpenFreeMap - Free tile provider
- iOS SQLite: Requires manual addition of
libsqlite3.tbdin Xcode Build Phases β Link Binary With Libraries- This may need to be re-added after clean builds or Xcode updates
- iOS Altitude Simulation: iOS Simulator ignores elevation data from GPX files (Apple limitation)
- Altitude-based features can only be tested on real iOS devices
- Android emulator correctly simulates altitude from GPX files
- Voyager-Koin: Integration removed due to compatibility issues (using Koin directly with parametersOf)
- iOS Map UIKitView: Uses deprecated UIKitView API (newer API available but current version stable)
- GPS Toggle Button on iOS (Fixed): GPS following now correctly disables on all user gestures (was only working on first gesture)
- Root cause: Delegate loss during Compose recomposition
- Solution: Persistent delegate using
remember+updateblock restoration - See GPS_TOGGLE_FEATURE.md for detailed technical documentation
Database Drivers
- Android:
AndroidSqliteDriverwith Context - iOS:
NativeSqliteDriver(requires libsqlite3.tbd)
Location Services
- Android:
FusedLocationProviderClient(Google Play Services)- High accuracy with FINE granularity for GPS-level precision
- LocationRequest with 5s interval and 2s minimum interval
- Foreground service (
LocationForegroundService) owns GPS and writes track points directly to DB - SharedPreferences persistence for process death recovery via START_STICKY
- Static StateFlow for service-to-app communication
- iOS:
CLLocationManager(Core Location)- CLActivityTypeFitness for hiking optimization
- pausesLocationUpdatesAutomatically = false (never pauses during active tracking)
- Time-based throttling in delegate callback (CoreLocation has no native time interval)
- desiredAccuracy and distanceFilter configuration (min 5m)
Permission Handling
- Android:
ActivityResultContracts.RequestMultiplePermissions()- rememberLauncherForActivityResult in Composables
- ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION
- iOS:
CLLocationManager.requestWhenInUseAuthorization()- Polling-based permission status check
- Info.plist entries for usage descriptions
Dependency Injection
- Shared
appModulein commonMain - Platform-specific modules using expect/actual pattern
- Android: androidContext() for Context injection
- iOS: KoinApplication wrapper in @Composable
- AppPreferences injection for database versioning
Map Integration
- MapLibre Native with platform-specific rendering
- Android:
MapViewwrapped inAndroidViewComposableMapLibreMapfor map instance managementGeoJsonSourcefor route dataLineLayerfor route paths with casingCircleLayerfor start/end markers- Color.parseColor() for hex color support
addOnCameraMoveStartedListenerfor gesture detection
- iOS:
MLNMapViewwrapped inUIKitViewComposableMLNShapeSourcefor route dataMLNLineStyleLayerfor route pathsMLNCircleStyleLayerfor markers- NSData conversion for GeoJSON handling
MLNMapViewDelegateProtocolwith persistent delegate pattern
- MapLayerController: expect/actual pattern for platform abstraction
- OpenFreeMap tiles: Free, offline-capable tile provider
- Gesture handling: Fixed scroll conflicts with pointerInput on Android
- Delegate persistence: Using
rememberandupdateblock to prevent iOS delegate loss during recomposition
Database:
- Indexes on frequently queried columns (location lat/lng, session timestamps)
- Flow-based reactive queries for efficient real-time updates
- Foreign key constraints with cascade deletes for data integrity
- Prepared statements via SQLDelight for query optimization
GPS Tracking:
- High-accuracy update intervals (5s default, min 2s)
- Minimum distance filter (5m) to avoid excessive updates
- GPS accuracy filtering: first-fix (30m) + ongoing (50m) thresholds
- Elevation dead band (3m) to prevent GPS altitude noise from inflating statistics
- O(1) direct track point inserts (replaced O(n) load-all/delete-all/re-insert pattern)
- Emulator/simulator detection for relaxed GPS filters during development
Calculations:
- Haversine formula for accurate GPS distance calculations
- Incremental statistics updates (not recalculating entire session)
- Efficient coordinate transformations for map projections
- Smart zoom calculation with logarithmic formula for route fitting
- Bounding box analysis with padding factor for optimal route visibility
- Aspect-ratio compensation for balanced map framing
- All route data stored locally in SQLite database
- GPX files embedded in app or downloaded to local storage
- Optional backend integration for updates (not dependent on external sites)
- Offline-first architecture for reliable operation without internet