Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search View #380

Merged
merged 44 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
eb766fc
WIP: search
nCastle1 Sep 30, 2021
5619dd9
WIP: WPF mostly working
nCastle1 Oct 1, 2021
bbcdecd
WIP code clean up and refactoring
nCastle1 Oct 4, 2021
1ceb69f
Finishes review of ported prototype work
nCastle1 Oct 6, 2021
d4a9ea3
Behavior, nullability
nCastle1 Oct 7, 2021
9b8ba6e
WPF appearance WIP
nCastle1 Oct 10, 2021
defb2cc
Improve appearance of results without subtitles
nCastle1 Oct 10, 2021
3845f05
Improve repeat search behavior, result appearance
nCastle1 Oct 10, 2021
c12074b
WIP WPF theming and behavior
nCastle1 Oct 12, 2021
ffb1633
Style and behavior updates
nCastle1 Oct 14, 2021
a044876
Behavior tweaks, warnings, clean up
nCastle1 Oct 15, 2021
291b551
Additional configuration options
nCastle1 Oct 15, 2021
9408aa3
Warnings and performance
nCastle1 Oct 20, 2021
c6b0bec
Updated UWP implementation
nCastle1 Oct 27, 2021
531ca0f
Update UWP to use popup
nCastle1 Oct 27, 2021
00d43f4
Code cleanup
nCastle1 Oct 27, 2021
f880c39
UWP - Color and margin refinement
nCastle1 Oct 27, 2021
892db50
XAML refactoring
nCastle1 Oct 27, 2021
1ab10bb
WIP: Forms implementation
nCastle1 Oct 28, 2021
d761d55
WIP Xamarin.Forms
nCastle1 Nov 1, 2021
919f480
UI improvements for Forms SearchView
nCastle1 Nov 16, 2021
c0f93e2
Code cleanup, refactoring, doc, samples
nCastle1 Nov 18, 2021
6538c31
Additional doc
nCastle1 Nov 18, 2021
24d5224
Merge branch 'main' into ncastle/search-2
nCastle1 Nov 18, 2021
e4cb745
Sample tidying
nCastle1 Nov 18, 2021
aa82aad
Improvements from self-review
nCastle1 Nov 18, 2021
8ac801c
Improves callout behavior for selected results
nCastle1 Nov 19, 2021
040498a
Improves repeat search behavior, appearance
nCastle1 Nov 19, 2021
26c51c3
Improves message appearance, localization support
nCastle1 Nov 19, 2021
3f430d2
XAML formatting
nCastle1 Nov 19, 2021
2f4bfac
WIP: changes from review
nCastle1 Nov 21, 2021
073a8f4
WIP: review changes
nCastle1 Nov 22, 2021
78f62c1
Address warnings
nCastle1 Nov 23, 2021
b67ba54
Enhanced WPF samples
nCastle1 Nov 23, 2021
c1134f0
Improvements from enhanced testing
nCastle1 Nov 23, 2021
a31aa4e
UWP samples
nCastle1 Nov 24, 2021
889b7bb
Forms sample updates and related improvements
nCastle1 Nov 25, 2021
525b981
Update samples for formatting, exercise SearchMode
nCastle1 Nov 25, 2021
3134a14
Remove redundant sample
nCastle1 Nov 25, 2021
b5c0fd3
Fix: placeholder doesn't appear on load, Forms
nCastle1 Nov 25, 2021
4fb71c4
Improves aspects of suggestion group header Forms
nCastle1 Nov 25, 2021
4acb256
Fixes formatting issue
nCastle1 Nov 26, 2021
6501181
Code cleanup
nCastle1 Nov 26, 2021
e7166e9
Correct spelling error
nCastle1 Nov 30, 2021
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: 1 addition & 2 deletions src/Toolkit.Forms/SearchView/SearchView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,7 @@ private async Task ConfigureForCurrentMap()

