30 Commits
Author SHA1 Message Date
alphafetus 0a0ea86d25 Changed version number to conform to dotnet rules. 2024-01-08 14:17:59 -05:00
alphafetus 33472c727d Incremented version number. 2024-01-08 14:16:53 -05:00
Lucas Vas a04cce4817 Merge pull request from BobdaFett/api-key-management
Add API key management.
2024-01-08 14:07:18 -05:00
alphafetus 5e516fc333 Removed unnecessary files. 2024-01-08 14:06:50 -05:00
alphafetus 0ff7edfed6 Refactored some code. 2024-01-08 14:02:58 -05:00
Lucas Vas af4f7567b4 Update README.md 2024-01-08 14:01:39 -05:00
alphafetus ba04d31c3e Added comments to CoinCollection code, and finished a basic framework for API key entry. 2023-12-01 23:15:27 -05:00
alphafetus 683a2cf87b Once again pushing broken code 2023-11-30 16:56:24 -05:00
alphafetus fab011b898 I made it work 2023-11-30 13:35:25 -05:00
alphafetus 2b4ed5374a Update README.md again. 2023-11-30 00:29:59 -05:00
alphafetus 7b3af8f7f1 Updated README.md 2023-11-30 00:28:27 -05:00
alphafetus c0b884fa1d Added a makefile - on windows run with nmake <option> 2023-11-29 21:54:13 -05:00
alphafetus 88d209ab83 Merged broken code. 2023-11-29 21:32:16 -05:00
alphafetus 7eb096767c Pushing more possibly broken code. 2023-11-29 21:31:36 -05:00
alphafetus 4b2504b48c Pushing currently broken code. 2023-11-29 21:29:47 -05:00
alphafetus 435d01ef90 Started framework of API key storage. 2023-11-25 02:16:53 -05:00
Lucas Vas e21ed50dc5 Delete .idea/.idea.pcgs-inv/.idea directory
Deleted gitignored file.
2023-11-25 02:01:19 -05:00
alphafetus a0da899537 Merge branch 'master' of https://github.com/BobdaFett/pcgs-inv 2023-11-23 19:08:18 -05:00
Lucas Vas 58a3bc1c7d Merge pull request #4 from BobdaFett/BobdaFett-patch-1
Update README.md
2023-11-23 19:07:09 -05:00
Lucas Vas 5433ee6719 Update README.md 2023-11-23 19:06:46 -05:00
Lucas Vas f0849cb1f9 Create README.md 2023-11-23 19:05:38 -05:00
alphafetus e17d467fb1 Merge branch 'vas-working' of https://github.com/BobdaFett/pcgs-inv into vas-working 2023-11-23 18:20:20 -05:00
alphafetus 1145f3ef5e Fixed totaling of CoinCollection's value. 2023-11-23 18:19:47 -05:00
alphafetus 71673206db Allowed ErrorWindow to close properly. 2023-11-23 18:19:47 -05:00
alphafetus f98ff942bb Fixed DeleteWindow and allowed it to close. 2023-11-23 18:19:47 -05:00
alphafetus 83a08edb2e Added code ensuring that there is not a crash upon creation of sqlite database. 2023-11-23 18:19:47 -05:00
alphafetus 602e9bbd8b Slapped bandaids on things for a fast release test. 2023-11-23 18:19:47 -05:00
alphafetus e73f9db02c ShowErrorWindow now actually shows the ErrorWindow 2023-11-23 18:19:47 -05:00
alphafetus d667e86b1b Added most of the framework for the error window. 2023-11-23 18:19:47 -05:00
alphafetus bc8f5e4d54 idek tbh 2023-11-23 18:19:47 -05:00
20 changed files with 341 additions and 107 deletions

No files matched your search

