close tabs by host

This commit is contained in:
Fabian Freund
2025-06-02 22:22:07 +02:00
parent f6e0f665ce
commit 8fccd767af
9 changed files with 91 additions and 16 deletions
@@ -7,7 +7,7 @@ part of 'providers.dart';
// ************************************************************************** // **************************************************************************
String _$selectionActionServiceHash() => String _$selectionActionServiceHash() =>
r'9393acfbc2cc0864d9e9b116e65e4c2713da2ab1'; r'c45737a0a403bff1bdb1931b89701300f8dc3726';
/// See also [selectionActionService]. /// See also [selectionActionService].
@ProviderFor(selectionActionService) @ProviderFor(selectionActionService)
@@ -76,11 +76,11 @@ class _TabSheetHeader extends HookConsumerWidget {
final container = ref.read(selectedContainerProvider); final container = ref.read(selectedContainerProvider);
await ref await ref
.read(tabDataRepositoryProvider.notifier) .read(tabDataRepositoryProvider.notifier)
.closeAllTabs(container); .closeAllTabsByContainer(container);
onClose(); onClose();
}, },
icon: const Icon(Icons.delete), icon: const Icon(MdiIcons.closeBoxMultiple),
label: const Text('Close All'), label: const Text('Close All'),
), ),
], ],
@@ -1,18 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; 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/presentation/widgets/tab_icon.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart';
class TabPreview extends StatelessWidget { class TabPreview extends HookWidget {
final TabState tab; final TabState tab;
final bool isActive; final bool isActive;
final VoidCallback? onTap; final VoidCallback? onTap;
final VoidCallback? onDoubleTap; final VoidCallback? onDoubleTap;
final VoidCallback? onDelete; final VoidCallback? onDelete;
final void Function(String host)? onDeleteAll;
const TabPreview({ const TabPreview({
required this.tab, required this.tab,
@@ -20,12 +25,15 @@ class TabPreview extends StatelessWidget {
this.onTap, this.onTap,
this.onDoubleTap, this.onDoubleTap,
this.onDelete, this.onDelete,
this.onDeleteAll,
super.key, super.key,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final extendedDeleteMenuController = useMenuController();
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
@@ -62,14 +70,38 @@ class TabPreview extends StatelessWidget {
), ),
), ),
), ),
IconButton( MenuAnchor(
controller: extendedDeleteMenuController,
builder: (context, controller, child) {
return child!;
},
menuChildren: [
MenuItemButton(
onPressed: onDeleteAll.mapNotNull(
(p0) => () {
p0(tab.url.host);
},
),
leadingIcon: const Icon(MdiIcons.closeBoxMultiple),
child: Text('Close all from ${tab.url.host}'),
),
],
child: IconButton(
visualDensity: const VisualDensity( visualDensity: const VisualDensity(
horizontal: -4.0, horizontal: -4.0,
vertical: -4.0, vertical: -4.0,
), ),
onPressed: onDelete, onPressed: onDelete,
onLongPress: () {
if (extendedDeleteMenuController.isOpen) {
extendedDeleteMenuController.close();
} else {
extendedDeleteMenuController.open();
}
},
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
), ),
),
], ],
), ),
Row( Row(
@@ -166,6 +198,15 @@ class TabPreviewDraggable extends HookConsumerWidget {
onClose(); onClose();
} }
}, },
onDeleteAll: (host) async {
final containerId = await ref
.read(tabDataRepositoryProvider.notifier)
.containerTabId(tab.id);
await ref
.read(tabDataRepositoryProvider.notifier)
.closeAllTabsByHost(containerId, host);
},
// onDoubleTap: () { // onDoubleTap: () {
// ref.read(overlayDialogControllerProvider.notifier).show( // ref.read(overlayDialogControllerProvider.notifier).show(
// TabActionDialog( // TabActionDialog(
@@ -66,6 +66,20 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
return query.map((row) => row.read(db.tab.id)!); return query.map((row) => row.read(db.tab.id)!);
} }
Selectable<TabData> getContainerTabsData(String? containerId) {
final query =
db.tab.select()
..where(
(t) =>
(containerId != null)
? t.containerId.equals(containerId)
: t.containerId.isNull(),
)
..orderBy([(t) => OrderingTerm.asc(t.orderKey)]);
return query;
}
SingleSelectable<String> generateLeadingOrderKey( SingleSelectable<String> generateLeadingOrderKey(
String? containerId, { String? containerId, {
int bucket = 0, int bucket = 0,
@@ -43,7 +43,9 @@ class ContainerRepository extends _$ContainerRepository {
} }
Future<void> deleteContainer(String id) async { Future<void> deleteContainer(String id) async {
await ref.read(tabDataRepositoryProvider.notifier).closeAllTabs(id); await ref
.read(tabDataRepositoryProvider.notifier)
.closeAllTabsByContainer(id);
return ref.read(tabDatabaseProvider).containerDao.deleteContainer(id); return ref.read(tabDatabaseProvider).containerDao.deleteContainer(id);
} }
@@ -7,7 +7,7 @@ part of 'container.dart';
// ************************************************************************** // **************************************************************************
String _$containerRepositoryHash() => String _$containerRepositoryHash() =>
r'9fff10b7896ea8865282bc315df21abde18b9d69'; r'5344ecde33c1c80c1fa2bc8383faca181da91593';
/// See also [ContainerRepository]. /// See also [ContainerRepository].
@ProviderFor(ContainerRepository) @ProviderFor(ContainerRepository)
@@ -20,7 +20,7 @@ class TabDataRepository extends _$TabDataRepository {
.assignOrderKey(tabId, orderKey: orderKey); .assignOrderKey(tabId, orderKey: orderKey);
} }
Future<void> closeAllTabs(String? containerId) async { Future<void> closeAllTabsByContainer(String? containerId) async {
final tabIds = final tabIds =
await ref await ref
.read(tabDatabaseProvider) .read(tabDatabaseProvider)
@@ -33,6 +33,25 @@ class TabDataRepository extends _$TabDataRepository {
} }
} }
Future<void> closeAllTabsByHost(String? containerId, String host) async {
final tabs =
await ref
.read(tabDatabaseProvider)
.containerDao
.getContainerTabsData(containerId)
.get();
final filtered =
tabs
.where((tab) => tab.url?.host == host)
.map((tab) => tab.id)
.toList();
if (filtered.isNotEmpty) {
await ref.read(tabRepositoryProvider.notifier).closeTabs(filtered);
}
}
Future<String?> containerTabId(String tabId) { Future<String?> containerTabId(String tabId) {
return ref return ref
.read(tabDatabaseProvider) .read(tabDatabaseProvider)
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$tabDataRepositoryHash() => r'b5193e58aa331c7d0dc374e01032fe3a44955c99'; String _$tabDataRepositoryHash() => r'0de1de9f948e38cc0a6d2fc18a06b1327d41c7ca';
/// See also [TabDataRepository]. /// See also [TabDataRepository].
@ProviderFor(TabDataRepository) @ProviderFor(TabDataRepository)
@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -8,6 +6,7 @@ import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
import 'package:universal_io/io.dart';
import 'package:weblibre/features/web_feed/data/database/database.dart'; import 'package:weblibre/features/web_feed/data/database/database.dart';
part 'providers.g.dart'; part 'providers.g.dart';