45 Commits

Author SHA1 Message Date
mbabienco 3be81f136f Update Windows installer script 2026-03-02 18:10:34 +09:00
mbabienco 2f14270379 Improve Linux build script with auto-zip 2026-03-02 17:50:57 +09:00
mbabienco 70e5215ca9 Fix some trimming issues 2026-03-02 17:13:33 +09:00
mbabienco cd5a89e35a Bump version 2026-03-02 16:39:18 +09:00
mbabienco 47fb765239 Sort on load if no prior report 2026-03-02 16:38:32 +09:00
mbabienco 68cf8a4145 Lock aspect ration 2026-03-02 16:34:40 +09:00
mbabienco e80d252b25 Fix image width on PDF 2026-03-02 16:34:27 +09:00
mbabienco fc89854bfc Add settings page for using legacy PDF mode 2026-03-02 16:32:40 +09:00
mbabienco d51017bb73 Allow rendering with Docnet PDF 2026-03-02 16:24:05 +09:00
mbabienco 5f99d0e4eb Fix log message for converted file paths 2026-03-02 14:33:36 +09:00
mbabienco 3720a6a25a Ensure output converted name is always right 2026-03-02 14:30:05 +09:00
mbabienco 6d4e7a7bfe Only open file long enough to read in pages 2026-03-02 14:18:36 +09:00
mbabienco be1fc5ec18 Bump MigraDoc version 2026-03-02 14:16:07 +09:00
mbabienco baaf6f8e2b Swap to Magick.NET-Q8 for faster processing 2026-02-27 17:36:34 +09:00
mbabienco bb6cdf0abf Tweak where height/width loaded/set 2026-02-25 09:33:44 +09:00
mbabienco 3c754dc196 Refine log messages 2026-02-25 09:15:02 +09:00
mbabienco af8cfa31e5 Fix comment 2026-02-25 09:14:28 +09:00
mbabienco f32568c918 Downsize if > 1.5 MB 2026-02-25 07:50:28 +09:00
mbabienco b42da7603b Auto-rotate images based on EXIF data 2026-02-25 07:45:51 +09:00
mbabienco 851772398c If file > 2.5 MB reduce size 2026-02-25 07:37:11 +09:00
mbabienco dfc1c557e0 Refactor extensions to Constants class 2026-02-25 07:35:51 +09:00
mbabienco f927667732 Add remove all button 2026-02-25 07:29:43 +09:00
mbabienco d77f9bab8c Don't show file not added for project json file 2026-02-24 21:03:14 +09:00
mbabienco d508222901 Update AppScreenshot.png 2026-02-24 21:02:16 +09:00
mbabienco 45e45d44af Update app.manifest 2026-02-24 19:48:18 +09:00
mbabienco d0d39ccd62 Add note 2026-02-24 19:47:06 +09:00
mbabienco ff96d727f1 Fix width on edit page 2026-02-24 19:44:58 +09:00
mbabienco 828430adf8 Fix sort not affecting saved status 2026-02-24 19:43:36 +09:00
mbabienco a6b60a4cc8 Bump app version 2026-02-24 19:40:43 +09:00
mbabienco 2fb0e1f73b Adjust max width in about screen 2026-02-24 19:39:11 +09:00
mbabienco 2a8bbf76bf Warn before software closed; fix bug on first run
Also put settings into proper dir
2026-02-24 19:38:20 +09:00
mbabienco bc5ce3e311 Don't highlight today in edit screen 2026-02-24 10:33:22 +09:00
mbabienco 64dc54bb70 Fix spacing of console line 2026-02-24 09:23:30 +09:00
mbabienco c6273ed8b3 Fix edit button icon 2026-02-24 09:14:21 +09:00
mbabienco 16f6aa511c Rename WarningDeleteItemModel to WarningDeleteItemViewModel 2026-02-18 09:49:54 +09:00
mbabienco b2917eda6b Add basic script for Linux building 2026-02-18 09:41:06 +09:00
mbabienco 2075a0be20 Namespace is now MayShow 2026-02-18 09:36:17 +09:00
mbabienco cc821da4da Update Add Item button title 2026-02-18 09:33:30 +09:00
mbabienco a466f04d20 Rename project files and fix installer w.r.t. new paths 2026-02-18 09:33:17 +09:00
mbabienco f4ed36ad38 Restore font files to right place
Moved to wrong folder on accident
2026-02-18 09:30:09 +09:00
mbabienco f6c7a205a8 Restore Windows installer
Accidentally put it in .gitignore folder
2026-02-18 09:28:30 +09:00
mbabienco b197f43341 Move files for better organization 2026-02-18 09:24:20 +09:00
mbabienco 6e23371858 Update README.md 2026-02-17 18:50:12 +09:00
mbabienco 75beb3d584 Fix typo 2026-02-17 18:43:37 +09:00
mbabienco 9ed6755072 Update README.md 2026-02-17 18:17:45 +09:00
77 changed files with 911 additions and 235 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net10.0/osx-arm64/ReceiptPDFBuilder.dll", "program": "${workspaceFolder}/src/bin/Debug/net10.0/osx-arm64/MayShow.dll",
"args": [], "args": [],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+3 -3
View File
@@ -7,7 +7,7 @@
"type": "process", "type": "process",
"args": [ "args": [
"build", "build",
"${workspaceFolder}/ReceiptPDFBuilder.csproj", "${workspaceFolder}/src/MayShow.csproj",
"/property:GenerateFullPaths=true", "/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign" "/consoleloggerparameters:NoSummary;ForceNoAlign"
], ],
@@ -19,7 +19,7 @@
"type": "process", "type": "process",
"args": [ "args": [
"publish", "publish",
"${workspaceFolder}/ReceiptPDFBuilder.csproj", "${workspaceFolder}/src/MayShow.csproj",
"/property:GenerateFullPaths=true", "/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign" "/consoleloggerparameters:NoSummary;ForceNoAlign"
], ],
@@ -33,7 +33,7 @@
"watch", "watch",
"run", "run",
"--project", "--project",
"${workspaceFolder}/ReceiptPDFBuilder.csproj" "${workspaceFolder}/src/MayShow.csproj"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
} }
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

