Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.3.0+1] - 2025-01-01

### Added
- **Full Offline Mode Implementation**: The app can now function completely without an internet connection.
- **Offline Map Regions**: Download specific regions (Luzon, Visayas, Mindanao) for offline map viewing.
- **Hybrid Routing Fallback**: Automatic switching to Haversine (point-to-point) routing when OSRM is unavailable or offline.
- **Geocoding Cache**: Persistent storage for recently searched locations.
- **Offline Fare Calculation**: All road formulas and rail/ferry matrices are available offline.
- **Smart Connectivity Detection**: Real-time monitoring of network status with automatic UI adjustments.
- **Offline UI Indicators**: Visual cues showing when the app is in offline mode and which features are limited.
- **Auto-Caching Strategy**: Intelligent background caching of map tiles for recently viewed areas.

### Changed
- Improved `HybridEngine` to handle offline state seamlessly.
- Updated `SettingsScreen` with offline management options.

## [2.2.0+4] - 2024-12-15
- Initial beta release with core fare calculation logic.
- Road formula support for Jeeps, Buses, Taxis.
- Static matrix support for LRT/MRT.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Flutter](https://img.shields.io/badge/Built%20with-Flutter-blue.svg)](https://flutter.dev/)
[![CI](https://github.com/MasuRii/ph-fare-calculator/actions/workflows/ci.yml/badge.svg)](https://github.com/MasuRii/ph-fare-calculator/actions/workflows/ci.yml)
[![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](https://github.com/MasuRii/ph-fare-calculator/releases)
[![Version](https://img.shields.io/badge/version-2.3.0-blue.svg)](https://github.com/MasuRii/ph-fare-calculator/releases)

**PH Fare Calculator** is a cross-platform mobile application designed to help tourists, expats, and locals estimate public transport costs across the Philippines.

## πŸ“± Offline Mode Features

The application now supports comprehensive offline functionality, ensuring you can calculate fares even in remote areas without internet coverage:

- **Regional Map Downloads**: Download vector-based map tiles for Luzon, Visayas, or Mindanao.
- **Persistent Geocoding**: Previously searched locations are cached locally using Hive for instant retrieval offline.
- **Smart Fallback Routing**: When internet is lost, the app automatically switches from OSRM (Road Distance) to Haversine (Direct Distance) routing to provide fare estimates.
- **Static Matrix Access**: Train (MRT/LRT) and Ferry fare matrices are bundled with the app, ensuring 100% availability.
- **Background Caching**: Intelligent caching of map tiles as you browse, making recently viewed areas available offline automatically.
- **Connectivity Awareness**: Real-time detection of 4G/5G/WiFi status with automatic mode switching.


Unlike city-centric navigation apps, this tool focuses on **"How much?"** rather than "How to?". It solves the complex problem of Philippine geography by combining distance-based formulas (for roads) with static fare matrices (for trains and ferries).

## πŸš€ Key Features
Expand Down
17 changes: 17 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:ph_fare_calculator/src/core/theme/app_theme.dart';
import 'package:ph_fare_calculator/src/l10n/app_localizations.dart';
import 'package:ph_fare_calculator/src/models/accuracy_level.dart';
import 'package:ph_fare_calculator/src/models/map_region.dart';
import 'package:ph_fare_calculator/src/presentation/screens/splash_screen.dart';
import 'package:ph_fare_calculator/src/services/geocoding/geocoding_cache_service.dart';
import 'package:ph_fare_calculator/src/services/offline/offline_mode_service.dart';
import 'package:ph_fare_calculator/src/services/settings_service.dart';
import 'package:ph_fare_calculator/src/core/di/injection.dart';
import 'package:shared_preferences/shared_preferences.dart';

Future<void> main() async {
Expand All @@ -16,6 +20,10 @@ Future<void> main() async {
Hive.registerAdapter(DownloadStatusAdapter());
Hive.registerAdapter(RegionTypeAdapter());
Hive.registerAdapter(MapRegionAdapter());
Hive.registerAdapter(AccuracyLevelAdapter());

// Initialize dependencies
await configureDependencies();

// Pre-initialize static notifiers from SharedPreferences to avoid race condition
// This ensures ValueListenableBuilders have correct values when the widget tree is built
Expand All @@ -26,9 +34,18 @@ Future<void> main() async {
SettingsService.themeModeNotifier.value = themeMode;
SettingsService.localeNotifier.value = Locale(languageCode);

// Initialize geocoding cache service
final geocodingCacheService = getIt<GeocodingCacheService>();
await geocodingCacheService.initialize();

// Initialize offline mode service
final offlineModeService = getIt<OfflineModeService>();
await offlineModeService.initialize();

runApp(const MyApp());
}


class MyApp extends StatelessWidget {
const MyApp({super.key});

Expand Down
61 changes: 50 additions & 11 deletions lib/src/core/di/injection.config.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 12 additions & 9 deletions lib/src/core/hybrid_engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ import '../models/transport_mode.dart';
import '../models/fare_formula.dart';
import '../models/static_fare.dart';
import '../models/discount_type.dart';
import '../services/routing/routing_service.dart';
import '../repositories/routing_repository.dart';
import '../services/settings_service.dart';
import '../models/fare_result.dart';

@lazySingleton
class HybridEngine {
final RoutingService _routingService;
final RoutingRepository _routingRepository;
final SettingsService _settingsService;
Map<String, List<StaticFare>> _trainFares = {};
List<StaticFare> _ferryFares = [];
bool _isInitialized = false;

HybridEngine(this._routingService, this._settingsService);
HybridEngine(this._routingRepository, this._settingsService);


/// Initializes the engine by loading static matrix data.
Future<void> initialize() async {
Expand Down Expand Up @@ -201,15 +202,17 @@ class HybridEngine {
int discountedCount = 0,
}) async {
try {
// 1. Get route result from routing service
final routeResult = await _routingService.getRoute(
originLat,
originLng,
destLat,
destLng,
// 1. Get route result from routing repository
final routeResult = await _routingRepository.getRoute(
originLat: originLat,
originLng: originLng,
destLat: destLat,
destLng: destLng,
preferredMode: TransportMode.fromString(formula.mode),
);

// 2. Extract distance in meters and convert to kilometers

final distanceInKm = routeResult.distance / 1000.0;

// 3. Apply Variance (1.15) as per PRD
Expand Down
73 changes: 73 additions & 0 deletions lib/src/models/accuracy_level.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';

part 'accuracy_level.g.dart';

/// Represents the accuracy level of fare/route information.
///
/// Follows the design in ADR-006.
@HiveType(typeId: 4)
enum AccuracyLevel {
/// Precise calculation using online services (OSRM, live data).
@HiveField(0)
precise,

/// Estimated calculation using cached data (valid, recent cache).
@HiveField(1)
estimated,

/// Approximate calculation using offline fallbacks (Haversine, static matrices).
@HiveField(2)
approximate,
}

/// Extension methods for AccuracyLevel to provide UI helpers.
extension AccuracyLevelX on AccuracyLevel {
/// Returns a human-readable label.
String get label {
switch (this) {
case AccuracyLevel.precise:
return 'Precise (Online)';
case AccuracyLevel.estimated:
return 'Estimated (Cached)';
case AccuracyLevel.approximate:
return 'Approximate (Offline)';
}
}

/// Returns a description of the accuracy level.
String get description {
switch (this) {
case AccuracyLevel.precise:
return 'Based on real-time road data and current conditions';
case AccuracyLevel.estimated:
return 'Based on previously cached route data';
case AccuracyLevel.approximate:
return 'Based on straight-line distance calculations';
}
}

/// Returns the appropriate color for UI display.
Color get color {
switch (this) {
case AccuracyLevel.precise:
return Colors.green;
case AccuracyLevel.estimated:
return Colors.yellow.shade700;
case AccuracyLevel.approximate:
return Colors.orange;
}
}

/// Returns an icon for the accuracy level.
IconData get icon {
switch (this) {
case AccuracyLevel.precise:
return Icons.wifi_rounded;
case AccuracyLevel.estimated:
return Icons.cached_rounded;
case AccuracyLevel.approximate:
return Icons.offline_bolt_rounded;
}
}
}
13 changes: 13 additions & 0 deletions lib/src/models/fare_result.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:hive/hive.dart';
import 'accuracy_level.dart';
import 'route_result.dart';

part 'fare_result.g.dart';

Expand Down Expand Up @@ -29,12 +31,23 @@ class FareResult {
@HiveField(5, defaultValue: 0.0)
final double totalFare;

/// Accuracy level of the calculation.
@HiveField(6)
final AccuracyLevel accuracy;

/// Source of the route calculation.
@HiveField(7)
final RouteSource routeSource;

FareResult({
required this.transportMode,
required this.fare,
required this.indicatorLevel,
this.isRecommended = false,
this.passengerCount = 1,
required this.totalFare,
this.accuracy = AccuracyLevel.precise,
this.routeSource = RouteSource.osrm,
});
}

10 changes: 8 additions & 2 deletions lib/src/models/fare_result.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading