37 Commits

Author SHA1 Message Date
Burak Kaan Köse
24c8cfd402 Revert padding for mail list container. 2024-08-31 18:23:22 +02:00
Burak Kaan Köse
8257b0b582 Merge pull request #345 from bkaankose/hotfix/Expander
New expander control for conversation threads
2024-08-31 16:12:32 +02:00
Burak Kaan Köse
e612f2c281 Remove redundant code. 2024-08-31 16:11:19 +02:00
Burak Kaan Köse
155df59b1d Increase pre-defined size to fit mail icon changes. 2024-08-31 16:11:11 +02:00
Burak Kaan Köse
6efab9f386 merged main 2024-08-31 15:31:29 +02:00
Burak Kaan Köse
422105a507 Merge pull request #344 from bkaankose/features/action-bar
Mail List action bar improvements
2024-08-31 15:28:59 +02:00
Burak Kaan Köse
d58438ab1d Removed margin and increased padding. 2024-08-31 15:26:22 +02:00
Burak Kaan Köse
209aa1a89f 4x top margin for mail list. 2024-08-31 15:22:20 +02:00
Aleh Khantsevich
07ac81583e Fix delete action on KeyPress 2024-08-31 15:11:28 +02:00
Burak Kaan Köse
ee6249bb17 Selecting first mail when thread is expanded. 2024-08-31 15:08:43 +02:00
Aleh Khantsevich
b8ca3f8604 Disable toolbar when nothing selected. 2024-08-31 14:29:57 +02:00
Aleh Khantsevich
85b5469d96 Fix wrong logic for set/unset flag 2024-08-31 14:19:43 +02:00
Aleh Khantsevich
d3ddf7b191 style/renaming 2024-08-31 13:56:31 +02:00
Aleh Khantsevich
ebf196ec73 Remove redundant message 2024-08-31 13:39:32 +02:00
Aleh Khantsevich
85c3833452 Make TopBar dynamic 2024-08-31 13:25:55 +02:00
Burak Kaan Köse
8fb4735fc2 Handling hover action tap delegates. 2024-08-31 03:37:04 +02:00
Burak Kaan Köse
6bb09f10d2 Finished wino expander implementation. 2024-08-31 03:18:43 +02:00
Aleh Khantsevich
a4ff67e8f4 Fix margins 2024-08-31 00:42:17 +02:00
Burak Kaan Köse
b9a1756f90 Moving expander chevron to mail display control. 2024-08-30 02:20:16 +02:00
Burak Kaan Köse
72ff8e67ed Merge branch 'main' into hotfix/Expander 2024-08-30 01:11:19 +02:00
Burak Kaan Köse
d7006365eb Merge pull request #342 from bkaankose/hotfix/InvalidContactCrashFix
Fixed invalid contacts causing folder loading to crash.
2024-08-30 01:05:01 +02:00
Burak Kaan Köse
86ef78b296 Fixed invalid contacts causing folder loading to crash. 2024-08-30 01:03:35 +02:00
Aleh Khantsevich
0d84e409c5 Fix button sizes 2024-08-30 00:15:13 +02:00
Burak Kaan Köse
17c7b33167 Merge pull request #341 from bkaankose/hotfix/FixGmailDelete
Fixing Gmail SENT label deletes.
2024-08-30 00:03:53 +02:00
Burak Kaan Köse
856e1613a0 Fixing Gmail sent folder deletes. 2024-08-29 23:58:39 +02:00
Burak Kaan Köse
8db34289a7 Fixing single threads for API threading strategy. 2024-08-29 23:43:49 +02:00
Burak Kaan Köse
3016f70349 Handling gmail errors. 2024-08-29 22:43:27 +02:00
Aleh Khantsevich
bdf212fdb3 Remove bottom padding of mail list 2024-08-29 17:36:50 +02:00
Aleh Khantsevich
c6216f54f8 simplify layout for mail list 2024-08-29 17:18:46 +02:00
Aleh Khantsevich
945c747e3e Added setting to show/hide action bar in mail list 2024-08-29 01:13:51 +02:00
Aleh Khantsevich
552fca8df7 Moved refresh and multi select buttons 2024-08-29 00:19:24 +02:00
Burak Kaan Köse
4dac160619 Fix 995 char limit on message headers for Outlook. 2024-08-28 22:17:13 +02:00
Burak Kaan Köse
fc0e746e1b Reduce outlook send draft delay to 6 secs. 2024-08-27 01:48:03 +02:00
Burak Kaan Köse
8374b5fc0c Fix replying issues with Outlook due to cc or bcc headers. 2024-08-27 01:47:50 +02:00
Burak Kaan Köse
52923ed35b Bump some more nugets, remove redundant events, and fix Outlook profile sync permission issue. 2024-08-26 22:09:00 +02:00
Aleh Khantsevich
f002ccfa3a Replace WinoPivot with segmented 2024-08-26 17:27:27 +02:00
Burak Kaan Köse
f4bbf6eb73 New expander control. 2024-08-26 01:07:51 +02:00
45 changed files with 785 additions and 806 deletions

View File

@@ -155,5 +155,10 @@ namespace Wino.Core.Domain.Interfaces
/// Setting: Gets or sets what should happen to server app when the client is terminated.
/// </summary>
ServerBackgroundMode ServerTerminationBehavior { get; set; }
/// <summary>
/// Setting: Whether the mail list action bar is enabled or not.
/// </summary>
bool IsMailListActionBarEnabled { get; set; }
}
}

View File

@@ -9,7 +9,6 @@ namespace Wino.Core.Domain.Interfaces
public interface IThemeService : IInitializeAsync
{
event EventHandler<ApplicationElementTheme> ElementThemeChanged;
event EventHandler<string> AccentColorChangedBySystem;
event EventHandler<string> AccentColorChanged;
Task<List<AppThemeBase>> GetAvailableThemesAsync();

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Interfaces
@@ -12,7 +13,7 @@ namespace Wino.Core.Domain.Interfaces
/// </summary>
/// <param name="items">Original mails.</param>
/// <returns>Original mails with thread mails.</returns>
Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items);
Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items, IMailItemFolder threadingForFolder);
bool ShouldThreadWithItem(IMailItem originalItem, IMailItem targetItem);
}
}

View File

@@ -14,19 +14,19 @@ namespace Wino.Core.Domain.Models.MailItem
public IMailItem LatestMailItem => ThreadItems.LastOrDefault();
public IMailItem FirstMailItem => ThreadItems.FirstOrDefault();
public void AddThreadItem(IMailItem item)
public bool AddThreadItem(IMailItem item)
{
if (item == null) return;
if (item == null) return false;
if (ThreadItems.Any(a => a.Id == item.Id))
{
return;
return false;
}
if (item != null && item.IsDraft)
{
ThreadItems.Insert(0, item);
return;
return true;
}
var insertItem = ThreadItems.FirstOrDefault(a => !a.IsDraft && a.CreationDate < item.CreationDate);
@@ -39,6 +39,8 @@ namespace Wino.Core.Domain.Models.MailItem
ThreadItems.Insert(index, item);
}
return true;
}
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;

View File

@@ -519,7 +519,7 @@
"SettingsRenameMergeAccount_Title": "Rename",
"SettingsSemanticZoom_Description": "This will allow you to click on the headers in messages list and go to specific date",
"SettingsSemanticZoom_Title": "Semantic Zoom for Date Headers",
"SettingsShowPreviewText_Description": "Hide/show thepreview text.",
"SettingsShowPreviewText_Description": "Hide/show the preview text.",
"SettingsShowPreviewText_Title": "Show Preview Text",
"SettingsShowSenderPictures_Description": "Hide/show the thumbnail sender pictures.",
"SettingsShowSenderPictures_Title": "Show Sender Avatars",
@@ -533,7 +533,9 @@
"SettingsStore_Title": "Rate in Store",
"SettingsThreads_Description": "Organize messages into conversation threads.",
"SettingsThreads_Title": "Conversation Threading",
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. This will not delete your accounts.",
"SettingsMailListActionBar_Description": "Hide/show action bar at top of message list.",
"SettingsMailListActionBar_Title": "Show mail list actions",
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
"SortingOption_Date": "by date",
"SortingOption_Name": "by name",

View File

@@ -2619,7 +2619,7 @@ namespace Wino.Core.Domain
public static string SettingsSemanticZoom_Title => Resources.GetTranslatedString(@"SettingsSemanticZoom_Title");
/// <summary>
/// Hide/show thepreview text.
/// Hide/show the preview text.
/// </summary>
public static string SettingsShowPreviewText_Description => Resources.GetTranslatedString(@"SettingsShowPreviewText_Description");
@@ -2689,7 +2689,17 @@ namespace Wino.Core.Domain
public static string SettingsThreads_Title => Resources.GetTranslatedString(@"SettingsThreads_Title");
/// <summary>
/// Remove the link between accounts. This will not delete your accounts.
/// Hide/show action bar at top of message list.
/// </summary>
public static string SettingsMailListActionBar_Description => Resources.GetTranslatedString(@"SettingsMailListActionBar_Description");
/// <summary>
/// Show mail list actions
/// </summary>
public static string SettingsMailListActionBar_Title => Resources.GetTranslatedString(@"SettingsMailListActionBar_Title");
/// <summary>
/// Remove the link between accounts. his will not delete your accounts.
/// </summary>
public static string SettingsUnlinkAccounts_Description => Resources.GetTranslatedString(@"SettingsUnlinkAccounts_Description");

View File

@@ -62,7 +62,7 @@
</PackageReference>
<PackageReference Include="MimeKit" Version="4.7.1" />
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
<ItemGroup>

View File

@@ -64,6 +64,12 @@ namespace Wino.Core.UWP.Services
set => SetPropertyAndSave(nameof(IsThreadingEnabled), value);
}
public bool IsMailListActionBarEnabled
{
get => _configurationService.Get(nameof(IsMailListActionBarEnabled), false);
set => SetPropertyAndSave(nameof(IsMailListActionBarEnabled), value);
}
public bool IsShowSenderPicturesEnabled
{
get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true);

View File

@@ -46,7 +46,6 @@ namespace Wino.Services
public event EventHandler<ApplicationElementTheme> ElementThemeChanged;
public event EventHandler<string> AccentColorChanged;
public event EventHandler<string> AccentColorChangedBySystem;
private const string AccentColorKey = nameof(AccentColorKey);
private const string CurrentApplicationThemeKey = nameof(CurrentApplicationThemeKey);

View File

@@ -112,7 +112,7 @@
<Version>7.1.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>5.0.4</Version>
<Version>5.0.5</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.14</Version>

View File

@@ -26,8 +26,6 @@ namespace Wino.Core.Authenticators
private readonly INativeAppService _nativeAppService;
public event EventHandler<string> InteractiveAuthenticationRequired;
public GmailAuthenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService)
{
_nativeAppService = nativeAppService;

View File

@@ -28,7 +28,16 @@ namespace Wino.Core.Authenticators
public string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
private readonly string[] MailScope = ["email", "mail.readwrite", "offline_access", "mail.send", "Mail.Send.Shared", "Mail.ReadWrite.Shared"];
private readonly string[] MailScope =
[
"email",
"mail.readwrite",
"offline_access",
"mail.send",
"Mail.Send.Shared",
"Mail.ReadWrite.Shared",
"User.Read"
];
public override MailProviderType ProviderType => MailProviderType.Outlook;

View File

@@ -180,7 +180,7 @@ namespace Wino.Core.Extensions
// Some headers also require to start with X- or x-.
string[] headersToIgnore = ["Date", "To", "MIME-Version", "From", "Subject", "Message-Id"];
string[] headersToIgnore = ["Date", "To", "Cc", "Bcc", "MIME-Version", "From", "Subject", "Message-Id"];
string[] headersToModify = ["In-Reply-To", "Reply-To", "References", "Thread-Topic"];
var headers = new List<InternetMessageHeader>();
@@ -191,15 +191,12 @@ namespace Wino.Core.Extensions
{
if (!headersToIgnore.Contains(header.Field))
{
if (headersToModify.Contains(header.Field))
{
headers.Add(new InternetMessageHeader() { Name = $"X-{header.Field}", Value = header.Value });
}
else
{
headers.Add(new InternetMessageHeader() { Name = header.Field, Value = header.Value });
}
var headerName = headersToModify.Contains(header.Field) ? $"X-{header.Field}" : header.Field;
// No header value should exceed 995 characters.
var headerValue = header.Value.Length >= 995 ? header.Value.Substring(0, 995) : header.Value;
headers.Add(new InternetMessageHeader() { Name = headerName, Value = headerValue });
includedHeaderCount++;
}

View File

@@ -21,7 +21,6 @@ namespace Wino.Core.Integration.Processors
{
Task UpdateAccountAsync(MailAccount account);
Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string deltaSynchronizationIdentifier);
Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
Task DeleteAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
Task ChangeMailReadStatusAsync(string mailCopyId, bool isRead);
Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged);
@@ -53,6 +52,7 @@ namespace Wino.Core.Integration.Processors
public interface IGmailChangeProcessor : IDefaultChangeProcessor
{
Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId);
Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
}
public interface IOutlookChangeProcessor : IDefaultChangeProcessor
@@ -135,8 +135,7 @@ namespace Wino.Core.Integration.Processors
public Task DeleteAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId)
=> MailService.DeleteAssignmentAsync(accountId, mailCopyId, remoteFolderId);
public Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId)
=> MailService.CreateAssignmentAsync(accountId, mailCopyId, remoteFolderId);
public Task DeleteMailAsync(Guid accountId, string mailId)
=> MailService.DeleteMailAsync(accountId, mailId);

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Services;
@@ -12,5 +13,8 @@ namespace Wino.Core.Integration.Processors
public Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId)
=> MailService.MapLocalDraftAsync(mailCopyId, newDraftId, newThreadId);
public Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId)
=> MailService.CreateAssignmentAsync(accountId, mailCopyId, remoteFolderId);
}
}

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Services;
@@ -27,7 +28,7 @@ namespace Wino.Core.Integration.Threading
}
///<inheritdoc/>
public async Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items)
public async Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items, IMailItemFolder threadingForFolder)
{
var assignedAccount = items[0].AssignedAccount;
@@ -62,11 +63,43 @@ namespace Wino.Core.Integration.Threading
}
var thread = new ThreadMailItem();
foreach (var childThreadItem in threadItem)
{
thread.AddThreadItem(childThreadItem);
if (thread.ThreadItems.Any(a => a.Id == childThreadItem.Id))
{
// Mail already exist in the thread.
// There should be only 1 instance of the mail in the thread.
// Make sure we add the correct one.
// Add the one with threading folder.
var threadingFolderItem = threadItem.FirstOrDefault(a => a.Id == childThreadItem.Id && a.FolderId == threadingForFolder.Id);
if (threadingFolderItem == null) continue;
// Remove the existing one.
thread.ThreadItems.Remove(thread.ThreadItems.First(a => a.Id == childThreadItem.Id));
// Add the correct one for listing.
thread.AddThreadItem(threadingFolderItem);
}
else
{
thread.AddThreadItem(childThreadItem);
}
}
if (thread.ThreadItems.Count > 1)
{
resultList.Add(thread);
}
else
{
// Don't make threads if the thread has only one item.
// Gmail has may have multiple assignments for the same item.
resultList.Add(thread.ThreadItems.First());
}
resultList.Add(thread);
}
}

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using SqlKata;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Extensions;
using Wino.Core.Services;
@@ -58,7 +59,7 @@ namespace Wino.Core.Integration.Threading
return _databaseService.Connection.FindWithQueryAsync<MailCopy>(query.GetRawQuery());
}
public async Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items)
public async Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items, IMailItemFolder threadingForFolder)
{
var threads = new List<ThreadMailItem>();

View File

@@ -192,10 +192,12 @@ namespace Wino.Core.MenuItems
item.IsExpanded = false;
item.IsSelected = false;
Remove(item);
try
{
Remove(item);
}
catch (Exception) { }
});
// RemoveRange(itemsToRemove);
}
}
}

View File

@@ -58,7 +58,7 @@ namespace Wino.Core.Requests
Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item)));
}
public override int ResynchronizationDelay => 10000;
public override int ResynchronizationDelay => 6000;
public override bool ExecuteSerialBatch => true;
}
}

View File

@@ -80,27 +80,26 @@ namespace Wino.Core.Services
}
else
{
bool isAllFlagged = selectedMailItems.All(a => a.IsFlagged);
bool isAllRead = selectedMailItems.All(a => a.IsRead);
bool isAllUnread = selectedMailItems.All(a => !a.IsRead);
bool isAllFlagged = selectedMailItems.All(a => a.IsFlagged);
bool isAllNotFlagged = selectedMailItems.All(a => !a.IsFlagged);
if (isAllRead)
operationList.Add(MailOperationMenuItem.Create(MailOperation.MarkAsUnread));
else
List<MailOperationMenuItem> readOperations = (isAllRead, isAllUnread) switch
{
if (!isAllUnread)
operationList.Add(MailOperationMenuItem.Create(MailOperation.MarkAsUnread));
(true, false) => [MailOperationMenuItem.Create(MailOperation.MarkAsUnread)],
(false, true) => [MailOperationMenuItem.Create(MailOperation.MarkAsRead)],
_ => [MailOperationMenuItem.Create(MailOperation.MarkAsRead), MailOperationMenuItem.Create(MailOperation.MarkAsUnread)]
};
operationList.AddRange(readOperations);
operationList.Add(MailOperationMenuItem.Create(MailOperation.MarkAsRead));
}
if (isAllFlagged)
operationList.Add(MailOperationMenuItem.Create(MailOperation.ClearFlag));
else
List<MailOperationMenuItem> flagsOperations = (isAllFlagged, isAllNotFlagged) switch
{
operationList.Add(MailOperationMenuItem.Create(MailOperation.ClearFlag));
operationList.Add(MailOperationMenuItem.Create(MailOperation.SetFlag));
}
(true, false) => [MailOperationMenuItem.Create(MailOperation.ClearFlag)],
(false, true) => [MailOperationMenuItem.Create(MailOperation.SetFlag)],
_ => [MailOperationMenuItem.Create(MailOperation.SetFlag), MailOperationMenuItem.Create(MailOperation.ClearFlag)]
};
operationList.AddRange(flagsOperations);
}
// Ignore

View File

@@ -241,7 +241,7 @@ namespace Wino.Core.Services
// Only thread items from Draft and Sent folders must present here.
// Otherwise this strategy will fetch the items that are in Deleted folder as well.
var accountThreadedItems = await threadingStrategy.ThreadItemsAsync([.. group]);
var accountThreadedItems = await threadingStrategy.ThreadItemsAsync([.. group], options.Folders.First());
// Populate threaded items with folder and account assignments.
// Almost everything already should be in cache from initial population.
@@ -298,7 +298,11 @@ namespace Wino.Core.Services
}
}
bool isContactCached = contactCache.TryGetValue(mailCopy.FromAddress, out AccountContact contactAssignment);
AccountContact contactAssignment = null;
bool isContactCached = !string.IsNullOrEmpty(mailCopy.FromAddress) ?
contactCache.TryGetValue(mailCopy.FromAddress, out contactAssignment) :
false;
if (!isContactCached && accountAssignment != null)
{
@@ -312,11 +316,33 @@ namespace Wino.Core.Services
mailCopy.AssignedFolder = folderAssignment;
mailCopy.AssignedAccount = accountAssignment;
mailCopy.SenderContact = contactAssignment ?? new AccountContact() { Name = mailCopy.FromName, Address = mailCopy.FromAddress };
mailCopy.SenderContact = contactAssignment ?? CreateUnknownContact(mailCopy.FromName, mailCopy.FromAddress);
}
}
}
private AccountContact CreateUnknownContact(string fromName, string fromAddress)
{
if (string.IsNullOrEmpty(fromName) && string.IsNullOrEmpty(fromAddress))
{
return new AccountContact()
{
Name = Translator.UnknownSender,
Address = Translator.UnknownAddress
};
}
else
{
if (string.IsNullOrEmpty(fromName)) fromName = fromAddress;
return new AccountContact()
{
Name = fromName,
Address = fromAddress
};
}
}
private async Task<List<MailCopy>> GetMailItemsAsync(string mailCopyId)
{
var mailCopies = await Connection.Table<MailCopy>().Where(a => a.Id == mailCopyId).ToListAsync();
@@ -409,12 +435,11 @@ namespace Wino.Core.Services
foreach (var mailItem in allMails)
{
await DeleteMailInternalAsync(mailItem).ConfigureAwait(false);
// Delete mime file.
// Delete mime file as well.
// Even though Gmail might have multiple copies for the same mail, we only have one MIME file for all.
// Their FileId is inserted same.
await _mimeFileService.DeleteMimeMessageAsync(accountId, mailItem.FileId);
await DeleteMailInternalAsync(mailItem, preserveMimeFile: false).ConfigureAwait(false);
}
}
@@ -457,7 +482,7 @@ namespace Wino.Core.Services
ReportUIChange(new MailUpdatedMessage(mailCopy));
}
private async Task DeleteMailInternalAsync(MailCopy mailCopy)
private async Task DeleteMailInternalAsync(MailCopy mailCopy, bool preserveMimeFile)
{
if (mailCopy == null)
{
@@ -473,7 +498,7 @@ namespace Wino.Core.Services
// If there are no more copies exists of the same mail, delete the MIME file as well.
var isMailExists = await IsMailExistsAsync(mailCopy.Id).ConfigureAwait(false);
if (!isMailExists)
if (!isMailExists && !preserveMimeFile)
{
await _mimeFileService.DeleteMimeMessageAsync(mailCopy.AssignedAccount.Id, mailCopy.FileId).ConfigureAwait(false);
}
@@ -554,6 +579,19 @@ namespace Wino.Core.Services
return;
}
if (mailCopy.AssignedFolder.SpecialFolderType == SpecialFolderType.Sent &&
localFolder.SpecialFolderType == SpecialFolderType.Deleted)
{
// Sent item is deleted.
// Gmail does not delete the sent items, but moves them to the deleted folder.
// API doesn't allow removing Sent label.
// Here we intercept this behavior, removing the Sent copy of the mail and adding the Deleted copy.
// This way item will only be visible in Trash folder as in Gmail Web UI.
// Don't delete MIME file since if exists.
await DeleteMailInternalAsync(mailCopy, preserveMimeFile: true).ConfigureAwait(false);
}
// Copy one of the mail copy and assign it to the new folder.
// We don't need to create a new MIME pack.
// Therefore FileId is not changed for the new MailCopy.
@@ -585,7 +623,7 @@ namespace Wino.Core.Services
return;
}
await DeleteMailInternalAsync(mailItem).ConfigureAwait(false);
await DeleteMailInternalAsync(mailItem, preserveMimeFile: false).ConfigureAwait(false);
}
public async Task<bool> CreateMailAsync(Guid accountId, NewMailItemPackage package)

