New attachment templates that support saving and opening attachment when composing message.

This commit is contained in:
Burak Kaan Köse
2024-11-27 19:49:10 +01:00
parent e586145f50
commit 96c98a6987
12 changed files with 196 additions and 62 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Common;
namespace Wino.Core.Domain.Interfaces
{
@@ -25,5 +26,7 @@ namespace Wino.Core.Domain.Interfaces
Task<bool> ShowCustomThemeBuilderDialogAsync();
Task<AccountCreationDialogResult> ShowAccountProviderSelectionDialogAsync(List<IProviderDetail> availableProviders);
IAccountCreationDialog GetAccountCreationDialog(MailProviderType type);
Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters);
Task<string> PickFilePathAsync(string saveFileName);
}
}

View File

@@ -0,0 +1,14 @@
using System.IO;
namespace Wino.Core.Domain.Models.Common
{
/// <summary>
/// Abstraction for StorageFile
/// </summary>
/// <param name="FullFilePath">Full path of the file.</param>
/// <param name="Data">Content</param>
public record SharedFile(string FullFilePath, byte[] Data)
{
public string FileName => Path.GetFileName(FullFilePath);
}
}

View File

@@ -261,6 +261,8 @@
"Info_BackgroundExecutionDeniedTitle": "Denied Background Execution",
"Info_BackgroundExecutionUnknownErrorMessage": "Unknown exception occurred when registering background synchronizer.",
"Info_BackgroundExecutionUnknownErrorTitle": "Background Execution Failure",
"Info_FailedToOpenFileTitle": "Failed to launch file.",
"Info_FailedToOpenFileMessage": "File might be removed from the disk.",
"Info_ComposerMissingMIMEMessage": "Couldn't locate the MIME file. Synchronizing may help.",
"Info_ComposerMissingMIMETitle": "Failed",
"Info_ContactExistsMessage": "This contact is already in the recipient list.",

View File

@@ -0,0 +1,16 @@
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Wino.Core.Domain.Models.Common;
namespace Wino.Core.UWP.Extensions
{
public static class StorageFileExtensions
{
public static async Task<SharedFile> ToSharedFileAsync(this Windows.Storage.StorageFile storageFile)
{
var content = await storageFile.ReadBytesAsync();
return new SharedFile(storageFile.Path, content);
}
}
}

View File

@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Toolkit.Uwp.Helpers;
using Serilog;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -13,6 +14,7 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Common;
using Wino.Core.UWP.Dialogs;
using Wino.Core.UWP.Extensions;
using Wino.Dialogs;
@@ -36,6 +38,66 @@ namespace Wino.Core.UWP.Services
ApplicationResourceManager = applicationResourceManager;
}
public async Task<string> PickFilePathAsync(string saveFileName)
{
var picker = new FolderPicker()
{
SuggestedStartLocation = PickerLocationId.Desktop
};
picker.FileTypeFilter.Add("*");
var folder = await picker.PickSingleFolderAsync();
if (folder == null) return string.Empty;
StorageApplicationPermissions.FutureAccessList.Add(folder);
return folder.Path;
//var picker = new FileSavePicker
//{
// SuggestedStartLocation = PickerLocationId.Desktop,
// SuggestedFileName = saveFileName
//};
//picker.FileTypeChoices.Add(Translator.FilteringOption_All, [".*"]);
//var file = await picker.PickSaveFileAsync();
//if (file == null) return string.Empty;
//StorageApplicationPermissions.FutureAccessList.Add(file);
//return file.Path;
}
public async Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters)
{
var returnList = new List<SharedFile>();
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.Desktop
};
foreach (var filter in typeFilters)
{
picker.FileTypeFilter.Add(filter.ToString());
}
var files = await picker.PickMultipleFilesAsync();
if (files == null) return returnList;
foreach (var file in files)
{
StorageApplicationPermissions.FutureAccessList.Add(file);
var sharedFile = await file.ToSharedFileAsync();
returnList.Add(sharedFile);
}
return returnList;
}
private async Task<StorageFile> PickFileAsync(params object[] typeFilters)
{
var picker = new FileOpenPicker
@@ -52,7 +114,7 @@ namespace Wino.Core.UWP.Services
if (file == null) return null;
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("FilePickerPath", file);
StorageApplicationPermissions.FutureAccessList.Add(file);
return file;
}

View File

@@ -100,6 +100,7 @@
<Compile Include="Dialogs\NewAccountDialog.xaml.cs">
<DependentUpon>NewAccountDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Extensions\StorageFileExtensions.cs" />
<Compile Include="Selectors\NavigationMenuTemplateSelector.cs" />
<Compile Include="Services\ApplicationResourceManager.cs" />
<Compile Include="Services\DialogServiceBase.cs" />