-13
View File
@@ -1,13 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/.idea.pcgs-inv.iml
/contentModel.xml
/modules.xml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<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" />
<entry key="PcgsInvUi/Views/NewView.axaml" value="PcgsInvUi/PcgsInvUi.csproj" />
</map>
</option>
</component>
</project>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+1 -7
View File
@@ -1,12 +1,9 @@
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;
@@ -23,14 +20,11 @@ public partial class App : Application {
DataContext = new MainWindowViewModel(coins),
};
// Allow the application to save the database when it exits.
desktop.Exit += (sender, args) => {
coins.UpdateCollection("CollectionTable");
};
}
base.OnFrameworkInitializationCompleted();
}
public void OnExit(object sender, ControlledApplicationLifetimeExitEventArgs e) {
// Save the database?
}
}
+2
View File
@@ -6,6 +6,8 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<Company>BobdaFett</Company>
<AssemblyVersion>0.0.5</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
-12
View File
@@ -1,12 +0,0 @@
using System.Collections.Generic;
using PcgsInvUi.Models;
namespace PcgsInvUi.Services;
public class CoinCollection {
public IEnumerable<Coin> GetItems() => new[] {
new Coin(),
new Coin(),
new Coin(),
};
}
+152 -6
View File
@@ -10,23 +10,29 @@ 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>
/// Creates and initilizes a new instance of the CoinDatabase.
/// Must call TryInitApiClient() after creating a new instance.
/// </summary>
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");
CreateApiTable();
Console.WriteLine("Connected to database.");
Collection = new ObservableCollection<Coin>();
GetCollection("CollectionTable");
Console.WriteLine("Got collection.");
}
/// <summary>
/// Destructor. Currently not used, due to the strange nature of desktop applications.
/// </summary>
~CoinDatabase() {
Console.WriteLine("Destructor called, saving collection.");
// Save collection.
@@ -35,12 +41,73 @@ public class CoinDatabase {
Connection.Close();
}
/// <summary>
/// Attempts to intitialize the API client by selecting the API key from the database.
/// </summary>
/// <returns>True if the API key was found, false otherwise.</returns>
public async Task<bool> TryInitApiClient() {
Console.Write("Attempting to initialize API client... ");
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = "SELECT API_KEY FROM ApiTable";
using (var reader = cmd.ExecuteReader()) {
if (reader.Read()) {
var apiKey = reader.GetString(0);
_pcgsClient = await PcgsClient.CreateClient(apiKey);
// Check that the client is valid.
if (_pcgsClient is null) {
IsClientConnected = false;
Console.WriteLine("Failed. API key was invalid.");
return IsClientConnected;
}
IsClientConnected = true;
Console.WriteLine("Done! API key was found from database.");
}
else {
IsClientConnected = false;
Console.WriteLine("Failed. API key not found.");
}
}
return IsClientConnected;
}
/// <summary>
/// Atttempts to initialize the PCGS API client.
/// </summary>
/// <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>
public async Task<bool> TryInitApiClient(string apiKey) {
Console.Write("Attempting to initialize API client with key param... ");
_pcgsClient = await PcgsClient.CreateClient(apiKey);
// Check if the API key is valid and if it is, then add to the database.
if (_pcgsClient is null) {
IsClientConnected = false;
Console.WriteLine("Failed. API key was invalid.");
return IsClientConnected;
}
var cmd = Connection.CreateCommand();
cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES (\"{apiKey}\", 1000)";
await cmd.ExecuteNonQueryAsync();
IsClientConnected = true;
Console.WriteLine("Done! API key was added to database.");
return IsClientConnected;
}
/// <summary>
/// Gets the collection from the database and stores it in the Collection property.
/// Creates the collection table if it does not exist already.
/// </summary>
/// <returns>The SQLiteConnection that corresponds to the connected database.</returns>
private SQLiteConnection CreateConnection() {
// Check if database exists.
if (!System.IO.File.Exists("coin_collection")) {
// Create the database.
// If not, create the database.
Console.Write("Creating database... ");
SQLiteConnection.CreateFile("coin_collection");
SQLiteConnection.CreateFile("coin_collection.sqlite");
Console.WriteLine("Done.");
}
@@ -60,6 +127,33 @@ public class CoinDatabase {
return connection;
}
/// <summary>
/// Creates the table that stores the API key and the number of requests remaining.
/// </summary>
public void CreateApiTable() {
// Create a table to store the API key and the number of requests remaining.
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 ApiTable (" +
"API_KEY TEXT, " +
"REQUESTS_REMAINING INTEGER, " +
"PRIMARY KEY(API_KEY))";
// Execute the command
cmd.ExecuteNonQuery();
Console.WriteLine("API table initialized.");
}
/// <summary>
/// Creates a table to store the collection.
/// </summary>
public void CreateCollection(string collectionName) {
// Create a table to store the collection.
if (!IsConnected) {
@@ -93,8 +187,12 @@ public class CoinDatabase {
// Execute the command.
cmd.ExecuteNonQuery();
Console.WriteLine("Collection table initialized.");
}
/// <summary>
/// Inserts a Coin object into the database.
/// </summary>
public void InsertCoin(Coin coin) {
Console.WriteLine("Inserting coin...");
// Use our existing connection to insert a coin to the database.
@@ -121,6 +219,13 @@ public class CoinDatabase {
Console.WriteLine("Coin inserted.");
}
/// <summary>
/// Creates a Coin object and saves it to the database.
/// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param>
/// <param name="quantity">The quantity of the coin.</param>
/// <returns>The error type returned by the API.</returns>
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.
@@ -146,6 +251,12 @@ public class CoinDatabase {
return apiResult.Item1;
}
/// <summary>
/// Gets a coin from the database.
/// </summary>
/// <param name="pcgsNumber">The PCGS number of the coin.</param>
/// <param name="grade">The grade of the coin.</param>
/// <returns>The coin if it exists, null otherwise.</returns>
public Coin? GetCoin(int pcgsNumber, string grade) {
Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
int gradeInt = GetGradeFromString(grade);
@@ -165,6 +276,10 @@ public class CoinDatabase {
return null;
}
/// <summary>
/// Used to get all Coin objects within the database.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
public void GetCollection(string collectionName) {
Console.WriteLine("Getting collection...");
SQLiteCommand cmd = Connection.CreateCommand();
@@ -178,6 +293,11 @@ public class CoinDatabase {
}
}
/// <summary>
/// Creates a CSV formatted string from a collection.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
/// <returns>A CSV formatted string.</returns>
public String CollectionToCSV(string collectionName) {
var cmd = Connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM {collectionName}";
@@ -226,6 +346,11 @@ public class CoinDatabase {
return resultString;
}
/// <summary>
/// Saves a Coin object to the database.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
/// <param name="coin">The coin to save.</param>
public void SaveCoin(string collectionName, Coin coin) {
// Get and replace the coin from the database.
Console.WriteLine("Saving coin...");
@@ -252,6 +377,10 @@ public class CoinDatabase {
cmd.ExecuteNonQuery();
}
/// <summary>
/// Synchronizes the database with the current contents of the specified collection.
/// </summary>
/// <param name="collectionName">The name of the collection table.</param>
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.
@@ -260,6 +389,10 @@ public class CoinDatabase {
}
}
/// <summary>
/// Creates a Coin from the information in a reader specifically from this database.
/// </summary>
/// <param name="reader">The reader to get the information from.</param>
private Coin CoinFromReader(SQLiteDataReader reader) {
// Create a coin while filtering out any possible null values - they throw exceptions.
Coin coin = new Coin();
@@ -283,6 +416,10 @@ public class CoinDatabase {
return coin;
}
/// <summary>
/// Deletes a Coin object from the database.
/// </summary>
/// <param name="delCoin">The coin to delete.</param>
public void DeleteCoin(Coin delCoin) {
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
@@ -292,9 +429,18 @@ public class CoinDatabase {
cmd.ExecuteNonQuery();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="grade">The grade string.</param>
private int GetGradeFromString(string grade) {
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
Console.WriteLine($"Grade is {actualGrade}");
return actualGrade;
}
}
public class NotConnectedToDatabaseException : Exception {
public NotConnectedToDatabaseException() : base("Not connected to database.") { }
}
+51 -11
View File
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using PcgsInvUi.Models;
using System.Text.Json;
using DynamicData.Kernel;
using ReactiveUI;
namespace PcgsInvUi.Services;
@@ -27,14 +25,47 @@ public class PcgsClient : ReactiveObject {
private readonly string _apiKey;
private int _numApiRequests;
public PcgsClient(string apiKey) {
// _apiKey = apiKey;
_apiKey =
"E8xivTVN_k4Q8ubk8Xv_uU11U91WX0wDht4S4m4BAf2HPmKnGrZ0J2f9nccM7Ns1VkGoT1SXYR74I7aNaMKqXlJGEy3oobseKV0nGiqu1IKvFkOTVjhV37SWUVExSDFMC6FLzs7xJQIHwkcxr5JFOQNmc4ZytEh_-hmCN3NL-z0fEk98tzBmPJ_F7bq_xNru86QMD2A9yalu_dWe-lCysfyE2bCA2EDqwvWenRYf-0XnNtp6qlxxCMlCd-TDqqRKLC3qefCwpXSemwBiNSP5gbC9pwcKrZugkruuJ0nyxxThZymm";
/// <summary>
/// Creates a new instance of the PcgsClient class.
/// Although this is a constructor, it's meant to be called only from the CreateClient method.
/// </summary>
private PcgsClient(string apiKey) {
_apiKey = apiKey;
}
/// <summary>
/// Creates a new instance of the PcgsClient class.
/// </summary>
/// <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>
public static async Task<PcgsClient?> CreateClient(string apiKey) {
// Check if the api key is valid.
var testClient = new PcgsClient(apiKey);
var validCreds = await testClient.VerifyCredentials();
if (!validCreds) return null;
return testClient;
}
/// <summary>
/// Sends a request to the API to verify the credentials.
/// </summary>
/// <returns>A boolean indicating whether or not the credentials are valid.</returns>
public async Task<bool> VerifyCredentials() {
// 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.
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.
if (result.Item1 == ErrorType.ApiKeyInvalid) return false;
return true;
}
/// <summary>
/// Gets the coin information for a given PCGS number and grade.
/// </summary>
/// <param name="pcgsNumber">The PCGS number 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>
/// <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) {
Coin newCoin = null;
using (HttpClient client = new HttpClient()) {
var gradeParam = GetGradeFromString(grade);
@@ -46,6 +77,7 @@ public class PcgsClient : ReactiveObject {
Console.WriteLine("Got response.");
if (response.IsSuccessStatusCode) {
// Check response for errors.
string jsonResponse = await response.Content.ReadAsStringAsync();
var parsedResponse = ParseJsonResponse(jsonResponse);
return parsedResponse;
@@ -55,17 +87,25 @@ public class PcgsClient : ReactiveObject {
return (ErrorType.ApiKeyInvalid, null);
}
/// <summary>
/// Utility function to parse the grade number from the grade string passed from the front end.
/// </summary>
/// <param name="grade">The grade string.</param>
/// <returns>The grade number.</returns>
private int GetGradeFromString(string grade) {
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
var actualGrade = int.Parse(grade.Substring(grade.Length - 2)); // Throws an exception if the grade doesn't exist.
Console.WriteLine($"Grade is {actualGrade}");
return actualGrade;
}
// TODO Will eventually create the other methods that are included in the Public API.
/// <summary>
/// Parses the json response from the api.
/// </summary>
/// <param name="json">The json response.</param>
/// <returns>A tuple containing an error type and the coin information.</returns>
private (ErrorType, Coin?) ParseJsonResponse(string json) {
Console.WriteLine(json);
// TODO 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("\"IsValidRequest\":false")) return (ErrorType.InvalidRequestFormat, null);
+30
View File
@@ -0,0 +1,30 @@
using ReactiveUI;
using System.Reactive;
namespace PcgsInvUi.ViewModels;
public class ApiKeyViewModel : ViewModelBase {
public string ApiKeyInput {
get => _apiKeyInput;
set => this.RaiseAndSetIfChanged(ref _apiKeyInput, value);
}
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);
}
}
+26 -14
View File
@@ -7,21 +7,10 @@ using System.Reactive.Linq;
using PcgsInvUi.Models;
using PcgsInvUi.Services;
using ReactiveUI;
using System.Threading.Tasks;
namespace PcgsInvUi.ViewModels;
// TODO Create proper sums for total value.
// 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 class MainWindowViewModel : ViewModelBase {
public ViewModelBase SidebarContent {
get => _sideContent;
@@ -50,12 +39,27 @@ public class MainWindowViewModel : ViewModelBase {
public MainWindowViewModel(CoinDatabase coins) {
ConnectedCoinDatabase = coins;
var keyFound = Task.Run(() => coins.TryInitApiClient()).Result;
Console.WriteLine("Function completed.");
var newViewModel = new NewViewModel();
_sideContent = newViewModel;
var apiKeyViewModel = new ApiKeyViewModel();
// DisplayedList = CoinCollection = new ObservableCollection<Coin>(coins.GetItems());
DisplayedList = ConnectedCoinDatabase.Collection;
TotalValue = DisplayedList.Sum(x => x.TotalPrice);
// Setup interaction to show ApiKeyWindow
apiKeyViewModel.OkCommand.Subscribe(async apiKey => {
if (await ConnectedCoinDatabase.TryInitApiClient(apiKey))
SidebarContent = newViewModel; // Must assign this to the observable property rather than private variable.
});
// Check if API key must be entered.
if (!keyFound) {
Console.WriteLine("API key must be entered by user.");
// Show the API key input view.
_sideContent = apiKeyViewModel;
} else _sideContent = newViewModel;
// Set up the ability to show the error window.
ShowErrorWindow = new Interaction<ErrorWindowViewModel, bool>();
@@ -113,7 +117,8 @@ public class MainWindowViewModel : ViewModelBase {
break;
}
// TODO Clear the text boxes.
// Clear the text boxes.
newViewModel.ClearText();
});
// Update TotalValue anytime a coin's quantity changes.
@@ -126,4 +131,11 @@ public class MainWindowViewModel : ViewModelBase {
TotalValue = ConnectedCoinDatabase.Collection.Sum(x => x.TotalPrice);
};
}
// Create destructor to save the database when the application exits.
// This is necessary because the database is initialized in this class.
~MainWindowViewModel() {
Console.WriteLine("Running MainWindowViewModel destructor.");
ConnectedCoinDatabase.UpdateCollection("CollectionTable");
}
}
+10 -3
View File
@@ -40,9 +40,7 @@ public class NewViewModel : ViewModelBase {
okEnabled);
ClearCommand = ReactiveCommand.Create(() => {
PcgsNumber = "";
Grade = "";
Quantity = "";
ClearText();
});
// Initialize grade values for use in the view.
@@ -83,4 +81,13 @@ public class NewViewModel : ViewModelBase {
public ReactiveCommand<Unit, (int, string, int)> OkCommand { get; }
public ReactiveCommand<Unit, Unit> ClearCommand { get; }
/// <summary>
/// Clears the text fields.
/// </summary>
public void ClearText() {
PcgsNumber = "";
Grade = "";
Quantity = "";
}
}
+14
View File
@@ -0,0 +1,14 @@
<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.ApiKeyView" x:DataType="viewModels:ApiKeyViewModel" >
<StackPanel>
<Label Content="API key was not found or did not authenticate." />
<Label Content="Please enter your API key below:" />
<TextBox Margin="10" Text="{Binding ApiKeyInput}" />
<Button HorizontalAlignment="Center" Content="Submit" Command="{Binding OkCommand}" />
</StackPanel>
</UserControl>
+9
View File
@@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace PcgsInvUi.Views;
public partial class ApiKeyView : UserControl {
public ApiKeyView() {
InitializeComponent();
}
}
+1 -1
View File
@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Platform.Storage;
@@ -36,6 +35,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel> {
private async Task ShowFilePickerAsync(InteractionContext<Unit, Uri> interationContext) {
var topLevel = GetTopLevel(this);
if (topLevel is null) throw new Exception("Could not find top level of the file path.");
var file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions {
Title = "Save CSV...",
DefaultExtension = ".csv",
+1 -3
View File
@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Controls;
namespace PcgsInvUi.Views;
+37
View File
@@ -0,0 +1,37 @@
# pcgs-inv
A coin collection inventory app, written in C# with the Avalonia UI framework utilizing the PCGS Public API.
# Requirements
[.NET Runtime >= 6](https://dotnet.microsoft.com/en-us/download) - you will be prompted for install before running the program if it is not installed.
# Install
Extract contents of the downloaded file to an accessible folder (ie. Documents/PCGSApp)
As of right now, there's no fancy installer to be used - shortcuts will have to be made by you.
# Usage
Usage is limited as of now, however input, deletion, and export of information is working and should not change much
over the development of the app.
To input a coin, you will need to know the grade and PCGS id (no NGS support, and is not planned yet) of the coin you
wish to input. You can put them into the sidebar to run a request to the PCGS Public API, at which point you will need
to be patient, due to the fact that the API is quite slow.
There is note functionality - with a coin selected, you may use the Notes section on the sidebar in order to input
that information. Multiline notes are not currently supported, but they are planned for the future.
To change the quantity of a specific coin, click on the corresponding value for the displayed coin. This will allow you
to type in a new value for the quantity. The total value of that row and the total value of the collection (displayed
in the lower right corner) will update automatically.
To export into a CSV file, click the Export to CSV button in the lower right corner. This will allow you to choose a
location and name for the file. These files are usable with any application that can open a .csv file, like Excel.
# Contributions
If you would like to help development, and don't mind looking through my spaghetti code (I'm still learning how to structure this)
then feel free to submit a pull request! I'm happy to take any help that I can get.
I've just started utilizing makefiles, however, since I'm currently using Windows, I'm not completely sure if there are formatting
changes that should be made to that. I will leave makefile creation for other platforms up to someone that uses these other
operating systems for now.
+5
View File
@@ -0,0 +1,5 @@
build:
dotnet build .\PcgsInvUi\PcgsInvUi.csproj
run:
dotnet run --project .\PcgsInvUi\PcgsInvUi.csproj
+2
View File
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CProgram_0020Files_005Cdotnet_005Cpacks_005CMicrosoft_002ENETCore_002EApp_002ERef_005C7_002E0_002E13_005Cref_005Cnet7_002E0_005CSystem_002EData_002Edll/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>