try
{
SearchViewModel.Sources.Clear();
SearchViewModel.Sources.Add(await LocatorSearchSource.CreateDefaultSourceAsync());
await SearchViewModel.ConfigureDefaultWorldGeocoder();
}
catch (Exception)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,11 +612,7 @@
<TextBlock
Style="{StaticResource ResultMessageStyle}"
Text="{TemplateBinding NoResultMessage}"
Visibility="{Binding SearchViewModel.Suggestions.Count, Mode=OneWay, Converter={StaticResource CollectionIsEmptyToBoolConverter}, ConverterParameter='Empty', FallbackValue=Collapsed}" />
<TextBlock
Style="{StaticResource ResultMessageStyle}"
Text="{TemplateBinding NoResultMessage}"
Visibility="{Binding SearchViewModel.Results.Count, Mode=OneWay, Converter={StaticResource CollectionIsEmptyToBoolConverter}, ConverterParameter='Empty', FallbackValue=Collapsed}" />
Visibility="{Binding ResultMessageVisibility, Mode=OneWay}" />
</Grid>
</Border>
<Popup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<converter:CollectionIsSingletonToBoolConverter x:Key="CollectionIsSingletonToBoolConverter" />
<converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converter:NullToBoolSelectionConverter x:Key="NullToBoolSelectionConverter" />
<converter:CollectionIsEmptyToVisibilityConverter x:Key="CollectionIsEmptyToVisibilityConverter" />
<Color x:Key="ButtonHoverColor">#f3f3f3</Color>
<Color x:Key="ButtonPressColor">#e2f1fb</Color>
<Color x:Key="BlueButtonColor">#007AC2</Color>
Expand Down Expand Up @@ -529,17 +528,7 @@
<Border
BorderThickness="0,1,0,0"
Style="{StaticResource ResultAreaBorderStyle}"
Visibility="{Binding SearchViewModel.Results.Count, Mode=OneWay, Converter={StaticResource CollectionIsEmptyToVisibilityConverter}, ConverterParameter='Empty'}">
<TextBlock
Margin="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{TemplateBinding NoResultMessage}" />
</Border>
<Border
BorderThickness="0,1,0,0"
Style="{StaticResource ResultAreaBorderStyle}"
Visibility="{Binding SearchViewModel.Suggestions.Count, Mode=OneWay, Converter={StaticResource CollectionIsEmptyToVisibilityConverter}, ConverterParameter='Empty'}">
Visibility="{Binding ResultMessageVisibility, Mode=OneWay}">
<TextBlock
Margin="8"
HorizontalAlignment="Center"
Expand Down
54 changes: 22 additions & 32 deletions src/Toolkit/Toolkit/UI/Controls/SearchView/LocatorSearchSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ public static async Task<LocatorSearchSource> CreateDefaultSourceAsync(Cancellat
return new WorldGeocoderSearchSource(_worldGeocoderTask, null);
}

private bool _loading;
private bool _hasPerformedInitialLoad;
protected Task? _loadTask;

/// <summary>
/// Gets or sets the name of the locator. Defaults to the locator's name, or "locator" if not set.
Expand Down Expand Up @@ -118,6 +117,11 @@ public static async Task<LocatorSearchSource> CreateDefaultSourceAsync(Cancellat
/// <inheritdoc />
public MapPoint? PreferredSearchLocation { get; set; }

/// <summary>
/// Gets or sets the attribute key to use as the subtitle when returning results. Key must be included in <see cref="GeocodeParameters.ResultAttributeNames"/>.
/// </summary>
public string? SubtitleAttributKey { get; set; }
nCastle1 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Initializes a new instance of the <see cref="LocatorSearchSource"/> class.
/// <seealso cref="SmartLocatorSearchSource"/> for a search source with advanced features intended for use with the World Geocoder Service.
Expand All @@ -127,20 +131,12 @@ public LocatorSearchSource(LocatorTask locator)
{
Locator = locator;

_ = EnsureLoaded();
_loadTask = EnsureLoaded();
}

private async Task EnsureLoaded()
{
// TODO = find a better way of handling this
if (_loading || _hasPerformedInitialLoad)
{
return;
}
else
{
_loading = true;
}
await Locator.LoadAsync();

Stream? resourceStream = Assembly.GetAssembly(typeof(LocatorSearchSource))?.GetManifestResourceStream(
"Esri.ArcGISRuntime.Toolkit.EmbeddedResources.pin_red.png");
Expand All @@ -155,24 +151,12 @@ private async Task EnsureLoaded()
DefaultSymbol = pinSymbol;
}

if (!_hasPerformedInitialLoad && Locator.LoadStatus != LoadStatus.Loaded)
if (DisplayName != Locator?.LocatorInfo?.Name && !string.IsNullOrWhiteSpace(Locator?.LocatorInfo?.Name))
{
// TODO = decide how to handle locators that throw.
await Locator.LoadAsync();
DisplayName = Locator?.LocatorInfo?.Name ?? "Locator";
}

if (!_hasPerformedInitialLoad)
{
if (DisplayName != Locator?.LocatorInfo?.Name && !string.IsNullOrWhiteSpace(Locator?.LocatorInfo?.Name))
{
DisplayName = Locator?.LocatorInfo?.Name ?? "Locator";
}

GeocodeParameters.ResultAttributeNames.Add("*");
}

_hasPerformedInitialLoad = true;
_loading = false;
GeocodeParameters.ResultAttributeNames.Add("*");
}

/// <summary>
Expand All @@ -194,7 +178,7 @@ public virtual void NotifyDeselected(SearchResult? result)
/// <inheritdoc/>
public virtual async Task<IList<SearchSuggestion>> SuggestAsync(string queryString, CancellationToken cancellationToken = default)
{
await EnsureLoaded();
await _loadTask;

cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -211,7 +195,7 @@ public virtual async Task<IList<SearchSuggestion>> SuggestAsync(string queryStri
/// <inheritdoc/>
public virtual async Task<IList<SearchResult>> SearchAsync(SearchSuggestion suggestion, CancellationToken cancellationToken = default)
{
await EnsureLoaded();
await _loadTask;

cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -225,7 +209,7 @@ public virtual async Task<IList<SearchResult>> SearchAsync(SearchSuggestion sugg
/// <inheritdoc/>
public virtual async Task<IList<SearchResult>> SearchAsync(string queryString, CancellationToken cancellationToken = default)
{
await EnsureLoaded();
await _loadTask;

cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -243,7 +227,7 @@ public virtual async Task<IList<SearchResult>> SearchAsync(string queryString, C
/// <inheritdoc />
public virtual async Task<IList<SearchResult>> RepeatSearchAsync(string queryString, Envelope queryExtent, CancellationToken cancellationToken = default)
{
await EnsureLoaded();
await _loadTask;

cancellationToken.ThrowIfCancellationRequested();

Expand Down Expand Up @@ -284,8 +268,14 @@ private IList<SearchSuggestion> SuggestionToSearchSuggestion(IReadOnlyList<Sugge
/// </summary>
private SearchResult GeocodeResultToSearchResult(GeocodeResult r)
{
string? subtitle = null;
if (SubtitleAttributKey != null && r.Attributes.ContainsKey(SubtitleAttributKey))
{
subtitle = r.Attributes[SubtitleAttributKey]?.ToString();
}

Mapping.Viewpoint? selectionViewpoint = r.Extent == null ? null : new Mapping.Viewpoint(r.Extent);
return new SearchResult(r.Label, null, this, new Graphic(r.DisplayLocation, r.Attributes, DefaultSymbol), selectionViewpoint) { CalloutDefinition = DefaultCalloutDefinition };
return new SearchResult(r.Label, subtitle, this, new Graphic(r.DisplayLocation, r.Attributes, DefaultSymbol), selectionViewpoint) { CalloutDefinition = DefaultCalloutDefinition };
}

/// <summary>
Expand Down
3 changes: 0 additions & 3 deletions src/Toolkit/Toolkit/UI/Controls/SearchView/SearchResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public GeoElement? GeoElement
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GeoElement)));

// Start loading image
// TODO = decide if it would be better to expose a top-level symbol property
if (_geoElement is Graphic graphic && graphic.Symbol is Symbol symbol)
{
_ = LoadImage(symbol);
Expand Down Expand Up @@ -139,8 +138,6 @@ public SearchResult(string title, string? subtitle, ISearchSource owner, GeoElem
SelectionViewpoint = viewpoint;
}

// TODO - cache these operations; multiple roundtrips to core for images is not fast

/// <summary>
/// Gets the image displayed for this result.
/// </summary>
Expand Down
42 changes: 30 additions & 12 deletions src/Toolkit/Toolkit/UI/Controls/SearchView/SearchView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.UI.Controls;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
#if !NETFX_CORE
using System.Windows;
using System.Windows.Controls;
Expand Down Expand Up @@ -124,7 +125,7 @@ private void SuggestionList_SelectionChanged(object sender, SelectionChangedEven

private async Task ConfigureForCurrentMap()
{
if (!EnableAutomaticConfiguration)
if (!EnableDefaultWorldGeocoder)
{
return;
}
Expand All @@ -138,8 +139,7 @@ private async Task ConfigureForCurrentMap()

try
{
SearchViewModel.Sources.Clear();
SearchViewModel.Sources.Add(await LocatorSearchSource.CreateDefaultSourceAsync(_configurationCancellationToken.Token));
await SearchViewModel.ConfigureDefaultWorldGeocoder(_configurationCancellationToken.Token);
}
catch (Exception)
{
Expand Down Expand Up @@ -223,6 +223,22 @@ public Visibility SourceSelectVisibility
}
}

/// <summary>
/// Gets the visibility for the presentation of the <see cref="NoResultMessage"/>.
/// </summary>
public Visibility ResultMessageVisibility
{
get
{
if (SearchViewModel?.Suggestions?.Count == 0 || SearchViewModel?.Results?.Count == 0)
{
return Visibility.Visible;
}

return Visibility.Collapsed;
}
}

/// <summary>
/// Gets or sets a value indicating whether the source selection view is being displayed.
/// </summary>
Expand Down Expand Up @@ -276,7 +292,7 @@ private static void OnViewModelChanged(DependencyObject d, DependencyPropertyCha
if (e.OldValue is SearchViewModel oldModel)
{
oldModel.PropertyChanged -= sendingView.SearchViewModel_PropertyChanged;
if (oldModel.Sources is ObservableCollection<ISearchSource> oldSources)
if (oldModel.Sources is INotifyCollectionChanged oldSources)
{
oldSources.CollectionChanged -= sendingView.Sources_CollectionChanged;
}
Expand All @@ -285,7 +301,7 @@ private static void OnViewModelChanged(DependencyObject d, DependencyPropertyCha
if (e.NewValue is SearchViewModel newModel)
{
newModel.PropertyChanged += sendingView.SearchViewModel_PropertyChanged;
if (newModel.Sources is ObservableCollection<ISearchSource> newSources)
if (newModel.Sources is INotifyCollectionChanged newSources)
{
newSources.CollectionChanged += sendingView.Sources_CollectionChanged;
}
Expand Down Expand Up @@ -432,6 +448,7 @@ private async Task HandleResultsCollectionChanged()
}

NotifyPropertyChange(nameof(ResultViewVisibility));
NotifyPropertyChange(nameof(ResultMessageVisibility));

if (SearchViewModel.Results == null)
{
Expand Down Expand Up @@ -541,12 +558,12 @@ public SearchViewModel? SearchViewModel
}

/// <summary>
/// Gets or sets a value indicating whether <see cref="SearchView"/> will automatically configure search settings based on the associated <see cref="GeoView"/>'s <see cref="GeoModel"/>.
/// Gets or sets a value indicating whether <see cref="SearchViewModel"/> will include the Esri World Geocoder service by default.
/// </summary>
public bool EnableAutomaticConfiguration
public bool EnableDefaultWorldGeocoder
{
get => (bool)GetValue(EnableAutomaticConfigurationProperty);
set => SetValue(EnableAutomaticConfigurationProperty, value);
get => (bool)GetValue(EnableDefaultWorldGeocoderProperty);
set => SetValue(EnableDefaultWorldGeocoderProperty, value);
}

/// <summary>
Expand Down Expand Up @@ -651,10 +668,10 @@ public string? RepeatSearchButtonText
DependencyProperty.Register(nameof(GeoView), typeof(GeoView), typeof(SearchView), new PropertyMetadata(null, OnGeoViewPropertyChanged));

/// <summary>
/// Identifies the <see cref="EnableAutomaticConfiguration"/> dependency property.
/// Identifies the <see cref="EnableDefaultWorldGeocoder"/> dependency property.
/// </summary>
public static readonly DependencyProperty EnableAutomaticConfigurationProperty =
DependencyProperty.Register(nameof(EnableAutomaticConfiguration), typeof(bool), typeof(SearchView), new PropertyMetadata(true));
public static readonly DependencyProperty EnableDefaultWorldGeocoderProperty =
DependencyProperty.Register(nameof(EnableDefaultWorldGeocoder), typeof(bool), typeof(SearchView), new PropertyMetadata(true));

/// <summary>
/// Identifies the <see cref="EnableRepeatSearchHereButton"/> dependency proeprty.
Expand Down Expand Up @@ -718,6 +735,7 @@ public string? RepeatSearchButtonText
private void HandleSuggestionsChanged()
{
NotifyPropertyChange(nameof(ResultViewVisibility));
NotifyPropertyChange(nameof(ResultMessageVisibility));
#if NETFX_CORE
UpdateGroupingForUWP();
#endif
Expand Down
22 changes: 10 additions & 12 deletions src/Toolkit/Toolkit/UI/Controls/SearchView/SearchViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,6 @@ public async Task CommitSearch()

ApplyNewResult(allResults.SelectMany(l => l).ToList(), null);
}
catch (Exception)
{
// TODO - decide on error handling
}
finally
{
_activeSearchCancellation = null;
Expand Down Expand Up @@ -295,10 +291,6 @@ public async Task RepeatSearchHere()

ApplyNewResult(allResults.SelectMany(l => l).ToList(), _lastSuggestion);
}
catch (Exception)
{
// TODO - decide how to handle exceptions.
}
finally
{
_activeSearchCancellation = null;
Expand Down Expand Up @@ -378,10 +370,6 @@ public async Task AcceptSuggestion(SearchSuggestion suggestion)

ApplyNewResult(results, suggestion);
}
catch (Exception)
{
// TODO - decide how to report.
}
finally
{
_activeSearchCancellation = null;
Expand Down Expand Up @@ -475,6 +463,16 @@ public void CancelSuggestion()
_activeSuggestCancellation?.Cancel();
}

/// <summary>
/// Configures the viewmodel with a search source optimized for use with the Esri World Geocoder service.
/// </summary>
/// <param name="token">Token used for cancellation.</param>
public async Task ConfigureDefaultWorldGeocoder(CancellationToken token = default)
{
Sources.Clear();
Sources.Add(await LocatorSearchSource.CreateDefaultSourceAsync(token));
}

private List<ISearchSource> SourcesToSearch()
{
var selectedSources = new List<ISearchSource>();
Expand Down
Loading