- Murattal: Spotify-style 5-button controls [Shuffle, Prev, Play, Next, Playlist] - Murattal: Animated 7-bar equalizer visualization in player circle - Murattal: Unsplash API background with frosted glass player overlay - Murattal: Transparent AppBar with backdrop blur - Murattal: Surah playlist bottom sheet with full 114 Surah list - Murattal: Auto-play disabled on screen open, enabled on navigation - Murattal: Shuffle mode for random Surah playback - Murattal: Photographer attribution per Unsplash guidelines - Dashboard: Auto-scroll prayer schedule to next active prayer - Fix: setState lifecycle errors on Reading & Murattal screens - Setup: flutter_dotenv, cached_network_image, url_launcher deps
87 lines
2.8 KiB
Dart
87 lines
2.8 KiB
Dart
import 'package:geolocator/geolocator.dart';
|
|
import 'package:geocoding/geocoding.dart' as geocoding;
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import '../local/hive_boxes.dart';
|
|
import '../local/models/app_settings.dart';
|
|
|
|
/// Location service with GPS + fallback to last known location.
|
|
class LocationService {
|
|
LocationService._();
|
|
static final LocationService instance = LocationService._();
|
|
|
|
/// Request permission and get current GPS location.
|
|
Future<Position?> getCurrentLocation() async {
|
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
if (!serviceEnabled) return null;
|
|
|
|
LocationPermission permission = await Geolocator.checkPermission();
|
|
if (permission == LocationPermission.denied) {
|
|
permission = await Geolocator.requestPermission();
|
|
if (permission == LocationPermission.denied) return null;
|
|
}
|
|
|
|
if (permission == LocationPermission.deniedForever) return null;
|
|
|
|
try {
|
|
final position = await Geolocator.getCurrentPosition(
|
|
locationSettings: const LocationSettings(
|
|
accuracy: LocationAccuracy.medium,
|
|
timeLimit: Duration(seconds: 10),
|
|
),
|
|
);
|
|
|
|
// Save to settings for fallback
|
|
await _saveLastKnown(position.latitude, position.longitude);
|
|
return position;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Get last known location from Hive settings.
|
|
({double lat, double lng, String? cityName})? getLastKnownLocation() {
|
|
final settingsBox = Hive.box<AppSettings>(HiveBoxes.settings);
|
|
final settings = settingsBox.get('default');
|
|
if (settings?.lastLat != null && settings?.lastLng != null) {
|
|
return (
|
|
lat: settings!.lastLat!,
|
|
lng: settings.lastLng!,
|
|
cityName: settings.lastCityName,
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Reverse geocode to get city name from coordinates.
|
|
Future<String> getCityName(double lat, double lng) async {
|
|
try {
|
|
final placemarks = await geocoding.placemarkFromCoordinates(lat, lng);
|
|
if (placemarks.isNotEmpty) {
|
|
final place = placemarks.first;
|
|
final city = place.locality ?? place.subAdministrativeArea ?? 'Unknown';
|
|
final country = place.country ?? '';
|
|
return '$city, $country';
|
|
}
|
|
} catch (_) {
|
|
// Geocoding may fail offline — return coords
|
|
}
|
|
return '${lat.toStringAsFixed(2)}, ${lng.toStringAsFixed(2)}';
|
|
}
|
|
|
|
/// Save last known position to Hive.
|
|
Future<void> _saveLastKnown(double lat, double lng) async {
|
|
final settingsBox = Hive.box<AppSettings>(HiveBoxes.settings);
|
|
final settings = settingsBox.get('default');
|
|
if (settings != null) {
|
|
settings.lastLat = lat;
|
|
settings.lastLng = lng;
|
|
try {
|
|
settings.lastCityName = await getCityName(lat, lng);
|
|
} catch (_) {
|
|
// Ignore geocoding errors
|
|
}
|
|
await settings.save();
|
|
}
|
|
}
|
|
}
|