diff --git a/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.Appearance.cs b/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.Appearance.cs index db4f84d3a..9f852809c 100644 --- a/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.Appearance.cs +++ b/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.Appearance.cs @@ -173,7 +173,7 @@ static BasemapGallery() - + diff --git a/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.cs b/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.cs index a840f4bb7..6cd47dc83 100644 --- a/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.cs +++ b/src/Toolkit/Toolkit.Maui/BasemapGallery/BasemapGallery.cs @@ -41,9 +41,20 @@ public BasemapGallery() ListItemTemplate = DefaultListDataTemplate; GridItemTemplate = DefaultGridDataTemplate; ControlTemplate = DefaultControlTemplate; - _ = _controller.LoadFromDefaultPortal(); - } - + Loaded += BasemapGallery_Loaded; + } + + private async void BasemapGallery_Loaded(object? sender, EventArgs e) + { + // Unsubscribe from the Loaded event to ensure this only runs once. + Loaded -= BasemapGallery_Loaded; + + if (AvailableBasemaps is null) + { + await _controller.LoadFromDefaultPortal(); + } + } + private void HandleControllerPropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) @@ -185,7 +196,7 @@ public GeoModel? GeoModel public IList? AvailableBasemaps { get => GetValue(AvailableBasemapsProperty) as IList; - set => SetValue(AvailableBasemapsProperty, value); + set => SetValue(AvailableBasemapsProperty, value); } /// diff --git a/src/Toolkit/Toolkit.UI.Controls/BasemapGallery/BasemapGallery.Windows.cs b/src/Toolkit/Toolkit.UI.Controls/BasemapGallery/BasemapGallery.Windows.cs index 5dca3ef46..66ecfb5f8 100644 --- a/src/Toolkit/Toolkit.UI.Controls/BasemapGallery/BasemapGallery.Windows.cs +++ b/src/Toolkit/Toolkit.UI.Controls/BasemapGallery/BasemapGallery.Windows.cs @@ -45,7 +45,18 @@ public BasemapGallery() SizeChanged += BasemapGallerySizeChanged; AvailableBasemaps = new ObservableCollection(); _controller.PropertyChanged += HandleControllerPropertyChanged; - _ = _controller.LoadFromDefaultPortal(); + Loaded += BasemapGallery_Loaded; + } + + private async void BasemapGallery_Loaded(object? sender, RoutedEventArgs e) + { + // Unsubscribe from the Loaded event to ensure this only runs once. + Loaded -= BasemapGallery_Loaded; + + if (AvailableBasemaps is null) + { + await _controller.LoadFromDefaultPortal(); + } } private void HandleControllerPropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs index c367ece8a..6d47a1e5b 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryController.cs @@ -29,7 +29,9 @@ namespace Esri.ArcGISRuntime.Toolkit.Maui namespace Esri.ArcGISRuntime.Toolkit.UI #endif { +#pragma warning disable CA1001 // Types that own disposable fields should be disposable internal class BasemapGalleryController : INotifyPropertyChanged +#pragma warning restore CA1001 // Types that own disposable fields should be disposable { private ArcGISPortal? _portal; private bool _ignoreEventsFlag; @@ -37,6 +39,7 @@ internal class BasemapGalleryController : INotifyPropertyChanged private GeoModel? _geoModel; private BasemapGalleryItem? _selectedBasemap; private bool _isLoading; + private CancellationTokenSource? _loadCancellationTokenSource; public bool IsLoading { @@ -72,6 +75,7 @@ public IList? AvailableBasemaps HandleAvailableBasemapsChanged(); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AvailableBasemaps))); + _loadCancellationTokenSource?.Cancel(); } } } @@ -227,10 +231,13 @@ private void HandleSelectedBasemapChanged() public async Task LoadFromDefaultPortal() { IsLoading = true; + _loadCancellationTokenSource = new CancellationTokenSource(); try { - AvailableBasemaps = await PopulateFromDefaultList(); + AvailableBasemaps = await PopulateFromDefaultList(_loadCancellationTokenSource.Token); } + catch (OperationCanceledException) + { } finally { IsLoading = false; @@ -305,11 +312,11 @@ private static async Task BasemapIsActuallyNotABasemap(Basemap input) return listOfBasemaps; } - private static async Task> PopulateFromDefaultList() + private static async Task> PopulateFromDefaultList(CancellationToken cancellationToken = default) { - ArcGISPortal defaultPortal = await ArcGISPortal.CreateAsync(); + ArcGISPortal defaultPortal = await ArcGISPortal.CreateAsync(cancellationToken); - var results = await defaultPortal.GetDeveloperBasemapsAsync(); + var results = await defaultPortal.GetDeveloperBasemapsAsync(cancellationToken); var listOfBasemaps = new List(); diff --git a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs index f95807278..d15258827 100644 --- a/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs +++ b/src/Toolkit/Toolkit/UI/Controls/BasemapGallery/BasemapGalleryItem.cs @@ -246,9 +246,53 @@ internal bool EqualsBasemap(Basemap? other) return false; } - return other == Basemap || other.Name == Basemap?.Name - || (other.Item?.ItemId != null && other.Item?.ItemId == Basemap?.Item?.ItemId) - || (other.Uri != null && other.Uri == Basemap?.Uri); + return other == Basemap || (other.Item?.ItemId != null && other.Item?.ItemId == Basemap?.Item?.ItemId) + || (other.Uri != null && other.Uri == Basemap?.Uri) + || AreBasemapsEqualByLayers(Basemap, other); + } + + private static bool AreBasemapsEqualByLayers(Basemap? basemap1, Basemap? basemap2) + { + if (basemap1 == null || basemap2 == null) return false; + + return LayersEqual(basemap1.BaseLayers, basemap2.BaseLayers) + && LayersEqual(basemap1.ReferenceLayers, basemap2.ReferenceLayers); + } + + private static bool LayersEqual(LayerCollection layers1, LayerCollection layers2) + { + return layers1.Count == layers2.Count + && layers1.Zip(layers2, LayerEquals).All(equal => equal); + } + + private static bool LayerEquals(Layer layer1, Layer layer2) + { + // This method can be extended to handle more specific layer comparisons if needed + if (layer1.GetType() != layer2.GetType()) return false; + + return layer1 switch + { + AnnotationLayer annotationLayer => annotationLayer.Source == ((AnnotationLayer)layer2).Source, + ArcGISMapImageLayer arcGISMapImageLayer => arcGISMapImageLayer.Source == ((ArcGISMapImageLayer)layer2).Source, + ArcGISSceneLayer sceneLayer => sceneLayer.Source == ((ArcGISSceneLayer)layer2).Source, + ArcGISTiledLayer tiledLayer => tiledLayer.Source == ((ArcGISTiledLayer)layer2).Source, + ArcGISVectorTiledLayer vectorTiledLayer => vectorTiledLayer.Source == ((ArcGISVectorTiledLayer)layer2).Source, + BingMapsLayer imageServiceLayer => imageServiceLayer.Portal?.Uri == ((BingMapsLayer)layer2).Portal?.Uri, + DimensionLayer dimensionLayer => dimensionLayer.Source == ((DimensionLayer)layer2).Source, + FeatureCollectionLayer featureCollectionLayer => featureCollectionLayer.FeatureCollection == ((FeatureCollectionLayer)layer2).FeatureCollection, + GroupLayer groupLayer => groupLayer.Layers.Count == ((GroupLayer)layer2).Layers.Count && LayersEqual(groupLayer.Layers, ((GroupLayer)layer2).Layers), + IntegratedMeshLayer integratedMeshLayer => integratedMeshLayer.Source == ((IntegratedMeshLayer)layer2).Source, + KmlLayer kmlLayer => kmlLayer.Dataset?.Source == ((KmlLayer)layer2).Dataset?.Source, + Ogc3DTilesLayer ogc3DTilesLayer => ogc3DTilesLayer.Source == ((Ogc3DTilesLayer)layer2).Source, + OpenStreetMapLayer openStreetMapLayer => true, // OpenStreetMap layers are considered equal if types match + PointCloudLayer pointCloudLayer => pointCloudLayer.Source == ((PointCloudLayer)layer2).Source, + WebTiledLayer webTiledLayer => webTiledLayer.TemplateUri == ((WebTiledLayer)layer2).TemplateUri, + ServiceImageTiledLayer serviceImageTiledLayer => serviceImageTiledLayer.TileInfo == ((ServiceImageTiledLayer)layer2).TileInfo && serviceImageTiledLayer.FullExtent == ((ServiceImageTiledLayer)layer2).FullExtent, + SubtypeFeatureLayer subtypeFeatureLayer => subtypeFeatureLayer.FeatureTable == ((SubtypeFeatureLayer)layer2).FeatureTable, + WmsLayer wmsLayer => wmsLayer.Source == ((WmsLayer)layer2).Source, + WmtsLayer wmtsLayer => wmtsLayer.Source == ((WmtsLayer)layer2).Source, + _ => false, + }; } ///