add bookmark contextual button and improve button availability logic
This commit is contained in:
+1
@@ -22,6 +22,7 @@ enum ToolbarButtonId {
|
|||||||
back,
|
back,
|
||||||
forward,
|
forward,
|
||||||
bookmarks,
|
bookmarks,
|
||||||
|
bookmarkToggle,
|
||||||
share,
|
share,
|
||||||
addTab,
|
addTab,
|
||||||
tabsCount,
|
tabsCount,
|
||||||
|
|||||||
+6
@@ -51,6 +51,11 @@ const bookmarksToolbarButtonSpec = ToolbarButtonSpec(
|
|||||||
defaultVisible: false,
|
defaultVisible: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const bookmarkToggleToolbarButtonSpec = ToolbarButtonSpec(
|
||||||
|
id: ToolbarButtonId.bookmarkToggle,
|
||||||
|
defaultVisible: false,
|
||||||
|
);
|
||||||
|
|
||||||
const shareToolbarButtonSpec = ToolbarButtonSpec(
|
const shareToolbarButtonSpec = ToolbarButtonSpec(
|
||||||
id: ToolbarButtonId.share,
|
id: ToolbarButtonId.share,
|
||||||
defaultVisible: false,
|
defaultVisible: false,
|
||||||
@@ -157,6 +162,7 @@ const toolbarButtonSpecs = [
|
|||||||
backToolbarButtonSpec,
|
backToolbarButtonSpec,
|
||||||
forwardToolbarButtonSpec,
|
forwardToolbarButtonSpec,
|
||||||
bookmarksToolbarButtonSpec,
|
bookmarksToolbarButtonSpec,
|
||||||
|
bookmarkToggleToolbarButtonSpec,
|
||||||
shareToolbarButtonSpec,
|
shareToolbarButtonSpec,
|
||||||
addTabToolbarButtonSpec,
|
addTabToolbarButtonSpec,
|
||||||
tabsCountToolbarButtonSpec,
|
tabsCountToolbarButtonSpec,
|
||||||
|
|||||||
+114
-28
@@ -125,10 +125,19 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
longPressActions: ['Add Bookmark', 'Remove Bookmark'],
|
longPressActions: ['Add Bookmark', 'Remove Bookmark'],
|
||||||
builder: (scope, context, ref) => _BookmarkToolbarButton(scope: scope),
|
builder: (scope, context, ref) => _BookmarkToolbarButton(scope: scope),
|
||||||
),
|
),
|
||||||
|
ToolbarButtonDefinition(
|
||||||
|
spec: bookmarkToggleToolbarButtonSpec,
|
||||||
|
label: 'Bookmark',
|
||||||
|
icon: Icons.bookmark_border,
|
||||||
|
longPressActions: ['Open Bookmarks'],
|
||||||
|
builder: (scope, context, ref) =>
|
||||||
|
_BookmarkToggleToolbarButton(scope: scope),
|
||||||
|
),
|
||||||
ToolbarButtonDefinition(
|
ToolbarButtonDefinition(
|
||||||
spec: shareToolbarButtonSpec,
|
spec: shareToolbarButtonSpec,
|
||||||
label: 'Share',
|
label: 'Share',
|
||||||
icon: Icons.share,
|
icon: Icons.share,
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) => scope.isPreview
|
builder: (scope, context, ref) => scope.isPreview
|
||||||
? ShareMenuButtonView(onPressed: () {})
|
? ShareMenuButtonView(onPressed: () {})
|
||||||
: ShareMenuButton(selectedTabId: scope.selectedTabId),
|
: ShareMenuButton(selectedTabId: scope.selectedTabId),
|
||||||
@@ -190,6 +199,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
icon: Icons.refresh,
|
icon: Icons.refresh,
|
||||||
longPressActions: ['Hard Refresh (bypass cache)'],
|
longPressActions: ['Hard Refresh (bypass cache)'],
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) => _ReloadToolbarButton(scope: scope),
|
builder: (scope, context, ref) => _ReloadToolbarButton(scope: scope),
|
||||||
),
|
),
|
||||||
ToolbarButtonDefinition(
|
ToolbarButtonDefinition(
|
||||||
@@ -227,6 +237,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
spec: desktopToolbarButtonSpec,
|
spec: desktopToolbarButtonSpec,
|
||||||
label: 'Desktop Site',
|
label: 'Desktop Site',
|
||||||
icon: Icons.desktop_windows,
|
icon: Icons.desktop_windows,
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) {
|
builder: (scope, context, ref) {
|
||||||
if (scope.isPreview) {
|
if (scope.isPreview) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
@@ -243,6 +254,10 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
icon: Icons.translate,
|
icon: Icons.translate,
|
||||||
longPressActions: ['Show Translation Options'],
|
longPressActions: ['Show Translation Options'],
|
||||||
isPrimaryAvailable: (scope, ref) {
|
isPrimaryAvailable: (scope, ref) {
|
||||||
|
if (scope.selectedTabId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final engineState = ref.read(translationEngineStateProvider);
|
final engineState = ref.read(translationEngineStateProvider);
|
||||||
final readerActive = scope.tabState?.readerableState.active ?? false;
|
final readerActive = scope.tabState?.readerableState.active ?? false;
|
||||||
return !readerActive && engineState?.isEngineSupported == true;
|
return !readerActive && engineState?.isEngineSupported == true;
|
||||||
@@ -258,6 +273,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
spec: findInPageToolbarButtonSpec,
|
spec: findInPageToolbarButtonSpec,
|
||||||
label: 'Find in Page',
|
label: 'Find in Page',
|
||||||
icon: Icons.search,
|
icon: Icons.search,
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) {
|
builder: (scope, context, ref) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: scope.isPreview
|
onPressed: scope.isPreview
|
||||||
@@ -277,6 +293,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
label: 'Close Tab',
|
label: 'Close Tab',
|
||||||
icon: MdiIcons.tabMinus,
|
icon: MdiIcons.tabMinus,
|
||||||
longPressActions: ['Close Others', 'Close from Same Host'],
|
longPressActions: ['Close Others', 'Close from Same Host'],
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) => _CloseTabToolbarButton(scope: scope),
|
builder: (scope, context, ref) => _CloseTabToolbarButton(scope: scope),
|
||||||
),
|
),
|
||||||
ToolbarButtonDefinition(
|
ToolbarButtonDefinition(
|
||||||
@@ -319,6 +336,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
'Clone as Private',
|
'Clone as Private',
|
||||||
'Clone as Isolated',
|
'Clone as Isolated',
|
||||||
],
|
],
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) {
|
builder: (scope, context, ref) {
|
||||||
return scope.isPreview
|
return scope.isPreview
|
||||||
? CloneTabButtonView(onPressed: () {}, onLongPress: () {})
|
? CloneTabButtonView(onPressed: () {}, onLongPress: () {})
|
||||||
@@ -367,6 +385,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
label: 'Page Up',
|
label: 'Page Up',
|
||||||
icon: MdiIcons.chevronDoubleUp,
|
icon: MdiIcons.chevronDoubleUp,
|
||||||
longPressActions: ['Scroll to Top'],
|
longPressActions: ['Scroll to Top'],
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) {
|
builder: (scope, context, ref) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: scope.isPreview
|
onPressed: scope.isPreview
|
||||||
@@ -398,6 +417,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
|
|||||||
label: 'Page Down',
|
label: 'Page Down',
|
||||||
icon: MdiIcons.chevronDoubleDown,
|
icon: MdiIcons.chevronDoubleDown,
|
||||||
longPressActions: ['Scroll to Bottom'],
|
longPressActions: ['Scroll to Bottom'],
|
||||||
|
isPrimaryAvailable: (scope, ref) => scope.selectedTabId != null,
|
||||||
builder: (scope, context, ref) {
|
builder: (scope, context, ref) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: scope.isPreview
|
onPressed: scope.isPreview
|
||||||
@@ -643,32 +663,10 @@ class _BookmarkToolbarButton extends HookConsumerWidget {
|
|||||||
final tabUrl = scope.tabState?.url;
|
final tabUrl = scope.tabState?.url;
|
||||||
final bookmarkable = tabUrl != null && !scope.isPreview;
|
final bookmarkable = tabUrl != null && !scope.isPreview;
|
||||||
|
|
||||||
// Walk the in-memory bookmark tree to find GUIDs for the current URL.
|
|
||||||
final existingGuids = ref.watch(
|
final existingGuids = ref.watch(
|
||||||
bookmarksRepositoryProvider.select((async) {
|
bookmarksRepositoryProvider.select(
|
||||||
final result = <String>[];
|
(async) => _bookmarkGuidsForUrl(async.value, tabUrl, bookmarkable),
|
||||||
|
),
|
||||||
if (!bookmarkable) return const <String>[];
|
|
||||||
|
|
||||||
final root = async.value;
|
|
||||||
if (root == null) return const <String>[];
|
|
||||||
|
|
||||||
void collect(BookmarkItem item) {
|
|
||||||
if (item is BookmarkEntry && item.url == tabUrl) {
|
|
||||||
result.add(item.guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is BookmarkFolder) {
|
|
||||||
for (final child in item.children ?? const <BookmarkItem>[]) {
|
|
||||||
collect(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collect(root);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isBookmarked = existingGuids.isNotEmpty;
|
final isBookmarked = existingGuids.isNotEmpty;
|
||||||
@@ -736,6 +734,94 @@ class _BookmarkToolbarButton extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BookmarkToggleToolbarButton extends ConsumerWidget {
|
||||||
|
final ContextualToolbarScope scope;
|
||||||
|
|
||||||
|
const _BookmarkToggleToolbarButton({required this.scope});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final tabUrl = scope.tabState?.url;
|
||||||
|
final bookmarkable = tabUrl != null && !scope.isPreview;
|
||||||
|
final existingGuids = ref.watch(
|
||||||
|
bookmarksRepositoryProvider.select(
|
||||||
|
(async) => _bookmarkGuidsForUrl(async.value, tabUrl, bookmarkable),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final isBookmarked = existingGuids.isNotEmpty;
|
||||||
|
|
||||||
|
return IconButton(
|
||||||
|
tooltip: isBookmarked ? 'Remove bookmark' : 'Add bookmark',
|
||||||
|
onPressed: scope.isPreview
|
||||||
|
? () {}
|
||||||
|
: !bookmarkable
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
if (isBookmarked) {
|
||||||
|
for (final guid in existingGuids) {
|
||||||
|
await ref
|
||||||
|
.read(bookmarksRepositoryProvider.notifier)
|
||||||
|
.delete(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showInfoMessage(context, 'Bookmark removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(bookmarksRepositoryProvider.notifier)
|
||||||
|
.addBookmark(
|
||||||
|
parentGuid: BookmarkRoot.mobile.id,
|
||||||
|
url: tabUrl,
|
||||||
|
title: scope.tabState!.titleOrAuthority,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showInfoMessage(context, 'Bookmark added');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: scope.isPreview
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
await BookmarkListRoute(
|
||||||
|
entryGuid: BookmarkRoot.root.id,
|
||||||
|
).push(context);
|
||||||
|
},
|
||||||
|
icon: Icon(isBookmarked ? Icons.bookmark : Icons.bookmark_border),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _bookmarkGuidsForUrl(
|
||||||
|
BookmarkItem? root,
|
||||||
|
Uri? tabUrl,
|
||||||
|
bool bookmarkable,
|
||||||
|
) {
|
||||||
|
final result = <String>[];
|
||||||
|
|
||||||
|
if (!bookmarkable || root == null || tabUrl == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void collect(BookmarkItem item) {
|
||||||
|
if (item is BookmarkEntry && item.url == tabUrl) {
|
||||||
|
result.add(item.guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is BookmarkFolder) {
|
||||||
|
for (final child in item.children ?? const <BookmarkItem>[]) {
|
||||||
|
collect(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collect(root);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _adjustFontSize(
|
Future<void> _adjustFontSize(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref, {
|
WidgetRef ref, {
|
||||||
@@ -814,9 +900,9 @@ class _DesktopModeToolbarButton extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
if (selectedTabId == null) {
|
if (selectedTabId == null) {
|
||||||
return IconButton(
|
return const IconButton(
|
||||||
onPressed: () {},
|
onPressed: null,
|
||||||
icon: const Icon(Icons.desktop_windows),
|
icon: Icon(Icons.desktop_windows),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -47,11 +47,13 @@ class ShareMenuButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ShareMenuButtonView(
|
return ShareMenuButtonView(
|
||||||
onPressed: () async {
|
onPressed: selectedTabId == null
|
||||||
final tabId = selectedTabId;
|
? null
|
||||||
if (tabId != null) {
|
: () async {
|
||||||
await showShareBottomSheet(context, selectedTabId: tabId);
|
await showShareBottomSheet(
|
||||||
}
|
context,
|
||||||
|
selectedTabId: selectedTabId!,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user