feat: implement map camera widget with integrated location data capture and display

This commit is contained in:
2026-04-02 13:11:38 +07:00
parent e5e55deeef
commit f6d80613d9
2 changed files with 61 additions and 21 deletions

View File

@@ -1,5 +1,8 @@
import 'dart:typed_data';
class ImageAndLocationData { class ImageAndLocationData {
final String? imagePath; final String? imagePath;
final Uint8List? imageBytes;
final LocationData? locationData; final LocationData? locationData;
String? get latitude => locationData?.latitude; String? get latitude => locationData?.latitude;
String? get longitude => locationData?.longitude; String? get longitude => locationData?.longitude;
@@ -8,6 +11,7 @@ class ImageAndLocationData {
ImageAndLocationData({ ImageAndLocationData({
required this.imagePath, required this.imagePath,
this.imageBytes,
required this.locationData, required this.locationData,
}); });
} }

View File

@@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io' as io;
import 'dart:math'; import 'dart:math';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
@@ -48,8 +48,8 @@ class _MapCameraLocationState extends State<MapCameraLocation> {
late AlignOnUpdate _followOnLocationUpdate; late AlignOnUpdate _followOnLocationUpdate;
late StreamController<double?> _followCurrentLocationStreamController; late StreamController<double?> _followCurrentLocationStreamController;
File? cameraImagePath; io.File? cameraImagePath;
File? ssImage; io.File? ssImage;
String? dateTime; String? dateTime;
final globalKey = GlobalKey(); final globalKey = GlobalKey();
@@ -184,6 +184,11 @@ class _MapCameraLocationState extends State<MapCameraLocation> {
CurrentLocationLayer( CurrentLocationLayer(
alignPositionStream: _followCurrentLocationStreamController.stream, alignPositionStream: _followCurrentLocationStreamController.stream,
alignPositionOnUpdate: _followOnLocationUpdate, alignPositionOnUpdate: _followOnLocationUpdate,
style: LocationMarkerStyle(
showHeadingSector: !kIsWeb,
showAccuracyCircle: !kIsWeb,
),
headingStream: kIsWeb ? const Stream.empty() : null,
), ),
], ],
), ),
@@ -233,18 +238,27 @@ class _MapCameraLocationState extends State<MapCameraLocation> {
final rng = Random(); final rng = Random();
final boundary = globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; final boundary = globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: 2.0); final image = await boundary.toImage(pixelRatio: 2.0);
final directory = (await getApplicationDocumentsDirectory()).path;
final byteData = await image.toByteData(format: ui.ImageByteFormat.png); final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List(); final pngBytes = byteData!.buffer.asUint8List();
final imgFile = File('$directory/screenshot${rng.nextInt(10000)}.png');
await imgFile.writeAsBytes(pngBytes); String finalPath = '';
if (imgFile.existsSync()) { if (kIsWeb) {
setState(() => cameraImagePath = imgFile); // On web we use the bytes to create a blob preview or similar
// For now we just return an empty path or a fake one to avoid crash
finalPath = 'web_capture_${rng.nextInt(10000)}.png';
} else {
final directory = (await getApplicationDocumentsDirectory()).path;
final imgFile = io.File('$directory/screenshot${rng.nextInt(10000)}.png');
await imgFile.writeAsBytes(pngBytes);
finalPath = imgFile.path;
}
if (finalPath.isNotEmpty) {
if (widget.onImageCaptured != null) { if (widget.onImageCaptured != null) {
widget.onImageCaptured!(ImageAndLocationData( widget.onImageCaptured!(ImageAndLocationData(
imagePath: imgFile.path, imagePath: finalPath,
imageBytes: pngBytes,
locationData: locationData, locationData: locationData,
)); ));
} }
@@ -265,6 +279,19 @@ class _MapCameraLocationState extends State<MapCameraLocation> {
lngVal = pos.longitude; lngVal = pos.longitude;
} }
// Reverse geocoding often doesn't work well on web without a provider
if (kIsWeb) {
setState(() {
locationData = LocationData(
latitude: latVal.toString(),
longitude: lngVal.toString(),
locationName: 'Web Location',
subLocation: 'Browser Accuracy',
);
});
return;
}
final placeMarks = await placemarkFromCoordinates(latVal, lngVal); final placeMarks = await placemarkFromCoordinates(latVal, lngVal);
if (mounted) { if (mounted) {
@@ -303,37 +330,46 @@ class _MapCameraLocationState extends State<MapCameraLocation> {
} }
Future<Position> _determinePosition() async { Future<Position> _determinePosition() async {
final serviceEnabled = await Geolocator.isLocationServiceEnabled(); // Basic service check
if (!serviceEnabled) { if (!kIsWeb) {
final lastKnown = await Geolocator.getLastKnownPosition(); final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (lastKnown != null) return lastKnown; if (!serviceEnabled) {
throw Exception('Location service disabled'); final lastKnown = await Geolocator.getLastKnownPosition();
if (lastKnown != null) return lastKnown;
throw Exception('Location service disabled');
}
} }
var permission = await Geolocator.checkPermission(); var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission(); permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
final lastKnown = await Geolocator.getLastKnownPosition(); if (!kIsWeb) {
if (lastKnown != null) return lastKnown; final lastKnown = await Geolocator.getLastKnownPosition();
if (lastKnown != null) return lastKnown;
}
throw Exception('Location permission denied'); throw Exception('Location permission denied');
} }
} }
if (permission == LocationPermission.deniedForever) { if (permission == LocationPermission.deniedForever) {
final lastKnown = await Geolocator.getLastKnownPosition(); if (!kIsWeb) {
if (lastKnown != null) return lastKnown; final lastKnown = await Geolocator.getLastKnownPosition();
if (lastKnown != null) return lastKnown;
}
throw Exception('Location permission permanently denied'); throw Exception('Location permission permanently denied');
} }
try { try {
return await Geolocator.getCurrentPosition( return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.medium, desiredAccuracy: LocationAccuracy.medium,
timeLimit: const Duration(seconds: 8), timeLimit: const Duration(seconds: 15),
); );
} catch (e) { } catch (e) {
final lastKnown = await Geolocator.getLastKnownPosition(); if (!kIsWeb) {
if (lastKnown != null) return lastKnown; final lastKnown = await Geolocator.getLastKnownPosition();
if (lastKnown != null) return lastKnown;
}
rethrow; rethrow;
} }
} }