WIP: Add iOS version #10

Draft
Deadpikle wants to merge 67 commits from feature/ios into main
9 changed files with 62 additions and 181 deletions
Showing only changes of commit 287ef24113 - Show all commits
+6 -2
View File
@@ -6,12 +6,16 @@
-cleanup empty uuid folders in case user gets an internal folder created but never saves
-add dropdown to Add Items button to have add items from folder and remove choose working folder option (data always saved internally)
-always save report data (file locations, title, etc.) to internal dir (might already be done?)
*-always save report data (file locations, title, etc.) to internal dir (might already be done?)
-no more option to save data internally -> ALWAYS save report_data.json internally
-Now that the BaseFolder is not really set...update option for PDF output
-output in internal dir
-always ask me every time
-always put in X folder
-update project title -> should update recently used data
-this sort of works, something is wrong with the upgrade process where the UUID is not brought over properly; need to test and fix (maybe fixed already and my dataset is wrong?)
-add option to backup added files to internal data directory
-make backup of last generated PDF
-make backup of last generated PDF somewhere
-iOS-specific (MAUI essentials?)
-Take picture
-Add pic from gallery
+18 -3
View File
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
@@ -7,9 +8,8 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.RegularExpressions;
using Avalonia.Utilities;
using System.Threading.Tasks;
using MayShow.Models;
using Tmds.DBus.Protocol;
namespace MayShow.Helpers;
@@ -115,7 +115,7 @@ class Utilities
{
if (context == null)
{
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
var jsonContext = new SourceGenerationContext(GetSerializerOptions());
context = jsonContext.PDFReport;
}
using var memoryStream = new MemoryStream();
@@ -125,4 +125,19 @@ class Utilities
var updatedJson = reader.ReadToEnd();
File.WriteAllText(path, updatedJson);
}
public static async Task SaveReportDataAsync(PDFReport reportData, string path, JsonTypeInfo<PDFReport>? context = null)
{
if (context == null)
{
var jsonContext = new SourceGenerationContext(GetSerializerOptions());
context = jsonContext.PDFReport;
}
using var memoryStream = new MemoryStream();
await JsonSerializer.SerializeAsync(memoryStream, reportData, context);
memoryStream.Position = 0;
using var reader = new StreamReader(memoryStream);
var json = await reader.ReadToEndAsync();
await File.WriteAllTextAsync(path, json);
}
}
+9 -3
View File
@@ -11,20 +11,21 @@ namespace MayShow.Models;
class PDFReportInfo : ChangeNotifier
{
private string? _baseFolder; // might be null; if set, use this to know where (initial) working dir is
private string _baseFolder;
private string _uuid;
private string _title;
private DateTime? _lastSaved;
public PDFReportInfo() : base()
{
_baseFolder = null;
_uuid = Guid.NewGuid().ToString();
_baseFolder = "";
UpdateBaseFolder();
_title = "";
_lastSaved = null;
}
public string? BaseFolder
public string BaseFolder
{
get => _baseFolder;
set { _baseFolder = value; NotifyPropertyChanged(); }
@@ -48,6 +49,11 @@ class PDFReportInfo : ChangeNotifier
set { _lastSaved = value; NotifyPropertyChanged(); }
}
public void UpdateBaseFolder()
{
_baseFolder = Path.Combine(Utilities.GetInternalDataPath(), _uuid);
}
public void ResetUUID()
{
UUID = Guid.NewGuid().ToString();
@@ -83,10 +83,10 @@ class ReportPDFCreator : ChangeNotifier
}
// https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025
public async Task<string?> CreatePDF(List<ReportFile> reportFiles, string reportTitle, string workingDirPath, PDFFontResolver fontResolver, Settings appSettings)
public async Task<string?> CreatePDF(List<ReportFile> reportFiles, string reportTitle, string outputFolderPath, PDFFontResolver fontResolver, Settings appSettings)
{
// safety checks
var outputDir = appSettings.SaveOutputPdfInWorkingDir ? workingDirPath : appSettings.OutputPdfDir;
var outputDir = appSettings.SaveOutputPdfInWorkingDir ? outputFolderPath : appSettings.OutputPdfDir;
if (!Directory.Exists(outputDir))
{
await DialogHost.Show(new WarningViewModel("Output directory not found! Please adjust your application Settings before continuing. Output directory: " + outputDir));
@@ -111,7 +111,7 @@ class ReportPDFCreator : ChangeNotifier
// start making PDF!
var outputFileName = reportTitle + ".pdf";
var convertedDir = Utilities.GetTempConvertedImagesFolderPath();
var folderName = new DirectoryInfo(workingDirPath).Name;
var folderName = new DirectoryInfo(outputFolderPath).Name;
if (folderName.Contains('-'))
{
// see if year/month format
@@ -170,7 +170,7 @@ class ReportPDFCreator : ChangeNotifier
var pdfRenderer = new PdfDocumentRenderer
{
Document = pdfDoc,
WorkingDirectory = workingDirPath
WorkingDirectory = outputFolderPath
};
var hasAddedData = false;
for (var i = 0; i < reportFiles.Count; i++)
@@ -46,23 +46,6 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
InitializeProgramLog();
}
// this is the "normal path" into the pdf report view
// pathToLoad is presumably _settings.LastUsedPath but doesn't have to be
// public CreatePDFReportViewModel(string pathToLoad, IChangeViewModel viewModelChanger) : this(viewModelChanger)
// {
// _isPerformingInitialLoad = true;
// if (!string.IsNullOrWhiteSpace(pathToLoad))
// {
// LogInfo("Loading report data at path: {0}", pathToLoad);
// ScanFolder(pathToLoad);
// }
// else
// {
// LogInfo("Choose a receipt folder to begin...");
// }
// _isPerformingInitialLoad = false;
// }
public CreatePDFReportViewModel(PDFReportInfo reportInfo, IChangeViewModel viewModelChanger) : this(viewModelChanger)
{
_isPerformingInitialLoad = true;
@@ -75,15 +58,15 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
}
else
{
// load data file in internal dir + UUID
var path = Path.Combine(Utilities.GetInternalDataPath(), _pdfReport.UUID);
if (Directory.Exists(path))
// load data file in internal data report dir
_pdfReport.BaseFolder = Path.Combine(Utilities.GetInternalDataPath(), _pdfReport.UUID);
if (Directory.Exists(_pdfReport.BaseFolder))
{
ScanFolder(path); // even if points entirely to internal folder, we will be A-OK loading here
ScanFolder(_pdfReport.BaseFolder); // even if points entirely to internal folder, we will be A-OK loading here
}
else
{
LogInfo("Erorr loading report! Folder does not exist: {0}", path);
LogInfo("Erorr loading report! Folder does not exist: {0}", _pdfReport.BaseFolder);
}
}
_isPerformingInitialLoad = false;
@@ -99,7 +82,6 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
_pdfReport = value;
NotifyPropertyChanged(nameof(ReportTitle));
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
NotifyPropertyChanged(nameof(WorkingFolder));
NotifyPropertyChanged(nameof(ReportFiles));
SetupFileCollectionChangedWatcher();
}
@@ -112,20 +94,14 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
{
_pdfReport.Title = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
NotifyPropertyChanged(nameof(CanAddItem));
HasUnsavedWork = true;
}
}
public bool IsTitleBoxVisible
{
get => !string.IsNullOrWhiteSpace(WorkingFolder);
}
public bool CanAddItem
{
get => IsTitleBoxVisible && !IsCreatingPDF;
get => !IsCreatingPDF;
}
public bool IsCreatingPDF
@@ -136,7 +112,6 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
_isCreatingPDF = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
NotifyPropertyChanged(nameof(CanAddItem));
}
}
@@ -146,38 +121,6 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
get => !_isCreatingPDF && _pdfReport.Files.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
{
if (string.IsNullOrWhiteSpace(_pdfReport.BaseFolder))
{
return Path.Combine(Utilities.GetInternalDataPath(), _pdfReport.UUID);
}
else
{
return _pdfReport.BaseFolder;
}
}
set
{
_pdfReport.BaseFolder = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(HasWorkingFolder));
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
}
}
public string ProgramLog
{
get => _programLog;
@@ -269,25 +212,20 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
}
}
private string GetReportSavedDataPath(string workingFolder)
private string GetReportSavedDataPath()
{
var internalPath = Utilities.GetInternalDataPath();
var internalReportDataDir = Path.Combine(internalPath, _pdfReport.UUID);
if (!Directory.Exists(internalReportDataDir))
if (!Directory.Exists(_pdfReport.BaseFolder))
{
Directory.CreateDirectory(internalReportDataDir);
Directory.CreateDirectory(_pdfReport.BaseFolder);
}
return Path.Combine(internalReportDataDir, Constants.ReportSavedDataFileName);
return Path.Combine(_pdfReport.BaseFolder, Constants.ReportSavedDataFileName);
}
private void ScanFolder(string path)
{
if (Directory.Exists(path))
{
WorkingFolder = path;
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
NotifyPropertyChanged(nameof(CanAddItem));
var reportFilePath = GetReportSavedDataPath(path);
var reportFilePath = GetReportSavedDataPath();
var successfullyLoadedPriorReportFile = false;
if (File.Exists(reportFilePath))
{
@@ -307,19 +245,19 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
// Scan folder for files and display in DataGrid
if (path != PDFReport.BaseFolder)
{
// in this case, there is essentially no report existing,
// in this case, there is essentially no existing report,
// so we need to make a new one.
PDFReport = new PDFReport()
{
Title = Path.GetDirectoryName(path) ?? "",
LastSaved = null,
UUID = Utilities.GetUniqueReportGuid(_settings).ToString(),
BaseFolder = path
};
PDFReport.UpdateBaseFolder();
}
ReportFiles.Clear();
ReportTitle = "";
var filePaths = Directory.GetFiles(WorkingFolder);
var filePaths = Directory.GetFiles(path);
foreach (var filePath in filePaths)
{
AddFileBasedOnPath(filePath);
@@ -539,7 +477,7 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
{
try
{
await Task.Run(() => CreatePDF(WorkingFolder));
await Task.Run(() => CreatePDF(outputFolderPath: _pdfReport.BaseFolder));
} catch (Exception e)
{
LogInfo("PDF process failed! Reason: " + e.Message);
@@ -578,14 +516,8 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
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 = GetReportSavedDataPath(WorkingFolder);
await File.WriteAllTextAsync(savePath, json);
var savePath = GetReportSavedDataPath();
await Utilities.SaveReportDataAsync(report, savePath);
LogInfo("Saved report information to {0}", savePath);
HasUnsavedWork = false;
UpdateRecentlyUsed?.UpdateRecentlyUsed(report);
@@ -602,11 +534,11 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
}
}
private async Task CreatePDF(string folderPath)
private async Task CreatePDF(string outputFolderPath)
{
IsCreatingPDF = true;
var reportCreator = new ReportPDFCreator(this);
var outputPdfFile = await reportCreator.CreatePDF(ReportFiles.ToList(), ReportTitle, folderPath, new PDFFontResolver(_processDir, this), _settings);
var outputPdfFile = await reportCreator.CreatePDF(ReportFiles.ToList(), ReportTitle, outputFolderPath, new PDFFontResolver(_processDir, this), _settings);
if (!string.IsNullOrWhiteSpace(outputPdfFile))
{
await CreateAndSaveReportObjectAfterReportCreation();
@@ -617,8 +549,7 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
public async void ReturnToMainMenu()
{
bool isSafeToReturn = await CheckIsSafeToShutdown();
if (isSafeToReturn)
if (await CheckIsSafeToShutdown())
{
PopViewModel();
}
@@ -626,7 +557,7 @@ class CreatePDFReportViewModel : BaseViewModel, ICanCheckShutdown, ILogger
public async Task<bool> CheckIsSafeToShutdown()
{
if (!HasUnsavedWork || string.IsNullOrWhiteSpace(WorkingFolder))
if (!HasUnsavedWork)
{
return true;
}
@@ -1,20 +1,9 @@
#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;
@@ -1,15 +1,12 @@
#nullable enable
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using DialogHostAvalonia;
using MayShow.Interfaces;
using MayShow.Models;
using MayShow.Helpers;
using System;
using System.Threading.Tasks;
using Avalonia.Platform.Storage;
namespace MayShow.ViewModels;
@@ -56,7 +53,7 @@ class StartNewChooseReportViewModel : BaseViewModel, ICanCheckShutdown, IUpdateR
LastSaved = null,
UUID = Utilities.GetUniqueReportGuid(_settings).ToString()
};
reportInfo.BaseFolder = Path.Combine(Utilities.GetInternalDataPath(), reportInfo.UUID); // default to internal directory
reportInfo.UpdateBaseFolder();
// now update UI
ViewModelChanger.PushViewModel(new CreatePDFReportViewModel(reportInfo, ViewModelChanger)
{
@@ -66,37 +63,6 @@ class StartNewChooseReportViewModel : BaseViewModel, ICanCheckShutdown, IUpdateR
CreatingReportTitle = ""; // when user comes back they can start another new report
}
public async void StartReportFromFolder()
{
// pick folder, then create new report based on folder
// use folder name as report title for now
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];
var reportInfo = new PDFReportInfo()
{
Title = Path.GetDirectoryName(folder.Path.LocalPath) ?? "",
LastSaved = null,
UUID = Utilities.GetUniqueReportGuid(_settings).ToString(),
BaseFolder = folder.Path.LocalPath
};
ViewModelChanger.PushViewModel(new CreatePDFReportViewModel(reportInfo, ViewModelChanger)
{
UpdateRecentlyUsed = this,
TopLevelGrabber = TopLevelGrabber
});
}
}
}
public void LoadExistingReport(object info) => LoadExistingReportImpl((PDFReportInfo) info);
public void LoadExistingReportImpl(PDFReportInfo reportInfo)
{
@@ -25,23 +25,8 @@
Margin="4,4,0,4">
<TextBlock><Run Text="&#xf060;" FontFamily="{StaticResource FontAwesomeSolid}"/> Return to Main Menu</TextBlock>
</Button>
<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}" />
<Label Content="Report Title" />
<TextBox Text="{Binding ReportTitle}"
IsVisible="{Binding IsTitleBoxVisible}"
Watermark="Receipts December 2024"
Margin="2,0,2,4"
Classes="clearButton"
@@ -249,7 +234,7 @@
<TextBlock><Run Text="&#xf162;" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock>
</Button>
<Button Command="{Binding SaveInterimReportInfo}"
IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}">
IsEnabled="{Binding !IsCreatingPDF}">
<TextBlock><Run Text="&#xf0c7;" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
</Button>
</StackPanel>
@@ -11,7 +11,7 @@
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
x:DataType="vm:StartNewChooseReportViewModel">
<Grid ColumnDefinitions="100, *, 100"
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto, Auto, *">
RowDefinitions="Auto, Auto, Auto, Auto, Auto, *">
<Button Command="{Binding ShowSettings}"
Grid.Row="0"
Grid.Column="0"
@@ -70,21 +70,6 @@
FontFamily="{StaticResource FontAwesomeSolid}" /> Create Blank Report</TextBlock>
</Button>
</Grid>
<Label Content="Start New Report From Existing Files"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="Bold"
Margin="0,8,0,0"
Grid.Column="1"
Grid.Row="4"/>
<Button Command="{Binding StartReportFromFolder}"
Classes="accent"
Grid.Row="5"
Grid.Column="1">
<TextBlock>
<Run Text="&#xe494;"
FontFamily="{StaticResource FontAwesomeSolid}" /> Create Report from Existing Folder</TextBlock>
</Button>
<Label Content="Load Previously Saved Report"
FontSize="16"
FontWeight="Bold"