Skip to content

Commit

Permalink
Basic database search tab added to the UI
Browse files Browse the repository at this point in the history
  • Loading branch information
davewalker5 committed Sep 13, 2023
1 parent 3c86146 commit c43682e
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 83 deletions.
4 changes: 3 additions & 1 deletion src/BaseStationReader.UI/Styles.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</Design.PreviewWith>

<Style Selector="DataGrid">
<Setter Property="Margin" Value="10" />
<Setter Property="Margin" Value="0 10 0 10" />
<Setter Property="Background" Value="Black" />
<Setter Property="GridLinesVisibility" Value="All" />
<Setter Property="RowHeight" Value="15"/>
Expand Down Expand Up @@ -38,6 +38,8 @@

<Style Selector="DatePicker">
<Setter Property="FontSize" Value="12" />
<Setter Property="DayFormat" Value="dd" />
<Setter Property="MonthFormat" Value="MMM" />
</Style>

<Style Selector="CheckBox">
Expand Down
75 changes: 75 additions & 0 deletions src/BaseStationReader.UI/ViewModels/DatabaseSearchViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using BaseStationReader.Data;
using BaseStationReader.Entities.Expressions;
using BaseStationReader.Entities.Tracking;
using BaseStationReader.Logic.Database;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace BaseStationReader.UI.ViewModels
{
internal class DatabaseSearchViewModel
{
public ObservableCollection<Aircraft> SearchResults { get; private set; } = new();

/// <summary>
/// Search the database for records matching the specified filtering criteria
/// </summary>
/// <param name="address"></param>
/// <param name="callsign"></param>
/// <param name="status"></param>
/// <param name="from"></param>
/// <param name="to"></param>
public void Search(string? address, string? callsign, string? status, DateTime? from, DateTime? to)
{
// Create an expression builder and add an expression for each non-null/blak filtering criterion
ExpressionBuilder<Aircraft> builder = new ExpressionBuilder<Aircraft>();

if (!string.IsNullOrEmpty(address))
{
builder.Add("Address", TrackerFilterOperator.Equals, address);
}

if (!string.IsNullOrEmpty(callsign))
{
builder.Add("Callsign", TrackerFilterOperator.Equals, callsign);
}

if (!string.IsNullOrEmpty(status) && Enum.TryParse<TrackingStatus>(status, out TrackingStatus statusEnumValue))
{
builder.Add("Status", TrackerFilterOperator.Equals, statusEnumValue);
}

if (from != null)
{
builder.Add("LastSeen", TrackerFilterOperator.GreaterThanOrEqual, from);
}

if (to != null)
{
builder.Add("LastSeen", TrackerFilterOperator.LessThanOrEqual, to);
}

// Create a database context and an instance of the (reader) writer
var context = new BaseStationReaderDbContextFactory().CreateDbContext(Array.Empty<string>());
var writer = new AircraftWriter(context);

// Build the filter expression. If there is one, use it to filter the collection of aircraft used
// to refresh the grid. Otherwise, just use all the current tracked aircraft
var filter = builder.Build();
List<Aircraft> aircraft;
if (filter != null)
{
aircraft = Task.Run(() => writer.ListAsync(filter)).Result;
}
else
{
aircraft = Task.Run(() => writer.ListAsync(x => true)).Result;
}

// Update the observable collection from the filtered aircraft list
SearchResults = new ObservableCollection<Aircraft>(aircraft);
}
}
}
87 changes: 87 additions & 0 deletions src/BaseStationReader.UI/ViewModels/LiveViewViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using BaseStationReader.Entities.Config;
using BaseStationReader.Entities.Expressions;
using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Tracking;
using BaseStationReader.Logic.Database;
using BaseStationReader.Logic.Tracking;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;