View File

@@ -96,6 +96,7 @@ namespace Wino.Mail.ViewModels
private readonly IMailDialogService _dialogService;
private readonly IMailService _mailService;
private readonly IMimeFileService _mimeFileService;
private readonly IFileService _fileService;
private readonly IFolderService _folderService;
private readonly IAccountService _accountService;
private readonly IWinoRequestDelegator _worker;
@@ -107,6 +108,7 @@ namespace Wino.Mail.ViewModels
public ComposePageViewModel(IMailDialogService dialogService,
IMailService mailService,
IMimeFileService mimeFileService,
IFileService fileService,
INativeAppService nativeAppService,
IFolderService folderService,
IAccountService accountService,
@@ -125,11 +127,58 @@ namespace Wino.Mail.ViewModels
_dialogService = dialogService;
_mailService = mailService;
_mimeFileService = mimeFileService;
_fileService = fileService;
_accountService = accountService;
_worker = worker;
_winoServerConnectionManager = winoServerConnectionManager;
}
[RelayCommand]
private async Task OpenAttachmentAsync(MailAttachmentViewModel attachmentViewModel)
{
if (string.IsNullOrEmpty(attachmentViewModel.FilePath)) return;
try
{
await NativeAppService.LaunchFileAsync(attachmentViewModel.FilePath);
}
catch (Exception ex)
{
_dialogService.InfoBarMessage(Translator.Info_FailedToOpenFileTitle, Translator.Info_FailedToOpenFileMessage, InfoBarMessageType.Error);
}
}
[RelayCommand]
private async Task SaveAttachmentAsync(MailAttachmentViewModel attachmentViewModel)
{
if (attachmentViewModel.Content == null) return;
var pickedFilePath = await _dialogService.PickFilePathAsync(attachmentViewModel.FileName);
if (string.IsNullOrWhiteSpace(pickedFilePath)) return;
try
{
await _fileService.CopyFileAsync(attachmentViewModel.FilePath, pickedFilePath);
}
catch (Exception ex)
{
_dialogService.InfoBarMessage(Translator.Info_FailedToOpenFileTitle, Translator.Info_FailedToOpenFileMessage, InfoBarMessageType.Error);
}
}
[RelayCommand]
private async Task AttachFilesAsync()
{
var pickedFiles = await _dialogService.PickFilesAsync("*");
if (pickedFiles?.Count == 0) return;
foreach (var file in pickedFiles)
{
var attachmentViewModel = new MailAttachmentViewModel(file);
IncludedAttachments.Add(attachmentViewModel);
}
}
[RelayCommand]
private void RemoveAttachment(MailAttachmentViewModel attachmentViewModel)
=> IncludedAttachments.Remove(attachmentViewModel);

View File

