using System.Globalization; using System.Text.Json; using Microsoft.Maui.Controls.Maps; using Microsoft.Maui.Handlers; using Microsoft.Maui.Maps; using Microsoft.Maui.Maps.Handlers; using Microsoft.Maui.Platform; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; using Windows.Devices.Geolocation; using IMap = Microsoft.Maui.Maps.IMap; namespace CommunityToolkit.Maui.Maps.Handlers; /// public partial class MapHandlerWindows : MapHandler { MapSpan? regionToGo; /// /// Initializes a new instance of the class. /// public MapHandlerWindows() : base(Mapper, CommandMapper) { Mapper.ModifyMapping(nameof(IMap.MapType), (handler, map, _) => MapMapType(handler, map)); Mapper.ModifyMapping(nameof(IMap.IsShowingUser), async (handler, map, _) => await MapIsShowingUser(handler, map)); Mapper.ModifyMapping(nameof(IMap.IsScrollEnabled), (handler, map, _) => MapIsScrollEnabled(handler, map)); Mapper.ModifyMapping(nameof(IMap.IsTrafficEnabled), (handler, map, _) => MapIsTrafficEnabled(handler, map)); Mapper.ModifyMapping(nameof(IMap.IsZoomEnabled), (handler, map, _) => MapIsZoomEnabled(handler, map)); Mapper.ModifyMapping(nameof(IMap.Pins), async (handler, map, _) => await MapPins(handler, map)); Mapper.ModifyMapping(nameof(IMap.Elements), (handler, map, _) => MapElements(handler, map)); CommandMapper.ModifyMapping(nameof(IMap.MoveToRegion), async (handler, map, args, _) => await MapMoveToRegion(handler, map, args)); } internal static string? MapsKey { get; set; } internal static string? MapPage { get; private set; } /// protected override FrameworkElement CreatePlatformView() { if (string.IsNullOrEmpty(MapsKey)) { throw new InvalidOperationException("You need to specify a Bing Maps Key"); } MapPage = GetMapHtmlPage(MapsKey); var webView = new MauiWebView(new WebViewHandler()); return webView; } /// protected override void ConnectHandler(FrameworkElement platformView) { if (platformView is MauiWebView mauiWebView) { LoadMap(platformView); mauiWebView.NavigationCompleted += HandleWebViewNavigationCompleted; mauiWebView.WebMessageReceived += WebViewWebMessageReceived; Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged; } base.ConnectHandler(platformView); } /// protected override void DisconnectHandler(FrameworkElement platformView) { if (platformView is MauiWebView mauiWebView) { Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged; mauiWebView.NavigationCompleted -= HandleWebViewNavigationCompleted; mauiWebView.WebMessageReceived -= WebViewWebMessageReceived; } base.DisconnectHandler(platformView); } /// /// Maps Map type /// public static new Task MapMapType(IMapHandler handler, IMap map) { return TryCallJSMethod(handler.PlatformView, $"setMapType('{map.MapType}');"); } /// /// Maps IsZoomEnabled /// public static new Task MapIsZoomEnabled(IMapHandler handler, IMap map) { return TryCallJSMethod(handler.PlatformView, $"disableMapZoom({(!map.IsZoomEnabled).ToString().ToLower()});"); } /// /// Maps IsScrollEnabled /// public static new Task MapIsScrollEnabled(IMapHandler handler, IMap map) { return TryCallJSMethod(handler.PlatformView, $"disablePanning({(!map.IsScrollEnabled).ToString().ToLower()});"); } /// /// Maps IsTrafficEnabled /// public static new Task MapIsTrafficEnabled(IMapHandler handler, IMap map) { return TryCallJSMethod(handler.PlatformView, $"disableTraffic({(!map.IsTrafficEnabled).ToString().ToLower()});"); } /// /// Maps IsShowingUser /// public static new async Task MapIsShowingUser(IMapHandler handler, IMap map) { if (map.IsShowingUser) { var location = await GetCurrentLocation(); if (location is not null) { await TryCallJSMethod(handler.PlatformView, $"addLocationPin({location.Latitude.ToString(CultureInfo.InvariantCulture)},{location.Longitude.ToString(CultureInfo.InvariantCulture)});"); } } else { await TryCallJSMethod(handler.PlatformView, "removeLocationPin();"); } } /// /// Map Pins /// public static new async Task MapPins(IMapHandler handler, IMap map) { await TryCallJSMethod(handler.PlatformView, "removeAllPins();"); var addPinTaskList = new List(); foreach (var pin in map.Pins) { addPinTaskList.Add(TryCallJSMethod(handler.PlatformView, $"addPin({pin.Location.Latitude.ToString(CultureInfo.InvariantCulture)}," + $"{pin.Location.Longitude.ToString(CultureInfo.InvariantCulture)},'{pin.Label}', '{pin.Address}', '{(pin as Pin)?.Id}');")); } await Task.WhenAll(addPinTaskList); } /// /// Map Elements /// public static new void MapElements(IMapHandler handler, IMap map) { } /// /// Maps MoveToRegion /// public static new async Task MapMoveToRegion(IMapHandler handler, IMap map, object? arg) { if (arg is not MapSpan newRegion) { return; } if (handler is MapHandlerWindows mapHandler) { mapHandler.regionToGo = newRegion; } await TryCallJSMethod(handler.PlatformView, $"setRegion({newRegion.Center.Latitude.ToString(CultureInfo.InvariantCulture)},{newRegion.Center.Longitude.ToString(CultureInfo.InvariantCulture)},{newRegion.LatitudeDegrees.ToString(CultureInfo.InvariantCulture)},{newRegion.LongitudeDegrees.ToString(CultureInfo.InvariantCulture)});"); } static async Task TryCallJSMethod(FrameworkElement platformWebView, string script) { if (platformWebView is not WebView2 webView2) { return false; } await webView2.EnsureCoreWebView2Async(); var tcs = new TaskCompletionSource(); var isEnqueueSuccessful = webView2.DispatcherQueue.TryEnqueue(async () => { await webView2.ExecuteScriptAsync(script); tcs.SetResult(); }); if (!isEnqueueSuccessful) { return false; } await tcs.Task; return true; } static string GetMapHtmlPage(string key) { var str = @$" "; str += @"
"; return str; } static async Task GetCurrentLocation() { if (await Geolocator.RequestAccessAsync() != GeolocationAccessStatus.Allowed) { return null; } try { var geoLocator = new Geolocator(); var position = await geoLocator.GetGeopositionAsync(); return new Location(position.Coordinate.Latitude, position.Coordinate.Longitude); } catch { return null; } } async void HandleWebViewNavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args) { // Update initial properties when our page is loaded Mapper.UpdateProperties(this, VirtualView); if (regionToGo is not null) { await MapMoveToRegion(this, VirtualView, regionToGo); } } async void WebViewWebMessageReceived(WebView2 sender, CoreWebView2WebMessageReceivedEventArgs args) { // For some reason the web message is empty if (string.IsNullOrEmpty(args.WebMessageAsJson)) { return; } var eventMessage = JsonSerializer.Deserialize(args.WebMessageAsJson, SerializerContext.Default.EventMessage); // The web message (or it's ID) could not be deserialized to something we recognize if (eventMessage is null || !Enum.TryParse(eventMessage.Id, true, out var eventId)) { return; } var payloadAsString = eventMessage.Payload?.ToString(); // The web message does not have a payload if (string.IsNullOrWhiteSpace(payloadAsString)) { return; } switch (eventId) { case EventIdentifier.BoundsChanged: var mapRect = JsonSerializer.Deserialize(payloadAsString, SerializerContext.Default.Bounds); if (mapRect?.Center is not null) { VirtualView.VisibleRegion = new MapSpan(new Location(mapRect.Center.Latitude, mapRect.Center.Longitude), mapRect.Height, mapRect.Width); } break; case EventIdentifier.MapClicked: var clickedLocation = JsonSerializer.Deserialize(payloadAsString, SerializerContext.Default.Location); if (clickedLocation is not null) { VirtualView.Clicked(clickedLocation); } break; case EventIdentifier.InfoWindowClicked: var clickedInfoWindowWebView = JsonSerializer.Deserialize(payloadAsString, SerializerContext.Default.InfoWindow); var clickedInfoWindowWebViewId = clickedInfoWindowWebView?.InfoWindowMarkerId; if (!string.IsNullOrEmpty(clickedInfoWindowWebViewId)) { var clickedPin = VirtualView.Pins.SingleOrDefault(p => (p as Pin)?.Id.ToString().Equals(clickedInfoWindowWebViewId) ?? false); var hideInfoWindow = clickedPin?.SendInfoWindowClick(); if (hideInfoWindow is not false) { await TryCallJSMethod(PlatformView, "hideInfoWindow();"); } } break; case EventIdentifier.PinClicked: var clickedPinWebView = JsonSerializer.Deserialize(payloadAsString, SerializerContext.Default.Pin); var clickedPinWebViewId = clickedPinWebView?.MarkerId?.ToString(); if (!string.IsNullOrEmpty(clickedPinWebViewId)) { var clickedPin = VirtualView.Pins.SingleOrDefault(p => (p as Pin)?.Id.ToString().Equals(clickedPinWebViewId) ?? false); var hideInfoWindow = clickedPin?.SendMarkerClick(); if (hideInfoWindow is not false) { await TryCallJSMethod(PlatformView, "hideInfoWindow();"); } } break; } } void Connectivity_ConnectivityChanged(object? sender, ConnectivityChangedEventArgs e) { LoadMap(PlatformView); } static void LoadMap(FrameworkElement platformView) { if (platformView is MauiWebView mauiWebView && Connectivity.NetworkAccess == NetworkAccess.Internet) { mauiWebView.LoadHtml(MapPage, null); } } }