namespace BaseStationReader.UI.ViewModels
{
internal class LiveViewViewModel
{
private ITrackerWrapper? _wrapper = null;

public ObservableCollection<Aircraft> TrackedAircraft { get; private set; } = new();
public bool IsTracking { get { return (_wrapper != null) && _wrapper.IsTracking; } }

/// <summary>
/// Initialise the tracker
/// </summary>
/// <param name="logger"></param>
/// <param name="settings"></param>
public void Initialise(ITrackerLogger logger, ApplicationSettings settings)
{
_wrapper = new TrackerWrapper(logger, settings);
_wrapper.Initialise();
}

/// <summary>
/// Start the tracker
/// </summary>
public void Start()
=> _wrapper!.Start();

/// <summary>
/// Stop the tracker
/// </summary>
public void Stop()
=> _wrapper!.Stop();

/// <summary>
/// Refresh the tracked aircraft collection
/// </summary>
/// <param name="address"></param>
/// <param name="callsign"></param>
/// <param name="status"></param>
public void Refresh(string? address, string? callsign, string? status)
{
// Build the filtering expression, if needed
var builder = new ExpressionBuilder<Aircraft>();
if (!string.IsNullOrEmpty(address))
{
builder.Add("Address", TrackerFilterOperator.Equals, address.ToUpper());
}

if (!string.IsNullOrEmpty(callsign))
{
builder.Add("Callsign", TrackerFilterOperator.Equals, callsign.ToUpper());
}

if (!string.IsNullOrEmpty(status) && Enum.TryParse<TrackingStatus>(status, out TrackingStatus statusEnumValue))
{
builder.Add("Status", TrackerFilterOperator.Equals, statusEnumValue);
}

// Build the filter expression. If there is one, use it to filter the collection of aircraft used
// to refresh the grid. Otherwise, just use all the current tracked aircraft
var filter = builder.Build();
List<Aircraft> aircraft;
if (filter != null)
{
aircraft = _wrapper!.TrackedAircraft.Values.AsQueryable().Where(filter).ToList();
}
else
{
aircraft = _wrapper!.TrackedAircraft.Values.ToList();
}

// Update the observable collection from the filtered aircraft list
TrackedAircraft = new ObservableCollection<Aircraft>(aircraft);
}
}
}
75 changes: 24 additions & 51 deletions src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
using BaseStationReader.Entities.Config;
using BaseStationReader.Entities.Expressions;
using BaseStationReader.Entities.Interfaces;
using BaseStationReader.Entities.Tracking;
using BaseStationReader.Logic.Database;
using BaseStationReader.Logic.Tracking;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace BaseStationReader.UI.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ITrackerWrapper? _wrapper = null;
private LiveViewViewModel _liveView = new LiveViewViewModel();

Check warning on line 11 in src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build

Make '_liveView' 'readonly'.

Check warning on line 11 in src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build

Make '_liveView' 'readonly'.
private DatabaseSearchViewModel _databaseSearch = new DatabaseSearchViewModel();

Check warning on line 12 in src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build

Make '_databaseSearch' 'readonly'.

Check warning on line 12 in src/BaseStationReader.UI/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build

Make '_databaseSearch' 'readonly'.

public ObservableCollection<Aircraft> TrackedAircraft { get; private set; } = new();
public ObservableCollection<string> Statuses { get; private set; } = new();
public bool IsTracking { get { return (_wrapper != null) && _wrapper.IsTracking; } }
public ObservableCollection<Aircraft> TrackedAircraft { get { return _liveView.TrackedAircraft; } }
public bool IsTracking { get { return _liveView.IsTracking; } }

public ObservableCollection<Aircraft> SearchResults { get { return _databaseSearch.SearchResults; } }

