Skip to content

Repository files navigation

Siphon

Real-time fuel station prices for Spain and Portugal — built with Expo.

Overview

Siphon is a React Native + Expo app for finding fuel station prices across Spain and Portugal. It uses MapLibre GL for maps, Expo Router for navigation, NativeWind for styling, and an offline cache (AsyncStorage + expo-file-system) so the app works without a live connection (unless to update data).

Stack: Expo SDK 57 · React Native · React 19 · MapLibre GL · Expo Router · NativeWind · New Architecture enabled

This is a shared React Native + Expo codebase — the TypeScript in src/ and app/ runs on both platforms as-is; you write it once and it renders native UIView (iOS) or ViewGroup (Android) under the hood. The only Platform.OS branching in the app is two small tweaks:

  • src/utils/location.ts - Apple Maps vs. MapLibre (OpenFreeMaps)
  • app/(tabs)/_layout.tsx - tab nav bar

Everything else — features, UI, logic — lives in shared code. No double work.

Features

  • Stations search with filters: brand, country, fuel type, price range, city, max distance, etc
  • Favorites
  • 5-language support: English, Portuguese, Spanish, French, German
  • Theme support: light / dark (future implementation: custom theme colors)
  • Customizable location marker (4 built-in icons, SVGs, or a custom image) - Placeholders still
  • Price-history charts per fuel type, 90-day rolling window (live in server - cached on device, max 100mb for 90 days)
  • Station detail sheet — prices per fuel unit (€/L, €/kg, €/m³), opening schedule, services, payment methods, margin, directions, copy address, distance (API source & Crowdsource)
  • GPS-based location with cached last known location, doesn't follow user. Used only when requested, so no battery drained continually

Prerequisites

  • Node.js
  • iOS: macOS, Xcode with the iOS Simulator, CocoaPods
  • Android: Android Studio with an emulator, or a physical device with USB debugging enabled

Setup - dev only

npm install
npx expo install --check
npx expo install --fix

Running the app

iOS (requires Xcode & iOS Simulator)

npx expo prebuild --clean --platform ios
npx expo run:ios

Android (requires Android Studio & phone connected via USB)

npx expo prebuild --clean --platform android
npx expo run:android

Building release packages - after previous setup

Android

npx expo prebuild --clean --platform android
cd android
gradlew assembleRelease

iOS

iOS release builds go through Xcode, not Gradle:

npx expo run:ios --configuration Release

ios/ and android/ are gitignored and generated by npx expo prebuild. Native config lives declaratively in app.json (under expo.ios / expo.android), not in the committed native folders.

Publishing & updates

Releases are built in the cloud by EAS and attached to a GitHub Release, so the app's "Check for updates" can point users at a single source of truth.

Releasing a new version

The git tag is the single source of truth. Just create and push a vX.Y.Z tag (e.g. v1.1.0) — nothing else needs to change:

git tag v1.1.0
git push origin v1.1.0

The GitHub Action (.github/workflows/release.yml) derives everything from the tag: it patches app.json (setting version, android.versionCode = major*10000 + minor*100 + patch, and ios.buildNumber), builds an Android APK via EAS, and attaches it to the release at https://github.com/8041q/Siphon/releases. Tags that aren't vX.Y.Z fail fast. Requires the EXPO_TOKEN repo secret (Expo → Account Settings → Access Tokens).

How the app checks for updates

  • On launch the app silently verifies against the GitHub releases API whether a newer version exists (no download, result cached 30 min).
  • Settings → Updates shows the status, a manual "Check for updates", and — when one exists — a "Download update" button:
    • Android: opens releases/latest/download/siphon.apk (user taps to install the APK).
    • iOS: opens the release page for now; swap the URL in src/hooks/useAppUpdate.ts (getUpdateUrl) when a real iOS distribution path exists.

How data sync works

On app launch, sync runs once, in order:

  1. checkForUpdates() — conditional GET on the root manifest using an ETag (If-None-Match). If nothing changed, the server returns 304 Not Modified with no body, and no countries are flagged as changed.
  2. syncAll(changedCountries) — refreshes only the tiles whose server-side hash differs from the cached one. On a 304, this step makes zero network calls.
  3. checkHistoryUpdates() — downloads missing or changed price-history files, gated by the "Save price history on device" setting (on by default).

All three steps only touch the network when something actually changed — on a no-change day, the app runs entirely from cache.

To protect the GitHub data source, sync is guarded client-side (persisted in file-backed storage so it survives cache clears): a rolling hourly request budget (~300), a minimum interval between sync cycles (10 min), and backoff on 429/403 responses. When a limit is hit, the app runs from cache and shows a short "sync paused" notice instead of spamming GitHub.

The "Search this area" button reads from the cached data for the current map region, and only hits the network if that country was flagged as changed at launch.

Tiles for Spain use a 3×3 grid-key lookup to handle boundary/edge cases between regions.

Data source

Fuel prices come from the Spanish and Portuguese government fuel-price feeds, processed by the companion SiphonAPI project and served to the app as a static manifest plus per-tile data.

Location & GPS

On launch, the app resolves a starting location via GPS, falling back to the last known location when GPS is unavailable or permission is denied. Precise GPS (getCurrentPositionAsync) is only requested once automatically at launch, and again whenever you tap My Location - it does not track your position continuously, so it won't drain your battery.

Troubleshooting (Android)

If a native build fails after a config change, clean the build artifacts:

cd android
./gradlew clean
rm -rf .cxx

CMake version bellow 4.1.2 may not work, you should use the latest version instead due to a know bug in previous versions.

Docs

See docs/API-Calls-Reference.md for the full client/API reference.

License

About

The official client application for SiphonAPI. Provides an intuitive interface to interact with, manage, and visualise data served by the SiphonAPI backend

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages