Move files for better organization

This commit is contained in:
2026-02-18 09:24:20 +09:00
parent 6e23371858
commit b197f43341
60 changed files with 32 additions and 63 deletions
+128
View File
@@ -0,0 +1,128 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ReceiptPDFBuilder.App"
xmlns:viewModels="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views"
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:behaviors="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers"
RequestedThemeVariant="Default"
Name="MayShow">
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://AvaloniaProgressRing/Styles/ProgressRing.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<dialogHostAvalonia:DialogHostStyles />
<StyleInclude Source="avares://Xaml.Behaviors.Interactions.DragAndDrop/Styles.axaml" />
<StyleInclude Source="avares://Xaml.Behaviors.Interactions.DragAndDrop.DataGrid/Styles.axaml" />
<Style Selector="DataGrid.DragAndDrop">
<Style.Resources>
<helpers:DataGridDropHandler x:Key="DataGridDropHandler" />
</Style.Resources>
<Setter
Property="RowHeaderWidth"
Value="24" />
<Setter Property="(Interaction.Behaviors)">
<BehaviorCollectionTemplate>
<BehaviorCollection>
<ContextDropBehavior Handler="{StaticResource DataGridDropHandler}" />
</BehaviorCollection>
</BehaviorCollectionTemplate>
</Setter>
</Style>
<!-- This makes only the DataGridRowHeader available for dragging, instead of making the entire row draggable -->
<!-- Which prevents a conflict between text selection in a cell and drag-and-drop -->
<Style Selector="DataGrid.DragAndDrop DataGridRowHeader">
<Setter Property="(Interaction.Behaviors)">
<BehaviorCollectionTemplate>
<BehaviorCollection>
<ContextDragBehavior HorizontalDragThreshold="3" VerticalDragThreshold="3" />
</BehaviorCollection>
</BehaviorCollectionTemplate>
</Setter>
<Setter Property="Content">
<Template>
<Image
Margin="12,0,12,0"
Width="12"
Height="12"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Image.Source>
<!-- Use your own image here, I used this: https://www.svgrepo.com/svg/347759/grabber -->
<DrawingImage Drawing="{StaticResource IconGrabber}" />
</Image.Source>
</Image>
</Template>
</Setter>
</Style>
<Style Selector="DataGrid.ItemsDragAndDrop">
<Style.Resources>
<helpers:DataGridDropHandler x:Key="DataGridDropHandler" />
</Style.Resources>
<Setter Property="(Interaction.Behaviors)">
<BehaviorCollectionTemplate>
<BehaviorCollection>
<ContextDropBehavior Handler="{StaticResource DataGridDropHandler}" />
</BehaviorCollection>
</BehaviorCollectionTemplate>
</Setter>
</Style>
<Style Selector="Button.Danger">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#b00202"/>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#bd0000"/>
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#820202"/>
</Style>
</Style>
</Application.Styles>
<Application.DataTemplates>
<DataTemplate DataType="{x:Type viewModels:MainViewModel}">
<views:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:WarningDeleteItemModel}">
<views:WarningDeleteItem/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:EditFileViewModel}">
<views:EditFile/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AboutViewModel}">
<views:AboutView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:WarningViewModel}">
<views:WarningView/>
</DataTemplate>
</Application.DataTemplates>
<Application.Resources>
<ResourceDictionary>
<helpers:DataGridDropHandler x:Key="DataGridDropHandler" />
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Light'>
<SolidColorBrush x:Key='DragDropBrush'>Black</SolidColorBrush>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<SolidColorBrush x:Key='DragDropBrush'>White</SolidColorBrush>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- IconGrabber, MIT License, author: Primer, taken from: https://www.svgrepo.com/svg/347759/grabber -->
<PathGeometry x:Key="IconGrabberGeometry">M15 18a1 1 0 100-2 1 1 0 000 2zm1-6a1 1 0 11-2 0 1 1 0 012 0zm-7 6a1 1 0 100-2 1 1 0 000 2zm0-5a1 1 0 100-2 1 1 0 000 2zm7-6a1 1 0 11-2 0 1 1 0 012 0zM9 8a1 1 0 100-2 1 1 0 000 2z</PathGeometry>
<GeometryDrawing x:Key="IconGrabber" Brush="{DynamicResource DragDropBrush}" Geometry="{StaticResource IconGrabberGeometry}" />
<FontFamily x:Key="FontAwesomeRegular">avares://MayShow/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Regular-400.otf#Font Awesome 7 Free Regular</FontFamily>
<FontFamily x:Key="FontAwesomeSolid">avares://MayShow/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Solid-900.otf#Font Awesome 7 Free Solid</FontFamily>
</ResourceDictionary>
</Application.Resources>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="About ReceiptBuilder" Click="AboutOnClick" />
</NativeMenu>
</NativeMenu.Menu>
</Application>
+33
View File
@@ -0,0 +1,33 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using DialogHostAvalonia;
using ReceiptPDFBuilder;
using ReceiptPDFBuilder.ViewModels;
namespace ReceiptPDFBuilder;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
public void AboutOnClick(object? sender, EventArgs args)
{
DialogHost.Show(new AboutViewModel());
}
}
+5
View File
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<AvaloniaVersion>11.3.12</AvaloniaVersion>
</PropertyGroup>
</Project>
+165
View File
@@ -0,0 +1,165 @@
Fonticons, Inc. (https://fontawesome.com)
--------------------------------------------------------------------------------
Font Awesome Free License
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
--------------------------------------------------------------------------------
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
The Font Awesome Free download is licensed under a Creative Commons
Attribution 4.0 International License and applies to all icons packaged
as SVG and JS file types.
--------------------------------------------------------------------------------
# Fonts: SIL OFL 1.1 License
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
Copyright (c) 2026 Fonticons, Inc. (https://fontawesome.com)
with Reserved Font Name: "Font Awesome".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
Copyright 2026 Fonticons, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
--------------------------------------------------------------------------------
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**
+93
View File
@@ -0,0 +1,93 @@
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+136
View File
@@ -0,0 +1,136 @@
Noto Sans Variable Font
=======================
This download contains Noto Sans as both variable fonts and static fonts.
Noto Sans is a variable font with these axes:
wdth
wght
This means all the styles are contained in these files:
Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf
Noto_Sans/NotoSans-Italic-VariableFont_wdth,wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Noto Sans:
Noto_Sans/static/NotoSans_ExtraCondensed-Thin.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLight.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-Light.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-Regular.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-Medium.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-SemiBold.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-Bold.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBold.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-Black.ttf
Noto_Sans/static/NotoSans_Condensed-Thin.ttf
Noto_Sans/static/NotoSans_Condensed-ExtraLight.ttf
Noto_Sans/static/NotoSans_Condensed-Light.ttf
Noto_Sans/static/NotoSans_Condensed-Regular.ttf
Noto_Sans/static/NotoSans_Condensed-Medium.ttf
Noto_Sans/static/NotoSans_Condensed-SemiBold.ttf
Noto_Sans/static/NotoSans_Condensed-Bold.ttf
Noto_Sans/static/NotoSans_Condensed-ExtraBold.ttf
Noto_Sans/static/NotoSans_Condensed-Black.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Thin.ttf
Noto_Sans/static/NotoSans_SemiCondensed-ExtraLight.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Light.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Regular.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Medium.ttf
Noto_Sans/static/NotoSans_SemiCondensed-SemiBold.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Bold.ttf
Noto_Sans/static/NotoSans_SemiCondensed-ExtraBold.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Black.ttf
Noto_Sans/static/NotoSans-Thin.ttf
Noto_Sans/static/NotoSans-ExtraLight.ttf
Noto_Sans/static/NotoSans-Light.ttf
Noto_Sans/static/NotoSans-Regular.ttf
Noto_Sans/static/NotoSans-Medium.ttf
Noto_Sans/static/NotoSans-SemiBold.ttf
Noto_Sans/static/NotoSans-Bold.ttf
Noto_Sans/static/NotoSans-ExtraBold.ttf
Noto_Sans/static/NotoSans-Black.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-ThinItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-LightItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-Italic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-MediumItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-BoldItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf
Noto_Sans/static/NotoSans_ExtraCondensed-BlackItalic.ttf
Noto_Sans/static/NotoSans_Condensed-ThinItalic.ttf
Noto_Sans/static/NotoSans_Condensed-ExtraLightItalic.ttf
Noto_Sans/static/NotoSans_Condensed-LightItalic.ttf
Noto_Sans/static/NotoSans_Condensed-Italic.ttf
Noto_Sans/static/NotoSans_Condensed-MediumItalic.ttf
Noto_Sans/static/NotoSans_Condensed-SemiBoldItalic.ttf
Noto_Sans/static/NotoSans_Condensed-BoldItalic.ttf
Noto_Sans/static/NotoSans_Condensed-ExtraBoldItalic.ttf
Noto_Sans/static/NotoSans_Condensed-BlackItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-ThinItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-LightItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-Italic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-MediumItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-BoldItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf
Noto_Sans/static/NotoSans_SemiCondensed-BlackItalic.ttf
Noto_Sans/static/NotoSans-ThinItalic.ttf
Noto_Sans/static/NotoSans-ExtraLightItalic.ttf
Noto_Sans/static/NotoSans-LightItalic.ttf
Noto_Sans/static/NotoSans-Italic.ttf
Noto_Sans/static/NotoSans-MediumItalic.ttf
Noto_Sans/static/NotoSans-SemiBoldItalic.ttf
Noto_Sans/static/NotoSans-BoldItalic.ttf
Noto_Sans/static/NotoSans-ExtraBoldItalic.ttf
Noto_Sans/static/NotoSans-BlackItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+93
View File
@@ -0,0 +1,93 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+71
View File
@@ -0,0 +1,71 @@
Noto Sans JP Variable Font
==========================
This download contains Noto Sans JP as both a variable font and static fonts.
Noto Sans JP is a variable font with this axis:
wght
This means all the styles are contained in a single file:
Noto_Sans_JP/NotoSansJP-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Noto Sans JP:
Noto_Sans_JP/static/NotoSansJP-Thin.ttf
Noto_Sans_JP/static/NotoSansJP-ExtraLight.ttf
Noto_Sans_JP/static/NotoSansJP-Light.ttf
Noto_Sans_JP/static/NotoSansJP-Regular.ttf
Noto_Sans_JP/static/NotoSansJP-Medium.ttf
Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf
Noto_Sans_JP/static/NotoSansJP-Bold.ttf
Noto_Sans_JP/static/NotoSansJP-ExtraBold.ttf
Noto_Sans_JP/static/NotoSansJP-Black.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
namespace ReceiptPDFBuilder.Helpers
{
// https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2
class ChangeNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
+55
View File
@@ -0,0 +1,55 @@
using System;
namespace ReceiptPDFBuilder.Helpers;
class Constants
{
public static string AppVersion = "1.1.0";
public static string[] GetQuotes()
{
// sources:
// https://www.reddit.com/r/dadjokes/comments/1ehid23/dads_of_reddit_whats_a_short_clean_joke_that/
// https://www.thepioneerwoman.com/home-lifestyle/a35617884/best-dad-jokes/
// https://www.today.com/life/dad-jokes-rcna27325
// https://www.microscooters.com.au/blogs/family/100-dad-jokes-that-are-the-best-worst-in-the-book?srsltid=AfmBOopcTDq26iDYUsqaTjvUcVW6yxE-u942tatHC7Arns85unMMNfEO
return [
"When in the crucible of life, always remember to take your friends with you.",
"What do you call a paper airplane that won't fly? Stationary.",
"I used to be addicted to dad jokes, but now I'm all groan up.",
"I used to have a phobia about speed bumps. But I'm slowly getting over it.",
"Be careful trusting stairs. They're always up to something.",
"What do you call a fish with no eye? A fsh",
"How do you throw a party in outer space? You planet.",
"Why don't eggs tell jokes? They might crack up!",
"What do you call a snowman with a six-pack? An abdominal snowman!",
"Why did the math book look sad? Because it had too many problems!",
"I was going to tell you a joke about time travel, but you didn't like it.",
"I'm writing a book about glue, but I'm stuck on the first chapter.",
"If two vegetarians get in an argument, is it still called beef?",
"How do you stop a bull from charging? Cancel its credit card.",
"Why do seagulls fly over the sea? If they flew over the bay, they would be bagels.",
"What vegetable is cool, but not *that* cool? Radish.",
"How you fix a broken pumpkin? With a pumpkin patch.",
"Where do boats go when they're sick? To the dock.",
"Why was the broom late to class? It over-swept.",
"Wanna hear a joke about construction? I'm still workin' on it!",
"Which state has the most streets? Rhode Island.",
"What kind of car does a sheep like to drive? A lamborghini.",
"Where do surfers go for an education? Boarding school.",
"What do you get when you cross a fish with an elephant? Swimming trunks.",
"What did one eye say to the other? “Between us, something smells.”",
"When does a joke become a dad joke? When the punchline becomes apparent.",
"What do you call a cold puppy? A chili dog.",
"Why did the spider go to school? He wanted to be a web designer.",
"I was going to tell a sodium joke, then I thought, “Na.”",
"Did you hear about the two rowboats that got into an argument? It was an oar-deal.",
"What do you call birds that stick together? Velcrows.",
"How do birds learn to fly? They wing it.",
"Humpty Dumpty had a great fall. Summer wasn't too bad either.",
"How does the moon cut his hair? Eclipse it!",
"Why did the candle quit his job? He felt burned out."
];
}
}
+34
View File
@@ -0,0 +1,34 @@
using System.Collections.ObjectModel;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.VisualTree;
using Avalonia.Xaml.Interactions.DragAndDrop;
using ReceiptPDFBuilder.Models;
using ReceiptPDFBuilder.ViewModels;
namespace ReceiptPDFBuilder.Helpers;
class DataGridDropHandler : BaseDataGridDropHandler<ReportFile>
{
// https://wieslawsoltes.github.io/Xaml.Behaviors/articles/drag-and-drop-datagrid/datagrid-drag-and-drop-overview.html
// ...that page's code basically doesn't work in the way you'd expect. so just use the source code sample's code.
protected override bool Validate(DataGrid dg, DragEventArgs e, object? sourceContext, object? targetContext, bool execute)
{
if (sourceContext is not ReportFile sourceItem
|| targetContext is not MainViewModel vm
|| dg.GetVisualAt(e.GetPosition(dg)) is not Control targetControl
|| targetControl.DataContext is not ReportFile targetItem)
{
return false;
}
var items = dg.ItemsSource as ObservableCollection<ReportFile>;
return RunDropAction(dg, e, execute, sourceItem, targetItem, items?? []);
}
protected override ReportFile MakeCopy(ObservableCollection<ReportFile> parentCollection, ReportFile item)
{
// Return a clone of the item if you support Copy operations
return new ReportFile { };
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace ReceiptPDFBuilders.Helpers;
public static class ThreadSafeRandom
{
[ThreadStatic] private static Random? Local;
public static Random ThisThreadsRandom
{
get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
}
}
// https://stackoverflow.com/a/1262619/3938401
static class ListExtensions
{
public static void Shuffle<T>(this IList<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
using ReceiptPDFBuilder.Models;
namespace ReceiptPDFBuilder.Helpers;
[JsonSerializable(typeof(Settings))]
[JsonSerializable(typeof(ReportFile))]
[JsonSerializable(typeof(PDFReport))]
internal partial class SourceGenerationContext : JsonSerializerContext { }
+38
View File
@@ -0,0 +1,38 @@
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace ReceiptPDFBuilders.Helpers;
class Utilities
{
public static JsonSerializerOptions GetSerializerOptions()
{
var opts = new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
return opts;
}
public static DateOnly? CheckValidDateInString(string str)
{
// https://stackoverflow.com/a/14918404/3938401
var rgx = new Regex(@"\d{4}-\d{2}-\d{2}");
var mat = rgx.Match(str);
if (mat.Success)
{
var dtStr = mat.ToString();
string[] formats = ["yyyy-MM-dd"];
DateTime parsedDateTime;
var didWork = DateTime.TryParseExact(dtStr, formats, CultureInfo.InvariantCulture,
DateTimeStyles.None, out parsedDateTime);
return didWork ? DateOnly.FromDateTime(parsedDateTime) : null;
}
return null;
}
}
+10
View File
@@ -0,0 +1,10 @@
using ReceiptPDFBuilder.ViewModels;
namespace ReceiptPDFBuilder.Interfaces
{
interface IChangeViewModel
{
void PushViewModel(BaseViewModel model);
void PopViewModel();
}
}
+9
View File
@@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace ReceiptPDFBuilder.Interfaces
{
interface ITopLevelGrabber
{
TopLevel GetTopLevel();
}
}
+23
View File
@@ -0,0 +1,23 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReceiptPDFBuilder.MainWindow"
Title="MayShow"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:MainWindowViewModel"
Width="800"
MinWidth="550"
Height="650"
MinHeight="550">
<dialogHost:DialogHost CloseOnClickAway="False"
Identifier="DialogHost">
<dialogHost:DialogHost.DialogContent>
<StackPanel/>
</dialogHost:DialogHost.DialogContent>
<!-- put the content over which the dialog is shown here (e.g. your main window grid)-->
<ContentControl Content="{Binding CurrentViewModel}"/>
</dialogHost:DialogHost>
</Window>
+19
View File
@@ -0,0 +1,19 @@
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;
}
}
+58
View File
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using ReceiptPDFBuilder.Helpers;
namespace ReceiptPDFBuilder.Models;
class PDFReport : ChangeNotifier
{
private string _baseFolder;
private string _title;
private List<ReportFile> _files;
private DateTime _lastSaved;
private DateTime? _lastGenerated;
public PDFReport()
{
_baseFolder = "";
_title = "";
_files = [];
_lastSaved = DateTime.Now;
_lastGenerated = null;
}
public string BaseFolder
{
get => _baseFolder;
set { _baseFolder = value; NotifyPropertyChanged(); }
}
public string Title
{
get => _title;
set { _title = value; NotifyPropertyChanged(); }
}
public List<ReportFile> Files
{
get => _files;
set { _files = value; NotifyPropertyChanged(); }
}
public DateTime LastSaved
{
get => _lastSaved;
set { _lastSaved = value; NotifyPropertyChanged(); }
}
public DateTime? LastGenerated
{
get => _lastGenerated;
set { _lastGenerated = value; NotifyPropertyChanged(); }
}
}
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.IO;
using System.Text.Json.Serialization;
using ReceiptPDFBuilder.Helpers;
namespace ReceiptPDFBuilder.Models;
class ReportFile : ChangeNotifier
{
private string _title;
private DateTime _receiptDateTime;
private string _notes;
private string _filePath;
public ReportFile()
{
_title = "";
_receiptDateTime = DateTime.Now;
_notes = "";
_filePath = "";
}
public ReportFile(ReportFile other)
{
Title = _title = other.Title;
ReceiptDateTime = _receiptDateTime = other.ReceiptDateTime;
Notes = _notes = other.Notes;
FilePath = _filePath = other.FilePath;
}
public string Title
{
get => _title;
set { _title = value; NotifyPropertyChanged(); }
}
public DateTime ReceiptDateTime
{
get => _receiptDateTime;
set
{
_receiptDateTime = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(ReceiptDate));
}
}
[JsonIgnore]
public DateOnly ReceiptDate
{
get => DateOnly.FromDateTime(_receiptDateTime);
}
public string Notes
{
get => _notes;
set { _notes = value; NotifyPropertyChanged(); }
}
public string FilePath
{
get => _filePath;
set
{
_filePath = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(FileName));
NotifyPropertyChanged(nameof(IsFileFoundOnDisk));
}
}
[JsonIgnore]
public string FileName
{
get => Path.GetFileName(_filePath);
}
[JsonIgnore]
public bool IsFileFoundOnDisk
{
get => File.Exists(FilePath);
}
}
+77
View File
@@ -0,0 +1,77 @@
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilders.Helpers;
namespace ReceiptPDFBuilder.Models;
class Settings : ChangeNotifier
{
private string _lastUsedPath;
public Settings()
{
_lastUsedPath = "";
}
[JsonInclude]
public string LastUsedPath
{
get => _lastUsedPath;
set { _lastUsedPath = value; NotifyPropertyChanged(); }
}
public static string GetSettingsFileName()
{
return "settings.json";
}
public static string GetSettingsPath()
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ReceiptPDFBuilder"
);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return Path.Combine(path, GetSettingsFileName());
}
public async Task<string> SaveSettingsAsync()
{
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
using MemoryStream memoryStream = new MemoryStream();
await JsonSerializer.SerializeAsync(memoryStream, this, jsonContext.Settings);
memoryStream.Position = 0;
using var reader = new StreamReader(memoryStream);
var json = await reader.ReadToEndAsync();
await File.WriteAllTextAsync(GetSettingsPath(), json);
return json;
}
public static Settings LoadSettings()
{
var path = GetSettingsPath();
if (!File.Exists(path))
{
return new Settings();
}
var json = File.ReadAllText(GetSettingsPath());
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
return JsonSerializer.Deserialize<Settings>(json, jsonContext.Settings) ?? new Settings();
}
public static async Task<Settings> LoadSettingsAsync()
{
using FileStream fileStream = File.OpenRead(GetSettingsPath());
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
var output = await JsonSerializer.DeserializeAsync<Settings>(fileStream, jsonContext.Settings) ?? new Settings();
return output;
}
}
+21
View File
@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace ReceiptPDFBuilder;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

+55
View File
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<PublishAot>true</PublishAot>
<AssemblyName>MayShow</AssemblyName>
<AssemblyVersion>1.1.0</AssemblyVersion> <!-- Also update Constants version -->
<ApplicationIcon>ReceiptPDFBuilder-icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="PdfSharp" />
<TrimmerRootAssembly Include="Avalonia.Controls.DataGrid" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Fonts\Noto_Sans\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Fonts\Noto_Sans\static\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Fonts\Noto_Sans_JP\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\Fonts\Noto_Sans_JP\static\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="PDFsharp-MigraDoc" Version="6.2.3" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.2" />
<PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" />
<PackageReference Include="DialogHost.Avalonia" Version="0.10.4" />
<PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" />
</ItemGroup>
</Project>
+33
View File
@@ -0,0 +1,33 @@
#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 ReceiptPDFBuilder.Interfaces;
using ReceiptPDFBuilder.Models;
namespace ReceiptPDFBuilder.ViewModels;
class AboutViewModel
{
public AboutViewModel()
{
}
public void Close()
{
DialogHost.Close("DialogHost", null);
}
}
+47
View File
@@ -0,0 +1,47 @@
using Avalonia.Controls;
using ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilder.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace ReceiptPDFBuilder.ViewModels
{
class BaseViewModel : ChangeNotifier
{
IChangeViewModel _viewModelChanger;
ITopLevelGrabber? _topLevelGrabber;
public BaseViewModel(IChangeViewModel viewModelChanger)
{
_viewModelChanger = viewModelChanger;
_topLevelGrabber = null;
}
public ITopLevelGrabber? TopLevelGrabber
{
get => _topLevelGrabber;
set { _topLevelGrabber = value; }
}
public IChangeViewModel ViewModelChanger
{
get { return _viewModelChanger; }
set { _viewModelChanger = value; }
}
#region IChangeViewModel
public void PopViewModel()
{
_viewModelChanger?.PopViewModel();
}
public void PushViewModel(BaseViewModel model)
{
_viewModelChanger?.PushViewModel(model);
}
#endregion
}
}
+53
View File
@@ -0,0 +1,53 @@
#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 ReceiptPDFBuilder.Interfaces;
using ReceiptPDFBuilder.Models;
namespace ReceiptPDFBuilder.ViewModels;
class EditFileViewModel : BaseViewModel
{
ReportFile _clonedFile;
ReportFile _originalFile;
public EditFileViewModel(ReportFile file, IChangeViewModel viewModelChanger) : base(viewModelChanger)
{
_clonedFile = new ReportFile(file);
_originalFile = file;
}
public ReportFile OriginalFile
{
get => _originalFile;
}
public ReportFile ClonedFile
{
get => _clonedFile;
}
public void Cancel()
{
DialogHost.Close("DialogHost", null);
}
public void Save()
{
DialogHost.Close("DialogHost", ClonedFile);
}
}
+701
View File
@@ -0,0 +1,701 @@
#nullable enable
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
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 ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilder.Interfaces;
using ReceiptPDFBuilder.Models;
using ReceiptPDFBuilders.Helpers;
namespace ReceiptPDFBuilder.ViewModels;
class MainViewModel : BaseViewModel, IFontResolver
{
private string _processDir;
private bool _isCreatingPDF;
private string _createPDFLog;
private string _workingFolder;
private string _reportTitle;
private ObservableCollection<ReportFile> _reportFiles;
private DateTime? _lastGeneratedTime;
private Settings _settings;
public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger)
{
_processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
Console.WriteLine("Process is running from: {0}", _processDir);
_isCreatingPDF = false;
var quotes = Constants.GetQuotes();
Random random = new Random();
var quoteIndex = random.Next(0, quotes.Length);
_createPDFLog = "----- MayShow v" + Constants.AppVersion + "------" + Environment.NewLine;
_createPDFLog += quotes[quoteIndex] + Environment.NewLine;
_createPDFLog += "---------------------------------------" + Environment.NewLine;
_createPDFLog += "Ready to create PDF!";
_workingFolder = "";
_reportFiles = new ObservableCollection<ReportFile>();
_reportFiles.CollectionChanged += ( sender, e ) => { NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); };
_reportTitle = "";
_lastGeneratedTime = null;
_settings = Settings.LoadSettings();
if (!string.IsNullOrWhiteSpace(_settings.LastUsedPath))
{
LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath);
ScanFolder(_settings.LastUsedPath);
}
}
public string ReportTitle
{
get => _reportTitle;
set { _reportTitle = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsTitleBoxVisible)); }
}
public bool IsTitleBoxVisible
{
get => !string.IsNullOrWhiteSpace(_workingFolder);
}
public bool IsCreatingPDF
{
get => _isCreatingPDF;
set
{
_isCreatingPDF = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
}
}
public bool IsCreatePDFButtonEnabled
{
get => !_isCreatingPDF && _reportFiles.Count > 0;
}
public bool HasWorkingFolder
{
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder);
}
public bool HasWorkingFolderAndNotMakingPDF
{
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder) && !_isCreatingPDF;
}
public string WorkingFolder
{
get => _workingFolder;
set
{
_workingFolder = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(HasWorkingFolder));
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
}
}
public string CreatePDFLog
{
get => _createPDFLog;
set { _createPDFLog = value; NotifyPropertyChanged(); }
}
public ObservableCollection<ReportFile> ReportFiles
{
get => _reportFiles;
set
{
_reportFiles = value;
NotifyPropertyChanged();
_reportFiles.CollectionChanged += ( sender, e ) =>
{
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
};
}
}
private void LogInfo(string message, params object[]? arguments)
{
var timestamp = string.Format("[{0:s}]", DateTime.Now);
Console.WriteLine(timestamp + " " + message, arguments);
CreatePDFLog += Environment.NewLine + string.Format(message, arguments ?? []);
}
public async void ChooseFolder()
{
var topLevel = TopLevelGrabber?.GetTopLevel();
if (topLevel is not null)
{
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
{
Title = "Pick a folder of files...",
AllowMultiple = false,
});
if (folders.Count == 1)
{
var folder = folders[0];
LogInfo("Clearing existing list and loading items in folder: " + folder.Path.LocalPath);
ReportFiles.Clear();
ScanFolder(folder.Path.LocalPath);
_settings.LastUsedPath = folder.Path.LocalPath;
await _settings.SaveSettingsAsync();
ResortPDFItemsByDate();
}
}
}
private void ScanFolder(string path)
{
if (Directory.Exists(path))
{
WorkingFolder = path;
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
var reportFilePath = Path.Combine(path, GetReportSavedDataFileName());
var successfullyLoadedPriorReport = false;
if (File.Exists(reportFilePath))
{
// load prior report
var json = File.ReadAllText(reportFilePath);
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
var report = JsonSerializer.Deserialize<PDFReport>(json, jsonContext.PDFReport);
if (report != null && report.Files.Count > 0)
{
ReportFiles = new ObservableCollection<ReportFile>(report.Files);
ReportTitle = report.Title;
WorkingFolder = report.BaseFolder;
_lastGeneratedTime = report.LastGenerated ?? null;
LogInfo("Reloaded report last saved at {0}", report.LastSaved);
successfullyLoadedPriorReport = true;
}
}
if (!successfullyLoadedPriorReport)
{
// Scan folder for files and display in DataGrid
ReportFiles.Clear();
ReportTitle = "";
var filePaths = Directory.GetFiles(_workingFolder);
foreach (var filePath in filePaths)
{
AddFileBasedOnPath(filePath);
}
ResortPDFItemsByDate();
}
}
else
{
LogInfo("Error: The directory {0} does not exist. Please select another folder.", path);
}
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
}
public void ShowAbout()
{
DialogHost.Show(new AboutViewModel());
}
public void RemoveFile(object f) => RemoveFileImpl((ReportFile)f);
public async void RemoveFileImpl(ReportFile file)
{
var result = await DialogHost.Show(new WarningDeleteItemModel(file));
if (result != null && (bool)result)
{
var idx = ReportFiles.IndexOf(file);
if (idx != -1)
{
ReportFiles.RemoveAt(idx);
}
}
}
// https://github.com/AvaloniaUI/Avalonia/issues/10075
public void EditFileProperties(object f) => EditFilePropertiesImpl((ReportFile)f);
public async void EditFilePropertiesImpl(ReportFile file)
{
var result = await DialogHost.Show(new EditFileViewModel(file, ViewModelChanger));
if (result != null && result is ReportFile updatedData)
{
file.Title = updatedData.Title;
file.ReceiptDateTime = updatedData.ReceiptDateTime;
file.Notes = updatedData.Notes;
}
}
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()
{
var topLevel = TopLevelGrabber?.GetTopLevel();
if (topLevel is not null)
{
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
{
Title = "Choose image or PDF files...",
AllowMultiple = true,
FileTypeFilter = [
new FilePickerFileType("All Types")
{
Patterns = GetAllowedFileExtensionPatterns(),
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
},
FilePickerFileTypes.ImageAll,
new FilePickerFileType("HEIC Images")
{
Patterns = [ "*.heic" ],
AppleUniformTypeIdentifiers = [ "public.heic" ],
MimeTypes = [ "image/heic" ]
},
FilePickerFileTypes.Pdf,
],
});
if (files.Count > 0)
{
foreach (var file in files)
{
var filePath = file.TryGetLocalPath();
AddFileBasedOnPath(filePath);
}
}
}
}
private void AddFileBasedOnPath(string? filePath)
{
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store"))
{
// make sure extensions are OK
var fileExtensions = GetAllowedFileExtensionPatternsWithoutStar();
var didMatch = false;
foreach (var fileExtension in fileExtensions)
{
if (filePath.ToLower().EndsWith("." + fileExtension.ToLower()))
{
didMatch = true;
break;
}
}
if (!didMatch)
{
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath);
}
else
{
var date = Utilities.CheckValidDateInString(filePath);
ReportFiles.Add(new ReportFile()
{
Title = Path.GetFileName(filePath),
ReceiptDateTime = date.HasValue ? date.Value.ToDateTime(TimeOnly.MinValue) : File.GetCreationTime(filePath),
Notes = "",
FilePath = filePath,
});
}
}
}
public void LocateFile(object f) => LocateFileImpl((ReportFile) f);
public async void LocateFileImpl(ReportFile reportFile)
{
var topLevel = TopLevelGrabber?.GetTopLevel();
if (topLevel is not null)
{
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
{
Title = "Choose image or PDF file...",
AllowMultiple = false,
FileTypeFilter = [
new FilePickerFileType("All Types")
{
Patterns = GetAllowedFileExtensionPatterns(),
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
},
FilePickerFileTypes.ImageAll,
new FilePickerFileType("HEIC Images")
{
Patterns = [ "*.heic" ],
AppleUniformTypeIdentifiers = [ "public.heic" ],
MimeTypes = [ "image/heic" ]
},
FilePickerFileTypes.Pdf,
],
});
if (files.Count > 0)
{
var file = files[0];
reportFile.FilePath = file.Path.LocalPath;
}
}
}
// https://github.com/AvaloniaUI/Avalonia/issues/10075
public void OpenFile(object f) => OpenFileImpl((ReportFile)f);
public void OpenFileImpl(ReportFile file)
{
var topLevel = TopLevelGrabber?.GetTopLevel();
if (topLevel is not null)
{
var launcher = topLevel.Launcher;
launcher.LaunchUriAsync(new Uri(file.FilePath));
}
}
public void OpenFileLocation(object f) => OpenFileLocationImpl((ReportFile)f);
private void OpenFileLocationImpl(ReportFile file)
{
OpenFolderForFileInFileViewer(file.FilePath);
}
private void OpenFolderForFileInFileViewer(string fullPathToFile)
{
var topLevel = TopLevelGrabber?.GetTopLevel();
var dirName = Path.GetDirectoryName(fullPathToFile);
if (topLevel is not null && dirName != null)
{
var launcher = topLevel.Launcher;
launcher.LaunchUriAsync(new Uri(dirName));
}
}
public void ResortPDFItemsByDate()
{
LogInfo("Sorting report files list...");
ReportFiles = new ObservableCollection<ReportFile>(ReportFiles.OrderBy(x => x.ReceiptDateTime));
}
public async void BuildPDF()
{
if (string.IsNullOrWhiteSpace(ReportTitle))
{
await DialogHost.Show(new WarningViewModel("You must provide a report title!"));
}
else
{
try
{
await Task.Run(() => CreatePDF(_workingFolder));
} catch (Exception e)
{
LogInfo("PDF process failed! Reason: " + e.Message);
if (e.StackTrace != null)
{
LogInfo(e.StackTrace);
}
if (e.InnerException != null)
{
LogInfo("Inner exception: " + e.InnerException.Message);
if (e.InnerException.StackTrace != null)
{
LogInfo(e.InnerException.StackTrace);
}
}
LogInfo("Please report this error to a programmer or fix the issue listed above.");
IsCreatingPDF = false;
}
}
}
public async void SaveInterimReportInfo()
{
var report = new PDFReport()
{
Title = ReportTitle,
Files = ReportFiles.ToList(),
BaseFolder = _workingFolder,
LastSaved = DateTime.Now,
LastGenerated = _lastGeneratedTime,
};
await SavePDFReportDataToDisk(report);
}
private async Task SavePDFReportDataToDisk(PDFReport report)
{
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
using var memoryStream = new MemoryStream();
await JsonSerializer.SerializeAsync(memoryStream, report, jsonContext.PDFReport);
memoryStream.Position = 0;
using var reader = new StreamReader(memoryStream);
var json = await reader.ReadToEndAsync();
var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName());
await File.WriteAllTextAsync(savePath, json);
LogInfo("Saved report information to {0}", savePath);
}
private async Task CreateAndSaveReportObjectAfterReportCreation()
{
var report = new PDFReport()
{
Title = ReportTitle,
Files = ReportFiles.ToList(),
BaseFolder = _workingFolder,
LastSaved = DateTime.Now,
LastGenerated = DateTime.Now,
};
_lastGeneratedTime = DateTime.Now;
await SavePDFReportDataToDisk(report);
}
private string GetReportSavedDataFileName()
{
return "report_data.json";
}
public byte[]? GetFont(string faceName)
{
LogInfo(string.Format("Loading font {0}", faceName));
if (faceName == "Noto Sans JP")
{
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
if (!File.Exists(path))
{
path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
}
return File.ReadAllBytes(path);
}
if (faceName == "Noto Sans JP Bold")
{
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
if (!File.Exists(path))
{
path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
}
return File.ReadAllBytes(path);
}
return null;
}
public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic)
{
// LogInfo(string.Format("Resolving font name {0}", familyName));
if (familyName == "Noto Sans JP")
{
if (bold)
{
return new FontResolverInfo(familyName + " Bold");
}
return new FontResolverInfo(familyName);
}
return null;
}
// https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025
private async Task CreatePDF(string folderPath)
{
// TODO: calculate needed width for images based on page width and margins and all that?
// TODO: resize (non-HEIC) images for smaller size...?
IsCreatingPDF = true;
var pdfDoc = new Document();
var outputFileName = ReportTitle + ".pdf";
var folderName = new DirectoryInfo(folderPath).Name;
const int imageWidth = 425;
if (folderName.Contains('-'))
{
// see if year/month format
var parts = folderName.Split('-');
if (parts[0].Length == 4 &&
parts[1].Length <= 2 &&
int.TryParse(parts[0], out int year) && int.TryParse(parts[1], out int month))
{
outputFileName = string.Format("{0} {1} Receipts.pdf",
CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month),
year);
LogInfo("Auto-changed output file name to " + outputFileName);
}
}
var section = pdfDoc.AddSection();
section.PageSetup.PageFormat = PageFormat.Letter;
section.PageSetup.PageWidth = "8.5in";
section.PageSetup.PageHeight = "11in";
section.PageSetup.TopMargin = "0.5in";
section.PageSetup.RightMargin = "0.5in";
section.PageSetup.BottomMargin = "0.5in";
section.PageSetup.LeftMargin = "0.5in";
// setup footer for page number
var footerPar = new Paragraph();
footerPar.Format.Alignment = ParagraphAlignment.Center;
footerPar.Format.Font.Size = 10;
footerPar.AddText("--Page ");
footerPar.AddPageField();
footerPar.AddText(" of ");
footerPar.AddNumPagesField();
footerPar.AddText("--");
footerPar.AddLineBreak();
footerPar.AddText("Report generated on " + DateTime.Now.ToString("f"));
section.Footers.Primary.Add(footerPar);
// add report title
var reportTitlePar = section.AddParagraph();
reportTitlePar.Format.Alignment = ParagraphAlignment.Center;
reportTitlePar.Format.Font.Size = 16;
reportTitlePar.Format.Font.Bold = true;
reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
reportTitlePar.AddText(ReportTitle);
//
GlobalFontSettings.FontResolver = this;
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
var hasAddedData = false;
for (var i = 0; i < ReportFiles.Count; i++)
{
var file = ReportFiles[i];
var fileName = file.FileName;
var filePath = file.FilePath;
if (!File.Exists(filePath))
{
LogInfo("ERROR: File \"{0}\" does not exist at path \"{1}\". Please remove it from the report or re-add it using the Add Item button if you still want it to be in this report.", file.Title, file.FilePath);
IsCreatingPDF = false;
return;
}
if (fileName == ".DS_Store" || fileName == outputFileName)
{
continue;
}
if (i > 0 && hasAddedData)
{
section.AddPageBreak();
}
var imageTitlePar = section.AddParagraph();
imageTitlePar.Format.Alignment = ParagraphAlignment.Center;
imageTitlePar.Format.Font.Size = 12;
imageTitlePar.Format.Font.Bold = true;
imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
if (string.IsNullOrWhiteSpace(file.Title))
{
imageTitlePar.AddText(file.FileName);
}
else
{
imageTitlePar.AddText(file.Title);
}
var receiptDatePar = section.AddParagraph();
receiptDatePar.Format.Alignment = ParagraphAlignment.Center;
receiptDatePar.Format.Font.Size = 12;
receiptDatePar.Format.Font.Bold = true;
receiptDatePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
receiptDatePar.AddText(file.ReceiptDate.ToString("yyyy-MM-dd"));
if (!string.IsNullOrWhiteSpace(file.Notes))
{
var imageNotesPar = section.AddParagraph();
imageNotesPar.Format.Alignment = ParagraphAlignment.Center;
imageNotesPar.Format.Font.Size = 10;
imageNotesPar.Format.Font.Bold = false;
imageNotesPar.Format.Font.Name = "Noto Sans JP";
imageNotesPar.AddText(file.Notes);
}
section.AddParagraph(); // add empty line for spacing
// now add the image
var lowerName = fileName.ToLower();
var isPDF = lowerName.EndsWith(".pdf");
// convert heic, webp, or png to JPEG for size and ease of use
// (and probably compat reasons too, though I haven't tested that...)
var isHEIC = lowerName.EndsWith(".heic");
var isWebp = lowerName.EndsWith(".webp");
var isPNG = lowerName.EndsWith(".png");
var info = new FileInfo(file.FilePath);
uint loadedImageWidth = 0;
uint loadedImageHeight = 0;
if (isHEIC || isWebp || isPNG)
{
// 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);
loadedImageWidth = mImage.Width;
loadedImageHeight = mImage.Height;
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);
}
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
{
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
}
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();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
image = paragraph.AddImage(filePath + "#" + j);
image.LockAspectRatio = true;
image.Width = imageWidth;
}
}
hasAddedData = true;
}
var pdfRenderer = new PdfDocumentRenderer
{
Document = pdfDoc,
WorkingDirectory = folderPath
};
LogInfo("Rendering document...");
pdfRenderer.RenderDocument();
string outputPDFFileName = Path.Join(folderPath, outputFileName);
LogInfo("Saving document to disk...");
pdfRenderer.PdfDocument.Save(outputPDFFileName);
LogInfo("Saved PDF output to: " + outputPDFFileName);
await CreateAndSaveReportObjectAfterReportCreation();
OpenFolderForFileInFileViewer(outputPDFFileName);
IsCreatingPDF = false;
}
}
+50
View File
@@ -0,0 +1,50 @@
using ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilder.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace ReceiptPDFBuilder.ViewModels
{
class MainWindowViewModel : ChangeNotifier, IChangeViewModel
{
BaseViewModel _currentViewModel;
Stack<BaseViewModel> _viewModels;
public MainWindowViewModel(ITopLevelGrabber topLevelGrabber)
{
_viewModels = new Stack<BaseViewModel>();
var initialViewModel = new MainViewModel(this)
{
TopLevelGrabber = topLevelGrabber
};
_viewModels.Push(initialViewModel);
_currentViewModel = initialViewModel;
}
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; NotifyPropertyChanged(); }
}
#region IChangeViewModel
public void PushViewModel(BaseViewModel model)
{
_viewModels.Push(model);
CurrentViewModel = model;
}
public void PopViewModel()
{
if (_viewModels.Count > 1)
{
_viewModels.Pop();
CurrentViewModel = _viewModels.Peek();
}
}
#endregion
}
}
+31
View File
@@ -0,0 +1,31 @@
using DialogHostAvalonia;
using ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilder.Models;
namespace ReceiptPDFBuilder.ViewModels
{
class WarningDeleteItemModel : ChangeNotifier
{
ReportFile _file;
public WarningDeleteItemModel(ReportFile file)
{
_file = file;
}
public ReportFile File
{
get => _file;
}
public void KeepItem()
{
DialogHost.Close("DialogHost", false);
}
public void RemoveItem()
{
DialogHost.Close("DialogHost", true);
}
}
}
+25
View File
@@ -0,0 +1,25 @@
#nullable enable
using DialogHostAvalonia;
namespace ReceiptPDFBuilder.ViewModels;
class WarningViewModel
{
private string _error;
public WarningViewModel(string error)
{
_error = error;
}
public string Error
{
get => _error;
}
public void Close()
{
DialogHost.Close("DialogHost", null);
}
}
+38
View File
@@ -0,0 +1,38 @@
<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="ReceiptPDFBuilder.Views.AboutView"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:AboutViewModel">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="MayShow"
HorizontalAlignment="Center"
TextWrapping="Wrap"
FontSize="18"
FontWeight="Bold"/>
<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"
FontSize="14"/>
<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"
FontSize="14"/>
<TextBlock Text="Copyright 2026 - Quickity Quack Productions"
MaxWidth="300"
TextWrapping="Wrap"
HorizontalAlignment="Center"
Margin="0,4,0,4"
FontSize="12"/>
<Button Command="{Binding Close}"
Classes="accent"
Content="Close"
HorizontalAlignment="Right"
Margin="0,0,4,4"/>
</StackPanel>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views
{
public partial class AboutView : UserControl
{
public AboutView()
{
this.InitializeComponent();
}
}
}
+55
View File
@@ -0,0 +1,55 @@
<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="ReceiptPDFBuilder.Views.EditFile"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:EditFileViewModel">
<ScrollViewer AllowAutoHide="False">
<StackPanel Orientation="Vertical"
Spacing="4"
Margin="12,4,12,0">
<Label Content="Edit File Details"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold" />
<Label Content="Title" />
<TextBox Watermark="Title"
Text="{Binding ClonedFile.Title}"
VerticalAlignment="Stretch"
TextWrapping="Wrap" />
<Label Content="Notes" />
<TextBox Watermark="Notes"
Text="{Binding ClonedFile.Notes}"
VerticalAlignment="Stretch"
AcceptsReturn="True"
ScrollViewer.AllowAutoHide="False"
Height="75" />
<Label Content="Receipt Date" />
<Calendar SelectionMode="SingleDate"
SelectedDate="{Binding ClonedFile.ReceiptDateTime}"
DisplayDate="{Binding ClonedFile.ReceiptDateTime}" />
<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 ReceiptPDFBuilder.Views
{
public partial class EditFile : UserControl
{
public EditFile()
{
this.InitializeComponent();
}
}
}
+274
View File
@@ -0,0 +1,274 @@
<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="ReceiptPDFBuilder.Views.MainView"
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
x:DataType="vm:MainViewModel">
<Grid ColumnDefinitions="*"
RowDefinitions="Auto, 2*, Auto, Auto, *">
<Button Command="{Binding ShowAbout}"
Grid.Row="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,2,4,0">
<TextBlock><Run Text="&#xf059;" FontFamily="{StaticResource FontAwesomeSolid}"/> About</TextBlock>
</Button>
<StackPanel Orientation="Vertical"
Spacing="2">
<Label Content="MayShow: Report Builder"
FontSize="20"
FontWeight="Bold"
HorizontalAlignment="Center"/>
<Grid ColumnDefinitions="Auto, *"
Margin="4,0,0,0">
<Button Content="Choose Receipt Folder"
Command="{Binding ChooseFolder}"
IsEnabled="{Binding !IsCreatingPDF}"
Grid.Column="0" />
<TextBlock Text="{Binding WorkingFolder}"
VerticalAlignment="Center"
TextWrapping="NoWrap"
Margin="4,0,4,0"
TextTrimming="PrefixCharacterEllipsis"
Grid.Column="1"/>
</Grid>
<Label Content="Report Title"
IsVisible="{Binding IsTitleBoxVisible}" />
<TextBox Text="{Binding ReportTitle}"
IsVisible="{Binding IsTitleBoxVisible}"
Watermark="Receipts December 2024"
Margin="2,0,2,4"
Classes="clearButton"
Name="TitleTextBox">
<TextBox.KeyBindings>
<KeyBinding Command="{Binding $parent[views:MainView].UnfocusTextbox}" Gesture="Enter" />
</TextBox.KeyBindings>
</TextBox>
</StackPanel>
<DataGrid x:Name="FilesGrid"
Classes="DragAndDrop ItemsDragAndDrop"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="2"
ItemsSource="{Binding ReportFiles}"
AutoGenerateColumns="False"
IsReadOnly="False"
GridLinesVisibility="All"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="False"
BorderThickness="1"
VerticalScrollBarVisibility="Visible"
ScrollViewer.AllowAutoHide="False"
HorizontalScrollBarVisibility="Disabled"
HeadersVisibility="All"
BorderBrush="Gray">
<DataGrid.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="NoWrap" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>
<Style Selector="TextBox">
<Setter Property="TextWrapping" Value="NoWrap" />
</Style>
<Style Selector="ToolTip">
<Setter Property="MaxWidth" Value="1000" />
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Title"
IsReadOnly="False"
Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid ColumnDefinitions="Auto, *">
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).LocateFile}"
CommandParameter="{Binding}"
IsVisible="{Binding !IsFileFoundOnDisk}"
Margin="2"
Content="&#xf071;"
VerticalContentAlignment="Center"
Background="Transparent"
Grid.Column="0"
FontFamily="{StaticResource FontAwesomeSolid}"
ToolTip.Tip="File not found; click to locate..."
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"/>
<TextBlock Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
ToolTip.Tip="{Binding Title}"
Grid.Column="1"
Margin="8,0,4,0"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="models:ReportFile">
<TextBox Text="{Binding Title}"
Watermark="Title"
ToolTip.Tip="{Binding Title}"
Classes="clearButton">
<TextBox.KeyBindings>
<KeyBinding Command="{Binding $parent[views:MainView].UnfocusTextbox}" Gesture="Enter" />
</TextBox.KeyBindings>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Receipt Date"
IsReadOnly="False"
Width="125">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding ReceiptDate}"
VerticalAlignment="Center"
Margin="8,0,8,0"
HorizontalAlignment="Left"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="models:ReportFile">
<CalendarDatePicker SelectedDate="{Binding ReceiptDateTime}"
DisplayDate="{Binding ReceiptDateTime}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="File Name"
IsReadOnly="True"
Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FileName}"
VerticalAlignment="Center"
ToolTip.Tip="{Binding FileName}"
Margin="8,0,8,0"
HorizontalAlignment="Left"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header=""
IsReadOnly="True"
Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Spacing="4">
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).EditFileProperties}"
CommandParameter="{Binding}"
Classes="accent"
Margin="2"
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}">
<Button.Content>
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock>
</Button.Content>
</Button>
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).RemoveFile}"
CommandParameter="{Binding}"
Classes="Danger"
Margin="2"
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}">
<Button.Content>
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove</TextBlock>
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate x:DataType="models:ReportFile">
<Grid ColumnDefinitions="*"
RowDefinitions="Auto, Auto, Auto">
<TextBlock TextWrapping="Wrap" Margin="4" Grid.Row="0">
<Run FontWeight="Bold" Text="File Path"/>: <Run Text="{Binding FilePath}"/>
</TextBlock>
<TextBlock TextWrapping="Wrap" Margin="4" Grid.Row="1">
<Run FontWeight="Bold" Text="Notes"/>: <Run Text="{Binding Notes}"/>
</TextBlock>
<StackPanel Orientation="Horizontal"
Spacing="8"
Margin="4"
Grid.Row="2">
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFileLocation}"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock FontSize="12"><Run Text="&#xf07c;" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File Location</TextBlock>
</Button.Content>
</Button>
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFile}"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock FontSize="12"><Run Text="&#xf07c;" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File</TextBlock>
</Button.Content>
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center"
Spacing="4"
Grid.Row="2"
Margin="4">
<StackPanel Orientation="Horizontal"
Spacing="4">
<Button Command="{Binding AddItem}"
IsEnabled="{Binding !IsCreatingPDF}">
<TextBlock><Run Text="&#x002b;" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item</TextBlock>
</Button>
<Button Command="{Binding SaveInterimReportInfo}"
IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}">
<TextBlock><Run Text="&#xf0c7;" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
</Button>
<Button Command="{Binding ResortPDFItemsByDate}"
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
<TextBlock><Run Text="&#xf162;" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock>
</Button>
<Button Command="{Binding BuildPDF}"
Classes="accent"
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
<TextBlock><Run Text="&#xf1c1;" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal"
IsVisible="{Binding IsCreatingPDF}"
Spacing="6"
HorizontalAlignment="Center">
<Label Content="Creating PDF..."
IsVisible="{Binding IsCreatingPDF}"
VerticalAlignment="Center"/>
<progRing:ProgressRing Width="30"
Height="30"
IsActive="{Binding IsCreatingPDF}"
Foreground="{DynamicResource SystemAccentColor}"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Stretch"
Spacing="2"
Grid.Row="3">
<Rectangle Fill="Gray" Height="3" HorizontalAlignment="Stretch"/>
<Label Content="Program Log" FontSize="14" FontWeight="Bold"/>
</StackPanel>
<ScrollViewer Margin="2"
Grid.Row="4"
x:Name="LogScrollView"
VerticalScrollBarVisibility="Visible"
AllowAutoHide="False">
<SelectableTextBlock Text="{Binding CreatePDFLog}"
Margin="2"
TextWrapping="Wrap"
x:Name="LogBlock"/>
</ScrollViewer>
</Grid>
</UserControl>
+31
View File
@@ -0,0 +1,31 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views
{
public partial class MainView : UserControl
{
public MainView()
{
this.InitializeComponent();
LogBlock.PropertyChanged += LogBlock_PropertyChanged;
}
private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.ToString() == "Text")
{
LogScrollView.ScrollToEnd();
}
}
public void UnfocusTextbox()
{
var topLevel = TopLevel.GetTopLevel(this);
topLevel?.FocusManager?.ClearFocus();
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<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="ReceiptPDFBuilder.Views.WarningDeleteItem"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:WarningDeleteItemModel">
<StackPanel HorizontalAlignment="Center"
Margin="6"
Spacing="8">
<TextBlock TextAlignment="Center"
FontWeight="Bold"
TextWrapping="Wrap"
FontSize="16"
MaxWidth="350">Are you sure you want to remove the item "<Run Text="{Binding File.Title}"/>"?</TextBlock>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="8">
<Button Command="{Binding KeepItem}"
Classes="accent">
<TextBlock><Run Text="&#xf02e;" FontFamily="{StaticResource FontAwesomeSolid}"/> Keep Item</TextBlock>
</Button>
<Button Command="{Binding RemoveItem}"
Classes="Danger">
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove Item</TextBlock>
</Button>
</StackPanel>
</StackPanel>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views
{
public partial class WarningDeleteItem : UserControl
{
public WarningDeleteItem()
{
this.InitializeComponent();
}
}
}
+29
View File
@@ -0,0 +1,29 @@
<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="ReceiptPDFBuilder.Views.WarningView"
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:WarningViewModel">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock TextAlignment="Center"
FontWeight="Bold"
FontSize="18"
Text="Error!"/>
<TextBlock TextAlignment="Center"
FontWeight="Bold"
TextWrapping="Wrap"
FontSize="14"
MaxWidth="350"
Text="{Binding Error}"/>
<Button Command="{Binding Close}"
Classes="accent"
Content="Close"
HorizontalAlignment="Right"
Margin="0,4,4,4"/>
</StackPanel>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReceiptPDFBuilder.Views
{
public partial class WarningView : UserControl
{
public WarningView()
{
this.InitializeComponent();
}
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.1.0.0" name="ReceiptPDFBuilder.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>