public MainWindowViewModel()
{
Expand All @@ -32,65 +30,40 @@ public MainWindowViewModel()
/// </summary>
/// <param name="logger"></param>
/// <param name="settings"></param>
public void Initialise(ITrackerLogger logger, ApplicationSettings settings)
{
_wrapper = new TrackerWrapper(logger, settings);
_wrapper.Initialise();
}
public void InitialiseTracker(ITrackerLogger logger, ApplicationSettings settings)
=> _liveView.Initialise(logger, settings);

/// <summary>
/// Start the tracker
/// </summary>
public void Start()
=> _wrapper!.Start();
public void StartTracking()
=> _liveView.Start();

/// <summary>
/// Stop the tracker
/// </summary>
public void Stop()
=> _wrapper!.Stop();
public void StopTracking()
=> _liveView.Stop();

/// <summary>
/// Refresh the tracked aircraft collection
/// </summary>
/// <param name="address"></param>
/// <param name="callsign"></param>
/// <param name="status"></param>
public void Refresh(string? address, string? callsign, string? status)
{
List<Aircraft> aircraft;

// Build the filtering expression, if needed
var builder = new ExpressionBuilder<Aircraft>();
if (!string.IsNullOrEmpty(address))
{
builder.Add("Address", TrackerFilterOperator.Equals, address.ToUpper());
}

if (!string.IsNullOrEmpty(callsign))
{
builder.Add("Callsign", TrackerFilterOperator.Equals, callsign.ToUpper());
}

if (!string.IsNullOrEmpty(status) && Enum.TryParse<TrackingStatus>(status, out TrackingStatus statusEnumValue))
{
builder.Add("Status", TrackerFilterOperator.Equals, statusEnumValue);
}
public void RefreshTrackedAircraft(string? address, string? callsign, string? status)
=> _liveView.Refresh(address, callsign, status);

// Build the filter expression. If there is one, use it to filter the collection of aircraft used
// to refresh the grid. Otherwise, just use all the current tracked aircraft
var filter = builder.Build();
if (filter != null)
{
aircraft = _wrapper!.TrackedAircraft.Values.AsQueryable().Where(filter).ToList();
}
else
{
aircraft = _wrapper!.TrackedAircraft.Values.ToList();
}

// Update the observable collection from the filtered aircraft list
TrackedAircraft = new ObservableCollection<Aircraft>(aircraft);
}
/// <summary>
/// Search the database for records matching the specified filtering criteria
/// </summary>
/// <param name="address"></param>
/// <param name="callsign"></param>
/// <param name="status"></param>
/// <param name="from"></param>
/// <param name="to"></param>
public void Search(string? address, string? callsign, string? status, DateTime? from, DateTime? to)
=> _databaseSearch.Search(address, callsign, status, from, to);
}
}
54 changes: 48 additions & 6 deletions src/BaseStationReader.UI/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/aircraft.png"
Title="BaseStationReader.UI"
Loaded="OnLoaded">
Loaded="OnLoaded"
WindowStartupLocation="CenterScreen"
SizeToContent="Width">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
Expand All @@ -25,15 +27,15 @@
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<TextBlock Text="ICAO Address" Margin="10 0 0 0" />
<TextBox Name="AddressFilter" Margin="10 0 0 0"/>
<TextBox Name="LiveAddressFilter" Margin="10 0 0 0"/>
<TextBlock Text="Callsign" Margin="10 0 0 0" />
<TextBox Name="CallsignFilter" Margin="10 0 0 0"/>
<TextBox Name="LiveCallsignFilter" Margin="10 0 0 0"/>
<TextBlock Text="Status" Margin="10 0 0 0"/>
<ComboBox Name="StatusFilter" Margin="10 0 0 0" ItemsSource="{Binding Statuses}" />
<ComboBox Name="LiveStatusFilter" Margin="10 0 0 0" ItemsSource="{Binding Statuses}" />
<Button Click="OnClearLiveFilters" Margin="10 0 0 0">Clear Filters</Button>
<TextBlock Margin="10 0 0 0">Refresh interval (s):</TextBlock>
<TextBox Name="RefreshInterval" AcceptsReturn="False" TextChanged="OnRefreshIntervalChanged" Margin="10 0 0 0"/>
<Button Name="StartStop" Click="OnStartStopTracking" Margin="10 0 0 0">Start</Button>
<Button Name="ClearFilters" Click="OnClearFilters" Margin="10 0 0 0">Clear Filters</Button>
<Button Name="StartStop" Click="OnStartStopTracking" Margin="10 0 0 0" IsDefault="True">Start</Button>
</StackPanel>
<DataGrid
DockPanel.Dock="Bottom"
Expand Down Expand Up @@ -64,6 +66,46 @@
<TabItem Header="Map View">
</TabItem>
<TabItem Header="Database Search">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<TextBlock Text="ICAO Address" Margin="10 0 0 0" />
<TextBox Name="DbAddressFilter" Margin="10 0 0 0"/>
<TextBlock Text="Callsign" Margin="10 0 0 0" />
<TextBox Name="DbCallsignFilter" Margin="10 0 0 0"/>
<TextBlock Text="Status" Margin="10 0 0 0"/>
<ComboBox Name="DbStatusFilter" Margin="10 0 0 0" ItemsSource="{Binding Statuses}" />
<TextBlock Text="From" Margin="10 0 0 0"/>
<DatePicker Name="DbFromDate" Margin="10 0 0 0"/>
<TextBlock Text="To" Margin="10 0 0 0"/>
<DatePicker Name="DbToDate" Margin="10 0 0 0"/>
<Button Click="OnClearDbFilters" Margin="10 0 0 0">Clear Filters</Button>
<Button Click="OnSearchDatabase" Margin="10 0 0 0" IsDefault="True">Search</Button>
</StackPanel>
<DataGrid
DockPanel.Dock="Bottom"
Name="DatabaseGrid"
ItemsSource="{Binding SearchResults}"
AutoGenerateColumns="False"
CanUserSortColumns="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
LoadingRow="OnLoadingRow">
<DataGrid.Columns>
<DataGridTextColumn Header="Address" CanUserSort="False" Binding="{Binding Address}"/>
<DataGridTextColumn Header="Callsign" CanUserSort="False" Binding="{Binding Callsign}" />
<DataGridTextColumn Header="Squawk" CanUserSort="False" Binding="{Binding Squawk}" />
<DataGridTextColumn Header="Altitude" CanUserSort="False" Binding="{Binding Altitude}" />
<DataGridTextColumn Header="GroundSpeed" CanUserSort="False" Binding="{Binding GroundSpeed}" />
<DataGridTextColumn Header="Track" CanUserSort="False" Binding="{Binding Track}" />
<DataGridTextColumn Header="Latitude" CanUserSort="False" Binding="{Binding Latitude}" />
<DataGridTextColumn Header="Longitude" CanUserSort="False" Binding="{Binding Longitude}" />
<DataGridTextColumn Header="VerticalRate" CanUserSort="False" Binding="{Binding VerticalRate}" />
<DataGridTextColumn Header="FirstSeen" CanUserSort="False" Binding="{Binding FirstSeen}" />
<DataGridTextColumn Header="LastSeen" CanUserSort="False" Binding="{Binding LastSeen}" />
<DataGridTextColumn Header="Status" CanUserSort="False" Binding="{Binding Status}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</TabItem>
</TabControl>
</DockPanel>
Expand Down
Loading

0 comments on commit c43682e

Please sign in to comment.