Merge pull request #2 from BobdaFett/vas-working
Bring master up to date with working directory.
This commit was merged in pull request #2.
This commit is contained in:
183 files changed
+939
-7362
No files matched your search
@@ -0,0 +1,4 @@
|
||||
/PcgsInvUi/[Bb]in/
|
||||
/PcgsInvUi/[Oo]bj/
|
||||
[Oo]bj/
|
||||
[Bb]in/
|
||||
Generated
+2
@@ -6,6 +6,8 @@
|
||||
<entry key="PcgsInvUi/App.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/CoinListView.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/DeleteWindow.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/ErrorWindow.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/FindWindow.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/MainWindow.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/MainWindowOld.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
<entry key="PcgsInvUi/Views/NewControl.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
|
||||
|
||||
+3
-2
@@ -2,11 +2,12 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="PcgsInvUi.App"
|
||||
xmlns:local="using:PcgsInvUi"
|
||||
RequestedThemeVariant="Dark">
|
||||
xmlns:pcgsInvUi="clr-namespace:PcgsInvUi"
|
||||
RequestedThemeVariant="Light">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
<pcgsInvUi:ViewLocator />
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
|
||||
+18
-14
@@ -1,32 +1,36 @@
|
||||
using System;
|
||||
using System.Data.Entity.Migrations.Model;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using PcgsInvUi.Services;
|
||||
using PcgsInvUi.ViewModels;
|
||||
using PcgsInvUi.Views;
|
||||
using PcgsInvUi.Models;
|
||||
|
||||
namespace PcgsInvUi;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
public partial class App : Application {
|
||||
public override void Initialize() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
public override void OnFrameworkInitializationCompleted() {
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var coins = new Services.CoinCollection();
|
||||
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
var coins = new CoinDatabase();
|
||||
desktop.MainWindow = new MainWindow {
|
||||
DataContext = new MainWindowViewModel(coins),
|
||||
};
|
||||
}
|
||||
|
||||
desktop.Exit += (sender, args) => {
|
||||
coins.UpdateCollection("CollectionTable");
|
||||
};
|
||||
}
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
public void OnExit(object sender, ControlledApplicationLifetimeExitEventArgs e) {
|
||||
// Save the database?
|
||||
}
|
||||
}
|
||||
+79
-49
@@ -1,135 +1,165 @@
|
||||
using System;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.Models;
|
||||
|
||||
public class Coin : ReactiveObject
|
||||
{
|
||||
public int PcgsNumber
|
||||
{
|
||||
get => _pcgsNumber;
|
||||
set => this.RaiseAndSetIfChanged(ref _pcgsNumber, value);
|
||||
public class Coin : ReactiveObject {
|
||||
public string PCGSNo {
|
||||
get => _pcgsNo;
|
||||
set => this.RaiseAndSetIfChanged(ref _pcgsNo, value);
|
||||
}
|
||||
private int _pcgsNumber;
|
||||
|
||||
public string Name
|
||||
{
|
||||
private string _pcgsNo;
|
||||
|
||||
public string Name {
|
||||
get => _name;
|
||||
set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public int Year
|
||||
{
|
||||
public int Year {
|
||||
get => _year;
|
||||
set => this.RaiseAndSetIfChanged(ref _year, value);
|
||||
}
|
||||
|
||||
private int _year;
|
||||
|
||||
public string Denomination
|
||||
{
|
||||
public string Denomination {
|
||||
get => _denomination;
|
||||
set => this.RaiseAndSetIfChanged(ref _denomination, value);
|
||||
}
|
||||
|
||||
private string _denomination;
|
||||
|
||||
public string MintMark
|
||||
{
|
||||
public string MintMark {
|
||||
get => _mintMark;
|
||||
set => this.RaiseAndSetIfChanged(ref _mintMark, value);
|
||||
}
|
||||
|
||||
private string _mintMark;
|
||||
|
||||
public string Grade
|
||||
{
|
||||
public string Grade {
|
||||
get => _grade;
|
||||
set => this.RaiseAndSetIfChanged(ref _grade, value);
|
||||
}
|
||||
|
||||
private string _grade;
|
||||
|
||||
public double PriceGuideValue
|
||||
{
|
||||
public double PriceGuideValue {
|
||||
get => _priceGuideValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _priceGuideValue, value);
|
||||
set {
|
||||
TotalPrice = value * Quantity;
|
||||
this.RaiseAndSetIfChanged(ref _priceGuideValue, value);
|
||||
}
|
||||
}
|
||||
|
||||
private double _priceGuideValue;
|
||||
|
||||
public string CoinFactsLink
|
||||
{
|
||||
public string CoinFactsLink {
|
||||
get => _coinFactsLink;
|
||||
set => this.RaiseAndSetIfChanged(ref _coinFactsLink, value);
|
||||
}
|
||||
|
||||
private string _coinFactsLink;
|
||||
|
||||
public string MajorVariety
|
||||
{
|
||||
public string MajorVariety {
|
||||
get => _majorVariety;
|
||||
set => this.RaiseAndSetIfChanged(ref _majorVariety, value);
|
||||
}
|
||||
|
||||
private string _majorVariety;
|
||||
|
||||
public string MinorVariety
|
||||
{
|
||||
public string MinorVariety {
|
||||
get => _minorVariety;
|
||||
set => this.RaiseAndSetIfChanged(ref _minorVariety, value);
|
||||
}
|
||||
|
||||
private string _minorVariety;
|
||||
|
||||
public string DieVariety
|
||||
{
|
||||
public string DieVariety {
|
||||
get => _dieVariety;
|
||||
set => this.RaiseAndSetIfChanged(ref _dieVariety, value);
|
||||
}
|
||||
|
||||
private string _dieVariety;
|
||||
|
||||
public string Series
|
||||
{
|
||||
get => _series;
|
||||
set => this.RaiseAndSetIfChanged(ref _series, value);
|
||||
public string SeriesName {
|
||||
get => _seriesName;
|
||||
set => this.RaiseAndSetIfChanged(ref _seriesName, value);
|
||||
}
|
||||
private string _series;
|
||||
|
||||
public string Category
|
||||
{
|
||||
private string _seriesName;
|
||||
|
||||
public string Category {
|
||||
get => _category;
|
||||
set => this.RaiseAndSetIfChanged(ref _category, value);
|
||||
}
|
||||
|
||||
private string _category;
|
||||
|
||||
public string Designation
|
||||
{
|
||||
public string Designation {
|
||||
get => _designation;
|
||||
set => this.RaiseAndSetIfChanged(ref _designation, value);
|
||||
}
|
||||
|
||||
private string _designation;
|
||||
|
||||
public int CertificateNumber
|
||||
{
|
||||
public int CertificateNumber {
|
||||
get => _certificateNumber;
|
||||
set => this.RaiseAndSetIfChanged(ref _certificateNumber, value);
|
||||
}
|
||||
|
||||
private int _certificateNumber;
|
||||
|
||||
public int Quantity
|
||||
{
|
||||
public int Quantity {
|
||||
get => _quantity;
|
||||
set => this.RaiseAndSetIfChanged(ref _quantity, value);
|
||||
set {
|
||||
TotalPrice = value * PriceGuideValue;
|
||||
this.RaiseAndSetIfChanged(ref _quantity, value);
|
||||
}
|
||||
}
|
||||
|
||||
private int _quantity;
|
||||
|
||||
public double TotalPrice => Quantity * PriceGuideValue;
|
||||
public double TotalPrice {
|
||||
get => _totalPrice;
|
||||
set => this.RaiseAndSetIfChanged(ref _totalPrice, value);
|
||||
}
|
||||
|
||||
public double PaidFor
|
||||
{
|
||||
private double _totalPrice;
|
||||
|
||||
public double PaidFor {
|
||||
get => _paidFor;
|
||||
set => this.RaiseAndSetIfChanged(ref _paidFor, value);
|
||||
}
|
||||
|
||||
private double _paidFor;
|
||||
|
||||
public String Notes
|
||||
{
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,85 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<AvaloniaResource Include="Assets\**"/>
|
||||
<AvaloniaResource Remove="obj\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.5" />
|
||||
<!--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.2" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="obj\Debug\net6.0\apphost.exe"/>
|
||||
<Content Include="obj\Debug\net6.0\Avalonia\original.dll"/>
|
||||
<Content Include="obj\Debug\net6.0\Avalonia\original.pdb"/>
|
||||
<Content Include="obj\Debug\net6.0\Avalonia\original.ref.dll"/>
|
||||
<Content Include="obj\Debug\net6.0\Avalonia\references"/>
|
||||
<Content Include="obj\Debug\net6.0\Avalonia\resources"/>
|
||||
<Content Include="obj\Debug\net6.0\Avalonia\Resources.Inputs.cache"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.AssemblyInfoInputs.cache"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.assets.cache"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.csproj.AssemblyReference.cache"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.csproj.CopyComplete"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.csproj.CoreCompileInputs.cache"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.csproj.FileListAbsolute.txt"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.dll"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.GeneratedMSBuildEditorConfig.editorconfig"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.genruntimeconfig.cache"/>
|
||||
<Content Include="obj\Debug\net6.0\PcgsInvUi.pdb"/>
|
||||
<Content Include="obj\Debug\net6.0\refint\PcgsInvUi.dll"/>
|
||||
<Content Include="obj\Debug\net6.0\ref\PcgsInvUi.dll"/>
|
||||
<Content Include="obj\PcgsInvUi.csproj.nuget.dgspec.json"/>
|
||||
<Content Include="obj\PcgsInvUi.csproj.nuget.g.props"/>
|
||||
<Content Include="obj\PcgsInvUi.csproj.nuget.g.targets"/>
|
||||
<Content Include="obj\project.assets.json"/>
|
||||
<Content Include="obj\project.nuget.cache"/>
|
||||
<Content Include="obj\project.packagespec.json"/>
|
||||
<Content Include="obj\rider.project.model.nuget.info"/>
|
||||
<Content Include="obj\rider.project.restore.info"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="obj\Debug\net6.0\.NETCoreApp,Version=v6.0.AssemblyAttributes.cs"/>
|
||||
<Compile Include="obj\Debug\net6.0\PcgsInvUi.AssemblyInfo.cs"/>
|
||||
<Compile Remove="obj\**"/>
|
||||
<Compile Remove="Services\CoinCollection.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaXaml Remove="obj\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="obj\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Remove="obj\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="obj\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,17 +1,16 @@
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using System;
|
||||
using PcgsInvUi.ViewModels;
|
||||
|
||||
namespace PcgsInvUi;
|
||||
|
||||
class Program
|
||||
{
|
||||
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);
|
||||
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
|
||||
@@ -3,10 +3,8 @@ using PcgsInvUi.Models;
|
||||
|
||||
namespace PcgsInvUi.Services;
|
||||
|
||||
public class CoinCollection
|
||||
{
|
||||
public IEnumerable<Coin> GetItems() => new[]
|
||||
{
|
||||
public class CoinCollection {
|
||||
public IEnumerable<Coin> GetItems() => new[] {
|
||||
new Coin(),
|
||||
new Coin(),
|
||||
new Coin(),
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data.SQLite;
|
||||
using System.Threading.Tasks;
|
||||
using PcgsInvUi.Models;
|
||||
|
||||
namespace PcgsInvUi.Services;
|
||||
|
||||
public class CoinDatabase {
|
||||
public SQLiteConnection Connection { get; set; }
|
||||
private Boolean IsConnected { get; set; }
|
||||
public ObservableCollection<Coin> Collection { get; set; }
|
||||
|
||||
private PcgsClient _pcgsClient;
|
||||
|
||||
public CoinDatabase() {
|
||||
// Create a connection to SQLite
|
||||
Connection = CreateConnection();
|
||||
// Create a client to access the API.
|
||||
_pcgsClient = new PcgsClient(
|
||||
"eAb8gS0I2XAvT_5gJeiGJaglMia1Tk-oB4kJUK6kuafyrny_S61vIJY-Ikl4nCQM67wrdxzUqLVWTV2kBSxD3d5XNBHxHnYBhcSS6dOPug0hZaF3qAv56df3gYSzOGh9Tif5y0eP3Iw0LrqKDr1Hj-dk6SV6GKog2IIqCPQhhHH8FMTWBTYO-_O8cx7qLdM5GM8KlTsic6g3VRUhM8EA_4OO04dCfmNLGhqINRl3jGZ0Q4ziI8fng2bVWsIyteqiPzUn10rIQ3-OPpqVZG_DxeOmOejj4GzbUNyqUOajy-nr5rYY");
|
||||
// Create a base table.
|
||||
CreateCollection("CollectionTable");
|
||||
Console.WriteLine("Connected to database.");
|
||||
Collection = new ObservableCollection<Coin>();
|
||||
GetCollection("CollectionTable");
|
||||
Console.WriteLine("Got collection.");
|
||||
}
|
||||
|
||||
~CoinDatabase() {
|
||||
Console.WriteLine("Destructor called, saving collection.");
|
||||
// Save collection.
|
||||
UpdateCollection("CollectionTable");
|
||||
// Close the connection.
|
||||
Connection.Close();
|
||||
}
|
||||
|
||||
private SQLiteConnection CreateConnection() {
|
||||
// Check if database exists.
|
||||
if (!System.IO.File.Exists("%USERPROFILE%\\coin_collection")) {
|
||||
// Create the database.
|
||||
SQLiteConnection.CreateFile("%USERPROFILE%\\coin_collection");
|
||||
}
|
||||
|
||||
// Create a connection to the database.
|
||||
SQLiteConnection connection =
|
||||
new SQLiteConnection("DataSource=%USERPROFILE%\\coin_collection");
|
||||
|
||||
// Open the connection.
|
||||
try {
|
||||
connection.Open();
|
||||
IsConnected = true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void CreateCollection(string collectionName) {
|
||||
// Create a table to store the collection.
|
||||
if (!IsConnected) {
|
||||
// TODO Create a NotConnectedToDatabaseException
|
||||
Console.WriteLine("Not connected to database.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a SQL command.
|
||||
SQLiteCommand cmd = Connection.CreateCommand();
|
||||
cmd.CommandText =
|
||||
$"CREATE TABLE IF NOT EXISTS {collectionName} (" +
|
||||
"PCGS_NUMBER TEXT," +
|
||||
"GRADE TEXT" +
|
||||
"NAME TEXT," +
|
||||
"YEAR INTEGER," +
|
||||
"DENOMINATION TEXT," +
|
||||
"MINT_MARK TEXT," +
|
||||
"PRICE_GUIDE_VALUE REAL," +
|
||||
"COIN_FACTS_LINK TEXT," +
|
||||
"MAJOR_VARIETY TEXT," +
|
||||
"MINOR_VARIETY TEXT," +
|
||||
"DIE_VARIETY TEXT," +
|
||||
"SERIES_NAME TEXT," +
|
||||
"CATEGORY TEXT," +
|
||||
"DESIGNATION TEXT," +
|
||||
"CERT_NUM INTEGER," +
|
||||
"QUANTITY INTEGER," +
|
||||
"NOTES TEXT," +
|
||||
"PRIMARY KEY(PCGS_NUMBER, GRADE))";
|
||||
|
||||
// Execute the command.
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public void InsertCoin(Coin coin) {
|
||||
Console.WriteLine("Inserting coin...");
|
||||
// Use our existing connection to insert a coin to the database.
|
||||
if (!IsConnected) {
|
||||
// TODO Create a NotConnectedToDatabaseException
|
||||
Console.WriteLine("Not connected to database.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a SQL command.
|
||||
SQLiteCommand cmd = Connection.CreateCommand();
|
||||
cmd.CommandText =
|
||||
"INSERT INTO CollectionTable (PCGS_NUMBER, GRADE, YEAR," +
|
||||
"DENOMINATION, MINT_MARK, PRICE_GUIDE_VALUE, COIN_FACTS_LINK," +
|
||||
"MAJOR_VARIETY, MINOR_VARIETY, DIE_VARIETY, SERIES_NAME, CATEGORY, DESIGNATION," +
|
||||
"CERT_NUM, QUANTITY, NOTES) " +
|
||||
$"VALUES ('{coin.PCGSNo}', '{coin.Grade}', {coin.Year}, '{coin.Denomination}', '{coin.MintMark}', {coin.PriceGuideValue}," +
|
||||
$"'{coin.CoinFactsLink}', '{coin.MajorVariety}', '{coin.MinorVariety}', '{coin.DieVariety}', '{coin.SeriesName}', " +
|
||||
$"'{coin.Category}', '{coin.Designation}', {coin.CertificateNumber}, {coin.Quantity}, @Notes)";
|
||||
cmd.Parameters.AddWithValue("@Notes", coin.Notes);
|
||||
|
||||
// Execute the command.
|
||||
cmd.ExecuteNonQuery();
|
||||
Console.WriteLine("Coin inserted.");
|
||||
}
|
||||
|
||||
public async Task<PcgsClient.ErrorType> CreateCoin(int pcgsNumber, string grade, int quantity) {
|
||||
Console.WriteLine("Creating coin...");
|
||||
// Create a coin by calling the API, then insert it into the database.
|
||||
// First, check if the coin already exists - if it does, just update the quantity by adding the amount entered.
|
||||
var coin = GetCoin(pcgsNumber, grade);
|
||||
if (coin is not null) {
|
||||
coin.Quantity += quantity;
|
||||
SaveCoin("CollectionTable", coin);
|
||||
return PcgsClient.ErrorType.None;
|
||||
}
|
||||
|
||||
// Otherwise, build a new coin with the API and insert it.
|
||||
var apiResult = await _pcgsClient.GetCoinFactsByGrade(pcgsNumber, grade);
|
||||
if (apiResult.Item1 == PcgsClient.ErrorType.None) {
|
||||
var newCoin = apiResult.Item2;
|
||||
newCoin.Quantity = quantity;
|
||||
InsertCoin(newCoin);
|
||||
Collection.Add(newCoin);
|
||||
return PcgsClient.ErrorType.None;
|
||||
}
|
||||
|
||||
Console.WriteLine("Error getting coin from API - {0}", apiResult.Item1);
|
||||
return apiResult.Item1;
|
||||
}
|
||||
|
||||
public Coin? GetCoin(int pcgsNumber, string grade) {
|
||||
Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
|
||||
int gradeInt = GetGradeFromString(grade);
|
||||
|
||||
// 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.
|
||||
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 (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.
|
||||
Console.WriteLine("Found coin.");
|
||||
return coin;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Couldn't find coin.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public void GetCollection(string collectionName) {
|
||||
Console.WriteLine("Getting collection...");
|
||||
SQLiteCommand cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = $"SELECT * FROM {collectionName}";
|
||||
|
||||
using (var reader = cmd.ExecuteReader()) {
|
||||
while (reader.Read()) {
|
||||
// Create coin from reader then add to list.
|
||||
Collection.Add(CoinFromReader(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String CollectionToCSV(string collectionName) {
|
||||
var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = $"SELECT * FROM {collectionName}";
|
||||
|
||||
var headersCommand = Connection.CreateCommand();
|
||||
headersCommand.CommandText = $"PRAGMA table_info({collectionName})";
|
||||
|
||||
var resultString = "";
|
||||
var numHeaders = 0;
|
||||
|
||||
// Get the headers.
|
||||
using (var reader = headersCommand.ExecuteReader()) {
|
||||
while (reader.Read()) {
|
||||
resultString += reader.GetString(1) + ",";
|
||||
numHeaders++;
|
||||
}
|
||||
}
|
||||
|
||||
resultString += "\n";
|
||||
|
||||
// Is there a more efficient way of doing this? This is running at O(n^2) time.
|
||||
using (var reader = cmd.ExecuteReader()) {
|
||||
while (reader.Read()) {
|
||||
// Read information from the reader, then add to CSV string.
|
||||
for (int i = 0; i < numHeaders; i++) {
|
||||
// Get the type of each column.
|
||||
var type = reader.GetFieldType(i);
|
||||
Console.WriteLine("Type: " + type);
|
||||
if (type == typeof(Int64)) {
|
||||
resultString += reader.GetInt32(i) + ",";
|
||||
}
|
||||
else if (type == typeof(Double)) {
|
||||
resultString += reader.GetDouble(i) + ",";
|
||||
}
|
||||
else if (type == typeof(String)) {
|
||||
resultString += reader.GetString(i) + ",";
|
||||
}
|
||||
else {
|
||||
resultString += "NULL" + ",";
|
||||
}
|
||||
}
|
||||
resultString += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return resultString;
|
||||
}
|
||||
|
||||
public void SaveCoin(string collectionName, Coin coin) {
|
||||
// Get and replace the coin from the database.
|
||||
Console.WriteLine("Saving coin...");
|
||||
SQLiteCommand cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = $"UPDATE {collectionName} " +
|
||||
$"SET YEAR = {coin.Year}, " +
|
||||
$"DENOMINATION = \'{coin.Denomination}\', " +
|
||||
$"MINT_MARK = \'{coin.MintMark}\', " +
|
||||
$"PRICE_GUIDE_VALUE = \'{coin.PriceGuideValue}\', " +
|
||||
$"COIN_FACTS_LINK = \'{coin.CoinFactsLink}\', " +
|
||||
$"MAJOR_VARIETY = \'{coin.MajorVariety}\', " +
|
||||
$"MINOR_VARIETY = \'{coin.MinorVariety}\', " +
|
||||
$"DIE_VARIETY = \'{coin.DieVariety}\', " +
|
||||
$"SERIES_NAME = \'{coin.SeriesName}\', " +
|
||||
$"CATEGORY = \'{coin.Category}\', " +
|
||||
$"DESIGNATION = \'{coin.Designation}\', " +
|
||||
$"CERT_NUM = \'{coin.CertificateNumber}\', " +
|
||||
$"QUANTITY = {coin.Quantity}, " +
|
||||
$"NOTES = @Notes " +
|
||||
$"WHERE PCGS_NUMBER = {coin.PCGSNo} AND GRADE = \'{coin.Grade}\'";
|
||||
cmd.Parameters.AddWithValue("@Notes", coin.Notes);
|
||||
Console.WriteLine(cmd.CommandText);
|
||||
// Execute the command.
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public void UpdateCollection(string collectionName) {
|
||||
// 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.
|
||||
foreach (var coin in Collection) {
|
||||
SaveCoin(collectionName, coin); // CAREFUL! This will not create a coin if it doesn't currently exist.
|
||||
}
|
||||
}
|
||||
|
||||
private Coin CoinFromReader(SQLiteDataReader reader) {
|
||||
// Create a coin while filtering out any possible null values - they throw exceptions.
|
||||
Coin coin = new Coin();
|
||||
if (!reader.IsDBNull(0)) coin.PCGSNo = reader.GetString(0);
|
||||
if (!reader.IsDBNull(1)) coin.Grade = reader.GetString(1);
|
||||
if (!reader.IsDBNull(2)) coin.Year = reader.GetInt32(2);
|
||||
if (!reader.IsDBNull(3)) coin.Denomination = reader.GetString(3);
|
||||
if (!reader.IsDBNull(4)) coin.MintMark = reader.GetString(4);
|
||||
if (!reader.IsDBNull(5)) coin.PriceGuideValue = reader.GetDouble(5);
|
||||
if (!reader.IsDBNull(6)) coin.CoinFactsLink = reader.GetString(6);
|
||||
if (!reader.IsDBNull(7)) coin.MajorVariety = reader.GetString(7);
|
||||
if (!reader.IsDBNull(8)) coin.MinorVariety = reader.GetString(8);
|
||||
if (!reader.IsDBNull(9)) coin.DieVariety = reader.GetString(9);
|
||||
if (!reader.IsDBNull(10)) coin.SeriesName = reader.GetString(10);
|
||||
if (!reader.IsDBNull(11)) coin.Category = reader.GetString(11);
|
||||
if (!reader.IsDBNull(12)) coin.Designation = reader.GetString(12);
|
||||
if (!reader.IsDBNull(13)) coin.CertificateNumber = reader.GetInt32(13);
|
||||
if (!reader.IsDBNull(14)) coin.Quantity = reader.GetInt32(14);
|
||||
if (!reader.IsDBNull(15)) coin.Notes = reader.GetString(15);
|
||||
coin.TotalPrice = coin.Quantity * coin.PriceGuideValue;
|
||||
return coin;
|
||||
}
|
||||
|
||||
public void DeleteCoin(Coin delCoin) {
|
||||
SQLiteCommand cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
|
||||
cmd.Parameters.AddWithValue("@PcgsNumber", delCoin.PCGSNo);
|
||||
cmd.Parameters.AddWithValue("@Grade", delCoin.Grade);
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private int GetGradeFromString(string grade) {
|
||||
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
|
||||
Console.WriteLine($"Grade is {actualGrade}");
|
||||
return actualGrade;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace PcgsInvUi.Services;
|
||||
|
||||
public class Database
|
||||
{
|
||||
// TODO Create a database using SQLite.
|
||||
}
|
||||
@@ -1,51 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PcgsInvUi.Models;
|
||||
using System.Text.Json;
|
||||
using DynamicData.Kernel;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.Services;
|
||||
|
||||
public class PcgsClient
|
||||
{
|
||||
public class PcgsClient : ReactiveObject {
|
||||
public enum ErrorType {
|
||||
None,
|
||||
NoCoinFound,
|
||||
InvalidRequestFormat,
|
||||
ApiKeyInvalid
|
||||
}
|
||||
|
||||
public int NumApiRequests {
|
||||
get => _numApiRequests;
|
||||
set => this.RaiseAndSetIfChanged(ref _numApiRequests, value);
|
||||
}
|
||||
|
||||
private string _apiUri = "https://api.pcgs.com/publicapi";
|
||||
private readonly string _apiKey;
|
||||
private int _numApiRequests;
|
||||
|
||||
public PcgsClient(string apiKey)
|
||||
{
|
||||
_apiKey = apiKey;
|
||||
public PcgsClient(string apiKey) {
|
||||
// _apiKey = apiKey;
|
||||
_apiKey =
|
||||
"E8xivTVN_k4Q8ubk8Xv_uU11U91WX0wDht4S4m4BAf2HPmKnGrZ0J2f9nccM7Ns1VkGoT1SXYR74I7aNaMKqXlJGEy3oobseKV0nGiqu1IKvFkOTVjhV37SWUVExSDFMC6FLzs7xJQIHwkcxr5JFOQNmc4ZytEh_-hmCN3NL-z0fEk98tzBmPJ_F7bq_xNru86QMD2A9yalu_dWe-lCysfyE2bCA2EDqwvWenRYf-0XnNtp6qlxxCMlCd-TDqqRKLC3qefCwpXSemwBiNSP5gbC9pwcKrZugkruuJ0nyxxThZymm";
|
||||
}
|
||||
|
||||
public async Task<Coin> GetCoinFactsByGrade(int pcgsNumber, int grade, bool plusGrade)
|
||||
{
|
||||
public async Task<(ErrorType, Coin?)> GetCoinFactsByGrade(int pcgsNumber, string grade, bool plusGrade = false) {
|
||||
Coin newCoin = null;
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
using (HttpClient client = new HttpClient()) {
|
||||
var gradeParam = GetGradeFromString(grade);
|
||||
|
||||
string requestUri =
|
||||
$"{_apiUri}/coindetail/GetCoinFactsByGrade/?PCGSNo={pcgsNumber}&GradeNo={grade}&PlusGrade={plusGrade}";
|
||||
$"{_apiUri}/coindetail/GetCoinFactsByGrade/?PCGSNo={pcgsNumber}&GradeNo={gradeParam}&PlusGrade={plusGrade}";
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", _apiKey);
|
||||
Console.WriteLine($"Sending request to {requestUri}");
|
||||
HttpResponseMessage response = await client.GetAsync(requestUri);
|
||||
Console.WriteLine("Got response.");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.IsSuccessStatusCode) {
|
||||
string jsonResponse = await response.Content.ReadAsStringAsync();
|
||||
newCoin = ParseJsonResponse(jsonResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Error: {response.StatusCode} - {response.ReasonPhrase}");
|
||||
var parsedResponse = ParseJsonResponse(jsonResponse);
|
||||
return parsedResponse;
|
||||
}
|
||||
}
|
||||
|
||||
return newCoin;
|
||||
return (ErrorType.ApiKeyInvalid, null);
|
||||
}
|
||||
|
||||
private int GetGradeFromString(string grade) {
|
||||
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
|
||||
Console.WriteLine($"Grade is {actualGrade}");
|
||||
return actualGrade;
|
||||
}
|
||||
|
||||
// TODO Will eventually create the other methods that are included in the Public API.
|
||||
|
||||
private Coin ParseJsonResponse(string json)
|
||||
{
|
||||
Coin newCoin = JsonConvert.DeserializeObject<Coin>(json);
|
||||
return newCoin;
|
||||
private (ErrorType, Coin?) ParseJsonResponse(string json) {
|
||||
Console.WriteLine(json);
|
||||
// TODO Check for errors in the response
|
||||
if (json.Contains("\"ServerMessage\":\"No data found\"")) return (ErrorType.NoCoinFound, null);
|
||||
if (json.Contains("\"IsValidRequest\":false")) return (ErrorType.InvalidRequestFormat, null);
|
||||
|
||||
var newCoin = JsonSerializer.Deserialize<Coin>(json);
|
||||
return (ErrorType.None, newCoin);
|
||||
}
|
||||
}
|
||||
|
||||
// Exception for invalid request format
|
||||
class InvalidRequestFormatException : Exception {
|
||||
public InvalidRequestFormatException() { }
|
||||
public InvalidRequestFormatException(string message) : base(message) { }
|
||||
public InvalidRequestFormatException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
// Exception for no coin found
|
||||
class CoinNotFoundException : Exception {
|
||||
public CoinNotFoundException() { }
|
||||
public CoinNotFoundException(string message) : base(message) { }
|
||||
public CoinNotFoundException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
@@ -5,23 +5,19 @@ using PcgsInvUi.ViewModels;
|
||||
|
||||
namespace PcgsInvUi;
|
||||
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public Control Build(object data)
|
||||
{
|
||||
public class ViewLocator : IDataTemplate {
|
||||
public Control Build(object data) {
|
||||
var name = data.GetType().FullName!.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
if (type != null) {
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
}
|
||||
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
public bool Match(object data) {
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,18 @@ using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class CoinListViewModel : ViewModelBase
|
||||
{
|
||||
public class CoinListViewModel : ViewModelBase {
|
||||
public ObservableCollection<Coin> CoinCollection { get; set; }
|
||||
private float _totalValue;
|
||||
public float TotalValue
|
||||
{
|
||||
|
||||
public float TotalValue {
|
||||
get => _totalValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _totalValue, value);
|
||||
}
|
||||
|
||||
public Coin? SelectedCoin { get; set; }
|
||||
|
||||
public CoinListViewModel(IEnumerable<Coin> items)
|
||||
{
|
||||
public CoinListViewModel(IEnumerable<Coin> items) {
|
||||
CoinCollection = new ObservableCollection<Coin>(items);
|
||||
TotalValue = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Reactive;
|
||||
using PcgsInvUi.Views;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class DeleteWindowViewModel : ViewModelBase {
|
||||
public ReactiveCommand<Unit, object> OkCommand { get; }
|
||||
public ReactiveCommand<DeleteWindow, Unit> CancelCommand { get; }
|
||||
|
||||
public DeleteWindowViewModel() {
|
||||
OkCommand = ReactiveCommand.Create(() => (object)true);
|
||||
CancelCommand = ReactiveCommand.Create<DeleteWindow>(window => window.Close());
|
||||
}
|
||||
}
|
||||
@@ -5,21 +5,19 @@ using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class EditViewModel : ViewModelBase
|
||||
{
|
||||
public class EditViewModel : ViewModelBase {
|
||||
public Coin CurrentCoin { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> OkCommand { get; }
|
||||
|
||||
public EditViewModel(Coin coin)
|
||||
{
|
||||
public EditViewModel(Coin coin) {
|
||||
CurrentCoin = coin;
|
||||
|
||||
var okEnabled = this.WhenAnyValue(
|
||||
x => x.CurrentCoin.PcgsNumber,
|
||||
x => x.CurrentCoin.PCGSNo,
|
||||
x => x.CurrentCoin.Grade,
|
||||
x => x.CurrentCoin.Quantity,
|
||||
(id, grade, quantity) => (id != 0) &&
|
||||
(id, grade, quantity) => !string.IsNullOrWhiteSpace(id) &&
|
||||
!string.IsNullOrWhiteSpace(grade) &&
|
||||
(quantity != 0)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Reactive;
|
||||
using System.Security.Principal;
|
||||
using PcgsInvUi.Views;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class ErrorWindowViewModel : ViewModelBase {
|
||||
|
||||
public string DisplayedMessage {
|
||||
get => _message;
|
||||
set => this.RaiseAndSetIfChanged(ref _message, value);
|
||||
}
|
||||
|
||||
private string _message;
|
||||
|
||||
public ReactiveCommand<ErrorWindow, Unit> CloseCommand { get; }
|
||||
|
||||
public ErrorWindowViewModel(string message)
|
||||
{
|
||||
DisplayedMessage = message;
|
||||
|
||||
CloseCommand = ReactiveCommand.Create<ErrorWindow>(window => window.Close());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Reactive;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class FindWindowViewModel : ViewModelBase {
|
||||
public ReactiveCommand<Unit, object> OkCommand { get; } = ReactiveCommand.Create(() => (object)1);
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using PcgsInvUi.Models;
|
||||
using PcgsInvUi.Services;
|
||||
@@ -6,61 +10,114 @@ using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
public CoinListViewModel List { get; }
|
||||
private ViewModelBase _mainContent;
|
||||
private ViewModelBase _sideContent;
|
||||
// TODO Error handling for API calls.
|
||||
// TODO Manual handling of API key.
|
||||
// TODO Use CoinFacts link.
|
||||
// TODO Splash screen during startup.
|
||||
// TODO Take giant sheet of coins to integrate into the application.
|
||||
// TODO Update all coins. Choose those that are older first.
|
||||
// TODO API request tracker (1000 per day)
|
||||
// TODO Ok button greys out while request is happening.
|
||||
// TODO Track when coins were last updated.
|
||||
// TODO Image for coin in notes view.
|
||||
|
||||
public MainWindowViewModel(CoinCollection coins)
|
||||
{
|
||||
List = new CoinListViewModel(coins.GetItems());
|
||||
MainContent = List;
|
||||
}
|
||||
|
||||
public ViewModelBase MainContent
|
||||
{
|
||||
get => _mainContent;
|
||||
private set => this.RaiseAndSetIfChanged(ref _mainContent, value);
|
||||
}
|
||||
|
||||
public ViewModelBase SidebarContent
|
||||
{
|
||||
public 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 void AddItem()
|
||||
{
|
||||
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, Unit> ShowErrorWindow { get; }
|
||||
|
||||
private ViewModelBase _sideContent;
|
||||
private Coin? _selectedCoin;
|
||||
private double _totalValue;
|
||||
|
||||
public MainWindowViewModel(CoinDatabase coins) {
|
||||
ConnectedCoinDatabase = coins;
|
||||
var newViewModel = new NewViewModel();
|
||||
_sideContent = newViewModel;
|
||||
// DisplayedList = CoinCollection = new ObservableCollection<Coin>(coins.GetItems());
|
||||
DisplayedList = ConnectedCoinDatabase.Collection;
|
||||
TotalValue = DisplayedList.Sum(x => x.TotalPrice);
|
||||
|
||||
Observable.Merge(
|
||||
newViewModel.OkCommand,
|
||||
newViewModel.CancelCommand.Select(_ => (Coin?)null))
|
||||
.Take(1)
|
||||
.Subscribe(model =>
|
||||
{
|
||||
if (model != null)
|
||||
List.CoinCollection.Add(model);
|
||||
// Set up the ability to show the error window.
|
||||
ShowErrorWindow = new Interaction<ErrorWindowViewModel, Unit>();
|
||||
|
||||
MainContent = List;
|
||||
// Set up the ability to open the delete window.
|
||||
ShowDeleteWindow = new Interaction<DeleteWindowViewModel, bool>();
|
||||
var deleteEnabled = this.WhenAnyValue(x => x.SelectedCoin)
|
||||
.Select(x => x != null);
|
||||
DeleteCommand = ReactiveCommand.CreateFromTask(async () => {
|
||||
var deleteViewModel = new DeleteWindowViewModel();
|
||||
var result = await ShowDeleteWindow.Handle(deleteViewModel);
|
||||
if (result && SelectedCoin is not null) {
|
||||
ConnectedCoinDatabase.DeleteCoin(SelectedCoin);
|
||||
ConnectedCoinDatabase.Collection.Remove(SelectedCoin); // possible null deref - ignored due to button being disabled.
|
||||
}
|
||||
}, deleteEnabled);
|
||||
|
||||
ShowExportWindow = new Interaction<Unit, Uri>();
|
||||
ExportCommand = ReactiveCommand.CreateFromTask(async () => {
|
||||
try {
|
||||
var filePath = await ShowExportWindow.Handle(Unit.Default);
|
||||
var stream = File.Open(filePath.AbsolutePath, FileMode.OpenOrCreate);
|
||||
var writer = new StreamWriter(stream);
|
||||
await writer.WriteAsync(ConnectedCoinDatabase.CollectionToCSV("CollectionTable"));
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Something is wrong with the file after it's been created.
|
||||
Console.WriteLine(e.Message);
|
||||
ShowErrorWindow.Handle(new ErrorWindowViewModel(e.Message));
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine(e.Message);
|
||||
ShowErrorWindow.Handle(new ErrorWindowViewModel(e.Message));
|
||||
}
|
||||
});
|
||||
|
||||
MainContent = newViewModel;
|
||||
// Subscribe to SidebarContent.OkCommand, which is an IObservable<(int, string, int)>
|
||||
// This is the same as the above, but with a different syntax.
|
||||
newViewModel.OkCommand
|
||||
.Subscribe(async requestStructure => {
|
||||
var result = await ConnectedCoinDatabase.CreateCoin(requestStructure.Item1, requestStructure.Item2,
|
||||
requestStructure.Item3);
|
||||
|
||||
switch (result) {
|
||||
case PcgsClient.ErrorType.ApiKeyInvalid:
|
||||
Console.WriteLine("API key is invalid - showing window.");
|
||||
await ShowErrorWindow.Handle(new ErrorWindowViewModel("API key is invalid - authorization failed."));
|
||||
break;
|
||||
case PcgsClient.ErrorType.InvalidRequestFormat:
|
||||
Console.WriteLine("Request format is invalid - showing window.");
|
||||
await ShowErrorWindow.Handle(new ErrorWindowViewModel("Request format is invalid."));
|
||||
break;
|
||||
case PcgsClient.ErrorType.NoCoinFound:
|
||||
Console.WriteLine("No coin was found with the given parameters.");
|
||||
await ShowErrorWindow.Handle(new ErrorWindowViewModel("No coin was found with the given parameters."));
|
||||
break;
|
||||
}
|
||||
|
||||
public void EditItem()
|
||||
{
|
||||
var editViewModel = new EditViewModel(List.SelectedCoin);
|
||||
}
|
||||
|
||||
public void DeleteItem()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void FindItem()
|
||||
{
|
||||
// TODO Clear the text boxes.
|
||||
});
|
||||
|
||||
// Change TotalValue anytime SelectedCoin.TotalPrice changes.
|
||||
// Possible null deref - how do you remove this?
|
||||
this.WhenAnyValue(x => x.SelectedCoin.TotalPrice)
|
||||
.Subscribe(_ => TotalValue = ConnectedCoinDatabase.Collection.Sum(x => x.TotalPrice));
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,32 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive;
|
||||
using System.Collections.Generic;
|
||||
using PcgsInvUi.Models;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class NewViewModel : ViewModelBase
|
||||
{
|
||||
private string _pcgsNumber;
|
||||
private string _grade;
|
||||
private string _quantity;
|
||||
public class NewViewModel : ViewModelBase {
|
||||
private string _pcgsNumber = "";
|
||||
private string _grade = "";
|
||||
private string _quantity = "";
|
||||
|
||||
public string PcgsNumber
|
||||
{
|
||||
public string PcgsNumber {
|
||||
get => _pcgsNumber;
|
||||
set => this.RaiseAndSetIfChanged(ref _pcgsNumber, value);
|
||||
}
|
||||
public string Grade
|
||||
{
|
||||
|
||||
public string Grade {
|
||||
get => _grade;
|
||||
set => this.RaiseAndSetIfChanged(ref _grade, value);
|
||||
}
|
||||
public string Quantity
|
||||
{
|
||||
|
||||
public string Quantity {
|
||||
get => _quantity;
|
||||
set => this.RaiseAndSetIfChanged(ref _quantity, value);
|
||||
}
|
||||
|
||||
public List<string> GradesList { get; }
|
||||
|
||||
public NewViewModel()
|
||||
{
|
||||
public NewViewModel() {
|
||||
var okEnabled = this.WhenAnyValue(
|
||||
x => x.PcgsNumber,
|
||||
x => x.Grade,
|
||||
@@ -39,19 +35,19 @@ public class NewViewModel : ViewModelBase
|
||||
!string.IsNullOrWhiteSpace(grade) &&
|
||||
!string.IsNullOrWhiteSpace(quantity));
|
||||
|
||||
// This will eventually send our request to the public API, then construct our coin.
|
||||
OkCommand = ReactiveCommand.Create(
|
||||
() => new Coin { PcgsNumber = Int32.Parse(PcgsNumber),
|
||||
Grade = Grade,
|
||||
Quantity = Int32.Parse(Quantity) },
|
||||
() => (int.Parse(PcgsNumber), Grade, int.Parse(Quantity)),
|
||||
okEnabled);
|
||||
|
||||
CancelCommand = ReactiveCommand.Create(() => { });
|
||||
ClearCommand = ReactiveCommand.Create(() => {
|
||||
PcgsNumber = "";
|
||||
Grade = "";
|
||||
Quantity = "";
|
||||
});
|
||||
|
||||
// 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.
|
||||
GradesList = new List<string>()
|
||||
{
|
||||
GradesList = new List<string> {
|
||||
"P-01",
|
||||
"FR-02",
|
||||
"AG-03",
|
||||
@@ -85,7 +81,6 @@ public class NewViewModel : ViewModelBase
|
||||
};
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Coin> OkCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> CancelCommand { get; }
|
||||
|
||||
public ReactiveCommand<Unit, (int, string, int)> OkCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> ClearCommand { get; }
|
||||
}
|
||||
@@ -2,6 +2,4 @@
|
||||
|
||||
namespace PcgsInvUi.ViewModels;
|
||||
|
||||
public class ViewModelBase : ReactiveObject
|
||||
{
|
||||
}
|
||||
public class ViewModelBase : ReactiveObject { }
|
||||
@@ -1,73 +0,0 @@
|
||||
<UserControl 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"
|
||||
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="PcgsInvUi.Views.CoinListView"
|
||||
x:DataType="viewModels:CoinListViewModel">
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Top">
|
||||
<DataGrid x:Name="CollectionView"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
DockPanel.Dock="Top"
|
||||
ItemsSource="{Binding CoinCollection}"
|
||||
SelectedItem="{Binding SelectedCoin, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserResizeColumns="True" >
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Series" Width="*"
|
||||
Binding="{Binding Series}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Year"
|
||||
Binding="{Binding Year}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Mint" Width="*"
|
||||
Binding="{Binding MintMark}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Denomination" Width="*"
|
||||
Binding="{Binding Denomination}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Variety" Width="*"
|
||||
Binding="{Binding MajorVariety}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Grade" Width="*"
|
||||
Binding="{Binding Grade}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Designation" Width="*"
|
||||
Binding="{Binding Designation}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Est. Price" Width="*"
|
||||
Binding="{Binding PriceGuideValue}"
|
||||
IsReadOnly="False"/>
|
||||
<DataGridTextColumn Header="PCGS #" Width="*"
|
||||
Binding="{Binding PcgsNumber}"
|
||||
IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Quantity" Width="*"
|
||||
Binding="{Binding Quantity}"
|
||||
IsReadOnly="False"/>
|
||||
<DataGridTextColumn Header="Total Price" Width="*"
|
||||
Binding="{Binding TotalPrice}"
|
||||
IsReadOnly="True" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
<DockPanel DockPanel.Dock="Bottom">
|
||||
<Grid HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="5" ColumnDefinitions="Auto, Auto, Auto, Auto" DockPanel.Dock="Left">
|
||||
<Button Grid.Column="0" Margin="5" Width="100" HorizontalContentAlignment="Center" Content="New..."
|
||||
Command="{Binding $parent[Window].((viewModels:MainWindowViewModel)DataContext).AddItem}"/>
|
||||
<Button Grid.Column="1" Margin="5" Width="100" HorizontalContentAlignment="Center" Content="Edit..."
|
||||
Command="{Binding $parent[Window].((viewModels:MainWindowViewModel)DataContext).EditItem}"/>
|
||||
<Button Grid.Column="2" Margin="5" Width="100" HorizontalContentAlignment="Center" Content="Delete"
|
||||
Command="{Binding $parent[Window].((viewModels:MainWindowViewModel)DataContext).DeleteItem}"/>
|
||||
<Button Grid.Column="3" Margin="5" Width="100" HorizontalContentAlignment="Center" Content="Find..."
|
||||
Command="{Binding $parent[Window].((viewModels:MainWindowViewModel)DataContext).FindItem}"/>
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="5" ColumnDefinitions="Auto, Auto" DockPanel.Dock="Right">
|
||||
<Label Grid.Column="0" Margin="5" VerticalAlignment="Center" Content="{Binding TotalValue, StringFormat='Total Value: ${0:0.00}'}" />
|
||||
<Button Grid.Column="1" Margin="5" Width="150" HorizontalContentAlignment="Center" Content="Export to CSV..." />
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -1,13 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PcgsInvUi.Views;
|
||||
|
||||
public partial class CoinListView : UserControl
|
||||
{
|
||||
public CoinListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
<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"
|
||||
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
|
||||
d:DesignWidth="325" d:DesignHeight="100"
|
||||
x:Class="PcgsInvUi.Views.DeleteWindow"
|
||||
x:Class="PcgsInvUi.Views.DeleteWindow" x:DataType="viewModels:DeleteWindowViewModel"
|
||||
Width="325" Height="100" WindowStartupLocation="CenterOwner"
|
||||
Title="DeleteWindow">
|
||||
<Grid ColumnDefinitions="*, *" RowDefinitions="*, *">
|
||||
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="10"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Content="Are you sure you want to delete this item?"/>
|
||||
Content="Are you sure you want to delete this item?" />
|
||||
<Button Grid.Row="1" Grid.Column="0" Margin="5"
|
||||
Width="100"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Content="Yes" Click="AcceptClicked"/>
|
||||
Content="Yes" Command="{Binding OkCommand}" />
|
||||
<Button Grid.Row="1" Grid.Column="1" Margin="5"
|
||||
Width="100"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Content="No" Click="CancelClicked"/>
|
||||
HorizontalContentAlignment="Center" IsEnabled="False"
|
||||
Content="Use X button" Command="{Binding CancelCommand}" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,27 +1,13 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using PcgsInvUi.ViewModels;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
|
||||
namespace PcgsInvUi.Views;
|
||||
|
||||
public partial class DeleteWindow : Window
|
||||
{
|
||||
public bool Accepted { get; set; }
|
||||
|
||||
public DeleteWindow()
|
||||
{
|
||||
public partial class DeleteWindow : ReactiveWindow<DeleteWindowViewModel> {
|
||||
public DeleteWindow() {
|
||||
InitializeComponent();
|
||||
Accepted = false;
|
||||
}
|
||||
|
||||
public void AcceptClicked(object source, RoutedEventArgs args)
|
||||
{
|
||||
// TODO Delete the SelectedCoin?
|
||||
Accepted = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
public void CancelClicked(object source, RoutedEventArgs args)
|
||||
{
|
||||
Close();
|
||||
this.WhenActivated(d => d(ViewModel!.OkCommand.Subscribe(Close)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<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"
|
||||
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="150"
|
||||
x:Class="PcgsInvUi.Views.ErrorWindow" x:DataType="viewModels:ErrorWindowViewModel"
|
||||
Title="Error!" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterOwner"
|
||||
x:Name="NameErrorWindow" CanResize="False" >
|
||||
<Grid RowDefinitions="*,*">
|
||||
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Stretch"
|
||||
Margin="15"
|
||||
Content="{Binding DisplayedMessage}" />
|
||||
<Button Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Bottom"
|
||||
Margin="10" Name="CloseButton" IsEnabled="False"
|
||||
Content="Use X button" Command="{Binding CloseCommand, Mode=OneWay}"
|
||||
CommandParameter="{Binding ElementName=NameErrorWindow}"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using PcgsInvUi.ViewModels;
|
||||
|
||||
namespace PcgsInvUi.Views;
|
||||
|
||||
public partial class ErrorWindow : ReactiveWindow<ErrorWindowViewModel> {
|
||||
public ErrorWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<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.FindWindow"
|
||||
Title="FindWindow">
|
||||
<Grid ColumnDefinitions="Auto, *" RowDefinitions="*, *">
|
||||
<Label Content="PCGS #:" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="5" />
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Margin="5" />
|
||||
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="Find"
|
||||
HorizontalAlignment="Center" Margin="5" Width="75" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,13 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using PcgsInvUi.ViewModels;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
|
||||
namespace PcgsInvUi.Views;
|
||||
|
||||
public partial class FindWindow : ReactiveWindow<FindWindowViewModel> {
|
||||
public FindWindow() {
|
||||
InitializeComponent();
|
||||
this.WhenActivated(d => d(ViewModel!.OkCommand.Subscribe(Close)));
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,77 @@
|
||||
xmlns:viewModels="clr-namespace:PcgsInvUi.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="PcgsInvUi.Views.MainWindow" x:DataType="viewModels:MainWindowViewModel"
|
||||
Title="MainWindow">
|
||||
<SplitView Name="Sidebar">
|
||||
Title="Main Window"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<SplitView IsPaneOpen="True"
|
||||
DisplayMode="Inline"
|
||||
PanePlacement="Right">
|
||||
<SplitView.Pane>
|
||||
<UserControl Content="{Binding SidebarContent}"/>
|
||||
<Grid RowDefinitions="Auto, *">
|
||||
<UserControl Grid.Row="0" Margin="10, 20, 10, 10" VerticalAlignment="Center"
|
||||
Content="{Binding SidebarContent}" />
|
||||
<TextBox Grid.Row="1" Margin="5" Watermark="Notes..."
|
||||
Text="{Binding SelectedCoin.Notes}"
|
||||
IsReadOnly="False" TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</SplitView.Pane>
|
||||
<UserControl Content="{Binding MainContent}"/>
|
||||
<Grid RowDefinitions="*, Auto" ShowGridLines="False">
|
||||
<DataGrid Grid.Row="0" HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ItemsSource="{Binding DisplayedList}"
|
||||
SelectedItem="{Binding SelectedCoin, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserResizeColumns="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Series" Width="*"
|
||||
Binding="{Binding SeriesName}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Year"
|
||||
Binding="{Binding Year}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Mint" Width="*"
|
||||
Binding="{Binding MintMark}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Denomination" Width="*"
|
||||
Binding="{Binding Denomination}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Variety" Width="*"
|
||||
Binding="{Binding MajorVariety}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Grade" Width="*"
|
||||
Binding="{Binding Grade}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Designation" Width="*"
|
||||
Binding="{Binding Designation}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Est. Price" Width="*"
|
||||
Binding="{Binding PriceGuideValue}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="PCGS #" Width="*"
|
||||
Binding="{Binding PCGSNo}"
|
||||
IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Quantity" Width="*"
|
||||
Binding="{Binding Quantity}"
|
||||
IsReadOnly="False" />
|
||||
<DataGridTextColumn Header="Total Price" Width="*"
|
||||
Binding="{Binding TotalPrice, StringFormat='${0:0.00}'}"
|
||||
IsReadOnly="True" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<DockPanel Grid.Row="1">
|
||||
<Grid HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="5" ColumnDefinitions="Auto, Auto"
|
||||
DockPanel.Dock="Left">
|
||||
<Button Grid.Column="0" Margin="5" Width="100" HorizontalContentAlignment="Center" Content="Delete"
|
||||
Command="{Binding DeleteCommand}" />
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="5" ColumnDefinitions="Auto, Auto"
|
||||
DockPanel.Dock="Right">
|
||||
<Label Grid.Column="0" Margin="5" VerticalAlignment="Center"
|
||||
Content="{Binding TotalValue, StringFormat='Total Value: ${0:0.00}'}" />
|
||||
<Button Grid.Column="1" Margin="5" Width="150" HorizontalContentAlignment="Center"
|
||||
Content="Export to CSV..." Command="{Binding ExportCommand}" />
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</SplitView>
|
||||
</Window>
|
||||
@@ -1,28 +1,63 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.ReactiveUI;
|
||||
using PcgsInvUi.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace PcgsInvUi.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// TODO New window.
|
||||
// TODO Edit window.
|
||||
// TODO Delete window.
|
||||
// TODO Find window.
|
||||
// TODO Export window.
|
||||
// TODO Error handling.
|
||||
// TODO Manual handling of API key.
|
||||
// TODO Use CoinFacts link.
|
||||
// TODO Export to CSV.
|
||||
// TODO Take giant sheet of coins to integrate into the application.
|
||||
// TODO Update all coins.
|
||||
// TODO API request tracker (1000 per day)
|
||||
// TODO API update schedules (per month? manual override? needs to store a date of last update in the coin object)
|
||||
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
public partial class MainWindow : ReactiveWindow<MainWindowViewModel> {
|
||||
public MainWindow() {
|
||||
InitializeComponent();
|
||||
this.WhenActivated(d => d(ViewModel!.ShowDeleteWindow.RegisterHandler(ShowDeleteWindowAsync)));
|
||||
this.WhenActivated(d => d(ViewModel!.ShowExportWindow.RegisterHandler(ShowFilePickerAsync)));
|
||||
this.WhenActivated(d => d(ViewModel!.ShowErrorWindow.RegisterHandler(ShowErrorWindowAsync)));
|
||||
// this.WhenActivated(d => d(ViewModel!.ShowFindWindow.RegisterHandler(ShowFindWindowAsync)));
|
||||
}
|
||||
|
||||
private async Task ShowDeleteWindowAsync(InteractionContext<DeleteWindowViewModel, Boolean> interaction) {
|
||||
var window = new DeleteWindow();
|
||||
window.DataContext = interaction.Input;
|
||||
|
||||
var result = await window.ShowDialog<bool>(this);
|
||||
interaction.SetOutput(result);
|
||||
}
|
||||
|
||||
private async Task ShowFindWindowAsync(InteractionContext<FindWindowViewModel, int> interaction) {
|
||||
var window = new FindWindow();
|
||||
window.DataContext = interaction.Input;
|
||||
|
||||
var result = await window.ShowDialog<int>(this);
|
||||
interaction.SetOutput(result);
|
||||
}
|
||||
|
||||
private async Task ShowFilePickerAsync(InteractionContext<Unit, Uri> interationContext) {
|
||||
var topLevel = GetTopLevel(this);
|
||||
var file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions {
|
||||
Title = "Save CSV...",
|
||||
DefaultExtension = ".csv",
|
||||
ShowOverwritePrompt = true,
|
||||
SuggestedFileName = "coins.csv",
|
||||
SuggestedStartLocation = await StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents)
|
||||
});
|
||||
|
||||
if (file is not null) {
|
||||
interationContext.SetOutput(file.Path);
|
||||
}
|
||||
else {
|
||||
// TODO Handle user closing the dialog.
|
||||
throw new Exception("User cancelled the file picker.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowErrorWindowAsync(InteractionContext<ErrorWindowViewModel, Unit> interaction) {
|
||||
var window = new ErrorWindow();
|
||||
window.DataContext = interaction.Input;
|
||||
|
||||
await window.ShowDialog(this);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,20 @@
|
||||
x:Class="PcgsInvUi.Views.NewView" x:DataType="viewmodels:NewViewModel">
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="Auto, *" RowDefinitions="*, *, *">
|
||||
<Label Grid.Column="0" Grid.Row="0" Margin="5, 5, 20, 5" VerticalAlignment="Center" Content="PCGS #:"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="0" Margin="5, 5, 10, 5" Text="{Binding PcgsNumber}"/>
|
||||
<Label Grid.Column="0" Grid.Row="1" Margin="5, 5, 20, 5" VerticalAlignment="Center" Content="Grade:"/>
|
||||
<Label Grid.Column="0" Grid.Row="0" Margin="5, 5, 20, 5" VerticalAlignment="Center" Content="PCGS #:" />
|
||||
<TextBox Grid.Column="1" Grid.Row="0" Margin="5, 5, 10, 5" Text="{Binding PcgsNumber}" />
|
||||
<Label Grid.Column="0" Grid.Row="1" Margin="5, 5, 20, 5" VerticalAlignment="Center" Content="Grade:" />
|
||||
<ComboBox Name="GradeChoices" Grid.Column="1" Grid.Row="1" SelectedIndex="0" Margin="5, 5, 10, 5"
|
||||
ItemsSource="{Binding GradesList}" SelectedItem="{Binding Grade}"/>
|
||||
<Label Grid.Column="0" Grid.Row="2" Margin="5, 5, 20, 5" VerticalAlignment="Center" Content="Quantity:"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="2" Margin="5, 5, 10, 5" Text="{Binding Quantity}"/>
|
||||
Width="150"
|
||||
ItemsSource="{Binding GradesList}" SelectedItem="{Binding Grade}" />
|
||||
<Label Grid.Column="0" Grid.Row="2" Margin="5, 5, 20, 5" VerticalAlignment="Center" Content="Quantity:" />
|
||||
<TextBox Grid.Column="1" Grid.Row="2" Margin="5, 5, 10, 5" Text="{Binding Quantity}" />
|
||||
</Grid>
|
||||
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*, *">
|
||||
<Button Grid.Column="0" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Width="90" HorizontalContentAlignment="Center" Content="OK" Command="{Binding OkCommand}"/>
|
||||
Width="90" HorizontalContentAlignment="Center" Content="OK" Command="{Binding OkCommand}" />
|
||||
<Button Grid.Column="1" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Width="90" HorizontalContentAlignment="Center" Content="Cancel" Command="{Binding CancelCommand}" />
|
||||
Width="90" HorizontalContentAlignment="Center" Content="Clear" Command="{Binding ClearCommand}" />
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -4,10 +4,8 @@ using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PcgsInvUi.Views;
|
||||
|
||||
public partial class NewView : UserControl
|
||||
{
|
||||
public NewView()
|
||||
{
|
||||
public partial class NewView : UserControl {
|
||||
public NewView() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net6.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "6.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Runtime.InteropServices.BuiltInComInterop.IsSupported": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 183 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user