-10
View File
@@ -1,10 +0,0 @@
using ReceiptPDFBuilder.ViewModels;
namespace ReceiptPDFBuilder.Interfaces
{
interface IChangeViewModel
{
void PushViewModel(BaseViewModel model);
void PopViewModel();
}
}
-9
View File
@@ -1,9 +0,0 @@
using Avalonia.Controls;
namespace ReceiptPDFBuilder.Interfaces
{
interface ITopLevelGrabber
{
TopLevel GetTopLevel();
}
}
-19
View File
@@ -1,19 +0,0 @@
using Avalonia.Controls;
using ReceiptPDFBuilder.Interfaces;
using ReceiptPDFBuilder.ViewModels;
namespace ReceiptPDFBuilder;
public partial class MainWindow : Window, ITopLevelGrabber
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(this);
}
public TopLevel GetTopLevel()
{
return this;
}
}
+5 -1
View File
@@ -1,6 +1,10 @@
# MayShow
MayShow (an intentional misspelling of 明証, pronounced may-shō, a Japanese word meaning proof, evidence, or corroboration) is a PDF report creation tool.
Throws a folder of images and PDFs into a single PDF. Simple tool for my own use, really, but maybe helpful to someone else. Throws a folder of images and PDFs into a single PDF. Simple tool for my own use, really, but maybe helpful to someone else.
![App screenshot](AppScreenshot.png) ![App screenshot](graphics/AppScreenshot.png)
Icon is from [Font Awesome](https://fontawesome.com/license/free) and generated via [Gauger's FontAwesome icon tool](https://gauger.me/fonticon/) and the macOS software [Icon Composer](https://developer.apple.com/icon-composer/). Icon is from [Font Awesome](https://fontawesome.com/license/free) and generated via [Gauger's FontAwesome icon tool](https://gauger.me/fonticon/) and the macOS software [Icon Composer](https://developer.apple.com/icon-composer/).
+29
View File
@@ -1,3 +1,32 @@
magick "2026-01-29 — \$210 — WORK PERMIT.pdf" -background white -alpha background -alpha off -density 288 -resize 25% page-%03d.jpg
https://github.com/dlemstra/Magick.NET/blob/main/docs/ConvertPDF.md
https://stackoverflow.com/questions/65089839/add-an-external-pdf-page-to-pdfsharp-migradoc
https://github.com/mephraim/ghostscriptsharp
https://stackoverflow.com/a/71485279/3938401
need a PDF viewer that works with annotations
https://github.com/BobLd/PdfPig.Rendering.Skia/ works but quality is low for some reason, I don't know why. probably need a bug report or some option that is hidden.
using (var document = UglyToad.PdfPig.PdfDocument.Open(filePath, UglyToad.PdfPig.Rendering.Skia.SkiaRenderingParsingOptions.Instance))
{
document.AddSkiaPageFactory(); // Same as document.AddPageFactory<SKPicture, SkiaPageFactory>() and document.AddPageFactory<PdfPageSize, PageSizeFactory>()
for (int p = 1; p <= document.NumberOfPages; p++)
{
using var skBitmap = document.GetPageAsSKBitmap(p, 1);
using (var fs = new FileStream(Path.Combine(convertedDir, $"{fileName}_{p}PIG.jpeg"), FileMode.Create))
using (var ms = document.GetPageAsPng(p, 1, 4))
{
MemoryStream memoryStream = new MemoryStream();
skBitmap.Encode(memoryStream, SkiaSharp.SKEncodedImageFormat.Jpeg, 100);
memoryStream.Position = 0L;
ms.WriteTo(fs);
}
}
}
https://github.com/GowenGit/docnet -- may need to fork this and publish our own package if some OS doesn't work
*-add more items *-add more items
*-save last opened folder to settings somewhere *-save last opened folder to settings somewhere
Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

+27
View File
@@ -0,0 +1,27 @@
{
"fill" : "system-dark",
"groups" : [
{
"layers" : [
{
"image-name" : "favicon(2).png",
"name" : "favicon(2)"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

@@ -3,7 +3,7 @@
; Non-commercial use only ; Non-commercial use only
#define MyAppName "MayShow" #define MyAppName "MayShow"
#define MyAppVersion "1.1.0" #define MyAppVersion "1.3.0"
#define MyAppPublisher "Quickity Quack Productions" #define MyAppPublisher "Quickity Quack Productions"
#define MyAppExeName "MayShow.exe" #define MyAppExeName "MayShow.exe"
@@ -31,8 +31,8 @@ DisableProgramGroupPage=yes
OutputBaseFilename=Install {#MyAppName} {#MyAppVersion} OutputBaseFilename=Install {#MyAppName} {#MyAppVersion}
SolidCompression=yes SolidCompression=yes
WizardStyle=modern dynamic WizardStyle=modern dynamic
SetupIconFile=ReceiptPDFBuilder-icon.ico SetupIconFile=../src/MayShow-icon.ico
OutputDir=bin OutputDir=..\src\bin
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -41,12 +41,14 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files] [Files]
Source: "bin\Release\net10.0\win-x64\publish\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "..\src\bin\Release\net10.0\win-x64\publish\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\Release\net10.0\win-x64\publish\av_libglesv2.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\src\bin\Release\net10.0\win-x64\publish\av_libglesv2.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\Release\net10.0\win-x64\publish\libHarfBuzzSharp.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\src\bin\Release\net10.0\win-x64\publish\libHarfBuzzSharp.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\Release\net10.0\win-x64\publish\libSkiaSharp.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\src\bin\Release\net10.0\win-x64\publish\libSkiaSharp.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin\Release\net10.0\win-x64\publish\Magick.Native-Q16-x64.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "..\src\bin\Release\net10.0\win-x64\publish\LICENSE"; DestDir: "{app}"; DestName: "PDFium-license.txt"; Flags: ignoreversion
Source: "bin\Release\net10.0\win-x64\publish\Assets\*"; DestDir: "{app}\Assets"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\src\bin\Release\net10.0\win-x64\publish\Magick.Native-Q8-x64.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\src\bin\Release\net10.0\win-x64\publish\pdfium.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\src\bin\Release\net10.0\win-x64\publish\Assets\*"; DestDir: "{app}\Assets"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
VERSION="1.3.0"
SRC_DIR="src" # user ran script from main folder
if [ ! -d "$SRC_DIR" ]; then
SRC_DIR= "../src" # try
fi
if [ ! -d "$SRC_DIR" ]; then
echo "Please run from "installers" dir or from main repo directory"
exit 1
fi
cd "$SRC_DIR"
echo "Building release for linux-x64..."
dotnet publish -c Release -r linux-x64 -p:StripSymbols=False -p:PublishAot=False
echo "Zipping up linux-x64..."
cd bin/Release/net10.0/linux-x64/publish
zip -r "../../../../MayShow $VERSION linux-arm64.zip" .
cd ../../../../../
echo "Building release for linux-arm64..."
dotnet publish -c Release -r linux-arm64 -p:StripSymbols=False -p:PublishAot=False
cd bin/Release/net10.0/linux-arm64/publish
zip -r "../../../../MayShow $VERSION linux-arm64.zip" .
cd ../../../../../
+14 -5
View File
@@ -1,11 +1,11 @@
<Application xmlns="https://github.com/avaloniaui" <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ReceiptPDFBuilder.App" x:Class="MayShow.App"
xmlns:viewModels="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:viewModels="clr-namespace:MayShow.ViewModels"
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views" xmlns:views="clr-namespace:MayShow.Views"
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:behaviors="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:behaviors="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers" xmlns:helpers="clr-namespace:MayShow.Helpers"
RequestedThemeVariant="Default" RequestedThemeVariant="Default"
Name="MayShow"> Name="MayShow">
<Application.Styles> <Application.Styles>
@@ -88,7 +88,7 @@
<DataTemplate DataType="{x:Type viewModels:MainViewModel}"> <DataTemplate DataType="{x:Type viewModels:MainViewModel}">
<views:MainView/> <views:MainView/>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type viewModels:WarningDeleteItemModel}"> <DataTemplate DataType="{x:Type viewModels:WarningDeleteItemViewModel}">
<views:WarningDeleteItem/> <views:WarningDeleteItem/>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type viewModels:EditFileViewModel}"> <DataTemplate DataType="{x:Type viewModels:EditFileViewModel}">
@@ -100,6 +100,15 @@
<DataTemplate DataType="{x:Type viewModels:WarningViewModel}"> <DataTemplate DataType="{x:Type viewModels:WarningViewModel}">
<views:WarningView/> <views:WarningView/>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ShutdownCheckViewModel}">
<views:ShutdownCheckView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ConfirmViewModel}">
<views:ConfirmView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SettingsViewModel}">
<views:SettingsView/>
</DataTemplate>
</Application.DataTemplates> </Application.DataTemplates>
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
+3 -3
View File
@@ -4,10 +4,10 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using DialogHostAvalonia; using DialogHostAvalonia;
using ReceiptPDFBuilder; using MayShow;
using ReceiptPDFBuilder.ViewModels; using MayShow.ViewModels;
namespace ReceiptPDFBuilder; namespace MayShow;
public partial class App : Application public partial class App : Application
{ {
@@ -4,7 +4,7 @@ using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
namespace ReceiptPDFBuilder.Helpers namespace MayShow.Helpers
{ {
// https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2 // https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2
@@ -1,11 +1,14 @@
using System; using System;
namespace ReceiptPDFBuilder.Helpers; namespace MayShow.Helpers;
class Constants class Constants
{ {
public static string AppVersion = "1.1.0"; public static string AppVersion = "1.3.0";
public static string[] AllowedFileExtensionPatterns = [ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp", "*.pdf", "*.heic", ];
public static string[] AllowedFileExtensionsNoStar = [ "png", "jpg", "jpeg", "gif", "bmp", "webp", "pdf", "heic", ];
public static string[] GetQuotes() public static string[] GetQuotes()
{ {
@@ -3,10 +3,10 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Xaml.Interactions.DragAndDrop; using Avalonia.Xaml.Interactions.DragAndDrop;
using ReceiptPDFBuilder.Models; using MayShow.Models;
using ReceiptPDFBuilder.ViewModels; using MayShow.ViewModels;
namespace ReceiptPDFBuilder.Helpers; namespace MayShow.Helpers;
class DataGridDropHandler : BaseDataGridDropHandler<ReportFile> class DataGridDropHandler : BaseDataGridDropHandler<ReportFile>
{ {
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace ReceiptPDFBuilders.Helpers; namespace MayShows.Helpers;
public static class ThreadSafeRandom public static class ThreadSafeRandom
{ {
@@ -1,7 +1,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using ReceiptPDFBuilder.Models; using MayShow.Models;
namespace ReceiptPDFBuilder.Helpers; namespace MayShow.Helpers;
[JsonSerializable(typeof(Settings))] [JsonSerializable(typeof(Settings))]
[JsonSerializable(typeof(ReportFile))] [JsonSerializable(typeof(ReportFile))]
@@ -5,7 +5,7 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace ReceiptPDFBuilders.Helpers; namespace MayShows.Helpers;
class Utilities class Utilities
{ {
+8
View File
@@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace MayShow.Interfaces;
interface ICanCheckShutdown
{
Task<bool> CheckIsSafeToShutdown();
}
+9
View File
@@ -0,0 +1,9 @@
using MayShow.ViewModels;
namespace MayShow.Interfaces;
interface IChangeViewModel
{
void PushViewModel(BaseViewModel model);
void PopViewModel();
}
+8
View File
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace MayShow.Interfaces;
interface ITopLevelGrabber
{
TopLevel GetTopLevel();
}
+4 -3
View File
@@ -3,9 +3,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.MainWindow" x:Class="MayShow.MainWindow"
Title="MayShow" Title="MayShow"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:MainWindowViewModel" x:DataType="vm:MainWindowViewModel"
Width="800" Width="800"
@@ -13,7 +13,8 @@
Height="650" Height="650"
MinHeight="550"> MinHeight="550">
<dialogHost:DialogHost CloseOnClickAway="False" <dialogHost:DialogHost CloseOnClickAway="False"
Identifier="DialogHost"> Identifier="DialogHost"
x:Name="WindowDialogHost">
<dialogHost:DialogHost.DialogContent> <dialogHost:DialogHost.DialogContent>
<StackPanel/> <StackPanel/>
</dialogHost:DialogHost.DialogContent> </dialogHost:DialogHost.DialogContent>
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using DialogHostAvalonia;
using MayShow.Interfaces;
using MayShow.ViewModels;
namespace MayShow;
public partial class MainWindow : Window, ITopLevelGrabber
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(this);
Closing += WindowIsClosing;
var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
// lifetime?.ShutdownRequested += ApplicationIsShuttingDown;
}
private async void WindowIsClosing(object? sender, WindowClosingEventArgs e)
{
e.Cancel = true; // async -> need to cancel immediately
if (await CheckIfClosePossible())
{
Closing -= WindowIsClosing;
var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
lifetime?.ShutdownRequested -= ApplicationIsShuttingDown;
Close();
}
}
private async void ApplicationIsShuttingDown(object? sender, ShutdownRequestedEventArgs e)
{
e.Cancel = true; // async -> need to cancel immediately
if (await CheckIfClosePossible())
{
Closing -= WindowIsClosing;
var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
lifetime?.ShutdownRequested -= ApplicationIsShuttingDown;
lifetime?.TryShutdown();
}
}
private async Task<bool> CheckIfClosePossible()
{
var canShutdown = true;
if (DataContext is MainWindowViewModel mwvm)
{
if (mwvm is ICanCheckShutdown canCheck)
{
canShutdown = await canCheck.CheckIsSafeToShutdown();
}
// only checking 1 level but for this app that is OK
if (canShutdown && mwvm.CurrentViewModel is ICanCheckShutdown currModel)
{
try
{
canShutdown = await currModel.CheckIsSafeToShutdown();
}
catch (Exception)
{
canShutdown = true;
}
}
}
return canShutdown;
}
public TopLevel GetTopLevel()
{
return this;
}
}

Before

Width:  |  Height:  |  Size: 401 KiB

After

Width:  |  Height:  |  Size: 401 KiB

@@ -12,12 +12,17 @@
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
<AssemblyName>MayShow</AssemblyName> <AssemblyName>MayShow</AssemblyName>
<AssemblyVersion>1.1.0</AssemblyVersion> <!-- Also update Constants version --> <AssemblyVersion>1.3.0</AssemblyVersion> <!-- Also update Constants version -->
<ApplicationIcon>ReceiptPDFBuilder-icon.ico</ApplicationIcon> <ApplicationIcon>MayShow-icon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<TrimmerRootAssembly Include="MigraDoc.DocumentObjectModel" />
<TrimmerRootAssembly Include="MigraDoc.Rendering" />
<TrimmerRootAssembly Include="MigraDoc.RtfRendering" />
<TrimmerRootAssembly Include="PdfSharp" /> <TrimmerRootAssembly Include="PdfSharp" />
<TrimmerRootAssembly Include="Avalonia.Controls.DataGrid" /> <TrimmerRootAssembly Include="Avalonia.Controls.DataGrid" />
<!-- <TrimmerRootAssembly Include="Docnet.Core" />
<TrimmerRootAssembly Include="SixLabors.ImageSharp" /> -->
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
@@ -46,10 +51,12 @@
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets> <IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets> <PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="PDFsharp-MigraDoc" Version="6.2.3" /> <PackageReference Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.2" /> <PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.3" />
<PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" /> <PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" />
<PackageReference Include="DialogHost.Avalonia" Version="0.10.4" /> <PackageReference Include="DialogHost.Avalonia" Version="0.10.4" />
<PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" /> <PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" />
<PackageReference Include="Docnet.Core" Version="2.6.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -5,9 +5,9 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
namespace ReceiptPDFBuilder.Models; namespace MayShow.Models;
class PDFReport : ChangeNotifier class PDFReport : ChangeNotifier
{ {
@@ -1,9 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
namespace ReceiptPDFBuilder.Models; namespace MayShow.Models;
class ReportFile : ChangeNotifier class ReportFile : ChangeNotifier
{ {
+13 -4
View File
@@ -4,18 +4,20 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
using ReceiptPDFBuilders.Helpers; using MayShows.Helpers;
namespace ReceiptPDFBuilder.Models; namespace MayShow.Models;
class Settings : ChangeNotifier class Settings : ChangeNotifier
{ {
private string _lastUsedPath; private string _lastUsedPath;
private bool _useDocnetPDFImageRendering;
public Settings() public Settings()
{ {
_lastUsedPath = ""; _lastUsedPath = "";
_useDocnetPDFImageRendering = true;
} }
[JsonInclude] [JsonInclude]
@@ -25,6 +27,13 @@ class Settings : ChangeNotifier
set { _lastUsedPath = value; NotifyPropertyChanged(); } set { _lastUsedPath = value; NotifyPropertyChanged(); }
} }
[JsonInclude]
public bool UseDocnetPFDImageRendering
{
get => _useDocnetPDFImageRendering;
set { _useDocnetPDFImageRendering = value; NotifyPropertyChanged(); }
}
public static string GetSettingsFileName() public static string GetSettingsFileName()
{ {
return "settings.json"; return "settings.json";
@@ -34,7 +43,7 @@ class Settings : ChangeNotifier
{ {
var path = Path.Combine( var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ReceiptPDFBuilder" "MayShow"
); );
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
+8
View File
@@ -0,0 +1,8 @@
namespace MayShow.Models;
enum ShutdownCheckOptions
{
SaveAndShutdown,
NoSaveShutdown,
CancelShutdown,
}
+1 -1
View File
@@ -1,7 +1,7 @@
using Avalonia; using Avalonia;
using System; using System;
namespace ReceiptPDFBuilder; namespace MayShow;
class Program class Program
{ {
@@ -15,10 +15,10 @@ using MigraDoc.Rendering;
using PdfSharp.Fonts; using PdfSharp.Fonts;
using PdfSharp.Pdf.IO; using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font; using PdfSharp.Snippets.Font;
using ReceiptPDFBuilder.Interfaces; using MayShow.Interfaces;
using ReceiptPDFBuilder.Models; using MayShow.Models;
namespace ReceiptPDFBuilder.ViewModels; namespace MayShow.ViewModels;
class AboutViewModel class AboutViewModel
{ {
@@ -1,11 +1,11 @@
using Avalonia.Controls; using Avalonia.Controls;
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
using ReceiptPDFBuilder.Interfaces; using MayShow.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace ReceiptPDFBuilder.ViewModels namespace MayShow.ViewModels
{ {
class BaseViewModel : ChangeNotifier class BaseViewModel : ChangeNotifier
{ {
+52
View File
@@ -0,0 +1,52 @@
#nullable enable
using DialogHostAvalonia;
using MayShow.Helpers;
namespace MayShow.ViewModels;
class ConfirmViewModel
{
private string _title;
private string _message;
private string _confirmTitle;
private string _declineTitle;
public ConfirmViewModel(string title, string message, string confirmTitle = "Yes", string declineTitle = "No")
{
_title = title;
_message = message;
_confirmTitle = confirmTitle;
_declineTitle = declineTitle;
}
public string Title
{
get => _title;
}
public string Message
{
get => _message;
}
public string ConfirmTitle
{
get => _confirmTitle;
}
public string DeclineTitle
{
get => _declineTitle;
}
public void Confirm()
{
DialogHost.Close("DialogHost", true);
}
public void Decline()
{
DialogHost.Close("DialogHost", false);
}
}
@@ -15,10 +15,10 @@ using MigraDoc.Rendering;
using PdfSharp.Fonts; using PdfSharp.Fonts;
using PdfSharp.Pdf.IO; using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font; using PdfSharp.Snippets.Font;
using ReceiptPDFBuilder.Interfaces; using MayShow.Interfaces;
using ReceiptPDFBuilder.Models; using MayShow.Models;
namespace ReceiptPDFBuilder.ViewModels; namespace MayShow.ViewModels;
class EditFileViewModel : BaseViewModel class EditFileViewModel : BaseViewModel
{ {
@@ -16,15 +16,24 @@ using MigraDoc.Rendering;
using PdfSharp.Fonts; using PdfSharp.Fonts;
using PdfSharp.Pdf.IO; using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font; using PdfSharp.Snippets.Font;
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
using ReceiptPDFBuilder.Interfaces; using MayShow.Interfaces;
using ReceiptPDFBuilder.Models; using MayShow.Models;
using ReceiptPDFBuilders.Helpers; using MayShows.Helpers;
namespace ReceiptPDFBuilder.ViewModels; using Docnet.Core.Models;
using Docnet.Core;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Reflection.Metadata.Ecma335;
using Docnet.Core.Readers;
class MainViewModel : BaseViewModel, IFontResolver namespace MayShow.ViewModels;
class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
{ {
private bool _isPerformingInitialLoad;
private string _processDir; private string _processDir;
private bool _isCreatingPDF; private bool _isCreatingPDF;
private string _createPDFLog; private string _createPDFLog;
@@ -36,21 +45,24 @@ class MainViewModel : BaseViewModel, IFontResolver
private Settings _settings; private Settings _settings;
private bool _hasUnsavedWork;
public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger) public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger)
{ {
_isPerformingInitialLoad = true;
_processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? ""; _processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
Console.WriteLine("Process is running from: {0}", _processDir); Console.WriteLine("Process is running from: {0}", _processDir);
_isCreatingPDF = false; _isCreatingPDF = false;
var quotes = Constants.GetQuotes(); var quotes = Constants.GetQuotes();
Random random = new Random(); Random random = new Random();
var quoteIndex = random.Next(0, quotes.Length); var quoteIndex = random.Next(0, quotes.Length);
_createPDFLog = "----- MayShow v" + Constants.AppVersion + "------" + Environment.NewLine; _createPDFLog = "----- MayShow v" + Constants.AppVersion + " ------" + Environment.NewLine;
_createPDFLog += quotes[quoteIndex] + Environment.NewLine; _createPDFLog += quotes[quoteIndex] + Environment.NewLine;
_createPDFLog += "---------------------------------------" + Environment.NewLine; _createPDFLog += "---------------------------------------" + Environment.NewLine;
_createPDFLog += "Ready to create PDF!"; _createPDFLog += "Loaded and ready to create report!" + Environment.NewLine;
_createPDFLog += "Please copy and send this Program Log when reporting any issues with the software.";
_workingFolder = ""; _workingFolder = "";
_reportFiles = new ObservableCollection<ReportFile>(); ReportFiles = _reportFiles = new ObservableCollection<ReportFile>();
_reportFiles.CollectionChanged += ( sender, e ) => { NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); };
_reportTitle = ""; _reportTitle = "";
_lastGeneratedTime = null; _lastGeneratedTime = null;
_settings = Settings.LoadSettings(); _settings = Settings.LoadSettings();
@@ -59,12 +71,24 @@ class MainViewModel : BaseViewModel, IFontResolver
LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath); LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath);
ScanFolder(_settings.LastUsedPath); ScanFolder(_settings.LastUsedPath);
} }
else
{
LogInfo("Choose a receipt folder to begin...");
}
HasUnsavedWork = false;
_isPerformingInitialLoad = false;
} }
public string ReportTitle public string ReportTitle
{ {
get => _reportTitle; get => _reportTitle;
set { _reportTitle = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsTitleBoxVisible)); } set
{
_reportTitle = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
NotifyPropertyChanged(nameof(CanAddItem));
}
} }
public bool IsTitleBoxVisible public bool IsTitleBoxVisible
@@ -72,6 +96,11 @@ class MainViewModel : BaseViewModel, IFontResolver
get => !string.IsNullOrWhiteSpace(_workingFolder); get => !string.IsNullOrWhiteSpace(_workingFolder);
} }
public bool CanAddItem
{
get => IsTitleBoxVisible && !IsCreatingPDF;
}
public bool IsCreatingPDF public bool IsCreatingPDF
{ {
get => _isCreatingPDF; get => _isCreatingPDF;
@@ -81,6 +110,7 @@ class MainViewModel : BaseViewModel, IFontResolver
NotifyPropertyChanged(); NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF)); NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
NotifyPropertyChanged(nameof(CanAddItem));
} }
} }
@@ -117,6 +147,16 @@ class MainViewModel : BaseViewModel, IFontResolver
set { _createPDFLog = value; NotifyPropertyChanged(); } set { _createPDFLog = value; NotifyPropertyChanged(); }
} }
public bool HasUnsavedWork
{
get => _hasUnsavedWork;
set
{
_hasUnsavedWork = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<ReportFile> ReportFiles public ObservableCollection<ReportFile> ReportFiles
{ {
get => _reportFiles; get => _reportFiles;
@@ -127,6 +167,7 @@ class MainViewModel : BaseViewModel, IFontResolver
_reportFiles.CollectionChanged += ( sender, e ) => _reportFiles.CollectionChanged += ( sender, e ) =>
{ {
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
HasUnsavedWork = true;
}; };
} }
} }
@@ -157,6 +198,7 @@ class MainViewModel : BaseViewModel, IFontResolver
_settings.LastUsedPath = folder.Path.LocalPath; _settings.LastUsedPath = folder.Path.LocalPath;
await _settings.SaveSettingsAsync(); await _settings.SaveSettingsAsync();
ResortPDFItemsByDate(); ResortPDFItemsByDate();
HasUnsavedWork = true;
} }
} }
} }
@@ -167,6 +209,7 @@ class MainViewModel : BaseViewModel, IFontResolver
{ {
WorkingFolder = path; WorkingFolder = path;
NotifyPropertyChanged(nameof(IsTitleBoxVisible)); NotifyPropertyChanged(nameof(IsTitleBoxVisible));
NotifyPropertyChanged(nameof(CanAddItem));
var reportFilePath = Path.Combine(path, GetReportSavedDataFileName()); var reportFilePath = Path.Combine(path, GetReportSavedDataFileName());
var successfullyLoadedPriorReport = false; var successfullyLoadedPriorReport = false;
if (File.Exists(reportFilePath)) if (File.Exists(reportFilePath))
@@ -196,6 +239,7 @@ class MainViewModel : BaseViewModel, IFontResolver
AddFileBasedOnPath(filePath); AddFileBasedOnPath(filePath);
} }
ResortPDFItemsByDate(); ResortPDFItemsByDate();
HasUnsavedWork = true;
} }
} }
else else
@@ -210,17 +254,29 @@ class MainViewModel : BaseViewModel, IFontResolver
DialogHost.Show(new AboutViewModel()); DialogHost.Show(new AboutViewModel());
} }
public async Task ShowSettings()
{
var updatedSettings = await DialogHost.Show(new SettingsViewModel(_settings));
if (updatedSettings != null)
{
_settings = (Settings)updatedSettings;
await _settings.SaveSettingsAsync();
LogInfo("Saved updated settings!");
}
}
public void RemoveFile(object f) => RemoveFileImpl((ReportFile)f); public void RemoveFile(object f) => RemoveFileImpl((ReportFile)f);
public async void RemoveFileImpl(ReportFile file) public async void RemoveFileImpl(ReportFile file)
{ {
var result = await DialogHost.Show(new WarningDeleteItemModel(file)); var result = await DialogHost.Show(new WarningDeleteItemViewModel(file));
if (result != null && (bool)result) if (result != null && (bool)result)
{ {
var idx = ReportFiles.IndexOf(file); var idx = ReportFiles.IndexOf(file);
if (idx != -1) if (idx != -1)
{ {
ReportFiles.RemoveAt(idx); ReportFiles.RemoveAt(idx);
HasUnsavedWork = true;
} }
} }
} }
@@ -236,20 +292,10 @@ class MainViewModel : BaseViewModel, IFontResolver
file.Title = updatedData.Title; file.Title = updatedData.Title;
file.ReceiptDateTime = updatedData.ReceiptDateTime; file.ReceiptDateTime = updatedData.ReceiptDateTime;
file.Notes = updatedData.Notes; file.Notes = updatedData.Notes;
HasUnsavedWork = true;
} }
} }
private string[] GetAllowedFileExtensionPatterns()
{
return [ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp", "*.pdf", "*.heic", ];
}
private string[] GetAllowedFileExtensionPatternsWithoutStar()
{
var list = GetAllowedFileExtensionPatterns();
return list.Select(x => x.Replace("*.", "")).ToArray();
}
public async void AddItem() public async void AddItem()
{ {
var topLevel = TopLevelGrabber?.GetTopLevel(); var topLevel = TopLevelGrabber?.GetTopLevel();
@@ -262,7 +308,7 @@ class MainViewModel : BaseViewModel, IFontResolver
FileTypeFilter = [ FileTypeFilter = [
new FilePickerFileType("All Types") new FilePickerFileType("All Types")
{ {
Patterns = GetAllowedFileExtensionPatterns(), Patterns = Constants.AllowedFileExtensionPatterns,
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ], AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
MimeTypes = [ "image/*", "application/pdf", "image/heic" ] MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
}, },
@@ -292,7 +338,7 @@ class MainViewModel : BaseViewModel, IFontResolver
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store")) if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store"))
{ {
// make sure extensions are OK // make sure extensions are OK
var fileExtensions = GetAllowedFileExtensionPatternsWithoutStar(); var fileExtensions = Constants.AllowedFileExtensionsNoStar;
var didMatch = false; var didMatch = false;
foreach (var fileExtension in fileExtensions) foreach (var fileExtension in fileExtensions)
{ {
@@ -304,7 +350,10 @@ class MainViewModel : BaseViewModel, IFontResolver
} }
if (!didMatch) if (!didMatch)
{ {
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath); if (!filePath.EndsWith(GetReportSavedDataFileName()))
{
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath);
}
} }
else else
{ {
@@ -316,10 +365,22 @@ class MainViewModel : BaseViewModel, IFontResolver
Notes = "", Notes = "",
FilePath = filePath, FilePath = filePath,
}); });
HasUnsavedWork = true;
} }
} }
} }
public async void RemoveAllItems()
{
var result = await DialogHost.Show(new ConfirmViewModel("Warning!", "Are you sure you want to remove all items from this report?", "Remove All Items", "Cancel"));
if (result != null && (bool)result)
{
ReportFiles.Clear();
HasUnsavedWork = true;
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
}
}
public void LocateFile(object f) => LocateFileImpl((ReportFile) f); public void LocateFile(object f) => LocateFileImpl((ReportFile) f);
public async void LocateFileImpl(ReportFile reportFile) public async void LocateFileImpl(ReportFile reportFile)
{ {
@@ -333,7 +394,7 @@ class MainViewModel : BaseViewModel, IFontResolver
FileTypeFilter = [ FileTypeFilter = [
new FilePickerFileType("All Types") new FilePickerFileType("All Types")
{ {
Patterns = GetAllowedFileExtensionPatterns(), Patterns = Constants.AllowedFileExtensionPatterns,
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ], AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
MimeTypes = [ "image/*", "application/pdf", "image/heic" ] MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
}, },
@@ -351,6 +412,7 @@ class MainViewModel : BaseViewModel, IFontResolver
{ {
var file = files[0]; var file = files[0];
reportFile.FilePath = file.Path.LocalPath; reportFile.FilePath = file.Path.LocalPath;
HasUnsavedWork = true;
} }
} }
} }
@@ -389,6 +451,7 @@ class MainViewModel : BaseViewModel, IFontResolver
{ {
LogInfo("Sorting report files list..."); LogInfo("Sorting report files list...");
ReportFiles = new ObservableCollection<ReportFile>(ReportFiles.OrderBy(x => x.ReceiptDateTime)); ReportFiles = new ObservableCollection<ReportFile>(ReportFiles.OrderBy(x => x.ReceiptDateTime));
HasUnsavedWork = true;
} }
public async void BuildPDF() public async void BuildPDF()
@@ -423,7 +486,7 @@ class MainViewModel : BaseViewModel, IFontResolver
} }
} }
public async void SaveInterimReportInfo() public async Task SaveInterimReportInfo()
{ {
var report = new PDFReport() var report = new PDFReport()
{ {
@@ -447,6 +510,7 @@ class MainViewModel : BaseViewModel, IFontResolver
var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName()); var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName());
await File.WriteAllTextAsync(savePath, json); await File.WriteAllTextAsync(savePath, json);
LogInfo("Saved report information to {0}", savePath); LogInfo("Saved report information to {0}", savePath);
HasUnsavedWork = false;
} }
private async Task CreateAndSaveReportObjectAfterReportCreation() private async Task CreateAndSaveReportObjectAfterReportCreation()
@@ -558,6 +622,12 @@ class MainViewModel : BaseViewModel, IFontResolver
reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
reportTitlePar.AddText(ReportTitle); reportTitlePar.AddText(ReportTitle);
// //
var convertedDir = Path.Combine(folderPath, "converted");
if (!Directory.Exists(convertedDir))
{
Directory.CreateDirectory(convertedDir);
}
//
GlobalFontSettings.FontResolver = this; GlobalFontSettings.FontResolver = this;
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver(); GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
var hasAddedData = false; var hasAddedData = false;
@@ -585,14 +655,7 @@ class MainViewModel : BaseViewModel, IFontResolver
imageTitlePar.Format.Font.Size = 12; imageTitlePar.Format.Font.Size = 12;
imageTitlePar.Format.Font.Bold = true; imageTitlePar.Format.Font.Bold = true;
imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
if (string.IsNullOrWhiteSpace(file.Title)) imageTitlePar.AddText(string.IsNullOrWhiteSpace(file.Title) ? file.FileName : file.Title);
{
imageTitlePar.AddText(file.FileName);
}
else
{
imageTitlePar.AddText(file.Title);
}
var receiptDatePar = section.AddParagraph(); var receiptDatePar = section.AddParagraph();
receiptDatePar.Format.Alignment = ParagraphAlignment.Center; receiptDatePar.Format.Alignment = ParagraphAlignment.Center;
receiptDatePar.Format.Font.Size = 12; receiptDatePar.Format.Font.Size = 12;
@@ -620,67 +683,142 @@ class MainViewModel : BaseViewModel, IFontResolver
var info = new FileInfo(file.FilePath); var info = new FileInfo(file.FilePath);
uint loadedImageWidth = 0; uint loadedImageWidth = 0;
uint loadedImageHeight = 0; uint loadedImageHeight = 0;
if (isHEIC || isWebp || isPNG) if (!isPDF)
{ {
// Save image as jpg
var convertedDir = Path.Combine(folderPath, "converted");
if (!Directory.Exists(convertedDir))
{
Directory.CreateDirectory(convertedDir);
}
var outputPath = Path.Combine(convertedDir, info.Name + ".jpg");
using var mImage = new MagickImage(info.FullName); using var mImage = new MagickImage(info.FullName);
loadedImageWidth = mImage.Width; loadedImageWidth = mImage.Width;
loadedImageHeight = mImage.Height; loadedImageHeight = mImage.Height;
mImage.Quality = 80; var convertedOutputPath = Path.Combine(convertedDir, info.Name + ".jpg");
if (mImage.Width >= 400 || mImage.Height >= 400) var didAdjust = false;
LogInfo("Image orientation of {0} is {1}", fileName, mImage.Orientation);
if (mImage.Orientation != OrientationType.TopLeft)
{ {
loadedImageWidth = (uint)Math.Floor(mImage.Width * 0.5); LogInfo("Auto-adjusted image orientation of {0}", fileName);
loadedImageHeight = (uint)Math.Floor(mImage.Height * 0.5); mImage.AutoOrient();
mImage.Scale(loadedImageWidth, loadedImageHeight); didAdjust = true;
}
// perform needed image manipulations
if (isHEIC || isWebp || isPNG || (!isPDF && info.Length > 1.5 * 1024 * 1024 /* 1.5 MB */))
{
// Save image as jpg
mImage.Quality = 80;
if (mImage.Width >= 400 || mImage.Height >= 400)
{
loadedImageWidth = (uint)Math.Floor(mImage.Width * 0.5);
loadedImageHeight = (uint)Math.Floor(mImage.Height * 0.5);
mImage.Scale(loadedImageWidth, loadedImageHeight);
LogInfo("Image {2} scaled to {0}x{1}", loadedImageWidth, loadedImageHeight, fileName);
}
didAdjust = true;
LogInfo("Converted image {0} to JPEG", fileName);
}
else
{
// load height/width
loadedImageWidth = mImage.Width;
loadedImageHeight = mImage.Height;
}
if (didAdjust)
{
await mImage.WriteAsync(convertedOutputPath);
filePath = convertedOutputPath;
LogInfo(string.Format("Saved adjusted image to JPEG; file path is now {0}", filePath));
}
// write to PDF
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var image = paragraph.AddImage(filePath);
image.LockAspectRatio = true;
if (!isPDF && loadedImageHeight > 600)
{
image.Height = 550; // make sure it will fit on one page
}
else
{
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
} }
await mImage.WriteAsync(outputPath);
filePath = Path.Combine("Converted", info.Name + ".jpg");
LogInfo(string.Format("Converted image to JPEG; fileName is now {0}", file.FilePath));
}
else if (!isPDF)
{
// load height/width
using var mImage = new MagickImage(info.FullName);
loadedImageWidth = mImage.Width;
loadedImageHeight = mImage.Height;
}
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var image = paragraph.AddImage(filePath);
image.LockAspectRatio = true;
if (!isPDF && loadedImageHeight > 600)
{
image.Height = 550; // make sure it will fit on one page
} }
else else
{ {
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins... // need to render PDF to images
} if (_settings.UseDocnetPFDImageRendering)
LogInfo(string.Format("Added image: {0} ({1})", file.Title, filePath));
if (isPDF)
{
// add other PDF pages
// see: https://stackoverflow.com/a/65091204/3938401
var pdfFileToAdd = PdfReader.Open(filePath);
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pdfFileToAdd.PageCount,
pdfFileToAdd.PageCount == 1 ? "" : "s"));
for (var j = 2; j <= pdfFileToAdd.PageCount; j++)
{ {
section.AddPageBreak(); // render using Docnet library (which utilizes pdfium, the chrome renderer)
paragraph = section.AddParagraph(); string RenderPdfPageToImage(IDocReader docReader, int pgNum)
{
Console.WriteLine("Rendering pg " + pgNum);
using var pageReader = docReader.GetPageReader(pgNum);
Console.WriteLine("Getting image for page " + pgNum);
var rawBytes = pageReader.GetImage(RenderFlags.RenderAnnotations);
Console.WriteLine("Getting width & height for page " + pgNum);
var width = pageReader.GetPageWidth();
var height = pageReader.GetPageHeight();
Console.WriteLine("Loading pixel data for page " + pgNum);
using var img = Image.LoadPixelData<Bgra32>(rawBytes, width, height);
// you are likely going to want this as well otherwise you might end up with transparent parts.
img.Mutate(x => x.BackgroundColor(SixLabors.ImageSharp.Color.White));
var pdfPageImageOutputPath = Path.Combine(convertedDir, info.Name + "-Page-"
+ (pgNum + 1).ToString().PadLeft(3, '0') + ".jpg");
img.Save(pdfPageImageOutputPath);
Console.WriteLine("Done rendering pg " + pgNum);
return pdfPageImageOutputPath;
}
// render all pages to images
var docReader = DocLib.Instance.GetDocReader(
filePath,
new PageDimensions(1080, 1920)); // TODO: are these dims right?
// add to document
var pgCount = docReader.GetPageCount();
if (pgCount > 0)
{
var convertedPdfImagePath = RenderPdfPageToImage(docReader, 0);
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pgCount,
pgCount == 1 ? "" : "s"));
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var image = paragraph.AddImage(convertedPdfImagePath);
image.Width = imageWidth;
image.LockAspectRatio = true;
for (var j = 1; j < pgCount; j++)
{
section.AddPageBreak();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
convertedPdfImagePath = RenderPdfPageToImage(docReader, j);
image = paragraph.AddImage(convertedPdfImagePath);
image.LockAspectRatio = true;
image.Width = imageWidth;
}
}
}
else
{
// render first page (eventually need to improve code to just do everything in a loop)
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center; paragraph.Format.Alignment = ParagraphAlignment.Center;
image = paragraph.AddImage(filePath + "#" + j); var image = paragraph.AddImage(filePath);
image.LockAspectRatio = true; image.LockAspectRatio = true;
image.Width = imageWidth; image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
// render other PDF pages, if any
// see: https://stackoverflow.com/a/65091204/3938401
var pdfFileToAdd = PdfReader.Open(filePath, PdfDocumentOpenMode.Import);
var pgCount = pdfFileToAdd.PageCount;
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pgCount,
pgCount == 1 ? "" : "s"));
for (var j = 2; j <= pgCount; j++)
{
section.AddPageBreak();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
image = paragraph.AddImage(filePath + "#" + j);
image.LockAspectRatio = true;
image.Width = imageWidth;
}
} }
} }
LogInfo(string.Format("Added image: {0} ({1})", file.Title, filePath));
hasAddedData = true; hasAddedData = true;
} }
var pdfRenderer = new PdfDocumentRenderer var pdfRenderer = new PdfDocumentRenderer
@@ -688,14 +826,43 @@ class MainViewModel : BaseViewModel, IFontResolver
Document = pdfDoc, Document = pdfDoc,
WorkingDirectory = folderPath WorkingDirectory = folderPath
}; };
LogInfo("Rendering document..."); LogInfo("Rendering document to PDF file...");
pdfRenderer.RenderDocument(); pdfRenderer.RenderDocument();
string outputPDFFileName = Path.Join(folderPath, outputFileName); string outputPDFFileName = Path.Join(folderPath, outputFileName);
LogInfo("Saving document to disk..."); LogInfo("Saving PDF document to disk...");
pdfRenderer.PdfDocument.Save(outputPDFFileName); pdfRenderer.PdfDocument.Save(outputPDFFileName);
LogInfo("Saved PDF output to: " + outputPDFFileName); LogInfo("Finished saving PDF output to: " + outputPDFFileName);
await CreateAndSaveReportObjectAfterReportCreation(); await CreateAndSaveReportObjectAfterReportCreation();
OpenFolderForFileInFileViewer(outputPDFFileName); OpenFolderForFileInFileViewer(outputPDFFileName);
IsCreatingPDF = false; IsCreatingPDF = false;
} }
public async Task<bool> CheckIsSafeToShutdown()
{
if (!HasUnsavedWork || string.IsNullOrWhiteSpace(WorkingFolder))
{
return true;
}
else
{
var result = await DialogHost.Show(new ShutdownCheckViewModel());
if (result != null && result is ShutdownCheckOptions opt)
{
if (opt == ShutdownCheckOptions.SaveAndShutdown)
{
await SaveInterimReportInfo();
return true;
}
else if (opt == ShutdownCheckOptions.NoSaveShutdown)
{
return true;
}
else if (opt == ShutdownCheckOptions.CancelShutdown)
{
return false;
}
}
}
return false;
}
} }
@@ -1,10 +1,10 @@
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
using ReceiptPDFBuilder.Interfaces; using MayShow.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace ReceiptPDFBuilder.ViewModels namespace MayShow.ViewModels
{ {
class MainWindowViewModel : ChangeNotifier, IChangeViewModel class MainWindowViewModel : ChangeNotifier, IChangeViewModel
{ {
+58
View File
@@ -0,0 +1,58 @@
#nullable enable
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform.Storage;
using Avalonia.Themes.Fluent;
using DialogHostAvalonia;
using ImageMagick;
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
using PdfSharp.Fonts;
using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font;
using MayShow.Interfaces;
using MayShow.Models;
using MayShow.Helpers;
namespace MayShow.ViewModels;
class SettingsViewModel: ChangeNotifier
{
private Settings _previousSettings;
private Settings _settings;
public SettingsViewModel(Settings settingsToEdit)
{
_previousSettings = settingsToEdit;
_settings = new Settings
{
LastUsedPath = _previousSettings.LastUsedPath,
UseDocnetPFDImageRendering = _previousSettings.UseDocnetPFDImageRendering
};
}
public bool UseDocnetPDFImageRendering
{
get => _settings.UseDocnetPFDImageRendering;
set
{
_settings.UseDocnetPFDImageRendering = value;
NotifyPropertyChanged();
}
}
public void Cancel()
{
DialogHost.Close("DialogHost", null);
}
public void Save()
{
DialogHost.Close("DialogHost", _settings);
}
}
+29
View File
@@ -0,0 +1,29 @@
#nullable enable
using DialogHostAvalonia;
using MayShow.Models;
namespace MayShow.ViewModels;
class ShutdownCheckViewModel
{
public ShutdownCheckViewModel()
{
}
public void SaveAndShutdown()
{
DialogHost.Close("DialogHost", ShutdownCheckOptions.SaveAndShutdown);
}
public void DoNotSaveAndShutdown()
{
DialogHost.Close("DialogHost", ShutdownCheckOptions.NoSaveShutdown);
}
public void CancelShutdown()
{
DialogHost.Close("DialogHost", ShutdownCheckOptions.CancelShutdown);
}
}
@@ -1,14 +1,14 @@
using DialogHostAvalonia; using DialogHostAvalonia;
using ReceiptPDFBuilder.Helpers; using MayShow.Helpers;
using ReceiptPDFBuilder.Models; using MayShow.Models;
namespace ReceiptPDFBuilder.ViewModels namespace MayShow.ViewModels
{ {
class WarningDeleteItemModel : ChangeNotifier class WarningDeleteItemViewModel : ChangeNotifier
{ {
ReportFile _file; ReportFile _file;
public WarningDeleteItemModel(ReportFile file) public WarningDeleteItemViewModel(ReportFile file)
{ {
_file = file; _file = file;
} }
@@ -2,7 +2,7 @@
using DialogHostAvalonia; using DialogHostAvalonia;
namespace ReceiptPDFBuilder.ViewModels; namespace MayShow.ViewModels;
class WarningViewModel class WarningViewModel
{ {
@@ -3,11 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.Views.AboutView" x:Class="MayShow.Views.AboutView"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models" xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:AboutViewModel"> x:DataType="vm:AboutViewModel"
MaxWidth="450">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="4"> Spacing="4">
<TextBlock Text="MayShow" <TextBlock Text="MayShow"
@@ -15,16 +16,13 @@
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="18" FontSize="18"
FontWeight="Bold"/> FontWeight="Bold"/>
<TextBlock Text="MayShow (an intentional mispelling of 明証, pronounced may-shō, a Japanese word meaning proof, evidence, or corroboration) is a PDF report creation tool. It was built by MB for A in 2026. May the quacking of ducks always be in your favor. Thanks for using our software!" <TextBlock Text="MayShow (an intentional misspelling of 明証, pronounced may-shō, a Japanese word meaning proof, evidence, or corroboration) is a PDF report creation tool. It was built by MB for A in 2026. May the quacking of ducks always be in your favor. Thanks for using our software!"
MaxWidth="300"
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="14"/> FontSize="14"/>
<TextBlock Text="App icon made using https://gauger.me/fonticon/ with FontAwesome icon 'file-invoice-dollar' and the macOS software Icon Composer." <TextBlock Text="App icon made using https://gauger.me/fonticon/ with FontAwesome icon 'file-invoice-dollar' and the macOS software Icon Composer."
MaxWidth="300"
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="14"/> FontSize="14"/>
<TextBlock Text="Copyright 2026 - Quickity Quack Productions" <TextBlock Text="Copyright 2026 - Quickity Quack Productions"
MaxWidth="300"
TextWrapping="Wrap" TextWrapping="Wrap"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="0,4,0,4" Margin="0,4,0,4"
@@ -3,7 +3,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views namespace MayShow.Views
{ {
public partial class AboutView : UserControl public partial class AboutView : UserControl
{ {
+35
View File
@@ -0,0 +1,35 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MayShow.Views.ConfirmView"
xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:MayShow.ViewModels"
x:DataType="vm:ConfirmViewModel">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock TextAlignment="Center"
FontWeight="Bold"
FontSize="18"
Text="{Binding Title}"/>
<TextBlock TextAlignment="Center"
FontWeight="Bold"
TextWrapping="Wrap"
FontSize="14"
MaxWidth="350"
Text="{Binding Message}"/>
<StackPanel Orientation="Horizontal"
Spacing="12"
HorizontalAlignment="Right"
Margin="4">
<Button Command="{Binding Decline}"
Content="{Binding DeclineTitle}"
HorizontalAlignment="Right"/>
<Button Command="{Binding Confirm}"
Classes="accent"
Content="{Binding ConfirmTitle}"
HorizontalAlignment="Right"/>
</StackPanel>
</StackPanel>
</UserControl>
+14
View File
@@ -0,0 +1,14 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace MayShow.Views;
public partial class ConfirmView : UserControl
{
public ConfirmView()
{
this.InitializeComponent();
}
}
@@ -5,11 +5,12 @@
mc:Ignorable="d" mc:Ignorable="d"
d:DesignWidth="800" d:DesignWidth="800"
d:DesignHeight="450" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.Views.EditFile" x:Class="MayShow.Views.EditFile"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models" xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:EditFileViewModel"> x:DataType="vm:EditFileViewModel"
MaxWidth="350">
<ScrollViewer AllowAutoHide="False"> <ScrollViewer AllowAutoHide="False">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="4" Spacing="4"
@@ -33,7 +34,8 @@
<Label Content="Receipt Date" /> <Label Content="Receipt Date" />
<Calendar SelectionMode="SingleDate" <Calendar SelectionMode="SingleDate"
SelectedDate="{Binding ClonedFile.ReceiptDateTime}" SelectedDate="{Binding ClonedFile.ReceiptDateTime}"
DisplayDate="{Binding ClonedFile.ReceiptDateTime}" /> DisplayDate="{Binding ClonedFile.ReceiptDateTime}"
IsTodayHighlighted="False" />
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Spacing="12" Spacing="12"
Margin="0,4,0,0" Margin="0,4,0,0"
@@ -3,7 +3,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views namespace MayShow.Views
{ {
public partial class EditFile : UserControl public partial class EditFile : UserControl
{ {
@@ -3,24 +3,32 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.Views.MainView" x:Class="MayShow.Views.MainView"
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers" xmlns:helpers="clr-namespace:MayShow.Helpers"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models" xmlns:models="clr-namespace:MayShow.Models"
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views" xmlns:views="clr-namespace:MayShow.Views"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing" xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
x:DataType="vm:MainViewModel"> x:DataType="vm:MainViewModel">
<Grid ColumnDefinitions="*" <Grid ColumnDefinitions="*"
RowDefinitions="Auto, 2*, Auto, Auto, *"> RowDefinitions="Auto, 2*, Auto, Auto, *">
<Button Command="{Binding ShowSettings}"
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="4,4,0,4">
<TextBlock><Run Text="&#xf013;" FontFamily="{StaticResource FontAwesomeSolid}"/> Settings</TextBlock>
</Button>
<Button Command="{Binding ShowAbout}" <Button Command="{Binding ShowAbout}"
Grid.Row="0" Grid.Row="0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Top" VerticalAlignment="Top"
Margin="0,2,4,0"> Margin="0,4,4,4">
<TextBlock><Run Text="&#xf059;" FontFamily="{StaticResource FontAwesomeSolid}"/> About</TextBlock> <TextBlock><Run Text="&#xf059;" FontFamily="{StaticResource FontAwesomeSolid}"/> About</TextBlock>
</Button> </Button>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="2"> Spacing="2"
Margin="0,4,0,0">
<Label Content="MayShow: Report Builder" <Label Content="MayShow: Report Builder"
FontSize="20" FontSize="20"
FontWeight="Bold" FontWeight="Bold"
@@ -167,7 +175,7 @@
Margin="2" Margin="2"
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"> IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}">
<Button.Content> <Button.Content>
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock> <TextBlock><Run Text="&#xf044;" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock>
</Button.Content> </Button.Content>
</Button> </Button>
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).RemoveFile}" <Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).RemoveFile}"
@@ -223,27 +231,31 @@
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Spacing="4"> Spacing="4">
<Button Command="{Binding AddItem}" <Button Command="{Binding AddItem}"
IsEnabled="{Binding !IsCreatingPDF}"> IsEnabled="{Binding CanAddItem}">
<TextBlock><Run Text="&#x002b;" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item</TextBlock> <TextBlock><Run Text="&#x002b;" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item(s)</TextBlock>
</Button> </Button>
<Button Command="{Binding SaveInterimReportInfo}" <Button Command="{Binding RemoveAllItems}"
IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}"> IsEnabled="{Binding IsCreatePDFButtonEnabled}"
<TextBlock><Run Text="&#xf0c7;" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock> Classes="Danger">
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove All Items</TextBlock>
</Button> </Button>
<Button Command="{Binding ResortPDFItemsByDate}" <Button Command="{Binding ResortPDFItemsByDate}"
IsEnabled="{Binding IsCreatePDFButtonEnabled}"> IsEnabled="{Binding IsCreatePDFButtonEnabled}">
<TextBlock><Run Text="&#xf162;" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock> <TextBlock><Run Text="&#xf162;" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock>
</Button> </Button>
<Button Command="{Binding BuildPDF}" <Button Command="{Binding SaveInterimReportInfo}"
Classes="accent" IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}">
IsEnabled="{Binding IsCreatePDFButtonEnabled}"> <TextBlock><Run Text="&#xf0c7;" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
<TextBlock><Run Text="&#xf1c1;" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
IsVisible="{Binding IsCreatingPDF}"
Spacing="6" Spacing="6"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Button Command="{Binding BuildPDF}"
Classes="accent"
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
<TextBlock><Run Text="&#xf1c1;" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
</Button>
<Label Content="Creating PDF..." <Label Content="Creating PDF..."
IsVisible="{Binding IsCreatingPDF}" IsVisible="{Binding IsCreatingPDF}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -3,8 +3,9 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using MayShow.ViewModels;
namespace ReceiptPDFBuilder.Views namespace MayShow.Views
{ {
public partial class MainView : UserControl public partial class MainView : UserControl
{ {
@@ -12,6 +13,7 @@ namespace ReceiptPDFBuilder.Views
{ {
this.InitializeComponent(); this.InitializeComponent();
LogBlock.PropertyChanged += LogBlock_PropertyChanged; LogBlock.PropertyChanged += LogBlock_PropertyChanged;
FilesGrid.CellEditEnded += FileCellEditEnded;
} }
private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
@@ -26,6 +28,18 @@ namespace ReceiptPDFBuilder.Views
{ {
var topLevel = TopLevel.GetTopLevel(this); var topLevel = TopLevel.GetTopLevel(this);
topLevel?.FocusManager?.ClearFocus(); topLevel?.FocusManager?.ClearFocus();
if (DataContext is MainViewModel mvm)
{
mvm?.HasUnsavedWork = true;
}
}
private void FileCellEditEnded(object? sender, DataGridCellEditEndedEventArgs args)
{
if (args.EditAction == DataGridEditAction.Commit && DataContext is MainViewModel mvm)
{
mvm?.HasUnsavedWork = true;
}
} }
} }
} }
+40
View File
@@ -0,0 +1,40 @@
<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"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="MayShow.Views.SettingsView"
xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:MayShow.ViewModels"
x:DataType="vm:SettingsViewModel"
MaxWidth="350">
<ScrollViewer AllowAutoHide="False">
<StackPanel Orientation="Vertical"
Spacing="4"
Margin="12,4,12,0">
<Label Content="Settings"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold" />
<CheckBox IsChecked="{Binding !UseDocnetPDFImageRendering}">Use legacy PDF handling (does not work with macOS annotations)</CheckBox>
<StackPanel Orientation="Horizontal"
Spacing="12"
Margin="0,4,0,0"
HorizontalAlignment="Right">
<Button Command="{Binding Cancel}">
<TextBlock>
<Run Text="&#xf0e2;"
FontFamily="{StaticResource FontAwesomeSolid}" /> Cancel</TextBlock>
</Button>
<Button Command="{Binding Save}"
Classes="accent">
<TextBlock>
<Run Text="&#xf0c7;"
FontFamily="{StaticResource FontAwesomeSolid}" /> Save</TextBlock>
</Button>
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace MayShow.Views
{
public partial class SettingsView : UserControl
{
public SettingsView()
{
this.InitializeComponent();
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MayShow.Views.ShutdownCheckView"
xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:ShutdownCheckViewModel">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock TextAlignment="Center"
FontWeight="Bold"
FontSize="18"
Text="Warning: You have unsaved report data!"/>
<TextBlock TextAlignment="Center"
FontWeight="Bold"
TextWrapping="Wrap"
FontSize="14"
Text="Do you want to save your data before the program is closed?"/>
<StackPanel Orientation="Horizontal"
Spacing="8">
<Button Command="{Binding SaveAndShutdown}"
Classes="accent"
Content="Save Data and Close"
HorizontalAlignment="Right"
Margin="0,4,0,4"/>
<Button Command="{Binding DoNotSaveAndShutdown}"
Content="Do NOT Save Data and Close"
HorizontalAlignment="Right"
Margin="0,4,0,4"/>
<Button Command="{Binding CancelShutdown}"
Content="Cancel Program Shutdown"
HorizontalAlignment="Right"
Margin="0,4,0,4"/>
</StackPanel>
</StackPanel>
</UserControl>
+14
View File
@@ -0,0 +1,14 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace MayShow.Views;
public partial class ShutdownCheckView : UserControl
{
public ShutdownCheckView()
{
this.InitializeComponent();
}
}
@@ -3,11 +3,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.Views.WarningDeleteItem" x:Class="MayShow.Views.WarningDeleteItem"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models" xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:WarningDeleteItemModel"> x:DataType="vm:WarningDeleteItemViewModel">
<StackPanel HorizontalAlignment="Center" <StackPanel HorizontalAlignment="Center"
Margin="6" Margin="6"
Spacing="8"> Spacing="8">
@@ -3,7 +3,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views namespace MayShow.Views
{ {
public partial class WarningDeleteItem : UserControl public partial class WarningDeleteItem : UserControl
{ {
@@ -3,9 +3,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.Views.WarningView" x:Class="MayShow.Views.WarningView"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models" xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:WarningViewModel"> x:DataType="vm:WarningViewModel">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
@@ -3,7 +3,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views namespace MayShow.Views
{ {
public partial class WarningView : UserControl public partial class WarningView : UserControl
{ {
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- This manifest is used on Windows only. <!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls. Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests --> For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.1.0.0" name="ReceiptPDFBuilder.Desktop"/> <assemblyIdentity version="1.3.0.0" name="MayShow.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application> <application>