Author SHA1 Message Date
alphafetus 51f1eb8c65 chore: commit missing files 2026-06-21 00:47:18 -04:00
alphafetus 3a819ce180 temp: broken push to laptop 2026-06-19 12:31:12 -04:00
44 changed files with 780 additions and 573 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI" Version="23.2.28" />
<PackageReference Include="ReactiveUI.SourceGenerators" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
@@ -1,40 +1,34 @@
using System; using System.Net.Http.Headers;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using PcgsInvUi.Models;
using System.Text.Json; using System.Text.Json;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace PcgsInvUi.Services; namespace PcgsApi;
public class PcgsClient : ReactiveObject { public partial class PcgsClient : ReactiveObject {
public enum ErrorType { public enum ErrorType {
None, None,
NoCoinFound, NoCoinFound,
InvalidRequestFormat, InvalidRequestFormat,
ApiKeyInvalid ApiKeyInvalid
} }
public int NumApiRequests { protected readonly string _apiKey;
get => _numApiRequests;
set => this.RaiseAndSetIfChanged(ref _numApiRequests, value); protected readonly string _apiUri = "https://api.pcgs.com/publicapi";
}
private string _apiUri = "https://api.pcgs.com/publicapi";
private readonly string _apiKey;
private int _numApiRequests;
/// <summary> /// <summary>
/// Creates a new instance of the PcgsClient class. /// Creates a new instance of the PcgsClient class.
/// Although this is a constructor, it's meant to be called only from the CreateClient method. /// Although this is a constructor, it's meant to be called only from the CreateClient method.
/// </summary> /// </summary>
private PcgsClient(string apiKey) { private PcgsClient(string apiKey) {
_apiKey = apiKey; _apiKey = apiKey;
} }
[Reactive] private int _numApiRequests;
/// <summary> /// <summary>
/// Creates a new instance of the PcgsClient class. /// Creates a new instance of the PcgsClient class.
/// </summary> /// </summary>
/// <param name="apiKey">The api key to use for requests.</param> /// <param name="apiKey">The api key to use for requests.</param>
/// <returns>A new instance of the PcgsClient class, or null if the key is invalid.</returns> /// <returns>A new instance of the PcgsClient class, or null if the key is invalid.</returns>
@@ -47,83 +41,87 @@ public class PcgsClient : ReactiveObject {
} }
/// <summary> /// <summary>
/// Sends a request to the API to verify the credentials. /// Sends a request to the API to verify the credentials.
/// </summary> /// </summary>
/// <returns>A boolean indicating whether or not the credentials are valid.</returns> /// <returns>A boolean indicating whether or not the credentials are valid.</returns>
public async Task<bool> VerifyCredentials() { public async Task<bool> VerifyCredentials() {
// Create a way to check if the api key given is valid // Create a way to check if the api key given is valid
// I think the best way to do this is to make a request to the api and see if it returns the expected response. // I think the best way to do this is to make a request to the api and see if it returns the expected response.
var result = await this.GetCoinFactsByGrade(1, "AG-03"); // This is a known invalid coin id - this is because the api is very fast with invalid requests. var result =
await GetCoinFactsByGrade(1,
"AG-03"); // This is a known invalid coin id - this is because the api is very fast with invalid requests.
if (result.Item1 == ErrorType.ApiKeyInvalid) return false; if (result.Item1 == ErrorType.ApiKeyInvalid) return false;
return true; return true;
} }
/// <summary> /// <summary>
/// Gets the coin information for a given PCGS number and grade. /// Gets the coin information for a given PCGS number and grade.
/// </summary> /// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param> /// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param> /// <param name="grade">The grade of the coin.</param>
/// <param name="plusGrade">Whether or not the coin is a plus grade.</param> /// <param name="plusGrade">Whether or not the coin is a plus grade.</param>
/// <returns>A tuple containing the error type and the coin information.</returns> /// <returns>A tuple containing the error type and the coin information.</returns>
public async Task<(ErrorType, Coin?)> GetCoinFactsByGrade(int pcgsNumber, string grade, bool plusGrade = false) { public async Task<(ErrorType, PcgsCoin?)>
using (HttpClient client = new HttpClient()) { GetCoinFactsByGrade(int pcgsNumber, string grade, bool plusGrade = false) {
using (var client = new HttpClient()) {
var gradeParam = GetGradeFromString(grade); var gradeParam = GetGradeFromString(grade);
string requestUri = var requestUri =
$"{_apiUri}/coindetail/GetCoinFactsByGrade/?PCGSNo={pcgsNumber}&GradeNo={gradeParam}&PlusGrade={plusGrade}"; $"{_apiUri}/coindetail/GetCoinFactsByGrade/?PCGSNo={pcgsNumber}&GradeNo={gradeParam}&PlusGrade={plusGrade}";
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", _apiKey); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", _apiKey);
Console.WriteLine($"Sending request to {requestUri}"); Console.WriteLine($"Sending request to {requestUri}");
HttpResponseMessage response = await client.GetAsync(requestUri); var response = await client.GetAsync(requestUri);
Console.WriteLine("Got response."); Console.WriteLine("Got response.");
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
// Check response for errors. // Check response for errors.
string jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
var parsedResponse = ParseJsonResponse(jsonResponse); var parsedResponse = ParseJsonResponse(jsonResponse);
return parsedResponse; return parsedResponse;
} }
} }
return (ErrorType.ApiKeyInvalid, null); return (ErrorType.ApiKeyInvalid, null);
} }
/// <summary> /// <summary>
/// Utility function to parse the grade number from the grade string passed from the front end. /// Utility function to parse the grade number from the grade string passed from the front end.
/// </summary> /// </summary>
/// <param name="grade">The grade string.</param> /// <param name="grade">The grade string.</param>
/// <returns>The grade number.</returns> /// <returns>The grade number.</returns>
private int GetGradeFromString(string grade) { private int GetGradeFromString(string grade) {
var actualGrade = int.Parse(grade.Substring(grade.Length - 2)); // Throws an exception if the grade doesn't exist. var actualGrade =
int.Parse(grade.Substring(grade.Length - 2)); // Throws an exception if the grade doesn't exist.
Console.WriteLine($"Grade is {actualGrade}"); Console.WriteLine($"Grade is {actualGrade}");
return actualGrade; return actualGrade;
} }
/// <summary> /// <summary>
/// Parses the json response from the api. /// Parses the json response from the api.
/// </summary> /// </summary>
/// <param name="json">The json response.</param> /// <param name="json">The json response.</param>
/// <returns>A tuple containing an error type and the coin information.</returns> /// <returns>A tuple containing an error type and the coin information.</returns>
private (ErrorType, Coin?) ParseJsonResponse(string json) { private (ErrorType, PcgsCoin?) ParseJsonResponse(string json) {
Console.WriteLine(json); Console.WriteLine(json);
// Check for errors in the response // Check for errors in the response
if (json.Contains("\"ServerMessage\":\"No data found\"")) return (ErrorType.NoCoinFound, null); if (json.Contains("\"ServerMessage\":\"No data found\"")) return (ErrorType.NoCoinFound, null);
if (json.Contains("\"IsValidRequest\":false")) return (ErrorType.InvalidRequestFormat, null); if (json.Contains("\"IsValidRequest\":false")) return (ErrorType.InvalidRequestFormat, null);
var newCoin = JsonSerializer.Deserialize<Coin>(json); var newCoin = JsonSerializer.Deserialize<PcgsCoin>(json);
return (ErrorType.None, newCoin); return (ErrorType.None, newCoin);
} }
} }
// Exception for invalid request format // Exception for invalid request format
class InvalidRequestFormatException : Exception { internal class InvalidRequestFormatException : Exception {
public InvalidRequestFormatException() { } public InvalidRequestFormatException() { }
public InvalidRequestFormatException(string message) : base(message) { } public InvalidRequestFormatException(string message) : base(message) { }
public InvalidRequestFormatException(string message, Exception inner) : base(message, inner) { } public InvalidRequestFormatException(string message, Exception inner) : base(message, inner) { }
} }
// Exception for no coin found // Exception for no coin found
class CoinNotFoundException : Exception { internal class CoinNotFoundException : Exception {
public CoinNotFoundException() { } public CoinNotFoundException() { }
public CoinNotFoundException(string message) : base(message) { } public CoinNotFoundException(string message) : base(message) { }
public CoinNotFoundException(string message, Exception inner) : base(message, inner) { } public CoinNotFoundException(string message, Exception inner) : base(message, inner) { }
} }
+38
View File
@@ -0,0 +1,38 @@
using System.Diagnostics;
using System.Reactive.Linq;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace PcgsApi;
public partial class PcgsCoin : ReactiveObject {
[Reactive] private string _pcgsNumber;
[Reactive] private string _name;
[Reactive] private int _year;
[Reactive] private string _denomination;
[Reactive] private string _mintMark;
[Reactive] private string _grade;
[Reactive] private string _coinFactsLink;
[Reactive] private string _majorVariety;
[Reactive] private string _minorVariety;
[Reactive] private string _dieVariety;
[Reactive] private string _seriesName;
[Reactive] private string _category;
[Reactive] private string _designation;
[Reactive] private int _certificateNumber;
[Reactive] private double _paidFor;
[Reactive] private string _notes;
[Reactive] private int _quantity;
[Reactive] private double _priceGuideValue;
[ObservableAsProperty] private double _totalPrice;
public PcgsCoin() {
_totalPriceHelper = this.WhenAnyValue(
x => x.Quantity,
x => x.PriceGuideValue,
(quantity, price) => quantity * price
)
.Do(_ => Debug.WriteLine("PcgsCoin total price calculated"))
.ToProperty(this, nameof(TotalPrice));
}
}
+15
View File
@@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PcgsInv.App"
xmlns:local="using:PcgsInv"
RequestedThemeVariant="Light">
<Application.DataTemplates>
<local:ViewLocator />
</Application.DataTemplates>
<Application.Styles>
<FluentTheme/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>
+22
View File
@@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using PcgsInv.ViewModels;
using PcgsInv.Views;
namespace PcgsInv;
public class App : Application {
public override void Initialize() {
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted() {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow {
DataContext = new MainWindowViewModel()
};
base.OnFrameworkInitializationCompleted();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+47
View File
@@ -0,0 +1,47 @@
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using DynamicData;
using DynamicData.Binding;
using PcgsApi;
namespace PcgsInv.Models;
/// <summary>
/// Contains all information related to a collection of coins.
/// </summary>
public partial class CoinCollection : ReactiveObject {
/// <summary>
/// The name of the collection.
/// </summary>
[Reactive] private string _name;
/// <summary>
/// The collection of <see cref="PcgsCoin"/> objects.
/// </summary>
[Reactive] private ObservableCollection<PcgsCoin> _coins;
/// <summary>
/// The total value of the collection.
/// </summary>
[ObservableAsProperty] private double _totalValue;
/// <summary>
/// Creates a new CoinCollection instance.
/// </summary>
public CoinCollection() {
Name = string.Empty;
Coins = [];
Coins.ToObservableChangeSet()
.AutoRefresh(x => x.TotalPrice)
.ToCollection()
.Select(coins => coins.Sum(c => c.TotalPrice))
.ToProperty(this, nameof(TotalValue), out _totalValueHelper);
}
}
+39
View File
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.0.4"/>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="12.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="12.0.4"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.4"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.4"/>
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.2">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="ReactiveUI" Version="23.2.28" />
<PackageReference Include="ReactiveUI.Avalonia" Version="12.0.3" />
<PackageReference Include="ReactiveUI.SourceGenerators" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Converters\"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PcgsApi\PcgsApi.csproj"/>
</ItemGroup>
</Project>
+27
View File
@@ -0,0 +1,27 @@
using System;
using Avalonia;
using ReactiveUI.Avalonia;
namespace PcgsInv;
internal sealed class Program {
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) {
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI(builder => builder.WithAvalonia())
#if DEBUG
.WithDeveloperTools()
#endif
.WithInterFont()
.LogToTrace();
}
+31
View File
@@ -0,0 +1,31 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using PcgsInv.ViewModels;
namespace PcgsInv;
/// <summary>
/// Given a view model, returns the corresponding view if possible.
/// </summary>
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate {
public Control? Build(object? param) {
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null) return (Control)Activator.CreateInstance(type)!;
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data) {
return data is ViewModelBase;
}
}
+30
View File
@@ -0,0 +1,30 @@
using System;
using System.Diagnostics;
using PcgsApi;
using PcgsInv.Models;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace PcgsInv.ViewModels;
public partial class MainWindowViewModel : ViewModelBase {
[Reactive] private CoinCollection _collection;
[Reactive] private PcgsCoin? _selectedCoin;
public MainWindowViewModel() {
Collection = new CoinCollection();
_selectedCoin = null;
Collection.PropertyChanged += (s, e) => {
Debug.WriteLine($"Collection raised event: {e.PropertyName}");
};
this.WhenAnyValue(x => x.Collection.TotalValue)
.Subscribe(x => Debug.WriteLine($"Total value was recomputed: {x}"));
}
[ReactiveCommand]
private void CreateTestingCoin() {
Collection.Coins.Add(new PcgsCoin());
}
}
+5
View File
@@ -0,0 +1,5 @@
using ReactiveUI;
namespace PcgsInv.ViewModels;
public abstract class ViewModelBase : ReactiveObject { }
+64
View File
@@ -0,0 +1,64 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PcgsInv.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Height="450" Width="800"
x:Class="PcgsInv.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="{Binding Collection.Name, StringFormat='PCGS Inventory - {0}'}">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid ColumnDefinitions="3*, Auto, *"
RowDefinitions="Auto, *">
<Menu Grid.Row="0" Grid.ColumnSpan="2">
<Menu.Items>
<MenuItem Header="File">
<MenuItem Header="Hello"/>
</MenuItem>
</Menu.Items>
</Menu>
<DockPanel Grid.Row="1" Grid.Column="0">
<Grid DockPanel.Dock="Top" Margin="5" ColumnDefinitions="Auto, *">
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="Collection name: "/>
<TextBox Grid.Column="1" Text="{Binding Collection.Name}" />
</Grid>
<Button DockPanel.Dock="Top"
Command="{Binding CreateTestingCoinCommand}"
Content="Create Test Coin"/>
<TextBlock DockPanel.Dock="Bottom"
Margin="5"
Text="{Binding Collection.TotalValue, StringFormat='Total Value: ${0}'}"/>
<DataGrid ItemsSource="{Binding Collection.Coins}"
SelectedItem="{Binding SelectedCoin}"
Margin="5">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}"/>
<DataGridTextColumn Header="Price" Binding="{Binding PriceGuideValue}"/>
<DataGridTextColumn Header="Total" Binding="{Binding TotalPrice}"
IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
<GridSplitter Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Background="Gray" ResizeDirection="Columns" Margin="3"/>
<!-- Some other content -->
<DockPanel Grid.Row="1" Grid.Column="2">
<TextBlock>This is a test!</TextBlock>
</DockPanel>
</Grid>
</Window>
+9
View File
@@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace PcgsInv.Views;
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="PcgsInv.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>
-1
View File
@@ -1,7 +1,6 @@
<Application xmlns="https://github.com/avaloniaui" <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PcgsInvUi.App" x:Class="PcgsInvUi.App"
xmlns:local="using:PcgsInvUi"
xmlns:pcgsInvUi="clr-namespace:PcgsInvUi" xmlns:pcgsInvUi="clr-namespace:PcgsInvUi"
RequestedThemeVariant="Light"> RequestedThemeVariant="Light">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
+6 -7
View File
@@ -7,7 +7,7 @@ using PcgsInvUi.Views;
namespace PcgsInvUi; namespace PcgsInvUi;
public partial class App : Application { public class App : Application {
public override void Initialize() { public override void Initialize() {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
@@ -17,14 +17,13 @@ public partial class App : Application {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
var coins = new CoinDatabase(); var coins = new CoinDatabase();
desktop.MainWindow = new MainWindow { desktop.MainWindow = new MainWindow {
DataContext = new MainWindowViewModel(coins), DataContext = new MainWindowViewModel(coins)
}; };
// Allow the application to save the database when it exits. // Allow the application to save the database when it exits.
desktop.Exit += (sender, args) => { desktop.Exit += (sender, args) => { coins.UpdateCollection("CollectionTable"); };
coins.UpdateCollection("CollectionTable");
};
} }
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
} }
+28 -28
View File
@@ -5,33 +5,33 @@ project(PcgsInvUi LANGUAGES CSharp)
include(CSharpUtilities) include(CSharpUtilities)
add_executable(PcgsInvUi add_executable(PcgsInvUi
App.axaml.cs App.axaml.cs
Program.cs Program.cs
ViewLocator.cs ViewLocator.cs
App.axaml App.axaml
app.manifest app.manifest
Models/Coin.cs Models/Coin.cs
Services/CoinCollection.cs Services/CoinCollection.cs
Services/CoinDatabase.cs Services/CoinDatabase.cs
Services/PcgsClient.cs Services/PcgsClient.cs
ViewModels/CoinListViewModel.cs ViewModels/CoinListViewModel.cs
ViewModels/DeleteWindowViewModel.cs ViewModels/DeleteWindowViewModel.cs
ViewModels/EditViewModel.cs ViewModels/EditViewModel.cs
ViewModels/ErrorWindowViewModel.cs ViewModels/ErrorWindowViewModel.cs
ViewModels/FindWindowViewModel.cs ViewModels/FindWindowViewModel.cs
ViewModels/MainWindowViewModel.cs ViewModels/MainWindowViewModel.cs
ViewModels/NewViewModel.cs ViewModels/NewViewModel.cs
ViewModels/ViewModelBase.cs ViewModels/ViewModelBase.cs
Views/DeleteWindow.axaml Views/DeleteWindow.axaml
Views/DeleteWindow.axaml.cs Views/DeleteWindow.axaml.cs
Views/ErrorWindow.axaml Views/ErrorWindow.axaml
Views/ErrorWindow.axaml.cs Views/ErrorWindow.axaml.cs
Views/FindWindow.axaml Views/FindWindow.axaml
Views/FindWindow.axaml.cs Views/FindWindow.axaml.cs
Views/MainWindow.axaml Views/MainWindow.axaml
Views/MainWindow.axaml.cs Views/MainWindow.axaml.cs
Views/NewView.axaml Views/NewView.axaml
Views/NewView.axaml.cs Views/NewView.axaml.cs
) )
# Set the .NET Framework version for the executable. # Set the .NET Framework version for the executable.
@@ -43,5 +43,5 @@ set(CMAKE_CSharp_FLAGS "/langversion:latest")
# Add the .NET reference libraries as dependencies. # Add the .NET reference libraries as dependencies.
set_property(TARGET PcgsInvUi PROPERTY VS_DOTNET_REFERENCES set_property(TARGET PcgsInvUi PROPERTY VS_DOTNET_REFERENCES
"Microsoft.CSharp" "Microsoft.CSharp"
) )
-165
View File
@@ -1,165 +0,0 @@
using ReactiveUI;
namespace PcgsInvUi.Models;
public class Coin : ReactiveObject {
public string PCGSNo {
get => _pcgsNo;
set => this.RaiseAndSetIfChanged(ref _pcgsNo, value);
}
private string _pcgsNo;
public string Name {
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
private string _name;
public int Year {
get => _year;
set => this.RaiseAndSetIfChanged(ref _year, value);
}
private int _year;
public string Denomination {
get => _denomination;
set => this.RaiseAndSetIfChanged(ref _denomination, value);
}
private string _denomination;
public string MintMark {
get => _mintMark;
set => this.RaiseAndSetIfChanged(ref _mintMark, value);
}
private string _mintMark;
public string Grade {
get => _grade;
set => this.RaiseAndSetIfChanged(ref _grade, value);
}
private string _grade;
public double PriceGuideValue {
get => _priceGuideValue;
set {
TotalPrice = value * Quantity;
this.RaiseAndSetIfChanged(ref _priceGuideValue, value);
}
}
private double _priceGuideValue;
public string CoinFactsLink {
get => _coinFactsLink;
set => this.RaiseAndSetIfChanged(ref _coinFactsLink, value);
}
private string _coinFactsLink;
public string MajorVariety {
get => _majorVariety;
set => this.RaiseAndSetIfChanged(ref _majorVariety, value);
}
private string _majorVariety;
public string MinorVariety {
get => _minorVariety;
set => this.RaiseAndSetIfChanged(ref _minorVariety, value);
}
private string _minorVariety;
public string DieVariety {
get => _dieVariety;
set => this.RaiseAndSetIfChanged(ref _dieVariety, value);
}
private string _dieVariety;
public string SeriesName {
get => _seriesName;
set => this.RaiseAndSetIfChanged(ref _seriesName, value);
}
private string _seriesName;
public string Category {
get => _category;
set => this.RaiseAndSetIfChanged(ref _category, value);
}
private string _category;
public string Designation {
get => _designation;
set => this.RaiseAndSetIfChanged(ref _designation, value);
}
private string _designation;
public int CertificateNumber {
get => _certificateNumber;
set => this.RaiseAndSetIfChanged(ref _certificateNumber, value);
}
private int _certificateNumber;
public int Quantity {
get => _quantity;
set {
TotalPrice = value * PriceGuideValue;
this.RaiseAndSetIfChanged(ref _quantity, value);
}
}
private int _quantity;
public double TotalPrice {
get => _totalPrice;
set => this.RaiseAndSetIfChanged(ref _totalPrice, value);
}
private double _totalPrice;
public double PaidFor {
get => _paidFor;
set => this.RaiseAndSetIfChanged(ref _paidFor, value);
}
private double _paidFor;
public string Notes {
get => _notes;
set => this.RaiseAndSetIfChanged(ref _notes, value);
}
private string _notes;
public Coin() {
PCGSNo = "";
Name = "";
Year = 0;
Denomination = "";
MintMark = "";
Grade = "";
PriceGuideValue = 0;
CoinFactsLink = "";
MajorVariety = "";
MinorVariety = "";
DieVariety = "";
SeriesName = "";
Category = "";
Designation = "";
CertificateNumber = 0;
Quantity = 0;
PaidFor = 0;
Notes = "";
}
}
+18 -11
View File
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<Company>BobdaFett</Company> <Company>BobdaFett</Company>
<AssemblyVersion>0.0.5</AssemblyVersion> <AssemblyVersion>0.0.5</AssemblyVersion>
<LangVersion>latestmajor</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -17,16 +18,17 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.5" /> <PackageReference Include="Avalonia" Version="12.0.4"/>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="12.0.0"/>
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" /> <PackageReference Include="Avalonia.Desktop" Version="12.0.4"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.4"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.5" /> <PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.4"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.5" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.17"/>
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.3.8"/>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/> <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="System.Data.SQLite" Version="1.0.118" /> <PackageReference Include="System.Data.SQLite" Version="2.0.3"/>
</ItemGroup> </ItemGroup>
@@ -65,7 +67,7 @@
<Compile Include="obj\Debug\net6.0\.NETCoreApp,Version=v6.0.AssemblyAttributes.cs"/> <Compile Include="obj\Debug\net6.0\.NETCoreApp,Version=v6.0.AssemblyAttributes.cs"/>
<Compile Include="obj\Debug\net6.0\PcgsInvUi.AssemblyInfo.cs"/> <Compile Include="obj\Debug\net6.0\PcgsInvUi.AssemblyInfo.cs"/>
<Compile Remove="obj\**"/> <Compile Remove="obj\**"/>
<Compile Remove="Services\CoinCollection.cs" /> <Compile Remove="Services\CoinCollection.cs"/>
</ItemGroup> </ItemGroup>
@@ -90,6 +92,11 @@
<ItemGroup> <ItemGroup>
<Reference Include="System.Data" /> <Reference Include="System.Data"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Models\"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+9 -7
View File
@@ -1,22 +1,24 @@
using Avalonia; using System;
using Avalonia;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using System;
using PcgsInvUi.ViewModels;
namespace PcgsInvUi; namespace PcgsInvUi;
class Program { internal class Program {
// Initialization code. Don't use any Avalonia, third-party APIs or any // Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break. // yet and stuff might break.
[STAThread] [STAThread]
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); public static void Main(string[] args) {
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer. // Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp() {
=> AppBuilder.Configure<App>() return AppBuilder.Configure<App>()
.UsePlatformDetect() .UsePlatformDetect()
.WithInterFont() .WithInterFont()
.LogToTrace() .LogToTrace()
.UseReactiveUI(); .UseReactiveUI();
}
} }
+128 -130
View File
@@ -1,22 +1,17 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Data.SQLite; using System.Data.SQLite;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using PcgsInvUi.Models;
namespace PcgsInvUi.Services; namespace PcgsInvUi.Services;
public class CoinDatabase { public class CoinDatabase {
public SQLiteConnection Connection { get; set; }
private Boolean IsConnected { get; set; }
public ObservableCollection<Coin> Collection { get; set; }
public Boolean IsClientConnected { get; set; }
private PcgsClient? _pcgsClient; private PcgsClient? _pcgsClient;
/// <summary> /// <summary>
/// Creates and initilizes a new instance of the CoinDatabase. /// Creates and initilizes a new instance of the CoinDatabase.
/// Must call TryInitApiClient() after creating a new instance. /// Must call TryInitApiClient() after creating a new instance.
/// </summary> /// </summary>
public CoinDatabase() { public CoinDatabase() {
// Create a connection to SQLite // Create a connection to SQLite
@@ -25,13 +20,18 @@ public class CoinDatabase {
CreateCollection("CollectionTable"); CreateCollection("CollectionTable");
CreateApiTable(); CreateApiTable();
Console.WriteLine("Connected to database."); Console.WriteLine("Connected to database.");
Collection = new ObservableCollection<Coin>(); Collection = new ObservableCollection<PcgsCoin>();
GetCollection("CollectionTable"); GetCollection("CollectionTable");
Console.WriteLine("Got collection."); Console.WriteLine("Got collection.");
} }
public SQLiteConnection Connection { get; set; }
private bool IsConnected { get; set; }
public ObservableCollection<PcgsCoin> Collection { get; set; }
public bool IsClientConnected { get; set; }
/// <summary> /// <summary>
/// Destructor. Currently not used, due to the strange nature of desktop applications. /// Destructor. Currently not used, due to the strange nature of desktop applications.
/// </summary> /// </summary>
~CoinDatabase() { ~CoinDatabase() {
Console.WriteLine("Destructor called, saving collection."); Console.WriteLine("Destructor called, saving collection.");
@@ -40,14 +40,14 @@ public class CoinDatabase {
// Close the connection. // Close the connection.
Connection.Close(); Connection.Close();
} }
/// <summary> /// <summary>
/// Attempts to intitialize the API client by selecting the API key from the database. /// Attempts to intitialize the API client by selecting the API key from the database.
/// </summary> /// </summary>
/// <returns>True if the API key was found, false otherwise.</returns> /// <returns>True if the API key was found, false otherwise.</returns>
public async Task<bool> TryInitApiClient() { public async Task<bool> TryInitApiClient() {
Console.Write("Attempting to initialize API client... "); Console.Write("Attempting to initialize API client... ");
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = "SELECT API_KEY FROM ApiTable"; cmd.CommandText = "SELECT API_KEY FROM ApiTable";
using (var reader = cmd.ExecuteReader()) { using (var reader = cmd.ExecuteReader()) {
@@ -70,13 +70,14 @@ public class CoinDatabase {
Console.WriteLine("Failed. API key not found."); Console.WriteLine("Failed. API key not found.");
} }
} }
return IsClientConnected; return IsClientConnected;
} }
/// <summary> /// <summary>
/// Atttempts to initialize the PCGS API client. /// Atttempts to initialize the PCGS API client.
/// </summary> /// </summary>
/// <param name=apiKey>The API key to verify.</param> /// <param name= apiKey>The API key to verify.</param>
/// <returns>A Task with a return value that returns true if the client intializes properly.</returns> /// <returns>A Task with a return value that returns true if the client intializes properly.</returns>
public async Task<bool> TryInitApiClient(string apiKey) { public async Task<bool> TryInitApiClient(string apiKey) {
Console.Write("Attempting to initialize API client with key param... "); Console.Write("Attempting to initialize API client with key param... ");
@@ -98,23 +99,23 @@ public class CoinDatabase {
} }
/// <summary> /// <summary>
/// Gets the collection from the database and stores it in the Collection property. /// Gets the collection from the database and stores it in the Collection property.
/// Creates the collection table if it does not exist already. /// Creates the collection table if it does not exist already.
/// </summary> /// </summary>
/// <returns>The SQLiteConnection that corresponds to the connected database.</returns> /// <returns>The SQLiteConnection that corresponds to the connected database.</returns>
private SQLiteConnection CreateConnection() { private SQLiteConnection CreateConnection() {
// Check if database exists. // Check if database exists.
if (!System.IO.File.Exists("coin_collection")) { if (!File.Exists("coin_collection")) {
// If not, create the database. // If not, create the database.
Console.Write("Creating database... "); Console.Write("Creating database... ");
SQLiteConnection.CreateFile("coin_collection.sqlite"); SQLiteConnection.CreateFile("coin_collection.sqlite");
Console.WriteLine("Done."); Console.WriteLine("Done.");
} }
// Create a connection to the database. // Create a connection to the database.
SQLiteConnection connection = var connection =
new SQLiteConnection("DataSource=coin_collection"); new SQLiteConnection("DataSource=coin_collection");
// Open the connection. // Open the connection.
try { try {
connection.Open(); connection.Open();
@@ -128,7 +129,7 @@ public class CoinDatabase {
} }
/// <summary> /// <summary>
/// Creates the table that stores the API key and the number of requests remaining. /// Creates the table that stores the API key and the number of requests remaining.
/// </summary> /// </summary>
public void CreateApiTable() { public void CreateApiTable() {
// Create a table to store the API key and the number of requests remaining. // Create a table to store the API key and the number of requests remaining.
@@ -139,12 +140,12 @@ public class CoinDatabase {
} }
// Create a SQL command. // Create a SQL command.
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = cmd.CommandText =
"CREATE TABLE IF NOT EXISTS ApiTable (" + "CREATE TABLE IF NOT EXISTS ApiTable (" +
"API_KEY TEXT, " + "API_KEY TEXT, " +
"REQUESTS_REMAINING INTEGER, " + "REQUESTS_REMAINING INTEGER, " +
"PRIMARY KEY(API_KEY))"; "PRIMARY KEY(API_KEY))";
// Execute the command // Execute the command
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
@@ -152,7 +153,7 @@ public class CoinDatabase {
} }
/// <summary> /// <summary>
/// Creates a table to store the collection. /// Creates a table to store the collection.
/// </summary> /// </summary>
public void CreateCollection(string collectionName) { public void CreateCollection(string collectionName) {
// Create a table to store the collection. // Create a table to store the collection.
@@ -161,9 +162,9 @@ public class CoinDatabase {
Console.WriteLine("Not connected to database."); Console.WriteLine("Not connected to database.");
return; return;
} }
// Create a SQL command. // Create a SQL command.
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = cmd.CommandText =
$"CREATE TABLE IF NOT EXISTS {collectionName} (" + $"CREATE TABLE IF NOT EXISTS {collectionName} (" +
"PCGS_NUMBER TEXT," + "PCGS_NUMBER TEXT," +
@@ -184,16 +185,16 @@ public class CoinDatabase {
"QUANTITY INTEGER," + "QUANTITY INTEGER," +
"NOTES TEXT," + "NOTES TEXT," +
"PRIMARY KEY(PCGS_NUMBER, GRADE))"; "PRIMARY KEY(PCGS_NUMBER, GRADE))";
// Execute the command. // Execute the command.
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
Console.WriteLine("Collection table initialized."); Console.WriteLine("Collection table initialized.");
} }
/// <summary> /// <summary>
/// Inserts a Coin object into the database. /// Inserts a Coin object into the database.
/// </summary> /// </summary>
public void InsertCoin(Coin coin) { public void InsertCoin(PcgsCoin pcgsCoin) {
Console.WriteLine("Inserting coin..."); Console.WriteLine("Inserting coin...");
// Use our existing connection to insert a coin to the database. // Use our existing connection to insert a coin to the database.
if (!IsConnected) { if (!IsConnected) {
@@ -201,26 +202,26 @@ public class CoinDatabase {
Console.WriteLine("Not connected to database."); Console.WriteLine("Not connected to database.");
return; return;
} }
// Create a SQL command. // Create a SQL command.
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = cmd.CommandText =
"INSERT INTO CollectionTable (PCGS_NUMBER, GRADE, YEAR," + "INSERT INTO CollectionTable (PCGS_NUMBER, GRADE, YEAR," +
"DENOMINATION, MINT_MARK, PRICE_GUIDE_VALUE, COIN_FACTS_LINK," + "DENOMINATION, MINT_MARK, PRICE_GUIDE_VALUE, COIN_FACTS_LINK," +
"MAJOR_VARIETY, MINOR_VARIETY, DIE_VARIETY, SERIES_NAME, CATEGORY, DESIGNATION," + "MAJOR_VARIETY, MINOR_VARIETY, DIE_VARIETY, SERIES_NAME, CATEGORY, DESIGNATION," +
"CERT_NUM, QUANTITY, NOTES) " + "CERT_NUM, QUANTITY, NOTES) " +
$"VALUES ('{coin.PCGSNo}', '{coin.Grade}', {coin.Year}, '{coin.Denomination}', '{coin.MintMark}', {coin.PriceGuideValue}," + $"VALUES ('{pcgsCoin.PcgsNumber}', '{pcgsCoin.Grade}', {pcgsCoin.Year}, '{pcgsCoin.Denomination}', '{pcgsCoin.MintMark}', {pcgsCoin.PriceGuideValue}," +
$"'{coin.CoinFactsLink}', '{coin.MajorVariety}', '{coin.MinorVariety}', '{coin.DieVariety}', '{coin.SeriesName}', " + $"'{pcgsCoin.CoinFactsLink}', '{pcgsCoin.MajorVariety}', '{pcgsCoin.MinorVariety}', '{pcgsCoin.DieVariety}', '{pcgsCoin.SeriesName}', " +
$"'{coin.Category}', '{coin.Designation}', {coin.CertificateNumber}, {coin.Quantity}, @Notes)"; $"'{pcgsCoin.Category}', '{pcgsCoin.Designation}', {pcgsCoin.CertificateNumber}, {pcgsCoin.Quantity}, @Notes)";
cmd.Parameters.AddWithValue("@Notes", coin.Notes); cmd.Parameters.AddWithValue("@Notes", pcgsCoin.Notes);
// Execute the command. // Execute the command.
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
Console.WriteLine("Coin inserted."); Console.WriteLine("Coin inserted.");
} }
/// <summary> /// <summary>
/// Creates a Coin object and saves it to the database. /// Creates a Coin object and saves it to the database.
/// </summary> /// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param> /// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param> /// <param name="grade">The grade of the coin.</param>
@@ -236,7 +237,7 @@ public class CoinDatabase {
SaveCoin("CollectionTable", coin); SaveCoin("CollectionTable", coin);
return PcgsClient.ErrorType.None; return PcgsClient.ErrorType.None;
} }
// Otherwise, build a new coin with the API and insert it. // Otherwise, build a new coin with the API and insert it.
var apiResult = await _pcgsClient.GetCoinFactsByGrade(pcgsNumber, grade); var apiResult = await _pcgsClient.GetCoinFactsByGrade(pcgsNumber, grade);
if (apiResult.Item1 == PcgsClient.ErrorType.None) { if (apiResult.Item1 == PcgsClient.ErrorType.None) {
@@ -246,68 +247,68 @@ public class CoinDatabase {
Collection.Add(newCoin); Collection.Add(newCoin);
return PcgsClient.ErrorType.None; return PcgsClient.ErrorType.None;
} }
Console.WriteLine("Error getting coin from API - {0}", apiResult.Item1); Console.WriteLine("Error getting coin from API - {0}", apiResult.Item1);
return apiResult.Item1; return apiResult.Item1;
} }
/// <summary> /// <summary>
/// Gets a coin from the database. /// Gets a coin from the database.
/// </summary> /// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param> /// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param> /// <param name="grade">The grade of the coin.</param>
/// <returns>The coin if it exists, null otherwise.</returns> /// <returns>The coin if it exists, null otherwise.</returns>
public Coin? GetCoin(int pcgsNumber, string grade) { public PcgsCoin? GetCoin(int pcgsNumber, string grade) {
Console.WriteLine($"Getting coin {pcgsNumber}, {grade}..."); Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
int gradeInt = GetGradeFromString(grade); var gradeInt = GetGradeFromString(grade);
// Check the collection to see if the coin exists. // Check the collection to see if the coin exists.
// We can't make a more efficient algorithm since there's not really an ordering to the collection. // We can't make a more efficient algorithm since there's not really an ordering to the collection.
foreach (var coin in Collection) { foreach (var coin in Collection)
if (coin.PCGSNo == pcgsNumber.ToString() && coin.Grade.Contains(gradeInt.ToString())) { // This has the possibility of a false positive for single digit grades. if (coin.PcgsNumber == pcgsNumber.ToString() && coin.Grade.Contains(gradeInt.ToString())) {
// This has the possibility of a false positive for single digit grades.
if (gradeInt < 10) if (gradeInt < 10)
if (char.IsDigit(coin.Grade[3])) continue; // TODO For right now, we're going to assume that the first 2 characters are ciruclation strings, and the next 2 are grade numbers. if (char.IsDigit(coin.Grade[3]))
continue; // TODO For right now, we're going to assume that the first 2 characters are ciruclation strings, and the next 2 are grade numbers.
Console.WriteLine("Found coin."); Console.WriteLine("Found coin.");
return coin; return coin;
} }
}
Console.WriteLine("Couldn't find coin."); Console.WriteLine("Couldn't find coin.");
return null; return null;
} }
/// <summary> /// <summary>
/// Used to get all Coin objects within the database. /// Used to get all Coin objects within the database.
/// </summary> /// </summary>
/// <param name="collectionName">The name of the collection table.</param> /// <param name="collectionName">The name of the collection table.</param>
public void GetCollection(string collectionName) { public void GetCollection(string collectionName) {
Console.WriteLine("Getting collection..."); Console.WriteLine("Getting collection...");
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM {collectionName}"; cmd.CommandText = $"SELECT * FROM {collectionName}";
using (var reader = cmd.ExecuteReader()) { using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) { while (reader.Read())
// Create coin from reader then add to list. // Create coin from reader then add to list.
Collection.Add(CoinFromReader(reader)); Collection.Add(CoinFromReader(reader));
}
} }
} }
/// <summary> /// <summary>
/// Creates a CSV formatted string from a collection. /// Creates a CSV formatted string from a collection.
/// </summary> /// </summary>
/// <param name="collectionName">The name of the collection table.</param> /// <param name="collectionName">The name of the collection table.</param>
/// <returns>A CSV formatted string.</returns> /// <returns>A CSV formatted string.</returns>
public String CollectionToCSV(string collectionName) { public string CollectionToCSV(string collectionName) {
var cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM {collectionName}"; cmd.CommandText = $"SELECT * FROM {collectionName}";
var headersCommand = Connection.CreateCommand(); var headersCommand = Connection.CreateCommand();
headersCommand.CommandText = $"PRAGMA table_info({collectionName})"; headersCommand.CommandText = $"PRAGMA table_info({collectionName})";
var resultString = ""; var resultString = "";
var numHeaders = 0; var numHeaders = 0;
// Get the headers. // Get the headers.
using (var reader = headersCommand.ExecuteReader()) { using (var reader = headersCommand.ExecuteReader()) {
while (reader.Read()) { while (reader.Read()) {
@@ -322,23 +323,20 @@ public class CoinDatabase {
using (var reader = cmd.ExecuteReader()) { using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) { while (reader.Read()) {
// Read information from the reader, then add to CSV string. // Read information from the reader, then add to CSV string.
for (int i = 0; i < numHeaders; i++) { for (var i = 0; i < numHeaders; i++) {
// Get the type of each column. // Get the type of each column.
var type = reader.GetFieldType(i); var type = reader.GetFieldType(i);
Console.WriteLine("Type: " + type); Console.WriteLine("Type: " + type);
if (type == typeof(Int64)) { if (type == typeof(long))
resultString += reader.GetInt32(i) + ","; resultString += reader.GetInt32(i) + ",";
} else if (type == typeof(double))
else if (type == typeof(Double)) {
resultString += reader.GetDouble(i) + ","; resultString += reader.GetDouble(i) + ",";
} else if (type == typeof(string))
else if (type == typeof(String)) {
resultString += reader.GetString(i) + ","; resultString += reader.GetString(i) + ",";
} else
else {
resultString += "NULL" + ","; resultString += "NULL" + ",";
}
} }
resultString += "\n"; resultString += "\n";
} }
} }
@@ -347,91 +345,91 @@ public class CoinDatabase {
} }
/// <summary> /// <summary>
/// Saves a Coin object to the database. /// Saves a Coin object to the database.
/// </summary> /// </summary>
/// <param name="collectionName">The name of the collection table.</param> /// <param name="collectionName">The name of the collection table.</param>
/// <param name="coin">The coin to save.</param> /// <param name="pcgsCoin">The coin to save.</param>
public void SaveCoin(string collectionName, Coin coin) { public void SaveCoin(string collectionName, PcgsCoin pcgsCoin) {
// Get and replace the coin from the database. // Get and replace the coin from the database.
Console.WriteLine("Saving coin..."); Console.WriteLine("Saving coin...");
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = $"UPDATE {collectionName} " + cmd.CommandText = $"UPDATE {collectionName} " +
$"SET YEAR = {coin.Year}, " + $"SET YEAR = {pcgsCoin.Year}, " +
$"DENOMINATION = \'{coin.Denomination}\', " + $"DENOMINATION = \'{pcgsCoin.Denomination}\', " +
$"MINT_MARK = \'{coin.MintMark}\', " + $"MINT_MARK = \'{pcgsCoin.MintMark}\', " +
$"PRICE_GUIDE_VALUE = \'{coin.PriceGuideValue}\', " + $"PRICE_GUIDE_VALUE = \'{pcgsCoin.PriceGuideValue}\', " +
$"COIN_FACTS_LINK = \'{coin.CoinFactsLink}\', " + $"COIN_FACTS_LINK = \'{pcgsCoin.CoinFactsLink}\', " +
$"MAJOR_VARIETY = \'{coin.MajorVariety}\', " + $"MAJOR_VARIETY = \'{pcgsCoin.MajorVariety}\', " +
$"MINOR_VARIETY = \'{coin.MinorVariety}\', " + $"MINOR_VARIETY = \'{pcgsCoin.MinorVariety}\', " +
$"DIE_VARIETY = \'{coin.DieVariety}\', " + $"DIE_VARIETY = \'{pcgsCoin.DieVariety}\', " +
$"SERIES_NAME = \'{coin.SeriesName}\', " + $"SERIES_NAME = \'{pcgsCoin.SeriesName}\', " +
$"CATEGORY = \'{coin.Category}\', " + $"CATEGORY = \'{pcgsCoin.Category}\', " +
$"DESIGNATION = \'{coin.Designation}\', " + $"DESIGNATION = \'{pcgsCoin.Designation}\', " +
$"CERT_NUM = \'{coin.CertificateNumber}\', " + $"CERT_NUM = \'{pcgsCoin.CertificateNumber}\', " +
$"QUANTITY = {coin.Quantity}, " + $"QUANTITY = {pcgsCoin.Quantity}, " +
$"NOTES = @Notes " + $"NOTES = @Notes " +
$"WHERE PCGS_NUMBER = {coin.PCGSNo} AND GRADE = \'{coin.Grade}\'"; $"WHERE PCGS_NUMBER = {pcgsCoin.PcgsNumber} AND GRADE = \'{pcgsCoin.Grade}\'";
cmd.Parameters.AddWithValue("@Notes", coin.Notes); cmd.Parameters.AddWithValue("@Notes", pcgsCoin.Notes);
Console.WriteLine(cmd.CommandText); Console.WriteLine(cmd.CommandText);
// Execute the command. // Execute the command.
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
/// <summary> /// <summary>
/// Synchronizes the database with the current contents of the specified collection. /// Synchronizes the database with the current contents of the specified collection.
/// </summary> /// </summary>
/// <param name="collectionName">The name of the collection table.</param> /// <param name="collectionName">The name of the collection table.</param>
public void UpdateCollection(string collectionName) { public void UpdateCollection(string collectionName) {
// Update the database with the contents of the collection. // Update the database with the contents of the collection.
// TODO This is a very inefficient way of doing this. I should be able to do this in one command. // TODO This is a very inefficient way of doing this. I should be able to do this in one command.
foreach (var coin in Collection) { foreach (var coin in
SaveCoin(collectionName, coin); // CAREFUL! This will not create a coin if it doesn't currently exist. Collection)
} SaveCoin(collectionName, coin); // CAREFUL! This will not create a coin if it doesn't currently exist.
} }
/// <summary> /// <summary>
/// Creates a Coin from the information in a reader specifically from this database. /// Creates a Coin from the information in a reader specifically from this database.
/// </summary> /// </summary>
/// <param name="reader">The reader to get the information from.</param> /// <param name="reader">The reader to get the information from.</param>
private Coin CoinFromReader(SQLiteDataReader reader) { private PcgsCoin CoinFromReader(SQLiteDataReader reader) {
// Create a coin while filtering out any possible null values - they throw exceptions. // Create a coin while filtering out any possible null values - they throw exceptions.
Coin coin = new Coin(); PcgsCoin pcgsCoin = new PcgsCoin();
if (!reader.IsDBNull(0)) coin.PCGSNo = reader.GetString(0); if (!reader.IsDBNull(0)) pcgsCoin.PcgsNumber = reader.GetString(0);
if (!reader.IsDBNull(1)) coin.Grade = reader.GetString(1); if (!reader.IsDBNull(1)) pcgsCoin.Grade = reader.GetString(1);
if (!reader.IsDBNull(2)) coin.Year = reader.GetInt32(2); if (!reader.IsDBNull(2)) pcgsCoin.Year = reader.GetInt32(2);
if (!reader.IsDBNull(3)) coin.Denomination = reader.GetString(3); if (!reader.IsDBNull(3)) pcgsCoin.Denomination = reader.GetString(3);
if (!reader.IsDBNull(4)) coin.MintMark = reader.GetString(4); if (!reader.IsDBNull(4)) pcgsCoin.MintMark = reader.GetString(4);
if (!reader.IsDBNull(5)) coin.PriceGuideValue = reader.GetDouble(5); if (!reader.IsDBNull(5)) pcgsCoin.PriceGuideValue = reader.GetDouble(5);
if (!reader.IsDBNull(6)) coin.CoinFactsLink = reader.GetString(6); if (!reader.IsDBNull(6)) pcgsCoin.CoinFactsLink = reader.GetString(6);
if (!reader.IsDBNull(7)) coin.MajorVariety = reader.GetString(7); if (!reader.IsDBNull(7)) pcgsCoin.MajorVariety = reader.GetString(7);
if (!reader.IsDBNull(8)) coin.MinorVariety = reader.GetString(8); if (!reader.IsDBNull(8)) pcgsCoin.MinorVariety = reader.GetString(8);
if (!reader.IsDBNull(9)) coin.DieVariety = reader.GetString(9); if (!reader.IsDBNull(9)) pcgsCoin.DieVariety = reader.GetString(9);
if (!reader.IsDBNull(10)) coin.SeriesName = reader.GetString(10); if (!reader.IsDBNull(10)) pcgsCoin.SeriesName = reader.GetString(10);
if (!reader.IsDBNull(11)) coin.Category = reader.GetString(11); if (!reader.IsDBNull(11)) pcgsCoin.Category = reader.GetString(11);
if (!reader.IsDBNull(12)) coin.Designation = reader.GetString(12); if (!reader.IsDBNull(12)) pcgsCoin.Designation = reader.GetString(12);
if (!reader.IsDBNull(13)) coin.CertificateNumber = reader.GetInt32(13); if (!reader.IsDBNull(13)) pcgsCoin.CertificateNumber = reader.GetInt32(13);
if (!reader.IsDBNull(14)) coin.Quantity = reader.GetInt32(14); if (!reader.IsDBNull(14)) pcgsCoin.Quantity = reader.GetInt32(14);
if (!reader.IsDBNull(15)) coin.Notes = reader.GetString(15); if (!reader.IsDBNull(15)) pcgsCoin.Notes = reader.GetString(15);
coin.TotalPrice = coin.Quantity * coin.PriceGuideValue; // pcgsCoin.TotalPrice = pcgsCoin.Quantity * pcgsCoin.PriceGuideValue;
return coin; return pcgsCoin;
} }
/// <summary> /// <summary>
/// Deletes a Coin object from the database. /// Deletes a Coin object from the database.
/// </summary> /// </summary>
/// <param name="delCoin">The coin to delete.</param> /// <param name="delPcgsCoin">The coin to delete.</param>
public void DeleteCoin(Coin delCoin) { public void DeleteCoin(PcgsCoin delPcgsCoin) {
SQLiteCommand cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade"; cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
cmd.Parameters.AddWithValue("@PcgsNumber", delCoin.PCGSNo); cmd.Parameters.AddWithValue("@PcgsNumber", delPcgsCoin.PcgsNumber);
cmd.Parameters.AddWithValue("@Grade", delCoin.Grade); cmd.Parameters.AddWithValue("@Grade", delPcgsCoin.Grade);
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
/// <summary> /// <summary>
/// Gets the grade from a string returned from the main window. /// Gets the grade from a string returned from the main window.
/// These are always in a format similar to "MS-65", so we can just get the last two characters. /// These are always in a format similar to "MS-65", so we can just get the last two characters.
/// </summary> /// </summary>
/// <param name="grade">The grade string.</param> /// <param name="grade">The grade string.</param>
private int GetGradeFromString(string grade) { private int GetGradeFromString(string grade) {
@@ -443,4 +441,4 @@ public class CoinDatabase {
public class NotConnectedToDatabaseException : Exception { public class NotConnectedToDatabaseException : Exception {
public NotConnectedToDatabaseException() : base("Not connected to database.") { } public NotConnectedToDatabaseException() : base("Not connected to database.") { }
} }
+1 -3
View File
@@ -10,9 +10,7 @@ public class ViewLocator : IDataTemplate {
var name = data.GetType().FullName!.Replace("ViewModel", "View"); var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name); var type = Type.GetType(name);
if (type != null) { if (type != null) return (Control)Activator.CreateInstance(type)!;
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name }; return new TextBlock { Text = "Not Found: " + name };
} }
+14 -26
View File
@@ -1,30 +1,18 @@
using ReactiveUI; using System.Diagnostics;
using System.Reactive; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class ApiKeyViewModel : ViewModelBase { public partial class ApiKeyViewModel : ViewModelBase {
public string ApiKeyInput { [ObservableProperty]
get => _apiKeyInput; public partial string ApiKeyInput { get; set; }
set => this.RaiseAndSetIfChanged(ref _apiKeyInput, value);
[ObservableProperty]
public partial bool OkEnabled { get; set; }
[RelayCommand]
public void OkCommand() {
Debug.WriteLine("Running OkCommand from ApiKeyViewModel");
} }
private string _apiKeyInput = ""; }
public bool OkEnabled {
get => _okEnabled;
set => this.RaiseAndSetIfChanged(ref _okEnabled, value);
}
private bool _okEnabled;
public ReactiveCommand<Unit, string> OkCommand { get; }
public ApiKeyViewModel() {
var okEnabled = this.WhenAnyValue(
x => x.ApiKeyInput,
x => !string.IsNullOrWhiteSpace(x));
// Get the API key from the user.
OkCommand = ReactiveCommand.Create(() => ApiKeyInput, okEnabled);
}
}
+12 -15
View File
@@ -1,23 +1,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using PcgsInvUi.Models; using CommunityToolkit.Mvvm.ComponentModel;
using ReactiveUI;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class CoinListViewModel : ViewModelBase { public partial class CoinListViewModel : ViewModelBase {
public ObservableCollection<Coin> CoinCollection { get; set; } public CoinListViewModel(IEnumerable<PcgsCoin> items) {
private float _totalValue; CoinCollection = new ObservableCollection<PcgsCoin>(items);
public float TotalValue {
get => _totalValue;
set => this.RaiseAndSetIfChanged(ref _totalValue, value);
}
public Coin? SelectedCoin { get; set; }
public CoinListViewModel(IEnumerable<Coin> items) {
CoinCollection = new ObservableCollection<Coin>(items);
TotalValue = 0; TotalValue = 0;
} }
public ObservableCollection<PcgsCoin> CoinCollection { get; set; }
[ObservableProperty]
public partial float TotalValue { get; set; }
[ObservableProperty]
public partial PcgsCoin? SelectedCoin { get; set; }
} }
@@ -4,11 +4,11 @@ using ReactiveUI;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class DeleteWindowViewModel : ViewModelBase { public class DeleteWindowViewModel : ViewModelBase {
public ReactiveCommand<Unit, object> OkCommand { get; }
public ReactiveCommand<Unit, object> CancelCommand { get; }
public DeleteWindowViewModel() { public DeleteWindowViewModel() {
OkCommand = ReactiveCommand.Create(() => (object)true); OkCommand = ReactiveCommand.Create(() => (object)true);
CancelCommand = ReactiveCommand.Create(() => (object)false); CancelCommand = ReactiveCommand.Create(() => (object)false);
} }
}
public ReactiveCommand<Unit, object> OkCommand { get; }
public ReactiveCommand<Unit, object> CancelCommand { get; }
}
+11 -12
View File
@@ -1,27 +1,26 @@
using System.Reactive; using System.Reactive;
using PcgsInvUi.Models;
using ReactiveUI; using ReactiveUI;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class EditViewModel : ViewModelBase { public class EditViewModel : ViewModelBase {
public Coin CurrentCoin { get; set; } public EditViewModel(PcgsCoin pcgsCoin) {
CurrentPcgsCoin = pcgsCoin;
public ReactiveCommand<Unit, Unit> OkCommand { get; }
public EditViewModel(Coin coin) {
CurrentCoin = coin;
var okEnabled = this.WhenAnyValue( var okEnabled = this.WhenAnyValue(
x => x.CurrentCoin.PCGSNo, x => x.CurrentPcgsCoin.PcgsNumber,
x => x.CurrentCoin.Grade, x => x.CurrentPcgsCoin.Grade,
x => x.CurrentCoin.Quantity, x => x.CurrentPcgsCoin.Quantity,
(id, grade, quantity) => !string.IsNullOrWhiteSpace(id) && (id, grade, quantity) => !string.IsNullOrWhiteSpace(id) &&
!string.IsNullOrWhiteSpace(grade) && !string.IsNullOrWhiteSpace(grade) &&
(quantity != 0) quantity != 0
); );
// Setup OkCommand - all Coin-related properties must be non-whitespace. // Setup OkCommand - all Coin-related properties must be non-whitespace.
OkCommand = ReactiveCommand.Create(() => { }); OkCommand = ReactiveCommand.Create(() => { });
} }
}
public PcgsCoin CurrentPcgsCoin { get; set; }
public ReactiveCommand<Unit, Unit> OkCommand { get; }
}
+17 -18
View File
@@ -1,23 +1,22 @@
using System.Reactive; using Avalonia.Controls;
using ReactiveUI; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class ErrorWindowViewModel : ViewModelBase { public partial class ErrorWindowViewModel : ViewModelBase {
private readonly Window _dialog;
public string DisplayedMessage { public ErrorWindowViewModel(Window dialog, string message) {
get => _message; _dialog = dialog;
set => this.RaiseAndSetIfChanged(ref _message, value); Message = message;
}
private string _message;
public ReactiveCommand<Unit, object> CloseCommand { get; }
public ErrorWindowViewModel(string message)
{
_message = message;
CloseCommand = ReactiveCommand.Create(() => (object)false);
} }
} [ObservableProperty]
public partial string Message { get; set; }
[RelayCommand]
public void Close() {
_dialog.Close();
}
}
+44 -45
View File
@@ -1,45 +1,19 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using PcgsInvUi.Models; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using PcgsInvUi.Services; using PcgsInvUi.Services;
using ReactiveUI; using ReactiveUI;
using System.Threading.Tasks;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class MainWindowViewModel : ViewModelBase { public partial class MainWindowViewModel : ViewModelBase {
public ViewModelBase SidebarContent {
get => _sideContent;
private set => this.RaiseAndSetIfChanged(ref _sideContent, value);
}
public ObservableCollection<Coin> DisplayedList { get; set; }
public CoinDatabase ConnectedCoinDatabase { get; set; }
public Coin? SelectedCoin {
get => _selectedCoin;
set => this.RaiseAndSetIfChanged(ref _selectedCoin, value);
}
public double TotalValue {
get => _totalValue;
set => this.RaiseAndSetIfChanged(ref _totalValue, value);
}
public ReactiveCommand<Unit, Unit> DeleteCommand { get; }
public Interaction<DeleteWindowViewModel, Boolean> ShowDeleteWindow { get; }
public ReactiveCommand<Unit, Unit> ExportCommand { get; }
public Interaction<Unit, Uri> ShowExportWindow { get; }
public Interaction<ErrorWindowViewModel, bool> ShowErrorWindow { get; }
private ViewModelBase _sideContent;
private Coin? _selectedCoin;
private double _totalValue;
public MainWindowViewModel(CoinDatabase coins) { public MainWindowViewModel(CoinDatabase coins) {
ConnectedCoinDatabase = coins; ConnectedCoinDatabase = coins;
var keyFound = Task.Run(() => coins.TryInitApiClient()).Result; var keyFound = Task.Run(coins.TryInitApiClient).Result;
Console.WriteLine("Function completed."); Console.WriteLine("Function completed.");
var newViewModel = new NewViewModel(); var newViewModel = new NewViewModel();
var apiKeyViewModel = new ApiKeyViewModel(); var apiKeyViewModel = new ApiKeyViewModel();
@@ -48,21 +22,25 @@ public class MainWindowViewModel : ViewModelBase {
TotalValue = DisplayedList.Sum(x => x.TotalPrice); TotalValue = DisplayedList.Sum(x => x.TotalPrice);
// Setup interaction to show ApiKeyWindow // Setup interaction to show ApiKeyWindow
apiKeyViewModel.OkCommand.Subscribe(async apiKey => { apiKeyViewModel.OkCommandCommand.Subscribe(async apiKey => {
if (await ConnectedCoinDatabase.TryInitApiClient(apiKey)) if (await ConnectedCoinDatabase.TryInitApiClient(apiKey))
SidebarContent = newViewModel; // Must assign this to the observable property rather than private variable. SidebarContent =
}); newViewModel; // Must assign this to the observable property rather than private variable.
});
// Check if API key must be entered. // Check if API key must be entered.
if (!keyFound) { if (!keyFound) {
Console.WriteLine("API key must be entered by user."); Console.WriteLine("API key must be entered by user.");
// Show the API key input view. // Show the API key input view.
_sideContent = apiKeyViewModel; SidebarContent = apiKeyViewModel;
} else _sideContent = newViewModel; }
else {
SidebarContent = newViewModel;
}
// Set up the ability to show the error window. // Set up the ability to show the error window.
ShowErrorWindow = new Interaction<ErrorWindowViewModel, bool>(); ShowErrorWindow = new Interaction<ErrorWindowViewModel, bool>();
// Set up the ability to open the delete window. // Set up the ability to open the delete window.
ShowDeleteWindow = new Interaction<DeleteWindowViewModel, bool>(); ShowDeleteWindow = new Interaction<DeleteWindowViewModel, bool>();
var deleteEnabled = this.WhenAnyValue(x => x.SelectedCoin) var deleteEnabled = this.WhenAnyValue(x => x.SelectedCoin)
@@ -89,23 +67,25 @@ public class MainWindowViewModel : ViewModelBase {
// Something is wrong with the file after it's been created. // Something is wrong with the file after it's been created.
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
await ShowErrorWindow.Handle(new ErrorWindowViewModel(e.Message)); await ShowErrorWindow.Handle(new ErrorWindowViewModel(e.Message));
} catch (Exception e) { }
catch (Exception e) {
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
await ShowErrorWindow.Handle(new ErrorWindowViewModel(e.Message)); await ShowErrorWindow.Handle(new ErrorWindowViewModel(e.Message));
} }
}); });
// Subscribe to SidebarContent.OkCommand, which is an IObservable<(int, string, int)> // Subscribe to SidebarContent.OkCommand, which is an IObservable<(int, string, int)>
// This is the same as the above, but with a different syntax. // This is the same as the above, but with a different syntax.
newViewModel.OkCommand newViewModel.OkCommand
.Subscribe(async requestStructure => { .Subscribe(async requestStructure => {
var result = await ConnectedCoinDatabase.CreateCoin(requestStructure.Item1, requestStructure.Item2, var result = await ConnectedCoinDatabase.CreateCoin(requestStructure.Item1, requestStructure.Item2,
requestStructure.Item3); requestStructure.Item3);
switch (result) { switch (result) {
case PcgsClient.ErrorType.ApiKeyInvalid: case PcgsClient.ErrorType.ApiKeyInvalid:
Console.WriteLine("API key is invalid - showing window."); Console.WriteLine("API key is invalid - showing window.");
await ShowErrorWindow.Handle(new ErrorWindowViewModel("API key is invalid - authorization failed.")); await ShowErrorWindow.Handle(
new ErrorWindowViewModel("API key is invalid - authorization failed."));
break; break;
case PcgsClient.ErrorType.InvalidRequestFormat: case PcgsClient.ErrorType.InvalidRequestFormat:
Console.WriteLine("Request format is invalid - showing window."); Console.WriteLine("Request format is invalid - showing window.");
@@ -113,14 +93,15 @@ public class MainWindowViewModel : ViewModelBase {
break; break;
case PcgsClient.ErrorType.NoCoinFound: case PcgsClient.ErrorType.NoCoinFound:
Console.WriteLine("No coin was found with the given parameters."); Console.WriteLine("No coin was found with the given parameters.");
await ShowErrorWindow.Handle(new ErrorWindowViewModel("No coin was found with the given parameters.")); await ShowErrorWindow.Handle(
new ErrorWindowViewModel("No coin was found with the given parameters."));
break; break;
} }
// Clear the text boxes. // Clear the text boxes.
newViewModel.ClearText(); newViewModel.ClearText();
}); });
// Update TotalValue anytime a coin's quantity changes. // Update TotalValue anytime a coin's quantity changes.
// Possible null reference is currently ignored - SelectedCoin will never be null. // Possible null reference is currently ignored - SelectedCoin will never be null.
this.WhenAnyValue(x => x.SelectedCoin.TotalPrice) this.WhenAnyValue(x => x.SelectedCoin.TotalPrice)
@@ -132,10 +113,28 @@ public class MainWindowViewModel : ViewModelBase {
}; };
} }
[ObservableProperty]
public partial ViewModelBase SidebarContent { get; private set; }
public ObservableCollection<PcgsCoin> DisplayedList { get; set; }
public CoinDatabase ConnectedCoinDatabase { get; set; }
[ObservableProperty]
public partial PcgsCoin? SelectedCoin { get; set; }
[ObservableProperty]
public partial double TotalValue { get; set; }
public ReactiveCommand<Unit, Unit> DeleteCommand { get; }
public Interaction<DeleteWindowViewModel, bool> ShowDeleteWindow { get; }
public ReactiveCommand<Unit, Unit> ExportCommand { get; }
public Interaction<Unit, Uri> ShowExportWindow { get; }
public Interaction<ErrorWindowViewModel, bool> ShowErrorWindow { get; }
// Create destructor to save the database when the application exits. // Create destructor to save the database when the application exits.
// This is necessary because the database is initialized in this class. // This is necessary because the database is initialized in this class.
~MainWindowViewModel() { ~MainWindowViewModel() {
Console.WriteLine("Running MainWindowViewModel destructor."); Console.WriteLine("Running MainWindowViewModel destructor.");
ConnectedCoinDatabase.UpdateCollection("CollectionTable"); ConnectedCoinDatabase.UpdateCollection("CollectionTable");
} }
} }
+23 -25
View File
@@ -1,31 +1,14 @@
using System.Reactive; using System.Collections.Generic;
using System.Collections.Generic; using System.Reactive;
using ReactiveUI; using ReactiveUI;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class NewViewModel : ViewModelBase { public class NewViewModel : ViewModelBase {
private string _pcgsNumber = "";
private string _grade = ""; private string _grade = "";
private string _pcgsNumber = "";
private string _quantity = ""; private string _quantity = "";
public string PcgsNumber {
get => _pcgsNumber;
set => this.RaiseAndSetIfChanged(ref _pcgsNumber, value);
}
public string Grade {
get => _grade;
set => this.RaiseAndSetIfChanged(ref _grade, value);
}
public string Quantity {
get => _quantity;
set => this.RaiseAndSetIfChanged(ref _quantity, value);
}
public List<string> GradesList { get; }
public NewViewModel() { public NewViewModel() {
var okEnabled = this.WhenAnyValue( var okEnabled = this.WhenAnyValue(
x => x.PcgsNumber, x => x.PcgsNumber,
@@ -39,9 +22,7 @@ public class NewViewModel : ViewModelBase {
() => (int.Parse(PcgsNumber), Grade, int.Parse(Quantity)), () => (int.Parse(PcgsNumber), Grade, int.Parse(Quantity)),
okEnabled); okEnabled);
ClearCommand = ReactiveCommand.Create(() => { ClearCommand = ReactiveCommand.Create(() => { ClearText(); });
ClearText();
});
// Initialize grade values for use in the view. // Initialize grade values for use in the view.
// This must be typed out manually, as there is no way to get a list of grades from the API. // This must be typed out manually, as there is no way to get a list of grades from the API.
@@ -79,15 +60,32 @@ public class NewViewModel : ViewModelBase {
}; };
} }
public string PcgsNumber {
get => _pcgsNumber;
set => this.RaiseAndSetIfChanged(ref _pcgsNumber, value);
}
public string Grade {
get => _grade;
set => this.RaiseAndSetIfChanged(ref _grade, value);
}
public string Quantity {
get => _quantity;
set => this.RaiseAndSetIfChanged(ref _quantity, value);
}
public List<string> GradesList { get; }
public ReactiveCommand<Unit, (int, string, int)> OkCommand { get; } public ReactiveCommand<Unit, (int, string, int)> OkCommand { get; }
public ReactiveCommand<Unit, Unit> ClearCommand { get; } public ReactiveCommand<Unit, Unit> ClearCommand { get; }
/// <summary> /// <summary>
/// Clears the text fields. /// Clears the text fields.
/// </summary> /// </summary>
public void ClearText() { public void ClearText() {
PcgsNumber = ""; PcgsNumber = "";
Grade = ""; Grade = "";
Quantity = ""; Quantity = "";
} }
} }
+2 -2
View File
@@ -1,5 +1,5 @@
using ReactiveUI; using CommunityToolkit.Mvvm.ComponentModel;
namespace PcgsInvUi.ViewModels; namespace PcgsInvUi.ViewModels;
public class ViewModelBase : ReactiveObject { } public class ViewModelBase : ObservableObject { }
+8 -8
View File
@@ -1,14 +1,14 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels" xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PcgsInvUi.Views.ApiKeyView" x:DataType="viewModels:ApiKeyViewModel" > x:Class="PcgsInvUi.Views.ApiKeyView" x:DataType="viewModels:ApiKeyViewModel">
<StackPanel> <StackPanel>
<Label Content="API key was not found or did not authenticate." /> <Label Content="API key was not found or did not authenticate." />
<Label Content="Please enter your API key below:" /> <Label Content="Please enter your API key below:" />
<TextBox Margin="10" Text="{Binding ApiKeyInput}" /> <TextBox Margin="10" Text="{Binding ApiKeyInput}" />
<Button HorizontalAlignment="Center" Content="Submit" Command="{Binding OkCommand}" /> <Button HorizontalAlignment="Center" Content="Submit" Command="{Binding OkCommand}" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>
+2 -2
View File
@@ -1,9 +1,9 @@
using Avalonia.Controls; using Avalonia.Controls;
namespace PcgsInvUi.Views; namespace PcgsInvUi.Views;
public partial class ApiKeyView : UserControl { public partial class ApiKeyView : UserControl {
public ApiKeyView() { public ApiKeyView() {
InitializeComponent(); InitializeComponent();
} }
} }
+1 -1
View File
@@ -21,4 +21,4 @@
HorizontalContentAlignment="Center" IsEnabled="True" HorizontalContentAlignment="Center" IsEnabled="True"
Content="No" Command="{Binding CancelCommand}" /> Content="No" Command="{Binding CancelCommand}" />
</Grid> </Grid>
</Window> </Window>
+3 -3
View File
@@ -1,7 +1,7 @@
using PcgsInvUi.ViewModels; using System;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using PcgsInvUi.ViewModels;
using ReactiveUI; using ReactiveUI;
using System;
namespace PcgsInvUi.Views; namespace PcgsInvUi.Views;
@@ -11,4 +11,4 @@ public partial class DeleteWindow : ReactiveWindow<DeleteWindowViewModel> {
this.WhenActivated(d => d(ViewModel!.OkCommand.Subscribe(Close))); this.WhenActivated(d => d(ViewModel!.OkCommand.Subscribe(Close)));
this.WhenActivated(d => d(ViewModel!.CancelCommand.Subscribe(Close))); this.WhenActivated(d => d(ViewModel!.CancelCommand.Subscribe(Close)));
} }
} }
+4 -4
View File
@@ -5,14 +5,14 @@
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels" xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="150" mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="150"
x:Class="PcgsInvUi.Views.ErrorWindow" x:DataType="viewModels:ErrorWindowViewModel" x:Class="PcgsInvUi.Views.ErrorWindow" x:DataType="viewModels:ErrorWindowViewModel"
Title="Error!" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterOwner" Title="Error!" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterOwner"
x:Name="NameErrorWindow" CanResize="False" > x:Name="NameErrorWindow" CanResize="False">
<Grid RowDefinitions="*,*"> <Grid RowDefinitions="*,*">
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Stretch" <Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Stretch"
Margin="15" Margin="15"
Content="{Binding DisplayedMessage}" /> Content="{Binding Message}" />
<Button Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" <Button Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Bottom"
Margin="10" Name="CloseButton" IsEnabled="True" Margin="10" Name="CloseButton" IsEnabled="True"
Content="OK" Command="{Binding CloseCommand, Mode=OneWay}" /> Content="OK" Command="{Binding CloseCommand, Mode=OneWay}" />
</Grid> </Grid>
</Window> </Window>
+3 -6
View File
@@ -1,14 +1,11 @@
using System;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using PcgsInvUi.ViewModels; using PcgsInvUi.ViewModels;
using ReactiveUI;
namespace PcgsInvUi.Views; namespace PcgsInvUi.Views;
public partial class ErrorWindow : ReactiveWindow<ErrorWindowViewModel> { public partial class ErrorWindow : ReactiveWindow<ErrorWindowViewModel> {
public ErrorWindow() public ErrorWindow() {
{
InitializeComponent(); InitializeComponent();
this.WhenActivated(d => d(ViewModel!.CloseCommand.Subscribe(Close))); this.WhenActivated(d => d(ViewModel!.CloseCommand.Subscribe(Close)));
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
using Avalonia.ReactiveUI; using System;
using Avalonia.ReactiveUI;
using PcgsInvUi.ViewModels; using PcgsInvUi.ViewModels;
using ReactiveUI; using ReactiveUI;
using System;
namespace PcgsInvUi.Views; namespace PcgsInvUi.Views;
+4 -4
View File
@@ -5,7 +5,7 @@
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels" xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PcgsInvUi.Views.MainWindow" x:DataType="viewModels:MainWindowViewModel" x:Class="PcgsInvUi.Views.MainWindow" x:DataType="viewModels:MainWindowViewModel"
Title="Main Window" WindowStartupLocation="CenterScreen" WindowState="Maximized" > Title="Main Window" WindowStartupLocation="CenterScreen" WindowState="Maximized">
<SplitView IsPaneOpen="True" <SplitView IsPaneOpen="True"
DisplayMode="Inline" DisplayMode="Inline"
PanePlacement="Right"> PanePlacement="Right">
@@ -15,7 +15,7 @@
Content="{Binding SidebarContent}" /> Content="{Binding SidebarContent}" />
<TextBox Grid.Row="1" Margin="5" Watermark="Notes..." <TextBox Grid.Row="1" Margin="5" Watermark="Notes..."
Text="{Binding SelectedCoin.Notes}" Text="{Binding SelectedCoin.Notes}"
IsReadOnly="False" TextWrapping="Wrap"/> IsReadOnly="False" TextWrapping="Wrap" />
</Grid> </Grid>
</SplitView.Pane> </SplitView.Pane>
<Grid RowDefinitions="*, Auto" ShowGridLines="False"> <Grid RowDefinitions="*, Auto" ShowGridLines="False">
@@ -51,7 +51,7 @@
Binding="{Binding PriceGuideValue, StringFormat='${0:0.00}'}" Binding="{Binding PriceGuideValue, StringFormat='${0:0.00}'}"
IsReadOnly="True" /> IsReadOnly="True" />
<DataGridTextColumn Header="PCGS #" Width="*" <DataGridTextColumn Header="PCGS #" Width="*"
Binding="{Binding PCGSNo}" Binding="{Binding PcgsNumber}"
IsReadOnly="True" /> IsReadOnly="True" />
<DataGridTextColumn Header="Quantity" Width="*" <DataGridTextColumn Header="Quantity" Width="*"
Binding="{Binding Quantity}" Binding="{Binding Quantity}"
@@ -77,4 +77,4 @@
</DockPanel> </DockPanel>
</Grid> </Grid>
</SplitView> </SplitView>
</Window> </Window>
+4 -6
View File
@@ -44,15 +44,13 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel> {
SuggestedStartLocation = await StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents) SuggestedStartLocation = await StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents)
}); });
if (file is not null) { if (file is not null)
interationContext.SetOutput(file.Path); interationContext.SetOutput(file.Path);
} else
else {
// TODO Handle user closing the dialog. // TODO Handle user closing the dialog.
throw new Exception("User cancelled the file picker."); throw new Exception("User cancelled the file picker.");
}
} }
private async Task ShowErrorWindowAsync(InteractionContext<ErrorWindowViewModel, bool> interaction) { private async Task ShowErrorWindowAsync(InteractionContext<ErrorWindowViewModel, bool> interaction) {
var window = new ErrorWindow(); var window = new ErrorWindow();
window.DataContext = interaction.Input; window.DataContext = interaction.Input;
@@ -60,4 +58,4 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel> {
var result = await window.ShowDialog<bool>(this); var result = await window.ShowDialog<bool>(this);
interaction.SetOutput(result); interaction.SetOutput(result);
} }
} }
+13
View File
@@ -0,0 +1,13 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PcgsInvUi.Views.MainWindowRewrite"
Title="MainWindowRewrite">
<Grid>
<TextBlock Text="This is a testing block!" />
</Grid>
</Window>
@@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace PcgsInvUi.Views;
public partial class MainWindowRewrite : Window {
public MainWindowRewrite() {
InitializeComponent();
}
}
+1 -1
View File
@@ -6,4 +6,4 @@ public partial class NewView : UserControl {
public NewView() { public NewView() {
InitializeComponent(); InitializeComponent();
} }
} }
+12
View File
@@ -2,6 +2,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PcgsInvUi", "PcgsInvUi\PcgsInvUi.csproj", "{887D7BD0-3E71-435D-82CD-3DAF3187D056}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PcgsInvUi", "PcgsInvUi\PcgsInvUi.csproj", "{887D7BD0-3E71-435D-82CD-3DAF3187D056}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PcgsInv", "PcgsInv\PcgsInv.csproj", "{8B62E70B-9F3C-4E1B-9E7D-FBADC680E5D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PcgsApi", "PcgsApi\PcgsApi.csproj", "{08B02A6D-EEA5-4335-AC5B-B1347CEBBE88}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -13,5 +17,13 @@ Global
{887D7BD0-3E71-435D-82CD-3DAF3187D056}.Release|Any CPU.ActiveCfg = Release|Any CPU {887D7BD0-3E71-435D-82CD-3DAF3187D056}.Release|Any CPU.ActiveCfg = Release|Any CPU
{887D7BD0-3E71-435D-82CD-3DAF3187D056}.Release|Any CPU.Build.0 = Release|Any CPU {887D7BD0-3E71-435D-82CD-3DAF3187D056}.Release|Any CPU.Build.0 = Release|Any CPU
{887D7BD0-3E71-435D-82CD-3DAF3187D056}.Release|Any CPU.Deploy.0 = Release|Any CPU {887D7BD0-3E71-435D-82CD-3DAF3187D056}.Release|Any CPU.Deploy.0 = Release|Any CPU
{8B62E70B-9F3C-4E1B-9E7D-FBADC680E5D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B62E70B-9F3C-4E1B-9E7D-FBADC680E5D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B62E70B-9F3C-4E1B-9E7D-FBADC680E5D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B62E70B-9F3C-4E1B-9E7D-FBADC680E5D0}.Release|Any CPU.Build.0 = Release|Any CPU
{08B02A6D-EEA5-4335-AC5B-B1347CEBBE88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08B02A6D-EEA5-4335-AC5B-B1347CEBBE88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08B02A6D-EEA5-4335-AC5B-B1347CEBBE88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08B02A6D-EEA5-4335-AC5B-B1347CEBBE88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal