Some selections.

This commit is contained in:
Burak Kaan Köse
2025-11-06 23:54:32 +01:00
parent 5f9b51e4db
commit 175ed24a66
2 changed files with 118 additions and 60 deletions
@@ -1008,7 +1008,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
} }
public Task SelectAllAsync() => ExecuteWithoutRaiseSelectionChangedAsync(a => a.IsSelected = true, true); public Task SelectAllAsync() => ExecuteWithoutRaiseSelectionChangedAsync(a => a.IsSelected = true, true);
public Task UnselectAllAsync() => ExecuteWithoutRaiseSelectionChangedAsync(a => a.IsSelected = false, true); public Task UnselectAllAsync(IMailListItem exceptItem = null) => ExecuteWithoutRaiseSelectionChangedAsync(a => a.IsSelected = false && a != exceptItem, true);
public Task CollapseAllThreadsAsync() => ExecuteWithoutRaiseSelectionChangedAsync(a => { if (a is ThreadMailItemViewModel thread) thread.IsThreadExpanded = false; }, true); public Task CollapseAllThreadsAsync() => ExecuteWithoutRaiseSelectionChangedAsync(a => { if (a is ThreadMailItemViewModel thread) thread.IsThreadExpanded = false; }, true);
private Task ExecuteUIThread(Action action) => CoreDispatcher?.ExecuteOnUIThread(action); private Task ExecuteUIThread(Action action) => CoreDispatcher?.ExecuteOnUIThread(action);
+117 -59
View File
@@ -558,90 +558,148 @@ public sealed partial class MailListPage : MailListPageAbstract,
{ {
if (clickedItem == null) return; if (clickedItem == null) return;
bool isSelectedItemFromThread = listView.IsThreadListView; // Requirements (summary):
// CTRL pressed -> multi-select behaviour
// * Clicking single item toggles only that item.
// * Clicking thread header toggles selection of thread AND all its children (all on or all off).
// * Clicking an item inside a thread toggles only that child item.
// CTRL NOT pressed -> single-select (exclusive) with toggle support (can leave zero selected)
// * Clicking thread header: unselect everything else, collapse all other threads, select only the thread + first child.
// If already in that state (thread selected and first child selected), clicking again unselects all (nothing selected).
// * Clicking a single (non-thread) item OR a child item: collapse & unselect all others then toggle that item's selection.
// If it was selected, result is nothing selected.
bool isCtrlPressed = KeyPressService.IsCtrlKeyPressed(); bool isCtrlPressed = KeyPressService.IsCtrlKeyPressed();
bool isClickingThreadItem = clickedItem is ThreadMailItemViewModel; // Helper local to collapse all other threads (we always collapse ALL then possibly re-expand the active thread per rules)
async Task CollapseAllThreadsExceptAsync(ThreadMailItemViewModel? except)
// Unselect all items. It's single selection.
if (!isCtrlPressed)
{ {
await ViewModel.MailCollection.UnselectAllAsync(); await ViewModel.MailCollection.CollapseAllThreadsAsync();
if (except != null)
if (!isSelectedItemFromThread && !isClickingThreadItem)
{ {
await ViewModel.MailCollection.CollapseAllThreadsAsync(); // We'll expand explicitly when required by logic below.
} }
} }
if (clickedItem is MailItemViewModel mailListItem) if (isCtrlPressed)
{ {
mailListItem.IsSelected = !mailListItem.IsSelected; switch (clickedItem)
}
else if (clickedItem is ThreadMailItemViewModel threadMailItemViewModel)
{
// Extended selection mode handling for threads
if (isCtrlPressed)
{ {
// If thread is selected and Ctrl is pressed case ThreadMailItemViewModel thread:
if (threadMailItemViewModel.IsSelected)
{
// If thread was collapsed, expand it
if (!threadMailItemViewModel.IsThreadExpanded)
{ {
threadMailItemViewModel.IsThreadExpanded = true; // Determine if thread + all children currently selected
} bool allSelected = thread.IsSelected && thread.ThreadEmails.All(e => e.IsSelected);
else if (allSelected)
{
// Check if all items are selected.
// If so, then unselect all items in the thread and unselect the thread itself.
if (threadMailItemViewModel.ThreadEmails.All(a => a.IsSelected))
{ {
foreach (var threadEmail in threadMailItemViewModel.ThreadEmails) // Unselect thread & all children
{ thread.IsSelected = false;
threadEmail.IsSelected = false; foreach (var child in thread.ThreadEmails)
} child.IsSelected = false;
threadMailItemViewModel.IsSelected = false;
return;
} }
else else
{ {
// If thread was already expanded, select all items in the thread // Select thread & all children (do NOT disturb other selections in CTRL mode)
foreach (var threadEmail in threadMailItemViewModel.ThreadEmails) thread.IsSelected = true;
{ foreach (var child in thread.ThreadEmails)
threadEmail.IsSelected = true; child.IsSelected = true;
} // Keep it expanded so user can see items
thread.IsThreadExpanded = true;
} }
break;
} }
} case MailItemViewModel mail:
else
{
// Thread is not selected, select and expand it.
if (!threadMailItemViewModel.IsThreadExpanded) threadMailItemViewModel.IsThreadExpanded = true;
if (!threadMailItemViewModel.IsSelected)
{ {
threadMailItemViewModel.IsSelected = true; // Toggle just this item; no collapse/unselect of others in multi-select mode.
mail.IsSelected = !mail.IsSelected;
break;
}
}
return; // Multi-select path ends here.
}
foreach (var threadEmail in threadMailItemViewModel.ThreadEmails) // SINGLE-SELECTION (exclusive) MODE WITH TOGGLE SUPPORT
{ if (clickedItem is ThreadMailItemViewModel clickedThread)
threadEmail.IsSelected = true; {
} bool wasThreadSelected = clickedThread.IsSelected;
// Reset everything first (exclusive selection scenario)
await ViewModel.MailCollection.UnselectAllAsync();
await CollapseAllThreadsExceptAsync(clickedThread);
if (wasThreadSelected)
{
// Toggle off -> leave nothing selected (all unselected, thread collapsed)
clickedThread.IsThreadExpanded = false;
return;
}
// Select thread + first child only
clickedThread.IsSelected = true;
var firstChild = clickedThread.ThreadEmails.FirstOrDefault();
if (firstChild != null)
{
// Ensure only first child selected
foreach (var child in clickedThread.ThreadEmails)
{
child.IsSelected = child == firstChild;
}
}
clickedThread.IsThreadExpanded = true; // Show contents of active thread
}
else if (clickedItem is MailItemViewModel clickedMail)
{
bool wasSelected = clickedMail.IsSelected;
// Determine if this mail belongs to an already selected & expanded thread.
// If so, we only want to switch the selection inside that thread without collapsing or unselecting the thread header.
ThreadMailItemViewModel? parentThread = null;
foreach (var group in ViewModel.MailCollection.MailItems)
{
foreach (var item in group)
{
if (item is ThreadMailItemViewModel thread && thread.ThreadEmails.Contains(clickedMail))
{
parentThread = thread;
break;
} }
} }
if (parentThread != null) break;
} }
else
{
// No Ctrl pressed, toggle expansion state (default behavior)
threadMailItemViewModel.IsThreadExpanded = !threadMailItemViewModel.IsThreadExpanded;
// Select the first item in the thread if none is selected bool isInSelectedExpandedThread = parentThread != null && parentThread.IsSelected && parentThread.IsThreadExpanded;
if (!threadMailItemViewModel.IsSelected)
if (isInSelectedExpandedThread)
{
// Switch selection within the thread: unselect previously selected children, select the clicked one.
if (parentThread?.ThreadEmails != null)
{ {
threadMailItemViewModel.IsSelected = true; foreach (var child in parentThread.ThreadEmails)
var firstEmail = threadMailItemViewModel.ThreadEmails.FirstOrDefault(); {
firstEmail?.IsSelected = true; child.IsSelected = child == clickedMail && !wasSelected; // If clicking an already selected child -> toggle off (none selected in thread except header)
}
} }
if (wasSelected && parentThread != null)
{
// Clicking the already selected child should leave the thread header selected (canonical state: thread + first child previously).
// Decide whether to keep a child selected; spec wants toggle off allowed, so leave no child selected.
// Ensure parent thread stays selected & expanded.
parentThread.IsSelected = true;
parentThread.IsThreadExpanded = true;
}
return; // Done.
} }
// Normal single-item (non-thread or entering a thread via child) behavior.
await ViewModel.MailCollection.UnselectAllAsync();
await ViewModel.MailCollection.CollapseAllThreadsAsync();
if (!wasSelected)
{
clickedMail.IsSelected = true; // Toggle on
}
// else leave all unselected (toggle off)
} }
} }