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
3 changes: 3 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
Expand Down
4 changes: 4 additions & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<resources>
<!-- Replace this placeholder for GoogleMap widgets; pass --dart-define=GOOGLE_MAPS_API_KEY=... for static thumbnails. -->
<string name="google_maps_key">YOUR_API_KEY</string>
</resources>
45 changes: 19 additions & 26 deletions lib/core/utils/location_utils.dart
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
import 'dart:async';

import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';

import '../../models/location_info.dart';
import '../../services/location_service.dart';

class LocationUtils {
static const LocationService _locationService = LocationService();

static Future<LocationInfo?> getCurrentLocationInfo() async {
try {
final enabled = await Geolocator.isLocationServiceEnabled();
if (!enabled) return null;

final pos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: const Duration(seconds: 10),
final geoPhoto = await _locationService.getCurrentGeoPhoto();
return LocationInfo(
address: geoPhoto.address,
date: geoPhoto.capturedAt.toLocal().toString().split(' ').first,
time: geoPhoto.formattedDateTime.split(' ').last,
latitude: geoPhoto.latitude,
longitude: geoPhoto.longitude,
placeName: geoPhoto.placeName,
locality: geoPhoto.locality,
administrativeArea: geoPhoto.administrativeArea,
country: geoPhoto.country,
postalCode: geoPhoto.postalCode,
altitude: geoPhoto.altitude,
speedMetersPerSecond: geoPhoto.speedMetersPerSecond,
heading: geoPhoto.heading,
accuracy: geoPhoto.accuracy,
);

final placemarks =
await placemarkFromCoordinates(pos.latitude, pos.longitude);
final p = placemarks.isNotEmpty ? placemarks.first : null;

final address = [
if ((p?.subThoroughfare ?? '').isNotEmpty) p?.subThoroughfare,
if ((p?.thoroughfare ?? '').isNotEmpty) p?.thoroughfare,
if ((p?.subLocality ?? '').isNotEmpty) p?.subLocality,
if ((p?.locality ?? '').isNotEmpty) p?.locality,
if ((p?.administrativeArea ?? '').isNotEmpty) p?.administrativeArea,
if ((p?.postalCode ?? '').isNotEmpty) p?.postalCode,
if ((p?.country ?? '').isNotEmpty) p?.country,
].whereType<String>().join(', ');

return LocationInfo.fromPosition(
pos, address.isEmpty ? 'Location unavailable' : address);
} catch (_) {
return null;
}
Expand Down
88 changes: 80 additions & 8 deletions lib/features/camera/screen/camera_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import '../../../core/utils/location_utils.dart';
import '../../../core/utils/permission_handler.dart';
import '../../../core/widgets/location_stamp_card.dart';
import '../../../models/captured_media.dart';
import '../../../models/geo_photo_model.dart';
import '../../../models/location_info.dart';
import '../../../services/location_service.dart';

class CameraScreen extends StatefulWidget {
const CameraScreen({super.key, this.permissionsGranted = true});
Expand Down Expand Up @@ -41,6 +43,8 @@ class _CameraScreenState extends State<CameraScreen>
Duration _recordDuration = Duration.zero;
late final AnimationController _captureAnim;
bool _flashOverlay = false;
bool _isGeoProcessing = false;
final LocationService _locationService = const LocationService();

@override
void initState() {
Expand Down Expand Up @@ -171,19 +175,18 @@ class _CameraScreenState extends State<CameraScreen>
});
final file = await _controller!.takePicture();
if (!mounted) return;
setState(() => _isGeoProcessing = true);

final locationInfo = await _resolveCaptureLocation();
if (!mounted) return;
setState(() => _isGeoProcessing = false);

Navigator.pushNamed(
context,
AppConstants.routeResult,
arguments: ResultScreenArgs(
filePath: file.path,
locationInfo: _locationInfo ??
const LocationInfo(
address: 'Location unavailable',
date: '',
time: '',
latitude: 0,
longitude: 0,
),
locationInfo: locationInfo,
type: MediaType.photo,
),
);
Expand All @@ -201,6 +204,53 @@ class _CameraScreenState extends State<CameraScreen>
}
}

Future<LocationInfo> _resolveCaptureLocation() async {
try {
final geoPhoto = await _locationService.getCurrentGeoPhoto();
return _locationInfoFromGeoPhoto(geoPhoto);
} on LocationServiceException catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.message)),
);
}
} catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Location unavailable: $error')),
);
}
}

return _locationInfo ??
const LocationInfo(
address: 'Location unavailable',
date: '',
time: '',
latitude: 0,
longitude: 0,
);
}

LocationInfo _locationInfoFromGeoPhoto(GeoPhotoModel geoPhoto) {
return LocationInfo(
address: geoPhoto.address,
date: geoPhoto.capturedAt.toLocal().toString().split(' ').first,
time: geoPhoto.formattedDateTime.split(' ').last,
latitude: geoPhoto.latitude,
longitude: geoPhoto.longitude,
placeName: geoPhoto.placeName,
locality: geoPhoto.locality,
administrativeArea: geoPhoto.administrativeArea,
country: geoPhoto.country,
postalCode: geoPhoto.postalCode,
altitude: geoPhoto.altitude,
speedMetersPerSecond: geoPhoto.speedMetersPerSecond,
heading: geoPhoto.heading,
accuracy: geoPhoto.accuracy,
);
}

Future<void> _startRecording() async {
await _controller!.startVideoRecording();
setState(() {
Expand Down Expand Up @@ -291,6 +341,28 @@ class _CameraScreenState extends State<CameraScreen>
child: Container(color: Colors.white),
),
),
if (_isGeoProcessing)
Positioned.fill(
child: ColoredBox(
color: Colors.black.withValues(alpha: 0.45),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: Colors.white),
SizedBox(height: 16),
Text(
'Getting GPS and address...',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
_buildTopToolbar(),
if (_isRecording)
Positioned(
Expand Down
Loading
Loading