View File

@@ -595,13 +595,23 @@ namespace Wino.Core.Synchronizers
{
return CreateBatchedHttpBundleFromGroup(request, (items) =>
{
// Sent label can't be removed from mails for Gmail.
// They are automatically assigned by Gmail.
// When you delete sent mail from gmail web portal, it's moved to Trash
// but still has Sent label. It's just hidden from the user.
// Proper assignments will be done later on CreateAssignment call to mimic this behavior.
var batchModifyRequest = new BatchModifyMessagesRequest
{
Ids = items.Select(a => a.Item.Id.ToString()).ToList(),
AddLabelIds = new[] { request.ToFolder.RemoteFolderId },
RemoveLabelIds = new[] { request.FromFolder.RemoteFolderId }
AddLabelIds = [request.ToFolder.RemoteFolderId]
};
// Only add remove label ids if the source folder is not sent folder.
if (request.FromFolder.SpecialFolderType != SpecialFolderType.Sent)
{
batchModifyRequest.RemoveLabelIds = [request.FromFolder.RemoteFolderId];
}
return _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me");
});
}
@@ -793,6 +803,8 @@ namespace Wino.Core.Synchronizers
var bundleRequestCount = bundle.Count();
var bundleTasks = new List<Task>();
for (int k = 0; k < bundleRequestCount; k++)
{
var requestBundle = bundle.ElementAt(k);
@@ -802,37 +814,40 @@ namespace Wino.Core.Synchronizers
request.ApplyUIChanges();
// TODO: Queue is synchronous. Create a task bucket to await all processing.
nativeBatchRequest.Queue<object>(nativeRequest, async (content, error, index, message)
=> await ProcessSingleNativeRequestResponseAsync(requestBundle, error, message, cancellationToken).ConfigureAwait(false));
nativeBatchRequest.Queue<object>(nativeRequest, (content, error, index, message)
=> bundleTasks.Add(ProcessSingleNativeRequestResponseAsync(requestBundle, error, message, cancellationToken)));
}
await nativeBatchRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false);
await Task.WhenAll(bundleTasks);
}
}
private void ProcessGmailRequestError(RequestError error)
private void ProcessGmailRequestError(RequestError error, IRequestBundle<IClientServiceRequest> bundle)
{
if (error == null) return;
// OutOfMemoryException is a known bug in Gmail SDK.
if (error.Code == 0)
{
bundle?.Request.RevertUIChanges();
throw new OutOfMemoryException(error.Message);
}
// Entity not found.
if (error.Code == 404)
{
bundle?.Request.RevertUIChanges();
throw new SynchronizerEntityNotFoundException(error.Message);
}
if (!string.IsNullOrEmpty(error.Message))
{
bundle?.Request.RevertUIChanges();
error.Errors?.ForEach(error => _logger.Error("Unknown Gmail SDK error for {Name}\n{Error}", Account.Name, error));
// TODO: Debug
// throw new SynchronizerException(error.Message);
throw new SynchronizerException(error.Message);
}
}
@@ -851,7 +866,7 @@ namespace Wino.Core.Synchronizers
{
try
{
ProcessGmailRequestError(error);
ProcessGmailRequestError(error, null);
}
catch (OutOfMemoryException)
{
@@ -900,7 +915,7 @@ namespace Wino.Core.Synchronizers
HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default)
{
ProcessGmailRequestError(error);
ProcessGmailRequestError(error, bundle);
if (bundle is HttpRequestBundle<IClientServiceRequest, Message> messageBundle)
{

View File

@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Graph.Models.ODataErrors;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
@@ -473,13 +474,27 @@ namespace Wino.Core.Synchronizers
/// <returns>Base64 encoded profile picture.</returns>
private async Task<string> GetUserProfilePictureAsync()
{
var photoStream = await _graphClient.Me.Photos["48x48"].Content.GetAsync();
try
{
var photoStream = await _graphClient.Me.Photos["48x48"].Content.GetAsync();
using var memoryStream = new MemoryStream();
await photoStream.CopyToAsync(memoryStream);
var byteArray = memoryStream.ToArray();
using var memoryStream = new MemoryStream();
await photoStream.CopyToAsync(memoryStream);
var byteArray = memoryStream.ToArray();
return Convert.ToBase64String(byteArray);
return Convert.ToBase64String(byteArray);
}
catch (ODataError odataError) when (odataError.Error.Code == "ImageNotFound")
{
// Accounts without profile picture will throw this error.
// At this point nothing we can do. Just return empty string.
return string.Empty;
}
catch (Exception)
{
throw;
}
}
/// <summary>

View File

@@ -18,7 +18,7 @@
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Google.Apis.Gmail.v1" Version="1.68.0.3427" />
<PackageReference Include="Google.Apis.PeopleService.v1" Version="1.68.0.3359" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.63" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.64" />
<PackageReference Include="HtmlKit" Version="1.1.0" />
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
@@ -31,12 +31,12 @@
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.64.0" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.64.0" />
<PackageReference Include="MimeKit" Version="4.7.1" />
<PackageReference Include="morelinq" Version="4.1.0" />
<PackageReference Include="morelinq" Version="4.3.0" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.8" />
<PackageReference Include="SqlKata" Version="2.4.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />

View File

@@ -41,8 +41,7 @@ namespace Wino.Mail.ViewModels
IRecipient<MailItemSelectionRemovedEvent>,
IRecipient<AccountSynchronizationCompleted>,
IRecipient<NewSynchronizationRequested>,
IRecipient<AccountSynchronizerStateChanged>,
IRecipient<SelectedMailItemsChanged>
IRecipient<AccountSynchronizerStateChanged>
{
private bool isChangingFolder = false;
@@ -63,6 +62,7 @@ namespace Wino.Mail.ViewModels
public ObservableCollection<MailItemViewModel> SelectedItems { get; set; } = [];
public ObservableCollection<FolderPivotViewModel> PivotFolders { get; set; } = [];
public ObservableCollection<MailOperationMenuItem> ActionItems { get; set; } = [];
private readonly SemaphoreSlim listManipulationSemepahore = new SemaphoreSlim(1);
private CancellationTokenSource listManipulationCancellationTokenSource = new CancellationTokenSource();
@@ -200,6 +200,13 @@ namespace Wino.Mail.ViewModels
};
}
private void SetupTopBarActions()
{
ActionItems.Clear();
var actions = GetAvailableMailActions(SelectedItems);
actions.ForEach(a => ActionItems.Add(a));
}
#region Properties
/// <summary>
@@ -365,7 +372,7 @@ namespace Wino.Mail.ViewModels
NotifyItemSelected();
Messenger.Send(new SelectedMailItemsChanged(SelectedItems.Count));
SetupTopBarActions();
}
private void UpdateFolderPivots()
@@ -415,19 +422,31 @@ namespace Wino.Mail.ViewModels
[RelayCommand]
public Task ExecuteHoverAction(MailOperationPreperationRequest request) => ExecuteMailOperationAsync(request);
[RelayCommand]
private async Task ExecuteTopBarAction(MailOperationMenuItem menuItem)
{
if (menuItem == null || !SelectedItems.Any()) return;
await HandleMailOperation(menuItem.Operation, SelectedItems);
}
/// <summary>
/// Executes the requested mail operation for currently selected items.
/// </summary>
/// <param name="operation">Action to execute for selected items.</param>
[RelayCommand]
private async Task MailOperationAsync(int mailOperationIndex)
private async Task ExecuteMailOperation(MailOperation mailOperation)
{
if (!SelectedItems.Any()) return;
// Commands don't like enums. So it has to be int.
var operation = (MailOperation)mailOperationIndex;
await HandleMailOperation(mailOperation, SelectedItems);
}
var package = new MailOperationPreperationRequest(operation, SelectedItems.Select(a => a.MailCopy));
private async Task HandleMailOperation(MailOperation mailOperation, IEnumerable<MailItemViewModel> mailItems)
{
if (!mailItems.Any()) return;
var package = new MailOperationPreperationRequest(mailOperation, mailItems.Select(a => a.MailCopy));
await ExecuteMailOperationAsync(package);
}
@@ -649,6 +668,8 @@ namespace Wino.Mail.ViewModels
Debug.WriteLine($"Updating {updatedMail.Id}-> {updatedMail.UniqueId}");
await MailCollection.UpdateMailCopy(updatedMail);
await ExecuteUIThread(() => { SetupTopBarActions(); });
}
protected override async void OnMailRemoved(MailCopy removedMail)
@@ -1012,7 +1033,5 @@ namespace Wino.Mail.ViewModels
await ExecuteUIThread(() => { IsAccountSynchronizerInSynchronization = isAnyAccountSynchronizing; });
}
public void Receive(SelectedMailItemsChanged message) => NotifyItemSelected();
}
}

View File

