From 7cc779cca29d94ca3603de202139160259491208 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Mon, 16 Feb 2026 14:21:33 -0500 Subject: [PATCH] feat(location): Add latitude and longitude to shift details and integrate Google Maps for location display --- apps/mobile/config.dev.json | 2 +- .../shifts_repository_impl.dart | 10 ++ .../shift_details/shift_location_map.dart | 124 ++++++++++-------- .../shift_details/shift_location_section.dart | 1 - .../connector/application/queries.gql | 2 + .../connector/shiftRole/queries.gql | 11 +- 6 files changed, 94 insertions(+), 56 deletions(-) diff --git a/apps/mobile/config.dev.json b/apps/mobile/config.dev.json index 4c630e51..95c65c67 100644 --- a/apps/mobile/config.dev.json +++ b/apps/mobile/config.dev.json @@ -1,3 +1,3 @@ { - "GOOGLE_MAPS_API_KEY": "AIzaSyAS9yTf4q51_CNSZ7mbmeS9V3l_LZR80lU" + "GOOGLE_MAPS_API_KEY": "AIzaSyAyRS9I4xxoVPAX91RJvWJHszB3ZY3-IC0" } \ No newline at end of file diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart index 5700c60a..d500819a 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart @@ -141,6 +141,8 @@ class ShiftsRepositoryImpl requiredSlots: app.shiftRole.count, filledSlots: app.shiftRole.assigned ?? 0, hasApplied: true, + latitude: app.shift.latitude, + longitude: app.shift.longitude, breakInfo: BreakAdapter.fromData( isPaid: app.shiftRole.isBreakPaid ?? false, breakTime: app.shiftRole.breakType?.stringValue, @@ -212,6 +214,8 @@ class ShiftsRepositoryImpl requiredSlots: app.shiftRole.count, filledSlots: app.shiftRole.assigned ?? 0, hasApplied: true, + latitude: app.shift.latitude, + longitude: app.shift.longitude, breakInfo: BreakAdapter.fromData( isPaid: app.shiftRole.isBreakPaid ?? false, breakTime: app.shiftRole.breakType?.stringValue, @@ -285,6 +289,8 @@ class ShiftsRepositoryImpl durationDays: sr.shift.durationDays, requiredSlots: sr.count, filledSlots: sr.assigned ?? 0, + latitude: sr.shift.latitude, + longitude: sr.shift.longitude, breakInfo: BreakAdapter.fromData( isPaid: sr.isBreakPaid ?? false, breakTime: sr.breakType?.stringValue, @@ -362,6 +368,8 @@ class ShiftsRepositoryImpl filledSlots: sr.assigned ?? 0, hasApplied: hasApplied, totalValue: sr.totalValue, + latitude: sr.shift.latitude, + longitude: sr.shift.longitude, breakInfo: BreakAdapter.fromData( isPaid: sr.isBreakPaid ?? false, breakTime: sr.breakType?.stringValue, @@ -417,6 +425,8 @@ class ShiftsRepositoryImpl durationDays: s.durationDays, requiredSlots: required, filledSlots: filled, + latitude: s.latitude, + longitude: s.longitude, breakInfo: breakInfo, ); } diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_map.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_map.dart index d5f8dc35..95cf174a 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_map.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_map.dart @@ -1,13 +1,20 @@ import 'package:flutter/material.dart'; import 'package:design_system/design_system.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:krow_core/core.dart'; // Import AppConfig from krow_core +import 'package:google_maps_flutter/google_maps_flutter.dart'; -class ShiftLocationMap extends StatelessWidget { +/// A widget that displays the shift location on an interactive Google Map. +class ShiftLocationMap extends StatefulWidget { + /// The shift entity containing location and coordinates. final Shift shift; + + /// The height of the map widget. final double height; + + /// The border radius for the map container. final double borderRadius; + /// Creates a [ShiftLocationMap]. const ShiftLocationMap({ super.key, required this.shift, @@ -16,77 +23,88 @@ class ShiftLocationMap extends StatelessWidget { }); @override - Widget build(BuildContext context) { - if (AppConfig.googleMapsApiKey.isEmpty) { - return _buildPlaceholder(context, "Config Map Key"); - } + State createState() => _ShiftLocationMapState(); +} - final String mapUrl = _generateStaticMapUrl(); +class _ShiftLocationMapState extends State { + late final CameraPosition _initialPosition; + final Set _markers = {}; - return Container( - height: height, - width: double.infinity, - decoration: BoxDecoration( - color: UiColors.background, - borderRadius: BorderRadius.circular(borderRadius), - ), - clipBehavior: Clip.antiAlias, - child: Image.network( - mapUrl, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return _buildPlaceholder(context, "Map unavailable"); - }, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) return child; - return Center( - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != null - ? loadingProgress.cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : null, - ), - ); - }, + @override + void initState() { + super.initState(); + + // Default to a fallback coordinate if latitude/longitude are null. + // In a real app, you might want to geocode the address if coordinates are missing. + final double lat = widget.shift.latitude ?? 0.0; + final double lng = widget.shift.longitude ?? 0.0; + + final LatLng position = LatLng(lat, lng); + + _initialPosition = CameraPosition( + target: position, + zoom: 15, + ); + + _markers.add( + Marker( + markerId: MarkerId(widget.shift.id), + position: position, + infoWindow: InfoWindow( + title: widget.shift.location, + snippet: widget.shift.locationAddress, + ), ), ); } - String _generateStaticMapUrl() { - // Base URL - const String baseUrl = "https://maps.googleapis.com/maps/api/staticmap"; - - // Parameters - String center; - if (shift.latitude != null && shift.longitude != null) { - center = "${shift.latitude},${shift.longitude}"; - } else { - center = Uri.encodeComponent(shift.locationAddress.isNotEmpty - ? shift.locationAddress - : shift.location); + @override + Widget build(BuildContext context) { + // If coordinates are missing, we show a placeholder. + if (widget.shift.latitude == null || widget.shift.longitude == null) { + return _buildPlaceholder(context, "Coordinates unavailable"); } - - // Construct URL - // scale=2 for retina displays - return "$baseUrl?center=$center&zoom=15&size=600x300&maptype=roadmap&markers=color:red%7C$center&key=${AppConfig.googleMapsApiKey}&scale=2"; + + return Container( + height: widget.height * 1.25, // Slightly taller to accommodate map controls + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(widget.borderRadius), + border: Border.all(color: UiColors.border), + ), + clipBehavior: Clip.antiAlias, + child: GoogleMap( + initialCameraPosition: _initialPosition, + markers: _markers, + liteModeEnabled: true, // Optimized for static-like display in details page + scrollGesturesEnabled: false, + zoomGesturesEnabled: true, + tiltGesturesEnabled: false, + rotateGesturesEnabled: false, + myLocationButtonEnabled: false, + mapToolbarEnabled: false, + compassEnabled: false, + ), + ); } Widget _buildPlaceholder(BuildContext context, String message) { return Container( - height: height, + height: widget.height, width: double.infinity, decoration: BoxDecoration( - color: UiColors.secondary, - borderRadius: BorderRadius.circular(borderRadius), + color: UiColors.bgThird, + borderRadius: BorderRadius.circular(widget.borderRadius), + border: Border.all(color: UiColors.border), ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( UiIcons.mapPin, size: 32, - color: UiColors.iconSecondary, + color: UiColors.primary, ), if (message.isNotEmpty) ...[ const SizedBox(height: UiConstants.space2), diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_section.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_section.dart index 2316bf78..7da82099 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_section.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/shift_details/shift_location_section.dart @@ -108,7 +108,6 @@ class ShiftLocationSection extends StatelessWidget { ShiftLocationMap( shift: shift, - height: 160, borderRadius: UiConstants.radiusBase, ), ], diff --git a/backend/dataconnect/connector/application/queries.gql b/backend/dataconnect/connector/application/queries.gql index 6d57438f..8b8d0845 100644 --- a/backend/dataconnect/connector/application/queries.gql +++ b/backend/dataconnect/connector/application/queries.gql @@ -661,6 +661,8 @@ query listCompletedApplicationsByStaffId( status description durationDays + latitude + longitude order { id diff --git a/backend/dataconnect/connector/shiftRole/queries.gql b/backend/dataconnect/connector/shiftRole/queries.gql index 98b83f94..c1569213 100644 --- a/backend/dataconnect/connector/shiftRole/queries.gql +++ b/backend/dataconnect/connector/shiftRole/queries.gql @@ -1,4 +1,3 @@ - query getShiftRoleById( $shiftId: UUID! $roleId: UUID! @@ -29,6 +28,8 @@ query getShiftRoleById( location locationAddress description + latitude + longitude orderId order{ @@ -91,6 +92,8 @@ query listShiftRolesByShiftId( location locationAddress description + latitude + longitude orderId order{ @@ -148,6 +151,8 @@ query listShiftRolesByRoleId( location locationAddress description + latitude + longitude orderId order{ @@ -212,6 +217,8 @@ query listShiftRolesByShiftIdAndTimeRange( location locationAddress description + latitude + longitude orderId order{ @@ -284,6 +291,8 @@ query listShiftRolesByVendorId( location locationAddress description + latitude + longitude orderId status durationDays