diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/bookmark_all_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/bookmark_all_dialog.dart
new file mode 100644
index 00000000..7b2b9fd4
--- /dev/null
+++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/bookmark_all_dialog.dart
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024-2025 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 .
+ */
+
+import 'package:flutter/material.dart';
+import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
+
+enum BookmarkAllChoice { fast, detailed }
+
+/// Dialog to select between fast (automatic) or detailed (one-by-one) bookmark import.
+Future showBookmarkAllDialog(BuildContext context) {
+ return showDialog(
+ context: context,
+ builder: (context) => SimpleDialog(
+ title: const Text('Bookmark All Tabs'),
+ children: [
+ ListTile(
+ leading: const Icon(MdiIcons.fastForward),
+ title: const Text('Fast'),
+ subtitle: const Text(
+ 'Automatically add all tabs to a selected folder',
+ ),
+ onTap: () {
+ Navigator.of(context).pop(BookmarkAllChoice.fast);
+ },
+ ),
+ ListTile(
+ leading: const Icon(MdiIcons.stepForward),
+ title: const Text('Detailed'),
+ subtitle: const Text('Review and edit each bookmark individually'),
+ onTap: () {
+ Navigator.of(context).pop(BookmarkAllChoice.detailed);
+ },
+ ),
+ ],
+ ),
+ );
+}
diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/select_folder_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/select_folder_dialog.dart
new file mode 100644
index 00000000..a92bf8b5
--- /dev/null
+++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/select_folder_dialog.dart
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024-2025 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 .
+ */
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:weblibre/features/geckoview/features/bookmarks/presentation/widgets/folder_tree_picker.dart';
+
+/// Dialog to select a bookmark folder.
+/// Returns the selected folder GUID or null if cancelled.
+Future showSelectFolderDialog(BuildContext context) {
+ return showDialog(
+ context: context,
+ builder: (context) => const _SelectFolderDialog(),
+ );
+}
+
+class _SelectFolderDialog extends HookConsumerWidget {
+ const _SelectFolderDialog();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final selectedFolderGuid = useState(BookmarkRoot.mobile.id);
+
+ return AlertDialog(
+ title: const Text('Select folder'),
+ content: SizedBox(
+ width: double.maxFinite,
+ child: SingleChildScrollView(
+ child: FolderTreePicker(
+ selectedFolderGuid: selectedFolderGuid,
+ entryGuid: BookmarkRoot.root.id,
+ ),
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: const Text('Cancel'),
+ ),
+ FilledButton(
+ onPressed: () => Navigator.of(context).pop(selectedFolderGuid.value),
+ child: const Text('Select'),
+ ),
+ ],
+ );
+ }
+}
diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart
index e37a7ea8..1bc2991d 100644
--- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart
+++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart
@@ -10,8 +10,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
+import 'package:weblibre/features/geckoview/features/bookmarks/domain/repositories/bookmarks.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.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/select_folder_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_tabs_dialog.dart';
@@ -252,6 +255,9 @@ class TabViewHeader extends HookConsumerWidget {
leadingIcon: const Icon(MdiIcons.bookmarkPlusOutline),
child: const Text('Bookmark all'),
onPressed: () async {
+ final choice = await showBookmarkAllDialog(context);
+ if (choice == null || !context.mounted) return;
+
final containerId = ref.read(
selectedContainerProvider,
);
@@ -260,16 +266,44 @@ class TabViewHeader extends HookConsumerWidget {
.read(tabDataRepositoryProvider.notifier)
.getContainerTabsData(containerId);
- for (final tab in tabData) {
+ if (choice == BookmarkAllChoice.fast) {
+ if (!context.mounted) return;
+ final folderGuid = await showSelectFolderDialog(
+ context,
+ );
+ if (folderGuid == null) return;
+
+ final repo = ref.read(
+ bookmarksRepositoryProvider.notifier,
+ );
+ for (final tab in tabData) {
+ if (tab.url != null) {
+ await repo.addBookmark(
+ parentGuid: folderGuid,
+ url: tab.url!,
+ title: tab.title ?? tab.url.toString(),
+ );
+ }
+ }
+
if (context.mounted) {
- await BookmarkEntryAddRoute(
- bookmarkInfo: jsonEncode(
- BookmarkInfo(
- title: tab.title,
- url: tab.url.toString(),
- ).encode(),
- ),
- ).push(context);
+ ui_helper.showInfoMessage(
+ context,
+ '${tabData.length} bookmark(s) added',
+ );
+ }
+ } else {
+ for (final tab in tabData) {
+ if (context.mounted) {
+ await BookmarkEntryAddRoute(
+ bookmarkInfo: jsonEncode(
+ BookmarkInfo(
+ title: tab.title,
+ url: tab.url.toString(),
+ ).encode(),
+ ),
+ ).push(context);
+ }
}
}
},