refactor bottom app abr title
This commit is contained in:
+79
-90
@@ -1,108 +1,97 @@
|
|||||||
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:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:text_scroll/text_scroll.dart';
|
import 'package:text_scroll/text_scroll.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/features/browser/presentation/widgets/tab_icon.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||||
|
|
||||||
class AppBarTitle extends StatelessWidget {
|
class AppBarTitle extends HookConsumerWidget {
|
||||||
final TabState tab;
|
const AppBarTitle({super.key});
|
||||||
|
|
||||||
final VoidCallback? onTap;
|
|
||||||
final VoidCallback? onLongPress;
|
|
||||||
final GestureDragStartCallback? onHorizontalDragStart;
|
|
||||||
final GestureDragEndCallback? onHorizontalDragEnd;
|
|
||||||
|
|
||||||
const AppBarTitle({
|
|
||||||
required this.tab,
|
|
||||||
this.onTap,
|
|
||||||
this.onLongPress,
|
|
||||||
this.onHorizontalDragStart,
|
|
||||||
this.onHorizontalDragEnd,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
Icon _securityStatusIcon(BuildContext context) {
|
|
||||||
if (tab.url.isScheme('http')) {
|
|
||||||
return Icon(
|
|
||||||
MdiIcons.lockOff,
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
size: 14,
|
|
||||||
);
|
|
||||||
} else if (!tab.securityInfoState.secure) {
|
|
||||||
return Icon(
|
|
||||||
MdiIcons.lockAlert,
|
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
|
||||||
size: 14,
|
|
||||||
);
|
|
||||||
} else if (!tab.isLoading) {
|
|
||||||
return const Icon(MdiIcons.lock, size: 14);
|
|
||||||
} else {
|
|
||||||
return const Icon(MdiIcons.timerSand, size: 14);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
final tabState = ref.watch(selectedTabStateProvider);
|
||||||
onLongPress: onLongPress,
|
|
||||||
onHorizontalDragStart: onHorizontalDragStart,
|
if (tabState == null) {
|
||||||
onHorizontalDragEnd: onHorizontalDragEnd,
|
return const SizedBox.shrink();
|
||||||
child: Row(
|
}
|
||||||
children: [
|
|
||||||
TabIcon(state: tab),
|
final icon = useMemoized(() {
|
||||||
const SizedBox(width: 8),
|
if (tabState.url.isScheme('http')) {
|
||||||
Expanded(
|
return Icon(
|
||||||
child: Column(
|
MdiIcons.lockOff,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
color: Theme.of(context).colorScheme.error,
|
||||||
mainAxisSize: MainAxisSize.min,
|
size: 14,
|
||||||
children: [
|
);
|
||||||
Skeletonizer(
|
} else if (!tabState.securityInfoState.secure) {
|
||||||
enabled: tab.title.isEmpty,
|
return Icon(
|
||||||
child: Skeleton.replace(
|
MdiIcons.lockAlert,
|
||||||
replacement: const Padding(
|
color: Theme.of(context).colorScheme.errorContainer,
|
||||||
padding: EdgeInsets.only(right: 4, top: 1, bottom: 1),
|
size: 14,
|
||||||
child: Bone.text(),
|
);
|
||||||
),
|
} else if (!tabState.isLoading) {
|
||||||
child: TextScroll(
|
return const Icon(MdiIcons.lock, size: 14);
|
||||||
key: ValueKey(tab.title),
|
} else {
|
||||||
tab.title,
|
return const Icon(MdiIcons.timerSand, size: 14);
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
}
|
||||||
color: theme.colorScheme.onSurface,
|
}, [tabState]);
|
||||||
),
|
|
||||||
// mode: TextScrollMode.bouncing,
|
return Row(
|
||||||
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
|
children: [
|
||||||
delayBefore: const Duration(milliseconds: 500),
|
TabIcon(state: tabState),
|
||||||
pauseBetween: const Duration(milliseconds: 5000),
|
const SizedBox(width: 8),
|
||||||
fadedBorder: true,
|
Expanded(
|
||||||
fadeBorderSide: FadeBorderSide.right,
|
child: Column(
|
||||||
fadedBorderWidth: 0.05,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
intervalSpaces: 4,
|
mainAxisSize: MainAxisSize.min,
|
||||||
numberOfReps: 2,
|
children: [
|
||||||
|
Skeletonizer(
|
||||||
|
enabled: tabState.title.isEmpty,
|
||||||
|
child: Skeleton.replace(
|
||||||
|
replacement: const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 4, top: 1, bottom: 1),
|
||||||
|
child: Bone.text(),
|
||||||
|
),
|
||||||
|
child: TextScroll(
|
||||||
|
key: ValueKey(tabState.title),
|
||||||
|
tabState.title,
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
// mode: TextScrollMode.bouncing,
|
||||||
|
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
|
||||||
|
delayBefore: const Duration(milliseconds: 500),
|
||||||
|
pauseBetween: const Duration(milliseconds: 5000),
|
||||||
|
fadedBorder: true,
|
||||||
|
fadeBorderSide: FadeBorderSide.right,
|
||||||
|
fadedBorderWidth: 0.05,
|
||||||
|
intervalSpaces: 4,
|
||||||
|
numberOfReps: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
),
|
||||||
children: [
|
Row(
|
||||||
_securityStatusIcon(context),
|
children: [
|
||||||
const SizedBox(width: 4),
|
icon,
|
||||||
Expanded(
|
const SizedBox(width: 4),
|
||||||
child: Text(
|
Expanded(
|
||||||
tab.url.authority,
|
child: Text(
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
tabState.url.authority,
|
||||||
color: theme.colorScheme.onSurface,
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
),
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+340
-342
@@ -41,375 +41,373 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
|||||||
selectedTabStateProvider.select((state) => state?.isPrivate ?? false),
|
selectedTabStateProvider.select((state) => state?.isPrivate ?? false),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final dragStartPosition = useRef(Offset.zero);
|
||||||
|
|
||||||
return BottomAppBar(
|
return BottomAppBar(
|
||||||
height: AppBar().preferredSize.height,
|
height: AppBar().preferredSize.height,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
child: AppBar(
|
child: GestureDetector(
|
||||||
automaticallyImplyLeading: false,
|
onTap: () async {
|
||||||
titleSpacing: 8.0,
|
final tabState = ref.read(selectedTabStateProvider);
|
||||||
backgroundColor:
|
|
||||||
(isPrivateTab && displayedSheet is! ViewTabsSheet)
|
|
||||||
? const Color(0x648000D7)
|
|
||||||
: null,
|
|
||||||
title:
|
|
||||||
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
|
||||||
? HookConsumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
final tabState = ref.watch(selectedTabStateProvider);
|
|
||||||
|
|
||||||
final dragStartPosition = useRef(Offset.zero);
|
if (tabState != null) {
|
||||||
|
await WebPageRoute(
|
||||||
|
url: tabState.url.toString(),
|
||||||
|
$extra: tabState,
|
||||||
|
).push(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () async {
|
||||||
|
final tabState = ref.read(selectedTabStateProvider);
|
||||||
|
|
||||||
return (tabState != null)
|
if (tabState != null) {
|
||||||
? AppBarTitle(
|
final newUrl = await showDialog<Uri?>(
|
||||||
tab: tabState,
|
context: context,
|
||||||
onTap: () async {
|
builder: (context) => EditUrlDialog(initialUrl: tabState.url),
|
||||||
await WebPageRoute(
|
);
|
||||||
url: tabState.url.toString(),
|
|
||||||
$extra: tabState,
|
|
||||||
).push(context);
|
|
||||||
},
|
|
||||||
onLongPress: () async {
|
|
||||||
final newUrl = await showDialog<Uri?>(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) =>
|
|
||||||
EditUrlDialog(initialUrl: tabState.url),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newUrl != null) {
|
if (newUrl != null) {
|
||||||
await ref
|
await ref
|
||||||
.read(
|
.read(tabSessionProvider(tabId: null).notifier)
|
||||||
tabSessionProvider(tabId: null).notifier,
|
.loadUrl(url: newUrl);
|
||||||
)
|
}
|
||||||
.loadUrl(url: newUrl);
|
}
|
||||||
}
|
},
|
||||||
},
|
onHorizontalDragStart: (details) {
|
||||||
onHorizontalDragStart: (details) {
|
dragStartPosition.value = details.globalPosition;
|
||||||
dragStartPosition.value = details.globalPosition;
|
},
|
||||||
},
|
onHorizontalDragEnd: (details) async {
|
||||||
onHorizontalDragEnd: (details) async {
|
final distance = dragStartPosition.value - details.globalPosition;
|
||||||
final distance =
|
|
||||||
dragStartPosition.value -
|
|
||||||
details.globalPosition;
|
|
||||||
|
|
||||||
if (distance.dx.abs() > 50) {
|
if (distance.dx.abs() > 50) {
|
||||||
await ref
|
await ref.read(tabRepositoryProvider.notifier).selectPreviousTab();
|
||||||
.read(tabRepositoryProvider.notifier)
|
}
|
||||||
.selectPreviousTab();
|
},
|
||||||
}
|
child: AppBar(
|
||||||
},
|
automaticallyImplyLeading: false,
|
||||||
)
|
titleSpacing: 8.0,
|
||||||
: const SizedBox.shrink();
|
backgroundColor:
|
||||||
},
|
(isPrivateTab && displayedSheet is! ViewTabsSheet)
|
||||||
)
|
? const Color(0x648000D7)
|
||||||
: null,
|
: null,
|
||||||
actions: [
|
title:
|
||||||
if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
||||||
ReaderButton(),
|
? const AppBarTitle()
|
||||||
MenuAnchor(
|
: null,
|
||||||
controller: tabMenuController,
|
actions: [
|
||||||
builder: (context, controller, child) {
|
if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
|
||||||
return child!;
|
ReaderButton(),
|
||||||
},
|
MenuAnchor(
|
||||||
menuChildren: [
|
controller: tabMenuController,
|
||||||
if (selectedTabId != null) ...[
|
builder: (context, controller, child) {
|
||||||
MenuItemButton(
|
return child!;
|
||||||
onPressed: () async {
|
|
||||||
await ref
|
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.closeTab(selectedTabId);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(Icons.close),
|
|
||||||
child: const Text('Close Tab'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
],
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await const SearchRoute(
|
|
||||||
tabType: TabType.private,
|
|
||||||
).push(context);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(MdiIcons.tabUnselected),
|
|
||||||
child: const Text('Add Private Tab'),
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await const SearchRoute(
|
|
||||||
tabType: TabType.regular,
|
|
||||||
).push(context);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(MdiIcons.tabPlus),
|
|
||||||
child: const Text('Add Tab'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: TabsActionButton(
|
|
||||||
isActive: displayedSheet is ViewTabsSheet,
|
|
||||||
onTap: () {
|
|
||||||
if (displayedSheet case ViewTabsSheet()) {
|
|
||||||
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
|
||||||
} else {
|
|
||||||
ref
|
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
|
||||||
.show(ViewTabsSheet());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
menuChildren: [
|
||||||
if (tabMenuController.isOpen) {
|
if (selectedTabId != null) ...[
|
||||||
tabMenuController.close();
|
MenuItemButton(
|
||||||
} else {
|
onPressed: () async {
|
||||||
tabMenuController.open();
|
await ref
|
||||||
}
|
.read(tabRepositoryProvider.notifier)
|
||||||
},
|
.closeTab(selectedTabId);
|
||||||
),
|
},
|
||||||
),
|
leadingIcon: const Icon(Icons.close),
|
||||||
MenuAnchor(
|
child: const Text('Close Tab'),
|
||||||
controller: trippleDotMenuController,
|
|
||||||
builder: (context, controller, child) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (controller.isOpen) {
|
|
||||||
controller.close();
|
|
||||||
} else {
|
|
||||||
controller.open();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
vertical: 15.0,
|
|
||||||
horizontal: 8.0,
|
|
||||||
),
|
|
||||||
child: Icon(Icons.more_vert),
|
|
||||||
),
|
),
|
||||||
),
|
const Divider(),
|
||||||
);
|
],
|
||||||
},
|
|
||||||
menuChildren: [
|
|
||||||
Consumer(
|
|
||||||
builder: (context, childRef, child) {
|
|
||||||
final pageExtensions = childRef.watch(
|
|
||||||
webExtensionsStateProvider(
|
|
||||||
WebExtensionActionType.page,
|
|
||||||
).select((value) => value.values.toList()),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
children: [
|
|
||||||
...pageExtensions.map(
|
|
||||||
(extension) => IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
//Use parents .ref because after onPressed this consumer gets disposed already
|
|
||||||
await addonService.invokeAddonAction(
|
|
||||||
extension.extensionId,
|
|
||||||
WebExtensionActionType.page,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: ExtensionBadgeIcon(extension),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await AboutRoute().push(context);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(Icons.info),
|
|
||||||
child: const Text('About'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await SettingsRoute().push(context);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(Icons.settings),
|
|
||||||
child: const Text('Settings'),
|
|
||||||
),
|
|
||||||
Consumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
final browserExtensions = ref.watch(
|
|
||||||
webExtensionsStateProvider(
|
|
||||||
WebExtensionActionType.browser,
|
|
||||||
).select((value) => value.values.toList()),
|
|
||||||
);
|
|
||||||
|
|
||||||
return SubmenuButton(
|
|
||||||
menuChildren: [
|
|
||||||
...browserExtensions.map(
|
|
||||||
(extension) => MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
//Use parents .ref because after onPressed this consumer gets disposed already
|
|
||||||
await addonService.invokeAddonAction(
|
|
||||||
extension.extensionId,
|
|
||||||
WebExtensionActionType.browser,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
leadingIcon: ExtensionBadgeIcon(extension),
|
|
||||||
child: Text(extension.title ?? ''),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await addonService.startAddonManagerActivity();
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(MdiIcons.puzzleEdit),
|
|
||||||
child: const Text('Addon Manager'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
leadingIcon: const Icon(MdiIcons.puzzle),
|
|
||||||
child: const Text('Addons'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await TorProxyRoute().push(context);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(TorIcons.onionAlt),
|
|
||||||
child: const Text('Tor'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await BangCategoriesRoute().push(context);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(MdiIcons.exclamationThick),
|
|
||||||
child: const Text('Bangs'),
|
|
||||||
),
|
|
||||||
MenuItemButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await context.push(FeedListRoute().location);
|
|
||||||
},
|
|
||||||
leadingIcon: const Icon(Icons.rss_feed),
|
|
||||||
child: const Text('Feeds'),
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
if (selectedTabId != null)
|
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId));
|
await const SearchRoute(
|
||||||
|
tabType: TabType.private,
|
||||||
if (tabState?.url case final Uri url) {
|
).push(context);
|
||||||
await ui_helper.launchUrlFeedback(context, url);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
leadingIcon: const Icon(Icons.open_in_browser),
|
leadingIcon: const Icon(MdiIcons.tabUnselected),
|
||||||
child: const Text('Launch External'),
|
child: const Text('Add Private Tab'),
|
||||||
),
|
),
|
||||||
if (selectedTabId != null)
|
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final tabState = ref.read(tabStateProvider(selectedTabId));
|
await const SearchRoute(
|
||||||
|
tabType: TabType.regular,
|
||||||
if (tabState?.url case final Uri url) {
|
).push(context);
|
||||||
await SharePlus.instance.share(ShareParams(uri: url));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
leadingIcon: const Icon(Icons.share),
|
leadingIcon: const Icon(MdiIcons.tabPlus),
|
||||||
child: const Text('Share'),
|
child: const Text('Add Tab'),
|
||||||
),
|
),
|
||||||
if (selectedTabId != null) const Divider(),
|
],
|
||||||
if (selectedTabId != null)
|
child: TabsActionButton(
|
||||||
MenuItemButton(
|
isActive: displayedSheet is ViewTabsSheet,
|
||||||
onPressed: () {
|
onTap: () {
|
||||||
ref.read(findInPageControllerProvider.notifier).show();
|
if (displayedSheet case ViewTabsSheet()) {
|
||||||
},
|
ref.read(bottomSheetControllerProvider.notifier).dismiss();
|
||||||
leadingIcon: const Icon(Icons.search),
|
} else {
|
||||||
child: const Text('Find in page'),
|
ref
|
||||||
),
|
.read(bottomSheetControllerProvider.notifier)
|
||||||
if (selectedTabId != null) const Divider(),
|
.show(ViewTabsSheet());
|
||||||
if (selectedTabId != null)
|
}
|
||||||
MenuItemButton(
|
},
|
||||||
onPressed: () async {
|
onLongPress: () {
|
||||||
final controller = ref.read(
|
if (tabMenuController.isOpen) {
|
||||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
tabMenuController.close();
|
||||||
);
|
} else {
|
||||||
|
tabMenuController.open();
|
||||||
await controller.reload();
|
}
|
||||||
trippleDotMenuController.close();
|
},
|
||||||
},
|
),
|
||||||
leadingIcon: const Icon(Icons.refresh),
|
),
|
||||||
child: const Text('Reload'),
|
MenuAnchor(
|
||||||
),
|
controller: trippleDotMenuController,
|
||||||
if (selectedTabId != null) const Divider(),
|
builder: (context, controller, child) {
|
||||||
if (selectedTabId != null)
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (controller.isOpen) {
|
||||||
|
controller.close();
|
||||||
|
} else {
|
||||||
|
controller.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 15.0,
|
||||||
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
|
child: Icon(Icons.more_vert),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
menuChildren: [
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, childRef, child) {
|
||||||
final history = ref.watch(
|
final pageExtensions = childRef.watch(
|
||||||
tabStateProvider(
|
webExtensionsStateProvider(
|
||||||
selectedTabId,
|
WebExtensionActionType.page,
|
||||||
).select((value) => value?.historyState),
|
).select((value) => value.values.toList()),
|
||||||
);
|
);
|
||||||
|
|
||||||
final isLoading = ref.watch(
|
return Wrap(
|
||||||
selectedTabStateProvider.select(
|
alignment: WrapAlignment.center,
|
||||||
(state) => state?.isLoading ?? false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
...pageExtensions.map(
|
||||||
child:
|
(extension) => IconButton(
|
||||||
(history?.canGoBack == true || isLoading)
|
onPressed: () async {
|
||||||
? IconButton(
|
//Use parents .ref because after onPressed this consumer gets disposed already
|
||||||
onPressed: () async {
|
await addonService.invokeAddonAction(
|
||||||
final controller = ref.read(
|
extension.extensionId,
|
||||||
tabSessionProvider(
|
WebExtensionActionType.page,
|
||||||
tabId: selectedTabId,
|
);
|
||||||
).notifier,
|
},
|
||||||
);
|
icon: ExtensionBadgeIcon(extension),
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
await controller.stopLoading();
|
|
||||||
} else {
|
|
||||||
await controller.goBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
trippleDotMenuController.close();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await ref
|
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.closeTab(selectedTabId);
|
|
||||||
trippleDotMenuController.close();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 48, child: VerticalDivider()),
|
|
||||||
Expanded(
|
|
||||||
child: IconButton(
|
|
||||||
onPressed:
|
|
||||||
(history?.canGoForward == true)
|
|
||||||
? () async {
|
|
||||||
final controller = ref.read(
|
|
||||||
tabSessionProvider(
|
|
||||||
tabId: selectedTabId,
|
|
||||||
).notifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
await controller.goForward();
|
|
||||||
trippleDotMenuController.close();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.arrow_forward),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
MenuItemButton(
|
||||||
),
|
onPressed: () async {
|
||||||
],
|
await AboutRoute().push(context);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(Icons.info),
|
||||||
|
child: const Text('About'),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await SettingsRoute().push(context);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(Icons.settings),
|
||||||
|
child: const Text('Settings'),
|
||||||
|
),
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final browserExtensions = ref.watch(
|
||||||
|
webExtensionsStateProvider(
|
||||||
|
WebExtensionActionType.browser,
|
||||||
|
).select((value) => value.values.toList()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return SubmenuButton(
|
||||||
|
menuChildren: [
|
||||||
|
...browserExtensions.map(
|
||||||
|
(extension) => MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
//Use parents .ref because after onPressed this consumer gets disposed already
|
||||||
|
await addonService.invokeAddonAction(
|
||||||
|
extension.extensionId,
|
||||||
|
WebExtensionActionType.browser,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
leadingIcon: ExtensionBadgeIcon(extension),
|
||||||
|
child: Text(extension.title ?? ''),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await addonService.startAddonManagerActivity();
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(MdiIcons.puzzleEdit),
|
||||||
|
child: const Text('Addon Manager'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
leadingIcon: const Icon(MdiIcons.puzzle),
|
||||||
|
child: const Text('Addons'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await TorProxyRoute().push(context);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(TorIcons.onionAlt),
|
||||||
|
child: const Text('Tor'),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await BangCategoriesRoute().push(context);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(MdiIcons.exclamationThick),
|
||||||
|
child: const Text('Bangs'),
|
||||||
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await context.push(FeedListRoute().location);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(Icons.rss_feed),
|
||||||
|
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: () {
|
||||||
|
ref.read(findInPageControllerProvider.notifier).show();
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(Icons.search),
|
||||||
|
child: const Text('Find in page'),
|
||||||
|
),
|
||||||
|
if (selectedTabId != null) const Divider(),
|
||||||
|
if (selectedTabId != null)
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final controller = ref.read(
|
||||||
|
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
await controller.reload();
|
||||||
|
trippleDotMenuController.close();
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(Icons.refresh),
|
||||||
|
child: const Text('Reload'),
|
||||||
|
),
|
||||||
|
if (selectedTabId != null) const Divider(),
|
||||||
|
if (selectedTabId != null)
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final history = ref.watch(
|
||||||
|
tabStateProvider(
|
||||||
|
selectedTabId,
|
||||||
|
).select((value) => value?.historyState),
|
||||||
|
);
|
||||||
|
|
||||||
|
final isLoading = ref.watch(
|
||||||
|
selectedTabStateProvider.select(
|
||||||
|
(state) => state?.isLoading ?? false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
(history?.canGoBack == true || isLoading)
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final controller = ref.read(
|
||||||
|
tabSessionProvider(
|
||||||
|
tabId: selectedTabId,
|
||||||
|
).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
await controller.stopLoading();
|
||||||
|
} else {
|
||||||
|
await controller.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
trippleDotMenuController.close();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
tabRepositoryProvider.notifier,
|
||||||
|
)
|
||||||
|
.closeTab(selectedTabId);
|
||||||
|
trippleDotMenuController.close();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 48, child: VerticalDivider()),
|
||||||
|
Expanded(
|
||||||
|
child: IconButton(
|
||||||
|
onPressed:
|
||||||
|
(history?.canGoForward == true)
|
||||||
|
? () async {
|
||||||
|
final controller = ref.read(
|
||||||
|
tabSessionProvider(
|
||||||
|
tabId: selectedTabId,
|
||||||
|
).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
await controller.goForward();
|
||||||
|
trippleDotMenuController.close();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.arrow_forward),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user