refactor tab close dialog
This commit is contained in:
+3
-6
@@ -36,6 +36,7 @@ import 'package:weblibre/features/geckoview/features/browser/domain/entities/fon
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart';
|
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart';
|
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/utils/tab_close_confirmation.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/font_size_bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/font_size_bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart';
|
||||||
@@ -542,9 +543,7 @@ class _CloseTabToolbarButton extends HookConsumerWidget {
|
|||||||
.where((id) => id != scope.selectedTabId)
|
.where((id) => id != scope.selectedTabId)
|
||||||
.toList();
|
.toList();
|
||||||
if (otherIds.isNotEmpty) {
|
if (otherIds.isNotEmpty) {
|
||||||
await ref
|
await closeTabsWithConfirmation(context, ref, otherIds);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.closeTabs(otherIds);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('Close Others'),
|
child: const Text('Close Others'),
|
||||||
@@ -559,9 +558,7 @@ class _CloseTabToolbarButton extends HookConsumerWidget {
|
|||||||
.map((e) => e.key)
|
.map((e) => e.key)
|
||||||
.toList();
|
.toList();
|
||||||
if (sameHostIds.isNotEmpty) {
|
if (sameHostIds.isNotEmpty) {
|
||||||
await ref
|
await closeTabsWithConfirmation(context, ref, sameHostIds);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.closeTabs(sameHostIds);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('Close from Same Host'),
|
child: const Text('Close from Same Host'),
|
||||||
|
|||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2026 Fabian Freund.
|
||||||
|
*
|
||||||
|
* This file is part of WebLibre
|
||||||
|
* (see https://weblibre.eu).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
|
int countIsolatedGroupsRemovedByClosingTabs(
|
||||||
|
WidgetRef ref,
|
||||||
|
Iterable<String> tabIds,
|
||||||
|
) {
|
||||||
|
final idsToClose = tabIds.toSet();
|
||||||
|
if (idsToClose.isEmpty) return 0;
|
||||||
|
|
||||||
|
final allStates = ref.read(tabStatesProvider);
|
||||||
|
final isolatedCloseCounts = <String, int>{};
|
||||||
|
|
||||||
|
for (final tabId in idsToClose) {
|
||||||
|
final contextId = allStates[tabId]?.isolationContextId;
|
||||||
|
if (contextId != null) {
|
||||||
|
isolatedCloseCounts.update(
|
||||||
|
contextId,
|
||||||
|
(count) => count + 1,
|
||||||
|
ifAbsent: () => 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isolatedCloseCounts.entries.where((entry) {
|
||||||
|
final totalInGroup = allStates.values
|
||||||
|
.where((state) => state.isolationContextId == entry.key)
|
||||||
|
.length;
|
||||||
|
return totalInGroup == entry.value;
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> confirmBulkTabCloseIfNeeded(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
Iterable<String> tabIds,
|
||||||
|
) async {
|
||||||
|
final groupsToDelete = countIsolatedGroupsRemovedByClosingTabs(ref, tabIds);
|
||||||
|
if (groupsToDelete <= 0) return true;
|
||||||
|
if (!context.mounted) return false;
|
||||||
|
|
||||||
|
return ui_helper.confirmIsolatedTabClose(context, groupCount: groupsToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> closeTabsWithConfirmation(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
Iterable<String> tabIds,
|
||||||
|
) async {
|
||||||
|
final idsToClose = tabIds.toSet().toList();
|
||||||
|
if (idsToClose.isEmpty) return false;
|
||||||
|
|
||||||
|
final tabRepository = ref.read(tabRepositoryProvider.notifier);
|
||||||
|
|
||||||
|
if (!await confirmBulkTabCloseIfNeeded(context, ref, idsToClose)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!context.mounted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tabRepository.closeTabs(idsToClose);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
+3
-6
@@ -48,6 +48,7 @@ import 'package:weblibre/features/geckoview/domain/providers/web_extensions_stat
|
|||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/utils/tab_close_confirmation.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/history_menu.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/history_menu.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/translation_bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/translation_bottom_sheet.dart';
|
||||||
@@ -319,9 +320,7 @@ class _NavigationRow extends HookConsumerWidget {
|
|||||||
.where((id) => id != selectedTabId)
|
.where((id) => id != selectedTabId)
|
||||||
.toList();
|
.toList();
|
||||||
if (otherIds.isNotEmpty) {
|
if (otherIds.isNotEmpty) {
|
||||||
await ref
|
await closeTabsWithConfirmation(context, ref, otherIds);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.closeTabs(otherIds);
|
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -339,9 +338,7 @@ class _NavigationRow extends HookConsumerWidget {
|
|||||||
.map((e) => e.key)
|
.map((e) => e.key)
|
||||||
.toList();
|
.toList();
|
||||||
if (sameHostIds.isNotEmpty) {
|
if (sameHostIds.isNotEmpty) {
|
||||||
await ref
|
await closeTabsWithConfirmation(context, ref, sameHostIds);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.closeTabs(sameHostIds);
|
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
+8
-25
@@ -29,6 +29,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
|||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/utils/tab_close_confirmation.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/utils/grid_calculations.dart';
|
import 'package:weblibre/features/geckoview/features/browser/utils/grid_calculations.dart';
|
||||||
@@ -139,32 +140,14 @@ class _TabTreePreview extends HookConsumerWidget {
|
|||||||
final tabs = await ref
|
final tabs = await ref
|
||||||
.read(tabDataRepositoryProvider.notifier)
|
.read(tabDataRepositoryProvider.notifier)
|
||||||
.getTabDescendants(entity.rootId);
|
.getTabDescendants(entity.rootId);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final allStates = ref.read(tabStatesProvider);
|
if (!await confirmBulkTabCloseIfNeeded(
|
||||||
final isolatedContextInCloseSet = <String, int>{};
|
context,
|
||||||
|
ref,
|
||||||
for (final tabId in tabs.keys) {
|
tabs.keys,
|
||||||
final contextId = allStates[tabId]?.isolationContextId;
|
)) {
|
||||||
if (contextId == null) continue;
|
return;
|
||||||
isolatedContextInCloseSet[contextId] =
|
|
||||||
(isolatedContextInCloseSet[contextId] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
final groupsToDelete = isolatedContextInCloseSet.entries.where((
|
|
||||||
entry,
|
|
||||||
) {
|
|
||||||
final totalInGroup = allStates.values
|
|
||||||
.where((state) => state.isolationContextId == entry.key)
|
|
||||||
.length;
|
|
||||||
return totalInGroup == entry.value;
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
if (groupsToDelete > 0 && context.mounted) {
|
|
||||||
final confirmed = await ui_helper.confirmIsolatedTabClose(
|
|
||||||
context,
|
|
||||||
groupCount: groupsToDelete,
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await ref
|
await ref
|
||||||
|
|||||||
+8
-24
@@ -41,6 +41,7 @@ import 'package:weblibre/features/geckoview/features/browser/domain/services/bro
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_view_controllers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/bookmark_all_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/bookmark_all_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/select_folder_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/select_folder_dialog.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/utils/tab_close_confirmation.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/dialogs/clear_container_data_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/dialogs/clear_container_data_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/dialogs/close_all_private_tabs_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/dialogs/close_all_private_tabs_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/dialogs/close_all_tabs_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/dialogs/close_all_tabs_dialog.dart';
|
||||||
@@ -846,34 +847,17 @@ class TabViewHeader extends HookConsumerWidget {
|
|||||||
.getFilteredTabIds(
|
.getFilteredTabIds(
|
||||||
selectedContainerId,
|
selectedContainerId,
|
||||||
);
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (filteredIds.isEmpty) return;
|
if (filteredIds.isEmpty) return;
|
||||||
|
|
||||||
// Check for isolated tabs
|
// Check for isolated tabs
|
||||||
final allStates = ref.read(
|
if (!await confirmBulkTabCloseIfNeeded(
|
||||||
tabStatesProvider,
|
context,
|
||||||
);
|
ref,
|
||||||
final isolatedContextIds = filteredIds
|
filteredIds,
|
||||||
.map((id) => allStates[id])
|
)) {
|
||||||
.where(
|
return;
|
||||||
(s) =>
|
|
||||||
s != null &&
|
|
||||||
s.tabMode
|
|
||||||
is IsolatedTabMode &&
|
|
||||||
s.isolationContextId != null,
|
|
||||||
)
|
|
||||||
.map((s) => s!.isolationContextId!)
|
|
||||||
.toSet();
|
|
||||||
|
|
||||||
if (isolatedContextIds.isNotEmpty &&
|
|
||||||
context.mounted) {
|
|
||||||
final confirmed = await ui_helper
|
|
||||||
.confirmIsolatedTabClose(
|
|
||||||
context,
|
|
||||||
groupCount:
|
|
||||||
isolatedContextIds.length,
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await ref
|
await ref
|
||||||
|
|||||||
Reference in New Issue
Block a user