@@ -2,6 +2,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using MimeKit;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Common;
using Wino.Core.Extensions;
namespace Wino.Mail.ViewModels.Data
@@ -41,14 +42,14 @@ namespace Wino.Mail.ViewModels.Data
AttachmentType = GetAttachmentType(extension);
}
public MailAttachmentViewModel(string fullFilePath, byte[] content)
public MailAttachmentViewModel(SharedFile sharedFile)
{
Content = content;
Content = sharedFile.Data;
FileName = Path.GetFileName(fullFilePath);
FilePath = fullFilePath;
FileName = sharedFile.FileName;
FilePath = sharedFile.FullFilePath;
ReadableSize = ((long)content.Length).GetBytesReadable();
ReadableSize = ((long)sharedFile.Data.Length).GetBytesReadable();
var extension = Path.GetExtension(FileName);
AttachmentType = GetAttachmentType(extension);

View File

@@ -1,20 +0,0 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Windows.Storage;
using Wino.Mail.ViewModels.Data;
namespace Wino.Extensions
{
public static class MimeKitExtensions
{
public static async Task<MailAttachmentViewModel> ToAttachmentViewModelAsync(this StorageFile storageFile)
{
if (storageFile == null) return null;
var bytes = await storageFile.ReadBytesAsync();
return new MailAttachmentViewModel(storageFile.Name, bytes);
}
}
}

View File

@@ -5,6 +5,7 @@
xmlns:abstract="using:Wino.Views.Abstract"
xmlns:controls="using:Wino.Controls"
xmlns:coreControls="using:Wino.Core.UWP.Controls"
xmlns:customcontrols="using:Wino.Core.UWP.Controls.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="using:Wino.Mail.ViewModels.Data"
xmlns:domain="using:Wino.Core.Domain"
@@ -15,6 +16,7 @@
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:reader="using:Wino.Core.Domain.Models.Reader"
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
x:Name="root"
d:Background="White"
Loaded="ComposerLoaded"
@@ -74,8 +76,24 @@
<Grid
Height="50"
Margin="-8,0,0,0"
Padding="0"
Background="Transparent"
ColumnSpacing="3">
<Grid.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem
Command="{Binding ElementName=root, Path=ViewModel.OpenAttachmentCommand}"
CommandParameter="{Binding}"
Text="{x:Bind domain:Translator.Buttons_Open}" />
<MenuFlyoutItem
Command="{Binding ElementName=root, Path=ViewModel.SaveAttachmentCommand}"
CommandParameter="{Binding}"
Text="{x:Bind domain:Translator.Buttons_Save}" />
</MenuFlyout>
</Grid.ContextFlyout>
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind FileName}" />
</ToolTipService.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="36" />
<ColumnDefinition Width="*" />
@@ -113,11 +131,21 @@
Text="{x:Bind ReadableSize}" />
</Grid>
<SymbolIcon
<Button
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{StaticResource DeleteBrush}"
Symbol="Cancel" />
Margin="0,4,-8,4"
VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
Command="{Binding ElementName=root, Path=ViewModel.RemoveAttachmentCommand}"
CommandParameter="{Binding}">
<SymbolIcon
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{StaticResource DeleteBrush}"
Symbol="Delete" />
</Button>
</Grid>
</DataTemplate>
</Page.Resources>
@@ -333,7 +361,7 @@
<toolkit:TabbedCommandBarItem Header="{x:Bind domain:Translator.EditorToolbarOption_Insert}">
<AppBarButton
x:Name="FilesButton"
Click="AddFilesClicked"
Command="{x:Bind ViewModel.AttachFilesCommand}"
Label="{x:Bind domain:Translator.Files}">
<AppBarButton.Icon>
<PathIcon Data="{StaticResource AttachPathIcon}" />
@@ -402,7 +430,7 @@
<!-- Mime Info -->
<Grid
Grid.Row="1"
Padding="16,0,16,10"
Padding="16,0,16,4"
ColumnSpacing="12"
RowSpacing="3">
<Grid.RowDefinitions>
@@ -550,15 +578,15 @@
x:Name="AttachmentsListView"
Grid.Row="5"
Grid.ColumnSpan="2"
ui:ListViewExtensions.Command="{x:Bind ViewModel.OpenAttachmentCommand}"
x:Load="{x:Bind helpers:XamlHelpers.CountToBooleanConverter(ViewModel.IncludedAttachments.Count), Mode=OneWay}"
IsItemClickEnabled="True"
ItemClick="AttachmentClicked"
ItemTemplate="{StaticResource ComposerFileAttachmentTemplate}"
ItemsSource="{x:Bind ViewModel.IncludedAttachments, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
<customcontrols:CustomWrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>

View File

@@ -17,7 +17,6 @@ using MimeKit;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.ViewManagement.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -28,7 +27,7 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Reader;
using Wino.Extensions;
using Wino.Core.UWP.Extensions;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Shell;
@@ -109,20 +108,6 @@ namespace Wino.Views
});
}
private async void AddFilesClicked(object sender, RoutedEventArgs e)
{
// TODO: Pick files
var picker = new FileOpenPicker()
{
SuggestedStartLocation = PickerLocationId.Desktop
};
picker.FileTypeFilter.Add("*");
var files = await picker.PickMultipleFilesAsync();
await AttachFiles(files);
}
private void OnComposeGridDragOver(object sender, DragEventArgs e)
{
ViewModel.IsDraggingOverComposerGrid = true;
@@ -250,9 +235,9 @@ namespace Wino.Views
// Convert files to MailAttachmentViewModel.
foreach (var file in files)
{
var attachmentViewModel = await file.ToAttachmentViewModelAsync();
var sharedFile = await file.ToSharedFileAsync();
ViewModel.IncludedAttachments.Add(attachmentViewModel);
ViewModel.IncludedAttachments.Add(new MailAttachmentViewModel(sharedFile));
}
}
@@ -628,14 +613,6 @@ namespace Wino.Views
}
}
private void AttachmentClicked(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is MailAttachmentViewModel attachmentViewModel)
{
ViewModel.RemoveAttachmentCommand.Execute(attachmentViewModel);
}
}
private async void AddressBoxLostFocus(object sender, RoutedEventArgs e)
{
// Automatically add current text as item if it is valid mail address.

View File

@@ -238,7 +238,6 @@
<Compile Include="Dialogs\SystemFolderConfigurationDialog.xaml.cs">
<DependentUpon>SystemFolderConfigurationDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Extensions\MimeKitExtensions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="MenuFlyouts\AccountSelectorFlyout.cs" />
<Compile Include="MenuFlyouts\FolderOperationFlyout.cs" />
@@ -623,7 +622,9 @@
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Extensions\" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>