im so tired man
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
Navigation mode application layer.
|
||||
|
||||
This layer is reserved for navigation Cubit/BLoC orchestration.
|
||||
@@ -0,0 +1,3 @@
|
||||
Navigation mode data layer.
|
||||
|
||||
This layer is reserved for location, OSM, and OSRM data adapters.
|
||||
@@ -0,0 +1,3 @@
|
||||
Navigation mode domain layer.
|
||||
|
||||
This layer is reserved for route, waypoint, and navigation use cases.
|
||||
+111
-97
@@ -61,35 +61,40 @@ class _NavState extends Cubit<int> {
|
||||
StreamSubscription<Position>? _posStream;
|
||||
|
||||
void _set(_NavPhase p, String status) {
|
||||
if (isClosed) return;
|
||||
phase = p;
|
||||
statusText = status;
|
||||
_notify();
|
||||
}
|
||||
|
||||
void _notify() => emit(state + 1);
|
||||
void _notify() {
|
||||
if (isClosed) return;
|
||||
emit(state + 1);
|
||||
}
|
||||
|
||||
// ── locate ──────────────────────────────────────────────────────────────
|
||||
Future<bool> locate() async {
|
||||
_set(_NavPhase.locating, 'Mencari lokasi GPS…');
|
||||
final located = await guarded<bool>(
|
||||
() async {
|
||||
LocationPermission perm = await Geolocator.checkPermission();
|
||||
if (perm == LocationPermission.denied) {
|
||||
perm = await Geolocator.requestPermission();
|
||||
}
|
||||
if (perm == LocationPermission.deniedForever) {
|
||||
_set(_NavPhase.error, 'Izin lokasi diblokir permanen.');
|
||||
return false;
|
||||
}
|
||||
final pos = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
).timeout(const Duration(seconds: 12));
|
||||
currentPosition = LatLng(pos.latitude, pos.longitude);
|
||||
_set(_NavPhase.idle, 'Lokasi ditemukan. Cari tujuan atau ketuk peta.');
|
||||
_reportToBackend(pos);
|
||||
return true;
|
||||
LocationPermission perm = await Geolocator.checkPermission();
|
||||
if (perm == LocationPermission.denied) {
|
||||
perm = await Geolocator.requestPermission();
|
||||
}
|
||||
if (perm == LocationPermission.deniedForever) {
|
||||
_set(_NavPhase.error, 'Izin lokasi diblokir permanen.');
|
||||
return false;
|
||||
}
|
||||
final pos = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
).timeout(const Duration(seconds: 12));
|
||||
currentPosition = LatLng(pos.latitude, pos.longitude);
|
||||
_set(_NavPhase.idle, 'Lokasi ditemukan. Cari tujuan atau ketuk peta.');
|
||||
_reportToBackend(pos);
|
||||
return true;
|
||||
},
|
||||
onTimeout: () => _set(_NavPhase.error, 'GPS timeout. Pastikan GPS aktif.'),
|
||||
onTimeout: () =>
|
||||
_set(_NavPhase.error, 'GPS timeout. Pastikan GPS aktif.'),
|
||||
onError: (_) => _set(_NavPhase.error,
|
||||
'Lokasi belum bisa dibaca. Pastikan GPS dan izin lokasi aktif.'),
|
||||
);
|
||||
@@ -113,33 +118,37 @@ class _NavState extends Cubit<int> {
|
||||
Future<List<_Place>> searchPlaces(String query) async {
|
||||
if (query.trim().isEmpty) return const [];
|
||||
return await guarded<List<_Place>>(
|
||||
() async {
|
||||
final res = await Dio().get(
|
||||
'https://nominatim.openstreetmap.org/search',
|
||||
queryParameters: {
|
||||
'q': query,
|
||||
'format': 'jsonv2',
|
||||
'limit': 6,
|
||||
'addressdetails': 0,
|
||||
if (currentPosition != null) 'viewbox': _viewbox(currentPosition!),
|
||||
if (currentPosition != null) 'bounded': 0,
|
||||
},
|
||||
options: Options(
|
||||
headers: {'User-Agent': 'WalkGuide/1.0 (walkguide@campus.ac.id)'},
|
||||
receiveTimeout: const Duration(seconds: 8),
|
||||
),
|
||||
);
|
||||
final list = res.data as List;
|
||||
return list.map((e) {
|
||||
final lat = double.tryParse(e['lat'].toString()) ?? 0;
|
||||
final lng = double.tryParse(e['lon'].toString()) ?? 0;
|
||||
return _Place(
|
||||
displayName: e['display_name'].toString(),
|
||||
position: LatLng(lat, lng),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
) ?? const [];
|
||||
() async {
|
||||
final res = await Dio().get(
|
||||
'https://nominatim.openstreetmap.org/search',
|
||||
queryParameters: {
|
||||
'q': query,
|
||||
'format': 'jsonv2',
|
||||
'limit': 6,
|
||||
'addressdetails': 0,
|
||||
if (currentPosition != null)
|
||||
'viewbox': _viewbox(currentPosition!),
|
||||
if (currentPosition != null) 'bounded': 0,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
'User-Agent': 'WalkGuide/1.0 (walkguide@campus.ac.id)'
|
||||
},
|
||||
receiveTimeout: const Duration(seconds: 8),
|
||||
),
|
||||
);
|
||||
final list = res.data as List;
|
||||
return list.map((e) {
|
||||
final lat = double.tryParse(e['lat'].toString()) ?? 0;
|
||||
final lng = double.tryParse(e['lon'].toString()) ?? 0;
|
||||
return _Place(
|
||||
displayName: e['display_name'].toString(),
|
||||
position: LatLng(lat, lng),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
) ??
|
||||
const [];
|
||||
}
|
||||
|
||||
String _viewbox(LatLng c) =>
|
||||
@@ -148,23 +157,25 @@ class _NavState extends Cubit<int> {
|
||||
// ── reverse geocode ──────────────────────────────────────────────────────
|
||||
Future<String> reverseGeocode(LatLng pos) async {
|
||||
return await guarded<String>(
|
||||
() async {
|
||||
final res = await Dio().get(
|
||||
'https://nominatim.openstreetmap.org/reverse',
|
||||
queryParameters: {
|
||||
'lat': pos.latitude,
|
||||
'lon': pos.longitude,
|
||||
'format': 'jsonv2',
|
||||
},
|
||||
options: Options(
|
||||
headers: {'User-Agent': 'WalkGuide/1.0 (walkguide@campus.ac.id)'},
|
||||
receiveTimeout: const Duration(seconds: 6),
|
||||
),
|
||||
);
|
||||
return res.data['display_name']?.toString() ??
|
||||
'${pos.latitude.toStringAsFixed(5)}, ${pos.longitude.toStringAsFixed(5)}';
|
||||
},
|
||||
) ??
|
||||
() async {
|
||||
final res = await Dio().get(
|
||||
'https://nominatim.openstreetmap.org/reverse',
|
||||
queryParameters: {
|
||||
'lat': pos.latitude,
|
||||
'lon': pos.longitude,
|
||||
'format': 'jsonv2',
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
'User-Agent': 'WalkGuide/1.0 (walkguide@campus.ac.id)'
|
||||
},
|
||||
receiveTimeout: const Duration(seconds: 6),
|
||||
),
|
||||
);
|
||||
return res.data['display_name']?.toString() ??
|
||||
'${pos.latitude.toStringAsFixed(5)}, ${pos.longitude.toStringAsFixed(5)}';
|
||||
},
|
||||
) ??
|
||||
'${pos.latitude.toStringAsFixed(5)}, ${pos.longitude.toStringAsFixed(5)}';
|
||||
}
|
||||
|
||||
@@ -180,47 +191,47 @@ class _NavState extends Cubit<int> {
|
||||
final origin = currentPosition!;
|
||||
await guarded<void>(
|
||||
() async {
|
||||
final url = 'http://router.project-osrm.org/route/v1/foot/'
|
||||
'${origin.longitude},${origin.latitude};'
|
||||
'${dest.position.longitude},${dest.position.latitude}'
|
||||
'?steps=true&geometries=geojson&overview=full&annotations=false';
|
||||
final url = 'http://router.project-osrm.org/route/v1/foot/'
|
||||
'${origin.longitude},${origin.latitude};'
|
||||
'${dest.position.longitude},${dest.position.latitude}'
|
||||
'?steps=true&geometries=geojson&overview=full&annotations=false';
|
||||
|
||||
final res = await Dio().get(
|
||||
url,
|
||||
options: Options(receiveTimeout: const Duration(seconds: 12)),
|
||||
);
|
||||
final res = await Dio().get(
|
||||
url,
|
||||
options: Options(receiveTimeout: const Duration(seconds: 12)),
|
||||
);
|
||||
|
||||
final route = res.data['routes'][0];
|
||||
final geom = route['geometry']['coordinates'] as List;
|
||||
routePoints = geom.map((c) {
|
||||
final lst = c as List;
|
||||
return LatLng((lst[1] as num).toDouble(), (lst[0] as num).toDouble());
|
||||
}).toList();
|
||||
final route = res.data['routes'][0];
|
||||
final geom = route['geometry']['coordinates'] as List;
|
||||
routePoints = geom.map((c) {
|
||||
final lst = c as List;
|
||||
return LatLng((lst[1] as num).toDouble(), (lst[0] as num).toDouble());
|
||||
}).toList();
|
||||
|
||||
// parse steps
|
||||
final legs = route['legs'] as List;
|
||||
final rawSteps = <_Step>[];
|
||||
for (final leg in legs) {
|
||||
for (final step in leg['steps'] as List) {
|
||||
final maneuver = step['maneuver'] as Map;
|
||||
final instruction = _buildInstruction(maneuver, step);
|
||||
final dist = (step['distance'] as num?)?.toDouble() ?? 0;
|
||||
final loc = maneuver['location'] as List;
|
||||
rawSteps.add(_Step(
|
||||
instruction: instruction,
|
||||
distanceM: dist,
|
||||
point:
|
||||
LatLng((loc[1] as num).toDouble(), (loc[0] as num).toDouble()),
|
||||
));
|
||||
// parse steps
|
||||
final legs = route['legs'] as List;
|
||||
final rawSteps = <_Step>[];
|
||||
for (final leg in legs) {
|
||||
for (final step in leg['steps'] as List) {
|
||||
final maneuver = step['maneuver'] as Map;
|
||||
final instruction = _buildInstruction(maneuver, step);
|
||||
final dist = (step['distance'] as num?)?.toDouble() ?? 0;
|
||||
final loc = maneuver['location'] as List;
|
||||
rawSteps.add(_Step(
|
||||
instruction: instruction,
|
||||
distanceM: dist,
|
||||
point: LatLng(
|
||||
(loc[1] as num).toDouble(), (loc[0] as num).toDouble()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
steps = rawSteps;
|
||||
currentStepIndex = 0;
|
||||
steps = rawSteps;
|
||||
currentStepIndex = 0;
|
||||
|
||||
_set(_NavPhase.navigating,
|
||||
steps.isNotEmpty ? steps[0].instruction : 'Ikuti rute biru.');
|
||||
_notify();
|
||||
_startTracking();
|
||||
_set(_NavPhase.navigating,
|
||||
steps.isNotEmpty ? steps[0].instruction : 'Ikuti rute biru.');
|
||||
_notify();
|
||||
_startTracking();
|
||||
},
|
||||
onError: (_) => _set(_NavPhase.error,
|
||||
'Gagal mendapat rute. Periksa koneksi lalu coba lagi.'),
|
||||
@@ -289,6 +300,7 @@ class _NavState extends Cubit<int> {
|
||||
distanceFilter: 5,
|
||||
),
|
||||
).listen((pos) {
|
||||
if (isClosed) return;
|
||||
currentPosition = LatLng(pos.latitude, pos.longitude);
|
||||
_reportToBackend(pos);
|
||||
_updateStep();
|
||||
@@ -298,6 +310,7 @@ class _NavState extends Cubit<int> {
|
||||
|
||||
void _updateStep() {
|
||||
if (steps.isEmpty || phase != _NavPhase.navigating) return;
|
||||
if (currentPosition == null) return;
|
||||
if (currentStepIndex >= steps.length - 1) return;
|
||||
|
||||
final current = steps[currentStepIndex];
|
||||
@@ -317,6 +330,7 @@ class _NavState extends Cubit<int> {
|
||||
}
|
||||
|
||||
void stopNavigation() {
|
||||
if (isClosed) return;
|
||||
_posStream?.cancel();
|
||||
_posStream = null;
|
||||
destination = null;
|
||||
|
||||
Reference in New Issue
Block a user