[bugfix] TF-2316 Can not delete folder when its children are hidden (#2519)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:collection';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
@@ -5,6 +6,8 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/extensions/mailbox_extension.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/domain/state/delete_multiple_mailbox_state.dart';
|
||||
|
||||
@@ -14,24 +17,40 @@ class DeleteMultipleMailboxInteractor {
|
||||
DeleteMultipleMailboxInteractor(this._mailboxRepository);
|
||||
|
||||
Stream<Either<Failure, Success>> execute(
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
Map<MailboxId, List<MailboxId>> mapMailboxIdToDelete,
|
||||
List<MailboxId> listMailboxIdToDelete
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
List<MailboxId> selectedMailboxIds,
|
||||
) async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(LoadingDeleteMultipleMailboxAll());
|
||||
|
||||
final currentMailboxState = await _mailboxRepository.getMailboxState(session, accountId);
|
||||
|
||||
final mailboxResponses = await _mailboxRepository.getAllMailbox(session, accountId).toList();
|
||||
|
||||
final Set<MailboxId> seenIds = {};
|
||||
final List<PresentationMailbox> allMailboxes = [];
|
||||
for (final response in mailboxResponses) {
|
||||
for (final mailbox in response.mailboxes) {
|
||||
final presentation = mailbox.toPresentationMailbox();
|
||||
if (seenIds.add(presentation.id)) {
|
||||
allMailboxes.add(presentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final deleteMap = _buildDeleteMap(selectedMailboxIds, allMailboxes);
|
||||
final mapMailboxIdToDelete = deleteMap.value1;
|
||||
final listMailboxIdToDelete = deleteMap.value2;
|
||||
|
||||
final listResult = await Future.wait(
|
||||
mapMailboxIdToDelete.keys.map((mailboxId) {
|
||||
final mailboxIdsToDelete = mapMailboxIdToDelete[mailboxId]!;
|
||||
return _mailboxRepository.deleteMultipleMailbox(
|
||||
session,
|
||||
accountId,
|
||||
mailboxIdsToDelete);
|
||||
})
|
||||
mapMailboxIdToDelete.keys.map((mailboxId) {
|
||||
return _mailboxRepository.deleteMultipleMailbox(
|
||||
session,
|
||||
accountId,
|
||||
mapMailboxIdToDelete[mailboxId]!,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
final allSuccess = listResult.every((result) => result.isEmpty);
|
||||
@@ -39,18 +58,85 @@ class DeleteMultipleMailboxInteractor {
|
||||
|
||||
if (allSuccess) {
|
||||
yield Right<Failure, Success>(DeleteMultipleMailboxAllSuccess(
|
||||
listMailboxIdToDelete,
|
||||
currentMailboxState: currentMailboxState));
|
||||
listMailboxIdToDelete,
|
||||
currentMailboxState: currentMailboxState,
|
||||
));
|
||||
} else if (allFailed) {
|
||||
yield Left<Failure, Success>(DeleteMultipleMailboxAllFailure());
|
||||
} else {
|
||||
yield Right<Failure, Success>(DeleteMultipleMailboxHasSomeSuccess(
|
||||
listMailboxIdToDelete,
|
||||
currentMailboxState: currentMailboxState));
|
||||
listMailboxIdToDelete,
|
||||
currentMailboxState: currentMailboxState,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
logWarning('DeleteMultipleMailboxInteractor::execute(): exception: $e');
|
||||
yield Left<Failure, Success>(DeleteMultipleMailboxFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the map of [MailboxId] → ordered delete list for each selected root,
|
||||
/// plus a flat list of all IDs to delete.
|
||||
///
|
||||
/// Uses [allMailboxes] (subscribed and unsubscribed) so hidden subfolders are
|
||||
/// automatically included — no separate unsubscribed-tree pass is needed.
|
||||
Tuple2<Map<MailboxId, List<MailboxId>>, List<MailboxId>> _buildDeleteMap(
|
||||
List<MailboxId> selectedMailboxIds,
|
||||
List<PresentationMailbox> allMailboxes,
|
||||
) {
|
||||
final parentToChildren = _buildParentToChildrenMap(allMailboxes);
|
||||
final Map<MailboxId, List<MailboxId>> mapDescendantIds = {};
|
||||
final Set<MailboxId> processedIds = {};
|
||||
|
||||
for (final mailboxId in selectedMailboxIds) {
|
||||
if (processedIds.contains(mailboxId)) continue;
|
||||
|
||||
// Collect all descendants (pre-order DFS), then reverse so deepest children
|
||||
// come first — the server requires children to be deleted before parents.
|
||||
final descendants = _collectDescendantsPreOrder(mailboxId, parentToChildren);
|
||||
final deleteOrder = descendants.reversed.toList();
|
||||
|
||||
mapDescendantIds[mailboxId] = deleteOrder;
|
||||
processedIds.addAll(deleteOrder);
|
||||
}
|
||||
|
||||
final allMailboxIds = mapDescendantIds.values.expand((ids) => ids).toList();
|
||||
return Tuple2(mapDescendantIds, allMailboxIds);
|
||||
}
|
||||
|
||||
Map<MailboxId, List<MailboxId>> _buildParentToChildrenMap(
|
||||
List<PresentationMailbox> mailboxes,
|
||||
) {
|
||||
final Map<MailboxId, List<MailboxId>> parentToChildren = HashMap();
|
||||
for (final mailbox in mailboxes) {
|
||||
final parentId = mailbox.parentId;
|
||||
if (parentId != null) {
|
||||
parentToChildren.putIfAbsent(parentId, () => []).add(mailbox.id);
|
||||
}
|
||||
}
|
||||
return parentToChildren;
|
||||
}
|
||||
|
||||
/// Pre-order DFS: root first, then children. Caller should reverse the result
|
||||
/// to get deepest-first deletion order.
|
||||
List<MailboxId> _collectDescendantsPreOrder(
|
||||
MailboxId rootId,
|
||||
Map<MailboxId, List<MailboxId>> parentToChildren,
|
||||
) {
|
||||
final result = <MailboxId>[];
|
||||
final stack = ListQueue<MailboxId>()..addLast(rootId);
|
||||
|
||||
while (stack.isNotEmpty) {
|
||||
final current = stack.removeLast();
|
||||
result.add(current);
|
||||
final children = parentToChildren[current];
|
||||
if (children != null) {
|
||||
// Push in reverse so the first child is processed first (stack is LIFO).
|
||||
for (final child in children.reversed) {
|
||||
stack.addLast(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.d
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/open_mailbox_view_event.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_action_reactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_utils.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/mailbox_creator_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_mailbox_arguments.dart';
|
||||
@@ -565,7 +564,7 @@ class MailboxController extends BaseMailboxController
|
||||
consumeState(Stream.value(Left(GetAllMailboxFailure(NotFoundSessionException()))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _refreshMailboxChanges({required jmap.State newState}) {
|
||||
log('MailboxController::_refreshMailboxChanges():newState: $newState');
|
||||
if (accountId == null ||
|
||||
@@ -1039,18 +1038,10 @@ class MailboxController extends BaseMailboxController
|
||||
|
||||
void _deleteMailboxAction(PresentationMailbox presentationMailbox) {
|
||||
if (session != null && accountId != null) {
|
||||
final tupleMap = MailboxUtils.generateMapDescendantIdsAndMailboxIdList(
|
||||
[presentationMailbox],
|
||||
defaultMailboxTree.value,
|
||||
personalMailboxTree.value);
|
||||
final mapDescendantIds = tupleMap.value1;
|
||||
final listMailboxId = tupleMap.value2;
|
||||
|
||||
consumeState(_deleteMultipleMailboxInteractor.execute(
|
||||
session!,
|
||||
accountId!,
|
||||
mapDescendantIds,
|
||||
listMailboxId,
|
||||
[presentationMailbox.id],
|
||||
));
|
||||
} else {
|
||||
_deleteMailboxFailure(DeleteMultipleMailboxFailure(null));
|
||||
|
||||
@@ -1,60 +1,9 @@
|
||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:email_recovery/email_recovery/capability_deleted_messages_vault.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/list_mailbox_node_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart';
|
||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||
|
||||
class MailboxUtils {
|
||||
|
||||
static Tuple2<Map<MailboxId, List<MailboxId>>, List<MailboxId>> generateMapDescendantIdsAndMailboxIdList(
|
||||
List<PresentationMailbox> selectedMailboxList,
|
||||
MailboxTree defaultMailboxTree,
|
||||
MailboxTree folderMailboxTree,
|
||||
) {
|
||||
Map<MailboxId, List<MailboxId>> mapDescendantIds = {};
|
||||
List<MailboxId> allMailboxIds = [];
|
||||
|
||||
for (var mailbox in selectedMailboxList) {
|
||||
final currentMailboxId = mailbox.id;
|
||||
|
||||
if (allMailboxIds.contains(currentMailboxId)) {
|
||||
continue;
|
||||
} else {
|
||||
final matchedNode = defaultMailboxTree.findNode((node) => node.item.id == currentMailboxId)
|
||||
?? folderMailboxTree.findNode((node) => node.item.id == currentMailboxId);
|
||||
|
||||
if (matchedNode != null) {
|
||||
final descendantIds = matchedNode.descendantsAsList().mailboxIds;
|
||||
final descendantIdsReversed = descendantIds.reversed.toList();
|
||||
|
||||
mapDescendantIds[currentMailboxId] = descendantIdsReversed;
|
||||
allMailboxIds.addAll(descendantIdsReversed);
|
||||
}
|
||||
}
|
||||
}
|
||||
log('MailboxUtils::generateMapDescendantIdsAndMailboxIdList(): mapDescendantIds: $mapDescendantIds');
|
||||
return Tuple2(mapDescendantIds, allMailboxIds);
|
||||
}
|
||||
|
||||
static EdgeInsets getPaddingListViewMailboxSearched(BuildContext context, ResponsiveUtils responsiveUtils) {
|
||||
if (responsiveUtils.isWebDesktop(context)) {
|
||||
return const EdgeInsets.only(left: 16, right: 16, bottom: 16);
|
||||
} else {
|
||||
if (responsiveUtils.isScreenWithShortestSide(context)) {
|
||||
return const EdgeInsets.all(16);
|
||||
} else {
|
||||
return const EdgeInsets.symmetric(horizontal: 32, vertical: 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool isDeletedMessageVaultSupported(Session? session, AccountId? accountId) {
|
||||
if (session == null || accountId == null) {
|
||||
return false;
|
||||
|
||||
@@ -63,7 +63,6 @@ import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentat
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_action_reactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_utils.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/mailbox_creator_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_mailbox_arguments.dart';
|
||||
@@ -584,19 +583,10 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
|
||||
|
||||
void _deleteMailboxAction(PresentationMailbox presentationMailbox) {
|
||||
if (session != null && accountId != null) {
|
||||
final tupleMap = MailboxUtils.generateMapDescendantIdsAndMailboxIdList(
|
||||
[presentationMailbox],
|
||||
defaultMailboxTree.value,
|
||||
personalMailboxTree.value
|
||||
);
|
||||
final mapDescendantIds = tupleMap.value1;
|
||||
final listMailboxId = tupleMap.value2;
|
||||
|
||||
consumeState(_deleteMultipleMailboxInteractor.execute(
|
||||
session!,
|
||||
accountId!,
|
||||
mapDescendantIds,
|
||||
listMailboxId
|
||||
[presentationMailbox.id],
|
||||
));
|
||||
} else {
|
||||
_deleteMailboxFailure(DeleteMultipleMailboxFailure(null));
|
||||
|
||||
Reference in New Issue
Block a user