New attachment templates that support saving and opening attachment when composing message.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
14
Wino.Core.Domain/Models/Common/SharedFile.cs
Normal file
14
Wino.Core.Domain/Models/Common/SharedFile.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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.",
|
||||
|
||||
16
Wino.Core.UWP/Extensions/StorageFileExtensions.cs
Normal file
16
Wino.Core.UWP/Extensions/StorageFileExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user