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);
+118 -60
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();
if (!isSelectedItemFromThread && !isClickingThreadItem)
{ {
await ViewModel.MailCollection.CollapseAllThreadsAsync(); await ViewModel.MailCollection.CollapseAllThreadsAsync();
if (except != null)
{
// We'll expand explicitly when required by logic below.
} }
} }
if (clickedItem is MailItemViewModel mailListItem)
{
mailListItem.IsSelected = !mailListItem.IsSelected;
}
else if (clickedItem is ThreadMailItemViewModel threadMailItemViewModel)
{
// Extended selection mode handling for threads
if (isCtrlPressed) if (isCtrlPressed)
{ {
// If thread is selected and Ctrl is pressed switch (clickedItem)
if (threadMailItemViewModel.IsSelected)
{ {
// If thread was collapsed, expand it case ThreadMailItemViewModel thread:
if (!threadMailItemViewModel.IsThreadExpanded)
{ {
threadMailItemViewModel.IsThreadExpanded = true; // Determine if thread + all children currently selected
bool allSelected = thread.IsSelected && thread.ThreadEmails.All(e => e.IsSelected);
if (allSelected)
{
// Unselect thread & all children
thread.IsSelected = false;
foreach (var child in thread.ThreadEmails)
child.IsSelected = false;
} }
else else
{ {
// Check if all items are selected. // Select thread & all children (do NOT disturb other selections in CTRL mode)
// If so, then unselect all items in the thread and unselect the thread itself. thread.IsSelected = true;
if (threadMailItemViewModel.ThreadEmails.All(a => a.IsSelected)) foreach (var child in thread.ThreadEmails)
{ child.IsSelected = true;
foreach (var threadEmail in threadMailItemViewModel.ThreadEmails) // Keep it expanded so user can see items
{ thread.IsThreadExpanded = true;
threadEmail.IsSelected = false;
} }
threadMailItemViewModel.IsSelected = false; break;
}
case MailItemViewModel mail:
{
// Toggle just this item; no collapse/unselect of others in multi-select mode.
mail.IsSelected = !mail.IsSelected;
break;
}
}
return; // Multi-select path ends here.
}
// SINGLE-SELECTION (exclusive) MODE WITH TOGGLE SUPPORT
if (clickedItem is ThreadMailItemViewModel clickedThread)
{
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; return;
} }
else
{
// If thread was already expanded, select all items in the thread
foreach (var threadEmail in threadMailItemViewModel.ThreadEmails)
{
threadEmail.IsSelected = true;
}
}
}
}
else
{
// Thread is not selected, select and expand it.
if (!threadMailItemViewModel.IsThreadExpanded) threadMailItemViewModel.IsThreadExpanded = true;
if (!threadMailItemViewModel.IsSelected)
{
threadMailItemViewModel.IsSelected = true;
foreach (var threadEmail in threadMailItemViewModel.ThreadEmails) // Select thread + first child only
clickedThread.IsSelected = true;
var firstChild = clickedThread.ThreadEmails.FirstOrDefault();
if (firstChild != null)
{ {
threadEmail.IsSelected = true; // Ensure only first child selected
} foreach (var child in clickedThread.ThreadEmails)
}
}
}
else
{ {
// No Ctrl pressed, toggle expansion state (default behavior) child.IsSelected = child == firstChild;
threadMailItemViewModel.IsThreadExpanded = !threadMailItemViewModel.IsThreadExpanded; }
}
clickedThread.IsThreadExpanded = true; // Show contents of active thread
}
else if (clickedItem is MailItemViewModel clickedMail)
{
bool wasSelected = clickedMail.IsSelected;
// Select the first item in the thread if none is selected // Determine if this mail belongs to an already selected & expanded thread.
if (!threadMailItemViewModel.IsSelected) // 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)
{ {
threadMailItemViewModel.IsSelected = true; foreach (var item in group)
var firstEmail = threadMailItemViewModel.ThreadEmails.FirstOrDefault(); {
firstEmail?.IsSelected = true; if (item is ThreadMailItemViewModel thread && thread.ThreadEmails.Contains(clickedMail))
{
parentThread = thread;
break;
} }
} }
if (parentThread != null) break;
}
bool isInSelectedExpandedThread = parentThread != null && parentThread.IsSelected && parentThread.IsThreadExpanded;
if (isInSelectedExpandedThread)
{
// Switch selection within the thread: unselect previously selected children, select the clicked one.
if (parentThread?.ThreadEmails != null)
{
foreach (var child in parentThread.ThreadEmails)
{
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)
} }
} }