bookmark all option dialog fast/detailed

This commit is contained in:
Fabian Freund
2026-01-10 19:39:24 +01:00
parent 2e3aed7303
commit 2baecd4d2a
3 changed files with 163 additions and 9 deletions
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<BookmarkAllChoice?> showBookmarkAllDialog(BuildContext context) {
return showDialog<BookmarkAllChoice>(
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);
},
),
],
),
);
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String?> showSelectFolderDialog(BuildContext context) {
return showDialog<String>(
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'),
),
],
);
}
}
@@ -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);
}
}
}
},