@@ -221,21 +221,9 @@ namespace Wino.Mail.ViewModels
_themeService.AccentColorChanged -= AccentColorChanged;
_themeService.ElementThemeChanged -= ElementThemeChanged;
_themeService.AccentColorChangedBySystem -= AccentColorChangedBySystem;
_themeService.AccentColorChanged += AccentColorChanged;
_themeService.ElementThemeChanged += ElementThemeChanged;
_themeService.AccentColorChangedBySystem += AccentColorChangedBySystem;
}
private void AccentColorChangedBySystem(object sender, string newAccentColorHex)
{
var accentInList = Colors.FirstOrDefault(a => a.IsAccentColor);
if (accentInList != null)
{
accentInList.Hex = newAccentColorHex;
}
}
private void AccentColorChanged(object sender, string e)
@@ -269,7 +257,6 @@ namespace Wino.Mail.ViewModels
_themeService.AccentColorChanged -= AccentColorChanged;
_themeService.ElementThemeChanged -= ElementThemeChanged;
_themeService.AccentColorChangedBySystem -= AccentColorChangedBySystem;
if (AppThemes != null)
{

View File

@@ -12,8 +12,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="5.0.4" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="5.0.5" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -11,6 +11,8 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<ResourceDictionary Source="/Styles/Converters.xaml" />
<ResourceDictionary Source="/Styles/FontIcons.xaml" />
<ResourceDictionary Source="/Styles/Colors.xaml" />
@@ -19,7 +21,9 @@
<ResourceDictionary Source="/Styles/CommandBarItems.xaml" />
<ResourceDictionary Source="/Styles/ItemContainerStyles.xaml" />
<ResourceDictionary Source="/Styles/WinoInfoBar.xaml" />
<styles:CustomMessageDialogStyles />
<styles:WinoExpanderStyle />
<ResourceDictionary>

View File

@@ -1,8 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml.Controls;
using MoreLinq;
using Serilog;
using Windows.UI.Xaml;
@@ -48,8 +48,15 @@ namespace Wino.Controls.Advanced
set { SetValue(LoadMoreCommandProperty, value); }
}
public bool IsThreadScrollingEnabled
{
get { return (bool)GetValue(IsThreadScrollingEnabledProperty); }
set { SetValue(IsThreadScrollingEnabledProperty, value); }
}
public static readonly DependencyProperty IsThreadScrollingEnabledProperty = DependencyProperty.Register(nameof(IsThreadScrollingEnabled), typeof(bool), typeof(WinoListView), new PropertyMetadata(false));
public static readonly DependencyProperty LoadMoreCommandProperty = DependencyProperty.Register(nameof(LoadMoreCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
public static readonly DependencyProperty IsThreadListViewProperty = DependencyProperty.Register(nameof(IsThreadListView), typeof(bool), typeof(WinoListView), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadListViewProperty = DependencyProperty.Register(nameof(IsThreadListView), typeof(bool), typeof(WinoListView), new PropertyMetadata(false, new PropertyChangedCallback(OnIsThreadViewChanged)));
public static readonly DependencyProperty ItemDeletedCommandProperty = DependencyProperty.Register(nameof(ItemDeletedCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
public WinoListView()
@@ -65,7 +72,6 @@ namespace Wino.Controls.Advanced
DragItemsCompleted += ItemDragCompleted;
DragItemsStarting += ItemDragStarting;
SelectionChanged += SelectedItemsChanged;
ItemClick += MailItemClicked;
ProcessKeyboardAccelerators += ProcessDelKey;
}
@@ -85,6 +91,22 @@ namespace Wino.Controls.Advanced
internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
}
private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoListView winoListView)
{
winoListView.AdjustThreadViewContainerVisuals();
}
}
private void AdjustThreadViewContainerVisuals()
{
if (IsThreadListView)
{
ItemContainerTransitions.Clear();
}
}
private double lastestRaisedOffset = 0;
private int lastItemSize = 0;
@@ -120,7 +142,7 @@ namespace Wino.Controls.Advanced
{
args.Handled = true;
ItemDeletedCommand?.Execute((int)MailOperation.SoftDelete);
ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
}
}
@@ -160,19 +182,6 @@ namespace Wino.Controls.Advanced
}
}
private void MailItemClicked(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is ThreadMailItemViewModel clickedThread)
{
clickedThread.IsThreadExpanded = !clickedThread.IsThreadExpanded;
if (!clickedThread.IsThreadExpanded)
{
SelectedItems.Clear();
}
}
}
public void ChangeSelectionMode(ListViewSelectionMode selectionMode)
{
SelectionMode = selectionMode;
@@ -294,6 +303,10 @@ namespace Wino.Controls.Advanced
removedMailItemViewModel.IsSelected = false;
WeakReferenceMessenger.Default.Send(new MailItemSelectionRemovedEvent(removedMailItemViewModel));
}
else if (removedItem is ThreadMailItemViewModel removedThreadItemViewModel)
{
removedThreadItemViewModel.IsThreadExpanded = false;
}
}
}
@@ -311,10 +324,19 @@ namespace Wino.Controls.Advanced
}
else if (addedItem is ThreadMailItemViewModel threadMailItemViewModel)
{
threadMailItemViewModel.IsThreadExpanded = true;
if (IsThreadScrollingEnabled)
{
if (internalScrollviewer != null && ContainerFromItem(threadMailItemViewModel) is FrameworkElement threadFrameworkElement)
{
internalScrollviewer.ScrollToElement(threadFrameworkElement, true, true, bringToTopOrLeft: true);
}
}
// Don't select thread containers.
SelectedItems.Remove(addedItem);
// Try to select first item.
if (GetThreadInternalListView(threadMailItemViewModel) is WinoListView internalListView)
{
internalListView.SelectFirstItem();
}
}
}
}
@@ -336,16 +358,23 @@ namespace Wino.Controls.Advanced
});
}
}
else
{
if (SelectionMode == ListViewSelectionMode.Extended && SelectedItems.Count == 1)
{
// Tell main list view to unselect all his items.
}
if (SelectedItems[0] is MailItemViewModel selectedMailItemViewModel)
public async void SelectFirstItem()
{
if (Items.Count > 0)
{
if (Items[0] is MailItemViewModel firstMailItemViewModel)
{
// Make sure the invisible container is realized.
await Task.Delay(250);
if (ContainerFromItem(firstMailItemViewModel) is ListViewItem firstItemContainer)
{
WeakReferenceMessenger.Default.Send(new ResetSingleMailItemSelectionEvent(selectedMailItemViewModel));
firstItemContainer.IsSelected = true;
}
firstMailItemViewModel.IsSelected = true;
}
}
}
@@ -356,7 +385,7 @@ namespace Wino.Controls.Advanced
if (itemContainer is ListViewItem listItem)
{
var expander = listItem.GetChildByName<Expander>("ThreadExpander");
var expander = listItem.GetChildByName<WinoExpander>("ThreadExpander");
if (expander != null)
return expander.Content as WinoListView;
@@ -370,7 +399,6 @@ namespace Wino.Controls.Advanced
DragItemsCompleted -= ItemDragCompleted;
DragItemsStarting -= ItemDragStarting;
SelectionChanged -= SelectedItemsChanged;
ItemClick -= MailItemClicked;
ProcessKeyboardAccelerators -= ProcessDelKey;
if (internalScrollviewer != null)

View File

@@ -80,8 +80,6 @@ namespace Wino.Controls
control.UpdateInformation();
}
private async void UpdateInformation()
{
if (KnownHostImage == null || InitialsGrid == null || InitialsTextblock == null || (string.IsNullOrEmpty(FromName) && string.IsNullOrEmpty(FromAddress)))

View File

@@ -2,15 +2,21 @@
x:Class="Wino.Controls.MailItemDisplayInformationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
xmlns:controls="using:Wino.Controls"
xmlns:enums="using:Wino.Core.Domain.Enums"
xmlns:domain="using:Wino.Core.Domain"
xmlns:enums="using:Wino.Core.Domain.Enums"
xmlns:helpers="using:Wino.Helpers"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
FocusVisualMargin="8"
FocusVisualPrimaryBrush="{StaticResource SystemControlRevealFocusVisualBrush}"
FocusVisualPrimaryThickness="2"
FocusVisualSecondaryBrush="{StaticResource SystemControlFocusVisualSecondaryBrush}"
FocusVisualSecondaryThickness="1"
xmlns:helpers="using:Wino.Helpers"
PointerEntered="ControlPointerEntered"
PointerExited="ControlPointerExited">
@@ -25,26 +31,24 @@
</Style>
</UserControl.Resources>
<Grid Background="Transparent" Tapped="ThreadHeaderTapped">
<Grid.RowDefinitions>
<RowDefinition x:Name="ContainerHeight" Height="Auto" />
</Grid.RowDefinitions>
<Grid>
<Grid
x:Name="RootContainer"
Padding="0,1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
x:DefaultBindMode="OneWay">
<!-- Custom Interaction Focus Indicator -->
<Ellipse
Width="8"
Height="8"
Canvas.ZIndex="9999"
Margin="0,12,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Visibility="{x:Bind IsCustomFocused, Mode=OneWay}"
Fill="{ThemeResource SystemAccentColor}" />
Canvas.ZIndex="9999"
Fill="{ThemeResource SystemAccentColor}"
Visibility="{x:Bind IsCustomFocused, Mode=OneWay}" />
<Border
x:Name="RootContainerVisualWrapper"
@@ -66,9 +70,9 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="14"
SenderContactPicture="{x:Bind MailItem.SenderContact.Base64ContactPicture}"
FromAddress="{x:Bind MailItem.FromAddress, Mode=OneWay}"
FromName="{x:Bind MailItem.FromName, Mode=OneWay}"
SenderContactPicture="{x:Bind MailItem.SenderContact.Base64ContactPicture}"
Visibility="{x:Bind IsAvatarVisible, Mode=OneWay}" />
<Grid
@@ -145,20 +149,47 @@
</Grid>
<!-- Subject + IsDraft -->
<Grid Grid.Row="1" ColumnSpacing="4">
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:AnimatedIcon
xmlns:local="using:Microsoft.UI.Xaml.Controls"
x:Name="ExpandCollapseChevron"
Width="14"
Height="14"
Margin="-4,0,2,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
local:AnimatedIcon.State="NormalOff"
AutomationProperties.AccessibilityView="Raw"
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
RenderTransformOrigin="0.5, 0.5"
Visibility="{x:Bind IsThreadExpanderVisible, Mode=OneWay}">
<animatedvisuals:AnimatedChevronRightDownSmallVisualSource />
<local:AnimatedIcon.FallbackIconSource>
<local:FontIconSource
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xE76C;"
IsTextScaleFactorEnabled="False" />
</local:AnimatedIcon.FallbackIconSource>
<local:AnimatedIcon.RenderTransform />
</local:AnimatedIcon>
<TextBlock
x:Name="TitleText"
Grid.Column="1"
MaxLines="1"
Text="{x:Bind MailItem.Subject}"
TextTrimming="CharacterEllipsis" />
<TextBlock
Grid.Column="1"
Grid.Column="2"
Margin="4,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="11"
@@ -231,7 +262,7 @@
<VisualStateGroup x:Name="SizingStates">
<VisualState x:Name="Compact">
<VisualState.Setters>
<Setter Target="ContainerHeight.Height" Value="50" />
<Setter Target="RootContainer.Height" Value="60" />
<Setter Target="ContentGrid.Padding" Value="8,0" />
<Setter Target="PreviewTextContainer.Visibility" Value="Collapsed" />
</VisualState.Setters>
@@ -243,7 +274,7 @@
<!-- Medium -->
<VisualState x:Name="Medium">
<VisualState.Setters>
<Setter Target="ContainerHeight.Height" Value="65" />
<Setter Target="RootContainer.Height" Value="80" />
<Setter Target="ContentGrid.Padding" Value="6,0" />
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
</VisualState.Setters>
@@ -255,7 +286,7 @@
<!-- Spacious -->
<VisualState x:Name="Spacious">
<VisualState.Setters>
<Setter Target="ContainerHeight.Height" Value="Auto" />
<Setter Target="RootContainer.Height" Value="Auto" />
<Setter Target="ContentGrid.Padding" Value="12,12,6,12" />
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
</VisualState.Setters>
@@ -277,6 +308,19 @@
</VisualState.StateTriggers>
</VisualState>
</VisualStateGroup>
<!-- Thread Expanding States -->
<VisualStateGroup x:Name="ExpanderStates">
<VisualState x:Name="NotExpanded" />
<VisualState x:Name="ExpandedState">
<VisualState.Setters>
<Setter Target="ExpandCollapseChevron.(controls:AnimatedIcon.State)" Value="NormalOn" />
</VisualState.Setters>
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind IsThreadExpanded, Mode=OneWay}" />
</VisualState.StateTriggers>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -1,9 +1,8 @@
using System.Numerics;
using System.Linq;
using System.Numerics;
using System.Windows.Input;
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
@@ -16,12 +15,14 @@ namespace Wino.Controls
{
public ImagePreviewControl GetImagePreviewControl() => ContactImage;
public bool IsRunningHoverAction { get; set; }
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(nameof(DisplayMode), typeof(MailListDisplayMode), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailListDisplayMode.Spacious));
public static readonly DependencyProperty ShowPreviewTextProperty = DependencyProperty.Register(nameof(ShowPreviewText), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty IsCustomFocusedProperty = DependencyProperty.Register(nameof(IsCustomFocused), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsAvatarVisibleProperty = DependencyProperty.Register(nameof(IsAvatarVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty IsSubjectVisibleProperty = DependencyProperty.Register(nameof(IsSubjectVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty ConnectedExpanderProperty = DependencyProperty.Register(nameof(ConnectedExpander), typeof(Expander), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty ConnectedExpanderProperty = DependencyProperty.Register(nameof(ConnectedExpander), typeof(WinoExpander), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty LeftHoverActionProperty = DependencyProperty.Register(nameof(LeftHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty CenterHoverActionProperty = DependencyProperty.Register(nameof(CenterHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
public static readonly DependencyProperty RightHoverActionProperty = DependencyProperty.Register(nameof(RightHoverAction), typeof(MailOperation), typeof(MailItemDisplayInformationControl), new PropertyMetadata(MailOperation.None));
@@ -29,6 +30,20 @@ namespace Wino.Controls
public static readonly DependencyProperty MailItemProperty = DependencyProperty.Register(nameof(MailItem), typeof(IMailItem), typeof(MailItemDisplayInformationControl), new PropertyMetadata(null));
public static readonly DependencyProperty IsHoverActionsEnabledProperty = DependencyProperty.Register(nameof(IsHoverActionsEnabled), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(true));
public static readonly DependencyProperty Prefer24HourTimeFormatProperty = DependencyProperty.Register(nameof(Prefer24HourTimeFormat), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadExpanderVisibleProperty = DependencyProperty.Register(nameof(IsThreadExpanderVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsThreadExpandedProperty = DependencyProperty.Register(nameof(IsThreadExpanded), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
public bool IsThreadExpanded
{
get { return (bool)GetValue(IsThreadExpandedProperty); }
set { SetValue(IsThreadExpandedProperty, value); }
}
public bool IsThreadExpanderVisible
{
get { return (bool)GetValue(IsThreadExpanderVisibleProperty); }
set { SetValue(IsThreadExpanderVisibleProperty, value); }
}
public bool Prefer24HourTimeFormat
{
@@ -72,10 +87,9 @@ namespace Wino.Controls
set { SetValue(RightHoverActionProperty, value); }
}
public Expander ConnectedExpander
public WinoExpander ConnectedExpander
{
get { return (Expander)GetValue(ConnectedExpanderProperty); }
get { return (WinoExpander)GetValue(ConnectedExpanderProperty); }
set { SetValue(ConnectedExpanderProperty, value); }
}
@@ -109,8 +123,6 @@ namespace Wino.Controls
set { SetValue(DisplayModeProperty, value); }
}
private bool tappedHandlingFlag = false;
public MailItemDisplayInformationControl()
{
this.InitializeComponent();
@@ -147,38 +159,22 @@ namespace Wino.Controls
private void ExecuteHoverAction(MailOperation operation)
{
IsRunningHoverAction = true;
MailOperationPreperationRequest package = null;
if (MailItem is MailCopy mailCopy)
package = new MailOperationPreperationRequest(operation, mailCopy, toggleExecution: true);
else if (MailItem is ThreadMailItemViewModel threadMailItemViewModel)
package = new MailOperationPreperationRequest(operation, threadMailItemViewModel.GetMailCopies(), toggleExecution: true);
else if (MailItem is ThreadMailItem threadMailItem)
package = new MailOperationPreperationRequest(operation, threadMailItem.ThreadItems.Cast<MailItemViewModel>().Select(a => a.MailCopy), toggleExecution: true);
if (package == null) return;
tappedHandlingFlag = true;
HoverActionExecutedCommand?.Execute(package);
}
private void ThreadHeaderTapped(object sender, TappedRoutedEventArgs e)
{
// Due to CanDrag=True, outer expander doesn't get the click event and it doesn't expand. We expand here manually.
// Also hover action button clicks will be delegated here after the execution runs.
// We should not expand the thread if the reason we are here is for hover actions.
if (tappedHandlingFlag)
{
tappedHandlingFlag = false;
e.Handled = true;
return;
}
if (ConnectedExpander == null) return;
ConnectedExpander.IsExpanded = !ConnectedExpander.IsExpanded;
}
private void FirstActionClicked(object sender, RoutedEventArgs e)
{
ExecuteHoverAction(LeftHoverAction);

View File

@@ -1,17 +1,129 @@
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using CommunityToolkit.Diagnostics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Markup;
namespace Wino.Controls
{
public class WinoExpander : Expander
[ContentProperty(Name = nameof(Content))]
public class WinoExpander : Control
{
private const string PART_HeaderGrid = "HeaderGrid";
private const string PART_ContentAreaWrapper = "ContentAreaWrapper";
private const string PART_ContentArea = "ContentArea";
private ContentControl HeaderGrid;
private ContentControl ContentArea;
private Grid ContentAreaWrapper;
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(nameof(Header), typeof(UIElement), typeof(WinoExpander), new PropertyMetadata(null));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(UIElement), typeof(WinoExpander), new PropertyMetadata(null));
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(WinoExpander), new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));
public static readonly DependencyProperty TemplateSettingsProperty = DependencyProperty.Register(nameof(TemplateSettings), typeof(WinoExpanderTemplateSettings), typeof(WinoExpander), new PropertyMetadata(new WinoExpanderTemplateSettings()));
public UIElement Content
{
get { return (UIElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public WinoExpanderTemplateSettings TemplateSettings
{
get { return (WinoExpanderTemplateSettings)GetValue(TemplateSettingsProperty); }
set { SetValue(TemplateSettingsProperty, value); }
}
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
public UIElement Header
{
get { return (UIElement)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (GetTemplateChild("ExpanderHeader") is ToggleButton toggleButton)
HeaderGrid = GetTemplateChild(PART_HeaderGrid) as ContentControl;
ContentAreaWrapper = GetTemplateChild(PART_ContentAreaWrapper) as Grid;
ContentArea = GetTemplateChild(PART_ContentArea) as ContentControl;
Guard.IsNotNull(HeaderGrid, nameof(HeaderGrid));
Guard.IsNotNull(ContentAreaWrapper, nameof(ContentAreaWrapper));
Guard.IsNotNull(ContentArea, nameof(ContentArea));
var clipComposition = ElementCompositionPreview.GetElementVisual(ContentAreaWrapper);
clipComposition.Clip = clipComposition.Compositor.CreateInsetClip();
ContentAreaWrapper.SizeChanged += ContentSizeChanged;
HeaderGrid.Tapped += HeaderTapped;
}
private void ContentSizeChanged(object sender, SizeChangedEventArgs e)
{
TemplateSettings.ContentHeight = e.NewSize.Height;
TemplateSettings.NegativeContentHeight = -1 * (double)e.NewSize.Height;
}
private void HeaderTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
// Tapped is delegated from executing hover action like flag or delete.
// No need to toggle the expander.
if (Header is MailItemDisplayInformationControl itemDisplayInformationControl &&
itemDisplayInformationControl.IsRunningHoverAction)
{
toggleButton.Padding = new Windows.UI.Xaml.Thickness(0, 4, 0, 4);
itemDisplayInformationControl.IsRunningHoverAction = false;
return;
}
IsExpanded = !IsExpanded;
}
private static void OnIsExpandedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoExpander control)
control.UpdateVisualStates();
}
private void UpdateVisualStates()
{
VisualStateManager.GoToState(this, IsExpanded ? "Expanded" : "Collapsed", true);
}
}
#region Settings
public class WinoExpanderTemplateSettings : DependencyObject
{
public static readonly DependencyProperty HeaderHeightProperty = DependencyProperty.Register(nameof(HeaderHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.Register(nameof(ContentHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public static readonly DependencyProperty NegativeContentHeightProperty = DependencyProperty.Register(nameof(NegativeContentHeight), typeof(double), typeof(WinoExpanderTemplateSettings), new PropertyMetadata(0.0));
public double NegativeContentHeight
{
get { return (double)GetValue(NegativeContentHeightProperty); }
set { SetValue(NegativeContentHeightProperty, value); }
}
public double HeaderHeight
{
get { return (double)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
public double ContentHeight
{
get { return (double)GetValue(ContentHeightProperty); }
set { SetValue(ContentHeightProperty, value); }
}
}
#endregion
}

View File

@@ -1,123 +0,0 @@
<UserControl
x:Class="Wino.Controls.WinoPivotControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
d:DesignHeight="300"
d:DesignWidth="400"
Loaded="ControlLoaded"
Unloaded="ControlUnloaded"
mc:Ignorable="d">
<UserControl.Resources>
<Style x:Key="WinoPivotControlListViewItemStyle" TargetType="ListViewItem">
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="Background" Value="{ThemeResource ListViewItemBackground}" />
<Setter Property="Foreground" Value="{ThemeResource ListViewItemForeground}" />
<Setter Property="TabNavigation" Value="Local" />
<Setter Property="IsHoldingEnabled" Value="True" />
<Setter Property="Padding" Value="12,4" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="{ThemeResource ListViewItemMinWidth}" />
<Setter Property="MinHeight" Value="{ThemeResource ListViewItemMinHeight}" />
<Setter Property="AllowDrop" Value="False" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter
x:Name="Root"
Margin="0,0,6,0"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
CheckBrush="{ThemeResource ListViewItemCheckBrush}"
CheckMode="{ThemeResource ListViewItemCheckMode}"
ContentMargin="{TemplateBinding Padding}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Control.IsTemplateFocusTarget="True"
CornerRadius="6"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragBackground="{ThemeResource ListViewItemDragBackground}"
DragForeground="{ThemeResource ListViewItemDragForeground}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
PressedBackground="Transparent"
ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
RevealBackground="Transparent"
RevealBorderBrush="Transparent"
RevealBorderThickness="{ThemeResource ListViewItemRevealBorderThemeThickness}"
SelectedBackground="Transparent"
SelectedForeground="{ThemeResource ApplicationForegroundThemeBrush}"
SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
SelectedPressedBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected" />
<VisualState x:Name="PointerOver" />
<VisualState x:Name="PointerOverSelected" />
<VisualState x:Name="PointerOverPressed" />
<VisualState x:Name="Pressed" />
<VisualState x:Name="PressedSelected" />
</VisualStateGroup>
<VisualStateGroup x:Name="DisabledStates">
<VisualState x:Name="Enabled" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ListViewItemPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView
x:Name="PivotHeaders"
ItemContainerStyle="{StaticResource WinoPivotControlListViewItemStyle}"
ItemTemplate="{x:Bind DataTemplate, Mode=OneWay}"
ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"
Transitions="{x:Null}"
ItemContainerTransitions="{x:Null}"
SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}"
SelectionChanged="PivotHeaders_SelectionChanged">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<Grid
x:Name="SelectorPipe"
Grid.Row="1"
Width="1"
Height="3"
Margin="0,6,0,0"
HorizontalAlignment="Left"
SizeChanged="SelectorPipeSizeChanged">
<Grid.TranslationTransition>
<Vector3Transition Duration="0:0:0.5" />
</Grid.TranslationTransition>
</Grid>
</Grid>
</UserControl>

View File

@@ -1,195 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Wino.Extensions;
namespace Wino.Controls
{
// TODO: Memory leak with FolderPivot bindings.
public sealed partial class WinoPivotControl : UserControl
{
private Compositor _compositor;
private ShapeVisual _shapeVisual;
private CompositionSpriteShape _spriteShape;
private CompositionRoundedRectangleGeometry _roundedRectangle;
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(WinoPivotControl), new PropertyMetadata(null));
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(WinoPivotControl), new PropertyMetadata(null));
public static readonly DependencyProperty SelectorPipeColorProperty = DependencyProperty.Register(nameof(SelectorPipeColor), typeof(SolidColorBrush), typeof(WinoPivotControl), new PropertyMetadata(Colors.Transparent, OnSelectorPipeColorChanged));
public static readonly DependencyProperty DataTemplateProperty = DependencyProperty.Register(nameof(DataTemplate), typeof(DataTemplate), typeof(WinoPivotControl), new PropertyMetadata(null));
public DataTemplate DataTemplate
{
get { return (DataTemplate)GetValue(DataTemplateProperty); }
set { SetValue(DataTemplateProperty, value); }
}
public SolidColorBrush SelectorPipeColor
{
get { return (SolidColorBrush)GetValue(SelectorPipeColorProperty); }
set { SetValue(SelectorPipeColorProperty, value); }
}
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnSelectorPipeColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj is WinoPivotControl control)
{
control.UpdateSelectorPipeColor();
}
}
private void UpdateSelectorPipeColor()
{
if (_spriteShape != null && _compositor != null)
{
_spriteShape.FillBrush = _compositor.CreateColorBrush(SelectorPipeColor.Color);
}
}
private void CreateSelectorVisuals()
{
_compositor = this.Visual().Compositor;
_roundedRectangle = _compositor.CreateRoundedRectangleGeometry();
_roundedRectangle.CornerRadius = new Vector2(3, 3);
_spriteShape = _compositor.CreateSpriteShape(_roundedRectangle);
_spriteShape.CenterPoint = new Vector2(100, 100);
_shapeVisual = _compositor.CreateShapeVisual();
_shapeVisual.Shapes.Clear();
_shapeVisual.Shapes.Add(_spriteShape);
SelectorPipe.SetChildVisual(_shapeVisual);
_shapeVisual.EnableImplicitAnimation(VisualPropertyType.Size, 400);
}
public WinoPivotControl()
{
this.InitializeComponent();
CreateSelectorVisuals();
}
private bool IsContainerPresent()
{
return SelectedItem != null && PivotHeaders.ContainerFromItem(SelectedItem) != null;
}
private void PivotHeaders_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateVisuals();
SelectionChanged?.Invoke(sender, e);
}
private void UpdateVisuals()
{
MoveSelector();
}
private void UpdateSelectorVisibility()
{
SelectorPipe.Visibility = IsContainerPresent() ? Visibility.Visible : Visibility.Collapsed;
}
private async void MoveSelector()
{
if (PivotHeaders.SelectedItem != null)
{
// Get selected item container position
// TODO: It's bad...
while(PivotHeaders.ContainerFromItem(PivotHeaders.SelectedItem) == null)
{
await Task.Delay(100);
}
UpdateSelectorVisibility();
var container = PivotHeaders.ContainerFromItem(PivotHeaders.SelectedItem) as FrameworkElement;
if (container != null)
{
var transformToVisual = container.TransformToVisual(this);
Point screenCoords = transformToVisual.TransformPoint(new Point(0, 0));
float actualWidth = 0, leftMargin = 0, translateX = 0;
leftMargin = (float)(screenCoords.X);
if (PivotHeaders.Items.Count > 1)
{
// Multiple items, pipe is centered.
actualWidth = (float)(container.ActualWidth + 12) / 2;
translateX = leftMargin - 10 + (actualWidth / 2);
}
else
{
actualWidth = (float)(container.ActualWidth) - 12;
translateX = leftMargin + 4;
}
SelectorPipe.Width = actualWidth;
SelectorPipe.Translation = new Vector3(translateX, 0, 0);
}
else
{
Debug.WriteLine("Container null");
}
}
}
private void SelectorPipeSizeChanged(object sender, SizeChangedEventArgs e)
{
_roundedRectangle.Size = e.NewSize.ToVector2();
_shapeVisual.Size = e.NewSize.ToVector2();
}
private void ControlUnloaded(object sender, RoutedEventArgs e)
{
//PivotHeaders.SelectionChanged -= PivotHeaders_SelectionChanged;
//PivotHeaders.SelectedItem = null;
//SelectedItem = null;
//ItemsSource = null;
}
private void ControlLoaded(object sender, RoutedEventArgs e)
{
// Bindings.Update();
}
}
}

View File

@@ -1,18 +0,0 @@
using System.Collections;
namespace Wino.Extensions
{
public static class EnumerableExtensions
{
public static IEnumerable OfType<T1, T2>(this IEnumerable source)
{
foreach (object item in source)
{
if (item is T1 || item is T2)
{
yield return item;
}
}
}
}
}

View File

@@ -93,7 +93,8 @@ namespace Wino.Extensions
if (isVerticalScrolling)
{
scrollViewer.ChangeView(null, position.Y, zoomFactor, !smoothScrolling);
// Accomodate for additional header.
scrollViewer.ChangeView(null, Math.Max(0, position.Y - 48), zoomFactor, !smoothScrolling);
}
else
{

View File

@@ -0,0 +1,89 @@
<ResourceDictionary
x:Class="Wino.Styles.WinoExpanderStyle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
xmlns:controls="using:Wino.Controls"
xmlns:local="using:Wino.Styles"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls">
<ControlTemplate x:Key="DefaultWinoThreadControlTemplate" TargetType="controls:WinoExpander">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<ContentControl
x:Name="HeaderGrid"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Header}" />
<!-- Content -->
<Grid x:Name="ContentAreaWrapper" Grid.Row="1">
<ContentControl
x:Name="ContentArea"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Content="{TemplateBinding Content}"
RenderTransformOrigin="0.5,0.5"
Visibility="Collapsed">
<ContentControl.RenderTransform>
<CompositeTransform />
</ContentControl.RenderTransform>
</ContentControl>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="OpenCloseStates">
<VisualState x:Name="Collapsed">
<VisualState.Storyboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentArea" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.111" Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentArea" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="0" />
<SplineDoubleKeyFrame
KeySpline="1.0, 1.0, 0.0, 1.0"
KeyTime="0:0:0.111"
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeContentHeight}" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState.Storyboard>
</VisualState>
<VisualState x:Name="Expanded">
<VisualState.Storyboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentArea" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentArea" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeContentHeight}" />
<SplineDoubleKeyFrame
KeySpline="0.0, 0.0, 0.0, 1.0"
KeyTime="0:0:0.333"
Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState.Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
<Style TargetType="controls:WinoExpander">
<Setter Property="Background" Value="{ThemeResource AppBarItemBackgroundThemeBrush}" />
<Setter Property="Template" Value="{StaticResource DefaultWinoThreadControlTemplate}" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,12 @@
using Windows.UI.Xaml;
namespace Wino.Styles
{
partial class WinoExpanderStyle : ResourceDictionary
{
public WinoExpanderStyle()
{
InitializeComponent();
}
}
}

View File

@@ -5,7 +5,6 @@
xmlns:abstract="using:Wino.Views.Abstract"
xmlns:collections="using:CommunityToolkit.Mvvm.Collections"
xmlns:controls="using:Wino.Controls"
xmlns:controls1="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:Wino.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain"
@@ -13,11 +12,14 @@
xmlns:helpers="using:Wino.Helpers"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:listview="using:Wino.Controls.Advanced"
xmlns:local="using:Wino.Behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:menuflyouts="using:Wino.MenuFlyouts"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:selectors="using:Wino.Selectors"
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
xmlns:viewModelData="using:Wino.Mail.ViewModels.Data"
xmlns:wino="using:Wino"
@@ -31,6 +33,28 @@
IsSourceGrouped="True"
Source="{x:Bind ViewModel.MailCollection.MailItems, Mode=OneWay}" />
<selectors:RendererCommandBarItemTemplateSelector
x:Key="RendererCommandBarItemTemplateSelector"
Archive="{StaticResource CommandBarItemArchiveTemplate}"
ClearFlag="{StaticResource CommandBarItemClearFlagTemplate}"
DarkEditor="{StaticResource CommandBarItemDarkEditorTemplate}"
Delete="{StaticResource CommandBarItemDeleteTemplate}"
Find="{StaticResource CommandBarItemFindTemplate}"
Forward="{StaticResource CommandBarItemForwardTemplate}"
LightEditor="{StaticResource CommandBarItemLightEditorTemplate}"
MarkAsRead="{StaticResource CommandBarItemMarkReadTemplate}"
MarkAsUnread="{StaticResource CommandBarItemMarkUnreadTemplate}"
Move="{StaticResource CommandBarItemMoveTemplate}"
MoveToJunk="{StaticResource CommandBarItemMoveToJunkTemplate}"
Print="{StaticResource CommandBarItemPrintTemplate}"
Reply="{StaticResource CommandBarItemReplyTemplate}"
ReplyAll="{StaticResource CommandBarItemReplyAllTemplate}"
SaveAs="{StaticResource CommandBarItemSaveTemplate}"
SeperatorTemplate="{StaticResource CommandBarItemSeperatorTemplate}"
SetFlag="{StaticResource CommandBarItemSetFlagTemplate}"
Unarchive="{StaticResource CommandBarItemUnarchiveTemplate}"
Zoom="{StaticResource CommandBarItemZoomTemplate}" />
<Thickness x:Key="ExpanderHeaderPadding">0,0,0,0</Thickness>
<Thickness x:Key="ExpanderChevronMargin">0,0,12,0</Thickness>
<Thickness x:Key="ExpanderHeaderBorderThickness">0,0,0,0</Thickness>
@@ -45,23 +69,10 @@
<SolidColorBrush x:Key="SystemControlRevealFocusVisualBrush" Color="#FF4C4A48" />
<SolidColorBrush x:Key="SystemControlFocusVisualSecondaryBrush" Color="#FFFFFFFF" />
<DataTemplate x:Key="FolderPivotTemplate" x:DataType="viewModelData:FolderPivotViewModel">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="{x:Bind FolderTitle}" />
<TextBlock
x:Name="CountTextBlock"
VerticalAlignment="Center"
FontWeight="SemiBold"
Visibility="{x:Bind ShouldDisplaySelectedItemCount, Mode=OneWay}">
<Run Text="(" /><Run Text="{x:Bind SelectedItemCount, Mode=OneWay}" /><Run Text=")" />
</TextBlock>
</StackPanel>
</DataTemplate>
<!-- Header Templates -->
<DataTemplate x:Key="MailGroupHeaderDefaultTemplate" x:DataType="collections:IReadOnlyObservableGroup">
<Grid
Margin="2,2,16,2"
Margin="2,2,6,2"
AllowFocusOnInteraction="False"
Background="{ThemeResource MailListHeaderBackgroundColor}"
CornerRadius="6">
@@ -158,18 +169,14 @@
<DataTemplate x:DataType="viewModelData:ThreadMailItemViewModel">
<controls:WinoExpander
x:Name="ThreadExpander"
Padding="0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
BackgroundSizing="InnerBorderEdge"
BorderThickness="0"
IsExpanded="{x:Bind IsThreadExpanded, Mode=TwoWay}">
<muxc:Expander.Header>
<controls:WinoExpander.Header>
<controls:MailItemDisplayInformationControl
x:DefaultBindMode="OneWay"
Background="Transparent"
BorderThickness="0"
CanDrag="True"
CenterHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.CenterHoverAction, Mode=OneWay}"
ConnectedExpander="{Binding ElementName=ThreadExpander}"
ContextRequested="MailItemContextRequested"
@@ -180,13 +187,15 @@
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
IsHitTestVisible="True"
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
IsThreadExpanded="{x:Bind IsThreadExpanded, Mode=TwoWay}"
IsThreadExpanderVisible="True"
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
MailItem="{x:Bind MailItem, Mode=OneWay}"
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
RightHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.RightHoverAction, Mode=OneWay}"
ShowPreviewText="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowPreviewEnabled, Mode=OneWay}" />
</muxc:Expander.Header>
<muxc:Expander.Content>
</controls:WinoExpander.Header>
<controls:WinoExpander.Content>
<listview:WinoListView
x:Name="ThreadItemsList"
ui:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
@@ -201,35 +210,13 @@
</TransitionCollection>
</ListView.ItemContainerTransitions>
</listview:WinoListView>
</muxc:Expander.Content>
</controls:WinoExpander.Content>
</controls:WinoExpander>
</DataTemplate>
</selectors:MailItemDisplaySelector.ThreadMailItemTemplate>
</selectors:MailItemDisplaySelector>
<SolidColorBrush x:Key="ButtonBackgroundDisabled">Transparent</SolidColorBrush>
<Style
x:Key="TopCommandBarButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Padding" Value="12" />
</Style>
<Style
x:Key="TopCommandBarToggleButtonStyle"
BasedOn="{StaticResource DefaultToggleButtonStyle}"
TargetType="ToggleButton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Padding" Value="12" />
</Style>
</Page.Resources>
<wino:BasePage.ShellContent>
@@ -272,7 +259,8 @@
<Border
x:Name="MailListContainer"
Grid.Column="0"
Padding="5,0,0,0"
Padding="5,5,5,0"
HorizontalAlignment="Stretch"
Background="{ThemeResource WinoContentZoneBackgroud}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
@@ -284,209 +272,113 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Top Area -->
<!-- Action bar -->
<Grid
Padding="2,6,4,6"
Margin="0,0,0,5"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
CornerRadius="8"
RowSpacing="4">
Visibility="{x:Bind ViewModel.PreferencesService.IsMailListActionBarEnabled}">
<CommandBar
HorizontalAlignment="Left"
DefaultLabelPosition="Collapsed"
IsEnabled="{x:Bind helpers:XamlHelpers.CountToBooleanConverter(ViewModel.SelectedItemCount), Mode=OneWay}"
OverflowButtonVisibility="Auto">
<interactivity:Interaction.Behaviors>
<local:BindableCommandBarBehavior
ItemClickedCommand="{x:Bind ViewModel.ExecuteTopBarActionCommand}"
ItemTemplateSelector="{StaticResource RendererCommandBarItemTemplateSelector}"
PrimaryCommands="{x:Bind ViewModel.ActionItems, Mode=OneWay}" />
</interactivity:Interaction.Behaviors>
</CommandBar>
</Grid>
<!-- Pivot + Sync + Multi Select -->
<Grid Grid.Row="1" Padding="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Top Commands -->
<Grid
Grid.Row="0"
Padding="2,0"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
CornerRadius="8">
<!-- Commands -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Command="{x:Bind ViewModel.SyncFolderCommand}"
IsEnabled="{x:Bind ViewModel.CanSynchronize, Mode=OneWay}"
Style="{StaticResource TopCommandBarButtonStyle}">
<Button.Content>
<controls:WinoFontIcon FontSize="16" Icon="Sync" />
</Button.Content>
</Button>
<ToggleButton
x:Name="SelectionModeToggle"
Grid.Column="1"
Checked="SelectionModeToggleChecked"
IsChecked="{x:Bind ViewModel.IsMultiSelectionModeEnabled, Mode=TwoWay}"
Style="{StaticResource TopCommandBarToggleButtonStyle}"
Unchecked="SelectionModeToggleUnchecked">
<ToggleButton.Content>
<controls:WinoFontIcon FontSize="18" Icon="MultiSelect" />
</ToggleButton.Content>
</ToggleButton>
<AppBarSeparator Grid.Column="2" Margin="2,0" />
<Button
x:Name="ArchiveAppBarButton"
Grid.Column="3"
x:Load="{x:Bind helpers:XamlHelpers.ReverseBoolConverter(ViewModel.IsArchiveSpecialFolder), Mode=OneWay}"
Command="{x:Bind ViewModel.MailOperationCommand}"
IsEnabled="{x:Bind ViewModel.HasSelectedItems, Mode=OneWay}"
Style="{StaticResource TopCommandBarButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.MailOperation_Archive}">
<Button.Content>
<controls:WinoFontIcon FontSize="18" Icon="Archive" />
</Button.Content>
<Button.CommandParameter>
<enums:MailOperation>Archive</enums:MailOperation>
</Button.CommandParameter>
</Button>
<Button
x:Name="UnarchiveAppBarButton"
Grid.Column="3"
x:Load="{x:Bind ViewModel.IsArchiveSpecialFolder, Mode=OneWay}"
Command="{x:Bind ViewModel.MailOperationCommand}"
IsEnabled="{x:Bind ViewModel.HasSelectedItems, Mode=OneWay}"
Style="{StaticResource TopCommandBarButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.MailOperation_Unarchive}">
<Button.Content>
<controls:WinoFontIcon FontSize="18" Icon="UnArchive" />
</Button.Content>
<Button.CommandParameter>
<enums:MailOperation>UnArchive</enums:MailOperation>
</Button.CommandParameter>
</Button>
<Button
Grid.Column="4"
Command="{x:Bind ViewModel.MailOperationCommand}"
IsEnabled="{x:Bind ViewModel.HasSelectedItems, Mode=OneWay}"
Style="{StaticResource TopCommandBarButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.MailOperation_Delete}">
<Button.CommandParameter>
<enums:MailOperation>SoftDelete</enums:MailOperation>
</Button.CommandParameter>
<Button.Content>
<controls:WinoFontIcon FontSize="18" Icon="Delete" />
</Button.Content>
</Button>
<Button
x:Name="MoveButtonAppBarButton"
Grid.Column="5"
Command="{x:Bind ViewModel.MailOperationCommand}"
IsEnabled="{x:Bind ViewModel.HasSelectedItems, Mode=OneWay}"
Style="{StaticResource TopCommandBarButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.MailOperation_Move}">
<Button.CommandParameter>
<enums:MailOperation>Move</enums:MailOperation>
</Button.CommandParameter>
<Button.Content>
<controls:WinoFontIcon FontSize="18" Icon="Move" />
</Button.Content>
</Button>
<Button
Grid.Column="6"
IsEnabled="{x:Bind ViewModel.HasSelectedItems, Mode=OneWay}"
Style="{StaticResource TopCommandBarButtonStyle}">
<Button.Content>
<controls:WinoFontIcon FontSize="20" Icon="More" />
</Button.Content>
<Button.Flyout>
<MenuFlyout AreOpenCloseAnimationsEnabled="False" Placement="BottomEdgeAlignedLeft">
<MenuFlyoutItem Command="{x:Bind ViewModel.MailOperationCommand}" Text="{x:Bind domain:Translator.MailOperation_SetFlag}">
<MenuFlyoutItem.CommandParameter>
<enums:MailOperation>SetFlag</enums:MailOperation>
</MenuFlyoutItem.CommandParameter>
<MenuFlyoutItem.Icon>
<controls:WinoFontIcon Icon="Flag" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.MailOperationCommand}" Text="{x:Bind domain:Translator.MailOperation_ClearFlag}">
<MenuFlyoutItem.CommandParameter>
<enums:MailOperation>ClearFlag</enums:MailOperation>
</MenuFlyoutItem.CommandParameter>
<MenuFlyoutItem.Icon>
<controls:WinoFontIcon Icon="ClearFlag" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.MailOperationCommand}" Text="{x:Bind domain:Translator.MailOperation_MarkAsRead}">
<MenuFlyoutItem.CommandParameter>
<enums:MailOperation>MarkAsRead</enums:MailOperation>
</MenuFlyoutItem.CommandParameter>
<MenuFlyoutItem.Icon>
<controls:WinoFontIcon Icon="MarkRead" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.MailOperationCommand}" Text="{x:Bind domain:Translator.MailOperation_MarkAsUnread}">
<MenuFlyoutItem.CommandParameter>
<enums:MailOperation>MarkAsUnread</enums:MailOperation>
</MenuFlyoutItem.CommandParameter>
<MenuFlyoutItem.Icon>
<controls:WinoFontIcon Icon="MarkUnread" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyout>
</Button.Flyout>
</Button>
</Grid>
</Grid>
<!-- Pivot + Sync + Multi Select -->
<Grid
<!-- Select All Checkbox -->
<CheckBox
x:Name="SelectAllCheckbox"
Grid.Row="1"
Margin="0,0,0,5"
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsInSearchMode), Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
MinWidth="0"
Margin="8,0,0,0"
VerticalAlignment="Center"
Canvas.ZIndex="100"
Checked="SelectAllCheckboxChecked"
Unchecked="SelectAllCheckboxUnchecked"
Visibility="{x:Bind helpers:XamlHelpers.IsSelectionModeMultiple(MailListView.SelectionMode), Mode=OneWay}" />
<!-- Select All Checkbox -->
<CheckBox
x:Name="SelectAllCheckbox"
Grid.Row="1"
MinWidth="0"
Margin="8,0,0,0"
VerticalAlignment="Center"
Canvas.ZIndex="100"
Checked="SelectAllCheckboxChecked"
Unchecked="SelectAllCheckboxUnchecked"
Visibility="{x:Bind helpers:XamlHelpers.IsSelectionModeMultiple(MailListView.SelectionMode), Mode=OneWay}" />
<!-- Folders -->
<toolkit:Segmented
Grid.Row="1"
Grid.Column="1"
ItemsSource="{x:Bind ViewModel.PivotFolders, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedFolderPivot, Mode=TwoWay}"
SelectionChanged="FolderPivotChanged"
Style="{StaticResource PivotSegmentedStyle}">
<toolkit:Segmented.ItemTemplate>
<DataTemplate x:DataType="viewModelData:FolderPivotViewModel">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="{x:Bind FolderTitle}" />
<TextBlock
x:Name="CountTextBlock"
VerticalAlignment="Center"
FontWeight="SemiBold"
Visibility="{x:Bind ShouldDisplaySelectedItemCount, Mode=OneWay}">
<Run Text="(" /><Run Text="{x:Bind SelectedItemCount, Mode=OneWay}" /><Run Text=")" />
</TextBlock>
</StackPanel>
</DataTemplate>
</toolkit:Segmented.ItemTemplate>
</toolkit:Segmented>
<!-- Folders -->
<controls:WinoPivotControl
x:Name="WinoPivot"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DataTemplate="{StaticResource FolderPivotTemplate}"
ItemsSource="{x:Bind ViewModel.PivotFolders, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedFolderPivot, Mode=TwoWay}"
SelectionChanged="FolderPivotChanged"
SelectorPipeColor="{ThemeResource NavigationViewSelectionIndicatorForeground}" />
<!-- Filtering -->
<!-- Sync + Multi Select + Filtering -->
<StackPanel
Grid.Row="1"
Grid.Column="2"
Orientation="Horizontal">
<Button
Width="36"
Height="36"
Background="Transparent"
BorderThickness="0"
Command="{x:Bind ViewModel.SyncFolderCommand}"
IsEnabled="{x:Bind ViewModel.CanSynchronize, Mode=OneWay}">
<Button.Content>
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="14"
Glyph="&#xE895;" />
</Button.Content>
</Button>
<ToggleButton
x:Name="SelectionModeToggle"
Width="36"
Height="36"
Background="Transparent"
BorderThickness="0"
Checked="SelectionModeToggleChecked"
IsChecked="{x:Bind ViewModel.IsMultiSelectionModeEnabled, Mode=TwoWay}"
Unchecked="SelectionModeToggleUnchecked">
<ToggleButton.Content>
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="14"
Glyph="&#xE762;" />
</ToggleButton.Content>
</ToggleButton>
<muxc:DropDownButton
Grid.Row="1"
Grid.Column="2"
Height="36"
Background="Transparent"
BorderThickness="0"
Content="{x:Bind ViewModel.SelectedFilterOption.Title, Mode=OneWay}"
ToolTipService.ToolTip="Filter">
<muxc:DropDownButton.Flyout>
@@ -502,20 +394,20 @@
SortingOptions="{x:Bind ViewModel.SortingOptions, Mode=OneTime}" />
</muxc:DropDownButton.Flyout>
</muxc:DropDownButton>
</StackPanel>
<muxc:InfoBar
Title="{x:Bind domain:Translator.InfoBarTitle_SynchronizationDisabledFolder}"
Grid.Row="0"
Grid.ColumnSpan="3"
IsClosable="True"
IsOpen="{x:Bind ViewModel.IsFolderSynchronizationEnabled, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}"
Message="{x:Bind domain:Translator.InfoBarMessage_SynchronizationDisabledFolder}"
Severity="Informational">
<muxc:InfoBar.ActionButton>
<Button Command="{x:Bind ViewModel.EnableFolderSynchronizationCommand}" Content="Enable" />
</muxc:InfoBar.ActionButton>
</muxc:InfoBar>
</Grid>
<muxc:InfoBar
Title="{x:Bind domain:Translator.InfoBarTitle_SynchronizationDisabledFolder}"
Grid.Row="0"
Grid.ColumnSpan="3"
IsClosable="True"
IsOpen="{x:Bind ViewModel.IsFolderSynchronizationEnabled, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}"
Message="{x:Bind domain:Translator.InfoBarMessage_SynchronizationDisabledFolder}"
Severity="Informational">
<muxc:InfoBar.ActionButton>
<Button Command="{x:Bind ViewModel.EnableFolderSynchronizationCommand}" Content="Enable" />
</muxc:InfoBar.ActionButton>
</muxc:InfoBar>
</Grid>
<!-- No items createria -->
@@ -570,11 +462,12 @@
<SemanticZoom.ZoomedInView>
<listview:WinoListView
x:Name="MailListView"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
ui:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
ui:ScrollViewerExtensions.EnableMiddleClickScrolling="True"
ui:ScrollViewerExtensions.VerticalScrollBarMargin="0"
ItemDeletedCommand="{x:Bind ViewModel.MailOperationCommand}"
ItemDeletedCommand="{x:Bind ViewModel.ExecuteMailOperationCommand}"
ItemTemplateSelector="{StaticResource MailItemDisplaySelector}"
ItemsSource="{x:Bind MailCollectionViewSource.View, Mode=OneWay}"
LoadMoreCommand="{x:Bind ViewModel.LoadMoreItemsCommand}"
@@ -615,10 +508,7 @@
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate x:DataType="ICollectionViewGroup">
<Grid
Margin="4,0"
Background="{ThemeResource MailListHeaderBackgroundColor}"
CornerRadius="4">
<Grid Background="{ThemeResource MailListHeaderBackgroundColor}" CornerRadius="4">
<TextBlock
Margin="12,0"
HorizontalAlignment="Center"
@@ -638,7 +528,7 @@
<controls:WinoInfoBar
Title="{x:Bind ViewModel.BarTitle, Mode=OneWay}"
Grid.Row="2"
Margin="6,0,6,6"
Margin="0,0,0,5"
VerticalAlignment="Bottom"
AnimationType="SlideFromBottomToTop"
DismissInterval="2"
@@ -649,7 +539,7 @@
</Grid>
</Border>
<controls1:PropertySizer
<toolkit:PropertySizer
x:Name="MailListSizer"
Grid.Column="1"
Width="16"

View File

@@ -375,7 +375,7 @@ namespace Wino.Views
{
args.Handled = true;
ViewModel?.MailOperationCommand?.Execute((int)MailOperation.SoftDelete);
ViewModel?.ExecuteMailOperationCommand?.Execute(MailOperation.SoftDelete);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -141,6 +141,9 @@
<PackageReference Include="CommunityToolkit.Uwp.Behaviors">
<Version>8.1.240821</Version>
</PackageReference>
<PackageReference Include="CommunityToolkit.Uwp.Controls.Segmented">
<Version>8.1.240821</Version>
</PackageReference>
<PackageReference Include="CommunityToolkit.Uwp.Controls.SettingsControls">
<Version>8.1.240821</Version>
</PackageReference>
@@ -157,10 +160,10 @@
<Version>1.2.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>5.0.4</Version>
<Version>5.0.5</Version>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Crashes">
<Version>5.0.4</Version>
<Version>5.0.5</Version>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection">
<Version>8.0.0</Version>
@@ -186,16 +189,16 @@
<Version>5.1.2</Version>
</PackageReference>
<PackageReference Include="ReactiveUI">
<Version>19.6.1</Version>
<Version>20.1.1</Version>
</PackageReference>
<PackageReference Include="Serilog">
<Version>3.1.1</Version>
<Version>4.0.1</Version>
</PackageReference>
<PackageReference Include="Serilog.Exceptions">
<Version>8.4.0</Version>
</PackageReference>
<PackageReference Include="sqlite-net-pcl">
<Version>1.8.116</Version>
<Version>1.9.172</Version>
</PackageReference>
<PackageReference Include="Win2D.uwp">
<Version>1.27.1</Version>
@@ -265,9 +268,6 @@
<Compile Include="MenuFlyouts\WinoOperationFlyout.cs" />
<Compile Include="MenuFlyouts\WinoOperationFlyoutItem.cs" />
<Compile Include="Controls\RendererCommandBar.cs" />
<Compile Include="Controls\WinoPivotControl.xaml.cs">
<DependentUpon>WinoPivotControl.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\ReverseBooleanConverter.cs" />
<Compile Include="Dialogs\AccountCreationDialog.xaml.cs">
<DependentUpon>AccountCreationDialog.xaml</DependentUpon>
@@ -276,7 +276,6 @@
<Compile Include="Extensions\CompositionExtensions.Implicit.cs" />
<Compile Include="Extensions\CompositionExtensions.Size.cs" />
<Compile Include="Extensions\CompositionEnums.cs" />
<Compile Include="Extensions\EnumerableExtensions.cs" />
<Compile Include="Extensions\UtilExtensions.cs" />
<Compile Include="MenuFlyouts\FilterMenuFlyout.cs" />
<Compile Include="Controls\ImagePreviewControl.cs" />
@@ -312,6 +311,9 @@
<Compile Include="Styles\CustomMessageDialogStyles.xaml.cs">
<DependentUpon>CustomMessageDialogStyles.xaml</DependentUpon>
</Compile>
<Compile Include="Styles\WinoExpanderStyle.xaml.cs">
<DependentUpon>WinoExpanderStyle.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Abstract\AboutPageAbstract.cs" />
<Compile Include="Views\Abstract\AccountDetailsPageAbstract.cs" />
<Compile Include="Views\Abstract\AccountManagementPageAbstract.cs" />
@@ -456,10 +458,6 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\WinoPivotControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\AccountEditDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -532,6 +530,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Styles\WinoExpanderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Styles\WinoInfoBar.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@@ -1,8 +0,0 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When selected mail count is changed.
/// </summary>
/// <param name="SelectedItemCount">New selected mail count.</param>
public record SelectedMailItemsChanged(int SelectedItemCount);
}

View File

@@ -11,7 +11,7 @@
<Identity
Name="58272BurakKSE.WinoMailPreview"
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
Version="1.8.4.0" />
Version="1.8.5.0" />
<Extensions>
<!-- Publisher Cache Folders -->