reorganize tab bar menus; readerview button visibility via setting;
This commit is contained in:
+72
-84
@@ -24,12 +24,11 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:weblibre/core/providers/defaults.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/desktop_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
@@ -40,8 +39,8 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
|
||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
|
||||
@@ -62,6 +61,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
|
||||
final tabMenuController = useMenuController();
|
||||
final extensionMenuController = useMenuController();
|
||||
final hamburgerMenuController = useMenuController();
|
||||
final trippleDotMenuController = useMenuController();
|
||||
|
||||
final selectedTabId = ref.watch(selectedTabProvider);
|
||||
@@ -138,7 +138,48 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
: null,
|
||||
actions: [
|
||||
if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
||||
ReaderButton(),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final tabBarReaderView = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.tabBarReaderView,
|
||||
),
|
||||
);
|
||||
|
||||
final readerabilityStateActive = ref.watch(
|
||||
selectedTabStateProvider.select(
|
||||
(state) =>
|
||||
(state?.readerableState ?? ReaderableState.$default())
|
||||
.active,
|
||||
),
|
||||
);
|
||||
|
||||
return Visibility(
|
||||
visible: tabBarReaderView || readerabilityStateActive,
|
||||
child: ReaderButton(
|
||||
buttonBuilder: (isLoading, readerActive, icon) => InkWell(
|
||||
onTap: isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref
|
||||
.read(
|
||||
readerableScreenControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.toggleReaderView(!readerActive);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (showExtensionShortcut)
|
||||
ExtensionShortcutMenu(
|
||||
controller: extensionMenuController,
|
||||
@@ -153,6 +194,27 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
icon: const Icon(MdiIcons.puzzle),
|
||||
),
|
||||
),
|
||||
if (selectedTabId != null)
|
||||
TabMenu(
|
||||
controller: trippleDotMenuController,
|
||||
selectedTabId: selectedTabId,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (trippleDotMenuController.isOpen) {
|
||||
trippleDotMenuController.close();
|
||||
} else {
|
||||
trippleDotMenuController.open();
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 15.0,
|
||||
),
|
||||
child: Icon(MdiIcons.dotsHorizontal),
|
||||
),
|
||||
),
|
||||
),
|
||||
TabCreationMenu(
|
||||
controller: tabMenuController,
|
||||
selectedTabId: selectedTabId,
|
||||
@@ -187,7 +249,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
MenuAnchor(
|
||||
controller: trippleDotMenuController,
|
||||
controller: hamburgerMenuController,
|
||||
builder: (context, controller, child) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
@@ -204,7 +266,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Icon(Icons.more_vert),
|
||||
child: Icon(Icons.menu),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -377,80 +439,6 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
child: const Text('Feeds'),
|
||||
),
|
||||
const Divider(),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(
|
||||
tabStateProvider(selectedTabId),
|
||||
);
|
||||
|
||||
if (tabState?.url case final Uri url) {
|
||||
await ui_helper.launchUrlFeedback(context, url);
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.open_in_browser),
|
||||
child: const Text('Launch External'),
|
||||
),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(
|
||||
tabStateProvider(selectedTabId),
|
||||
);
|
||||
|
||||
if (tabState?.url case final Uri url) {
|
||||
await SharePlus.instance.share(ShareParams(uri: url));
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.share),
|
||||
child: const Text('Share'),
|
||||
),
|
||||
if (selectedTabId != null) const Divider(),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
final tabId = ref.read(selectedTabProvider);
|
||||
if (tabId != null) {
|
||||
ref
|
||||
.read(findInPageControllerProvider(tabId).notifier)
|
||||
.show();
|
||||
}
|
||||
},
|
||||
leadingIcon: const Icon(Icons.search),
|
||||
child: const Text('Find in page'),
|
||||
),
|
||||
if (selectedTabId != null)
|
||||
Consumer(
|
||||
builder: (context, childRef, child) {
|
||||
final enabled = childRef.watch(
|
||||
desktopModeProvider(selectedTabId),
|
||||
);
|
||||
|
||||
return MenuItemButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(desktopModeProvider(selectedTabId).notifier)
|
||||
.toggle();
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.monitor),
|
||||
trailingIcon: Checkbox(
|
||||
value: enabled,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
ref
|
||||
.read(
|
||||
desktopModeProvider(selectedTabId).notifier,
|
||||
)
|
||||
.enabled(value);
|
||||
trippleDotMenuController.close();
|
||||
}
|
||||
},
|
||||
),
|
||||
child: const Text('Desktop Mode'),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (selectedTabId != null) const Divider(),
|
||||
if (selectedTabId != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
@@ -459,7 +447,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
await controller.reload();
|
||||
trippleDotMenuController.close();
|
||||
hamburgerMenuController.close();
|
||||
},
|
||||
leadingIcon: const Icon(Icons.refresh),
|
||||
child: const Text('Reload'),
|
||||
@@ -513,7 +501,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
await controller.goBack();
|
||||
}
|
||||
|
||||
trippleDotMenuController.close();
|
||||
hamburgerMenuController.close();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
)
|
||||
@@ -523,7 +511,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.closeTab(selectedTabId);
|
||||
|
||||
trippleDotMenuController.close();
|
||||
hamburgerMenuController.close();
|
||||
|
||||
if (context.mounted) {
|
||||
ui_helper.showTabUndoClose(
|
||||
@@ -551,7 +539,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
await controller.goForward();
|
||||
trippleDotMenuController.close();
|
||||
hamburgerMenuController.close();
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
|
||||
-197
@@ -17,33 +17,18 @@
|
||||
* 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 'dart:ui' as ui;
|
||||
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:weblibre/features/bangs/presentation/widgets/site_search.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/certificate_tile.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:weblibre/presentation/widgets/share_tile.dart';
|
||||
import 'package:weblibre/presentation/widgets/website_feed_tile.dart';
|
||||
import 'package:weblibre/presentation/widgets/website_title_tile.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
class ClampingScrollPhysicsWithoutImplicit extends ClampingScrollPhysics {
|
||||
const ClampingScrollPhysicsWithoutImplicit({super.parent});
|
||||
@@ -220,188 +205,6 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.contentCopy),
|
||||
title: const Text('Copy address'),
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: initialTabState.url.toString()),
|
||||
);
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await ui_helper.launchUrlFeedback(
|
||||
context,
|
||||
initialTabState.url,
|
||||
);
|
||||
},
|
||||
leading: const Icon(Icons.open_in_browser),
|
||||
title: const Text('Launch External'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.tabPlus),
|
||||
title: const Text('Clone tab'),
|
||||
onTap: () async {
|
||||
final tabId = await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: initialTabState.url,
|
||||
private: false,
|
||||
selectTab: false,
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
//save reference before pop `ref` gets disposed
|
||||
final repo = ref.read(tabRepositoryProvider.notifier);
|
||||
|
||||
ui_helper.showTabSwitchMessage(
|
||||
context,
|
||||
onSwitch: () async {
|
||||
await repo.selectTab(tabId);
|
||||
},
|
||||
);
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.tabUnselected),
|
||||
title: const Text('Clone as private tab'),
|
||||
onTap: () async {
|
||||
final tabId = await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: initialTabState.url,
|
||||
private: true,
|
||||
selectTab: false,
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
//save reference before pop `ref` gets disposed
|
||||
final repo = ref.read(tabRepositoryProvider.notifier);
|
||||
|
||||
ui_helper.showTabSwitchMessage(
|
||||
context,
|
||||
onSwitch: () async {
|
||||
await repo.selectTab(tabId);
|
||||
},
|
||||
);
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.folderArrowUpDownOutline),
|
||||
title: const Text('Assign container'),
|
||||
onTap: () async {
|
||||
final targetContainerId = await ContainerSelectionRoute()
|
||||
.push<String?>(context);
|
||||
|
||||
if (targetContainerId != null) {
|
||||
final containerData = await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.getContainerData(targetContainerId);
|
||||
|
||||
if (containerData != null) {
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(initialTabState.id, containerData);
|
||||
}
|
||||
}
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
},
|
||||
),
|
||||
Consumer(
|
||||
child: ListTile(
|
||||
leading: const Icon(MdiIcons.folderCancelOutline),
|
||||
title: const Text('Unassign container'),
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.unassignContainer(initialTabState.id);
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
},
|
||||
),
|
||||
builder: (context, ref, child) {
|
||||
final containerId = ref.watch(
|
||||
watchContainerTabIdProvider(
|
||||
initialTabState.id,
|
||||
).select((value) => value.value),
|
||||
);
|
||||
|
||||
return Visibility(
|
||||
visible: containerId != null,
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
),
|
||||
ShareTile(
|
||||
onTap: () async {
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(uri: initialTabState.url),
|
||||
);
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
},
|
||||
onTapQr: () async {
|
||||
await showQrCode(context, initialTabState.url.toString());
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.mobile_screen_share),
|
||||
title: const Text('Share screenshot'),
|
||||
onTap: () async {
|
||||
final screenshot = await ref
|
||||
.read(selectedTabSessionProvider)
|
||||
.requestScreenshot();
|
||||
|
||||
if (screenshot != null) {
|
||||
ui.decodeImageFromList(screenshot, (result) async {
|
||||
final png = await result.toByteData(
|
||||
format: ui.ImageByteFormat.png,
|
||||
);
|
||||
|
||||
if (png != null) {
|
||||
final file = XFile.fromData(
|
||||
png.buffer.asUint8List(),
|
||||
mimeType: 'image/png',
|
||||
);
|
||||
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [file],
|
||||
subject: initialTabState.title,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.requestDismiss();
|
||||
},
|
||||
),
|
||||
WebsiteFeedTile(initialTabState),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/desktop_mode.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.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/features/browser/presentation/dialogs/qr_code.dart';
|
||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/presentation/widgets/website_feed_menu_button.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
class TabMenu extends HookConsumerWidget {
|
||||
final Widget child;
|
||||
final MenuController controller;
|
||||
final String selectedTabId;
|
||||
|
||||
const TabMenu({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.controller,
|
||||
required this.selectedTabId,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return MenuAnchor(
|
||||
controller: controller,
|
||||
builder: (context, controller, child) {
|
||||
return child!;
|
||||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
|
||||
|
||||
ref
|
||||
.read(findInPageControllerProvider(selectedTabId).notifier)
|
||||
.show();
|
||||
},
|
||||
leadingIcon: const Icon(Icons.search),
|
||||
child: const Text('Find in page'),
|
||||
),
|
||||
ReaderButton(
|
||||
buttonBuilder: (isLoading, readerActive, icon) => MenuItemButton(
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(!readerActive);
|
||||
},
|
||||
leadingIcon: icon,
|
||||
trailingIcon: Checkbox(
|
||||
value: readerActive,
|
||||
onChanged: (value) async {
|
||||
if (value != null && !isLoading) {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(!readerActive);
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
),
|
||||
child: const Text('Reader Mode'),
|
||||
),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, childRef, child) {
|
||||
final enabled = childRef.watch(desktopModeProvider(selectedTabId));
|
||||
|
||||
return MenuItemButton(
|
||||
onPressed: () {
|
||||
ref.read(desktopModeProvider(selectedTabId).notifier).toggle();
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.monitor),
|
||||
trailingIcon: Checkbox(
|
||||
value: enabled,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
ref
|
||||
.read(desktopModeProvider(selectedTabId).notifier)
|
||||
.enabled(value);
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
),
|
||||
child: const Text('Desktop Mode'),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(MdiIcons.contentCopy),
|
||||
child: const Text('Copy address'),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: tabState.url.toString()),
|
||||
);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.open_in_browser),
|
||||
child: const Text('Launch External'),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
await ui_helper.launchUrlFeedback(context, tabState.url);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(MdiIcons.tabPlus),
|
||||
child: const Text('Clone tab'),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
final tabId = await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: tabState.url, private: false, selectTab: false);
|
||||
|
||||
if (context.mounted) {
|
||||
//save reference before pop `ref` gets disposed
|
||||
final repo = ref.read(tabRepositoryProvider.notifier);
|
||||
|
||||
ui_helper.showTabSwitchMessage(
|
||||
context,
|
||||
onSwitch: () async {
|
||||
await repo.selectTab(tabId);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(MdiIcons.tabUnselected),
|
||||
child: const Text('Clone as private tab'),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
final tabId = await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: tabState.url, private: true, selectTab: false);
|
||||
|
||||
if (context.mounted) {
|
||||
//save reference before pop `ref` gets disposed
|
||||
final repo = ref.read(tabRepositoryProvider.notifier);
|
||||
|
||||
ui_helper.showTabSwitchMessage(
|
||||
context,
|
||||
onSwitch: () async {
|
||||
await repo.selectTab(tabId);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(MdiIcons.folderArrowUpDownOutline),
|
||||
child: const Text('Assign container'),
|
||||
onPressed: () async {
|
||||
final targetContainerId = await ContainerSelectionRoute()
|
||||
.push<String?>(context);
|
||||
|
||||
if (targetContainerId != null) {
|
||||
final containerData = await ref
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.getContainerData(targetContainerId);
|
||||
|
||||
if (containerData != null) {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(tabState.id, containerData);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
Consumer(
|
||||
child: MenuItemButton(
|
||||
leadingIcon: const Icon(MdiIcons.folderCancelOutline),
|
||||
child: const Text('Unassign container'),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.unassignContainer(tabState.id);
|
||||
},
|
||||
),
|
||||
builder: (context, ref, child) {
|
||||
final containerId = ref.watch(
|
||||
watchContainerTabIdProvider(
|
||||
selectedTabId,
|
||||
).select((value) => value.value),
|
||||
);
|
||||
|
||||
return Visibility(visible: containerId != null, child: child!);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.share),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
await SharePlus.instance.share(ShareParams(uri: tabState.url));
|
||||
},
|
||||
trailingIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const VerticalDivider(indent: 4, endIndent: 4),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.qr_code),
|
||||
onPressed: () async {
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
await showQrCode(context, tabState.url.toString());
|
||||
controller.close();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Text('Share link'),
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.mobile_screen_share),
|
||||
child: const Text('Share screenshot'),
|
||||
onPressed: () async {
|
||||
final screenshot = await ref
|
||||
.read(selectedTabSessionProvider)
|
||||
.requestScreenshot();
|
||||
|
||||
final tabState = ref.read(tabStateProvider(selectedTabId))!;
|
||||
|
||||
if (screenshot != null) {
|
||||
ui.decodeImageFromList(screenshot, (result) async {
|
||||
final png = await result.toByteData(
|
||||
format: ui.ImageByteFormat.png,
|
||||
);
|
||||
|
||||
if (png != null) {
|
||||
final file = XFile.fromData(
|
||||
png.buffer.asUint8List(),
|
||||
mimeType: 'image/png',
|
||||
);
|
||||
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(files: [file], subject: tabState.title),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
WebsiteFeedMenuButton(selectedTabId),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-22
@@ -27,6 +27,11 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
|
||||
import 'package:weblibre/presentation/widgets/animate_gradient_shader.dart';
|
||||
|
||||
class ReaderButton extends HookConsumerWidget {
|
||||
final Widget Function(bool isLoading, bool readerActive, Widget icon)
|
||||
buttonBuilder;
|
||||
|
||||
const ReaderButton({super.key, required this.buttonBuilder});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
@@ -65,21 +70,10 @@ class ReaderButton extends HookConsumerWidget {
|
||||
(enforceReadability && enableReadability),
|
||||
child: readerChanging.when(
|
||||
skipLoadingOnReload: true,
|
||||
data: (_) => InkWell(
|
||||
onTap: readerChanging.isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(!readerabilityState.active);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
data: (_) => buttonBuilder(
|
||||
readerChanging.isLoading,
|
||||
readerabilityState.active,
|
||||
icon,
|
||||
),
|
||||
error: (error, stackTrace) => SizedBox.shrink(),
|
||||
loading: () => AnimateGradientShader(
|
||||
@@ -91,13 +85,7 @@ class ReaderButton extends HookConsumerWidget {
|
||||
colorScheme.secondary,
|
||||
colorScheme.secondaryContainer,
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
child: buttonBuilder(true, readerabilityState.active, icon),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+1
-1
@@ -10,8 +10,8 @@ import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/core/uuid.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/view_tabs.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/view_tabs.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart';
|
||||
|
||||
Reference in New Issue
Block a user