merge navigation and tab menu into bottom sheet
This commit is contained in:
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 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:convert';
|
||||
|
||||
import 'package:riverpod/experimental/persist.dart';
|
||||
import 'package:riverpod_annotation/experimental/persist.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/features/user/data/providers.dart';
|
||||
|
||||
part 'extensions_expanded.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ExtensionsExpanded extends _$ExtensionsExpanded {
|
||||
void toggle() {
|
||||
state = !state;
|
||||
}
|
||||
|
||||
@override
|
||||
bool build() {
|
||||
persist(
|
||||
ref.watch(riverpodDatabaseStorageProvider),
|
||||
key: 'ExtensionsExpanded',
|
||||
encode: (state) => jsonEncode([state]),
|
||||
decode: (encoded) => (jsonDecode(encoded) as List<dynamic>).first as bool,
|
||||
);
|
||||
|
||||
return stateOrNull ?? false;
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'extensions_expanded.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ExtensionsExpanded)
|
||||
final extensionsExpandedProvider = ExtensionsExpandedProvider._();
|
||||
|
||||
final class ExtensionsExpandedProvider
|
||||
extends $NotifierProvider<ExtensionsExpanded, bool> {
|
||||
ExtensionsExpandedProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'extensionsExpandedProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$extensionsExpandedHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ExtensionsExpanded create() => ExtensionsExpanded();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(bool value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<bool>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$extensionsExpandedHash() =>
|
||||
r'c07d67f3d00a8c374b598664f7f54c1bcc6de5b7';
|
||||
|
||||
abstract class _$ExtensionsExpanded extends $Notifier<bool> {
|
||||
bool build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<bool, bool>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<bool, bool>,
|
||||
bool,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,6 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/draggable_fab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/navigation_drawer.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/view_tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart';
|
||||
@@ -292,11 +291,6 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
final drawerGestureEnabled = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.drawerGestureEnabled,
|
||||
),
|
||||
);
|
||||
|
||||
ref.listen(overlayControllerProvider, (previous, next) {
|
||||
if (next != null) {
|
||||
@@ -567,8 +561,6 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
child: Scaffold(
|
||||
// Minimal scaffold - only for Material overlay support (SnackBars)
|
||||
resizeToAvoidBottomInset: false,
|
||||
endDrawer: const BrowserNavigationDrawer(),
|
||||
endDrawerEnableOpenDragGesture: drawerGestureEnabled,
|
||||
body: Stack(
|
||||
children: [
|
||||
// Layer 0: Browser content
|
||||
@@ -879,6 +871,13 @@ class _Browser extends HookConsumerWidget {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dismiss modal routes (e.g. showModalBottomSheet)
|
||||
final rootNavigator = Navigator.of(context, rootNavigator: true);
|
||||
if (rootNavigator.canPop()) {
|
||||
rootNavigator.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ref.read(bottomSheetControllerProvider) != null) {
|
||||
ref
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
|
||||
+1805
File diff suppressed because it is too large
Load Diff
+3
-43
@@ -38,6 +38,7 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/toolbar_visibility.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_menu_sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
||||
@@ -55,7 +56,6 @@ import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||
import 'package:weblibre/presentation/icons/weblibre_icons.dart';
|
||||
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
|
||||
@@ -80,7 +80,6 @@ class BrowserTopAppBar extends HookConsumerWidget {
|
||||
showQuickTabSwitcherBar: false,
|
||||
showMainToolbarNavigationButton: !showContextualToolbar,
|
||||
showMainToolbarTabsCount: !showContextualToolbar,
|
||||
showMainToolbarTabActionButton: !showContextualToolbar,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +116,6 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
|
||||
showMainToolbarNavigationButton: !showContextualToolbar,
|
||||
showMainToolbarTabsCount: !showContextualToolbar,
|
||||
showMainToolbarTabActionButton: !showContextualToolbar,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -147,7 +145,6 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
|
||||
final bool showMainToolbarTabsCount;
|
||||
final bool showMainToolbarNavigationButton;
|
||||
final bool showMainToolbarTabActionButton;
|
||||
|
||||
const BrowserTabBar({
|
||||
super.key,
|
||||
@@ -157,7 +154,6 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
required this.showQuickTabSwitcherBar,
|
||||
required this.showMainToolbarTabsCount,
|
||||
required this.showMainToolbarNavigationButton,
|
||||
required this.showMainToolbarTabActionButton,
|
||||
});
|
||||
|
||||
static const contextualToolabarHeight = 54.0;
|
||||
@@ -191,7 +187,6 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final extensionMenuController = useMenuController();
|
||||
final trippleDotMenuController = useMenuController();
|
||||
|
||||
final selectedTabId = ref.watch(selectedTabProvider);
|
||||
final settings = ref.watch(generalSettingsWithDefaultsProvider);
|
||||
@@ -360,23 +355,6 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
displayedSheet: displayedSheet,
|
||||
showLongPressMenu: true,
|
||||
),
|
||||
if (selectedTabId != null && showMainToolbarTabActionButton)
|
||||
TabMenu(
|
||||
controller: trippleDotMenuController,
|
||||
selectedTabId: selectedTabId,
|
||||
builder: (context, controller, child) {
|
||||
return ToolbarButton(
|
||||
onTap: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
child: const Icon(WebLibreIcons.tabOptions),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -444,24 +422,6 @@ class ContextualToolbar extends HookConsumerWidget {
|
||||
displayedSheet: displayedSheet,
|
||||
showLongPressMenu: false,
|
||||
),
|
||||
if (selectedTabId != null)
|
||||
TabMenu(
|
||||
controller: useMenuController(),
|
||||
selectedTabId: selectedTabId!,
|
||||
enableNavigationButtons: false,
|
||||
builder: (context, controller, child) {
|
||||
return ToolbarButton(
|
||||
onTap: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
child: const Icon(WebLibreIcons.tabOptions),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -679,8 +639,8 @@ class NavigationMenuButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ToolbarButton(
|
||||
onTap: () {
|
||||
Scaffold.of(context).openEndDrawer();
|
||||
onTap: () async {
|
||||
await showBrowserMenuSheet(context);
|
||||
},
|
||||
child: const Icon(Icons.menu),
|
||||
);
|
||||
|
||||
-422
@@ -1,422 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 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:async';
|
||||
|
||||
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_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/core/design/app_colors.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/web_extensions_state.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||
import 'package:weblibre/features/sync/domain/entities/sync_repository_state.dart';
|
||||
import 'package:weblibre/features/sync/domain/repositories/sync.dart';
|
||||
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
|
||||
import 'package:weblibre/features/user/domain/presentation/dialogs/quit_browser_dialog.dart';
|
||||
import 'package:weblibre/features/user/domain/providers.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/icons/tor_icons.dart';
|
||||
import 'package:weblibre/utils/exit_app.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
/// Navigation drawer for the browser screen.
|
||||
/// Contains all navigation destinations and settings.
|
||||
class BrowserNavigationDrawer extends HookConsumerWidget {
|
||||
const BrowserNavigationDrawer({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final showContainerUi = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select((s) => s.showContainerUi),
|
||||
);
|
||||
|
||||
return NavigationDrawer(
|
||||
backgroundColor: colorScheme.surface,
|
||||
header: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [_ProfileHeader(), _SyncTile(), const Divider()],
|
||||
),
|
||||
children: [
|
||||
// Section 1: Tools & Configuration
|
||||
_ExtensionsSection(),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: const Text('Settings'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await SettingsRoute().push(context);
|
||||
},
|
||||
),
|
||||
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final torConnected = ref.watch(
|
||||
torProxyServiceProvider.select(
|
||||
(value) => value.value?.isRunning == true,
|
||||
),
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
leading: Badge(
|
||||
isLabelVisible: torConnected,
|
||||
backgroundColor: AppColors.of(context).torActiveGreen,
|
||||
child: const Icon(TorIcons.onionAlt),
|
||||
),
|
||||
title: const Text('Tor™ Proxy'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await const TorProxyRoute().push(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const Divider(),
|
||||
|
||||
// Section 2: Data & Content
|
||||
ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: const Text('History'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await const HistoryRoute().push(context);
|
||||
},
|
||||
),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.fileDownload),
|
||||
title: const Text('Downloads'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await const HistoryDownloadsRoute().push(context);
|
||||
},
|
||||
),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.bookmarkMultiple),
|
||||
title: const Text('Bookmarks'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await BookmarkListRoute(
|
||||
entryGuid: BookmarkRoot.root.id,
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.exclamationThick),
|
||||
title: const Text('Bangs'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await const BangMenuRoute().push(context);
|
||||
},
|
||||
),
|
||||
|
||||
if (showContainerUi)
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.folder),
|
||||
title: const Text('Containers'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await const ContainerListRoute().push(context);
|
||||
},
|
||||
),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.rss_feed),
|
||||
title: const Text('Feeds'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await context.push(FeedListRoute().location);
|
||||
},
|
||||
),
|
||||
|
||||
const Divider(),
|
||||
|
||||
// Section 3: App
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: const Text('About'),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await AboutRoute().push(context);
|
||||
},
|
||||
),
|
||||
|
||||
// Quit Browser - styled as destructive action
|
||||
ListTile(
|
||||
leading: Icon(MdiIcons.power, color: colorScheme.error),
|
||||
title: Text(
|
||||
'Quit Browser',
|
||||
style: TextStyle(color: colorScheme.error),
|
||||
),
|
||||
onTap: () async {
|
||||
final result = await showQuitBrowserDialog(context);
|
||||
|
||||
if (result == true && context.mounted) {
|
||||
await exitApp(ProviderScope.containerOf(context));
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Profile header widget showing current profile information.
|
||||
class _ProfileHeader extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final profile = ref.watch(selectedProfileProvider);
|
||||
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(); // Close drawer
|
||||
await const SelectProfileRoute().push(context);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 28,
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
size: 28,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
profile.value?.name ?? 'User',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Tap to switch profile',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions section with expandable list of installed extensions.
|
||||
class _ExtensionsSection extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final addonService = ref.watch(addonServiceProvider);
|
||||
final pageExtensions = ref.watch(
|
||||
webExtensionsStateProvider(
|
||||
WebExtensionActionType.page,
|
||||
).select((value) => value.values.toList()),
|
||||
);
|
||||
final browserExtensions = ref.watch(
|
||||
webExtensionsStateProvider(
|
||||
WebExtensionActionType.browser,
|
||||
).select((value) => value.values.toList()),
|
||||
);
|
||||
|
||||
final isExpanded = useState(false);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.puzzle),
|
||||
title: const Text('Extensions'),
|
||||
trailing: Icon(
|
||||
isExpanded.value ? Icons.expand_less : Icons.expand_more,
|
||||
),
|
||||
onTap: () => isExpanded.value = !isExpanded.value,
|
||||
),
|
||||
if (isExpanded.value)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
// Page extensions
|
||||
if (pageExtensions.isNotEmpty) ...[
|
||||
...pageExtensions.map(
|
||||
(extension) => ListTile(
|
||||
leading: ExtensionBadgeIcon(extension),
|
||||
title: Text(extension.title ?? 'Extension'),
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(); // Close drawer
|
||||
await addonService.invokeAddonAction(
|
||||
extension.extensionId,
|
||||
WebExtensionActionType.page,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(indent: 16, endIndent: 16),
|
||||
],
|
||||
// Browser extensions
|
||||
if (browserExtensions.isNotEmpty) ...[
|
||||
...browserExtensions.map(
|
||||
(extension) => ListTile(
|
||||
leading: ExtensionBadgeIcon(extension),
|
||||
title: Text(extension.title ?? 'Extension'),
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(); // Close drawer
|
||||
await addonService.invokeAddonAction(
|
||||
extension.extensionId,
|
||||
WebExtensionActionType.browser,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(indent: 16, endIndent: 16),
|
||||
],
|
||||
// Management options
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.puzzleEdit),
|
||||
title: const Text('Manage Extensions'),
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(); // Close drawer
|
||||
await addonService.startAddonManagerActivity();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.puzzlePlus),
|
||||
title: const Text('Get Extensions'),
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop(); // Close drawer
|
||||
final tabMode = TabMode.fromTabType(
|
||||
ref
|
||||
.read(generalSettingsWithDefaultsProvider)
|
||||
.effectiveDefaultCreateTabType,
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(
|
||||
url: Uri.parse('https://addons.mozilla.org'),
|
||||
tabMode: tabMode,
|
||||
containerSelection:
|
||||
const TabContainerSelection.unassigned(),
|
||||
selectTab: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync tile widget shown in navigation drawer when sync is active.
|
||||
/// Displays sync status and allows manual sync trigger.
|
||||
class _SyncTile extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isAuthenticated = ref.watch(syncIsAuthenticatedProvider);
|
||||
|
||||
// Only show if sync is active
|
||||
if (!isAuthenticated) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final syncInfo = ref.watch(
|
||||
syncRepositoryProvider.select((value) => value.value?.account),
|
||||
);
|
||||
|
||||
final syncStarted = ref.watch(
|
||||
syncEventProvider.select(
|
||||
(value) => value.isLoading || value.value?.$1 == SyncEvent.started,
|
||||
),
|
||||
);
|
||||
final isSyncing = syncStarted || syncInfo?.syncing == true;
|
||||
|
||||
final controller = useAnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
if (isSyncing) {
|
||||
unawaited(controller.repeat());
|
||||
} else {
|
||||
controller.stop();
|
||||
controller.reset();
|
||||
}
|
||||
return null;
|
||||
}, [isSyncing]);
|
||||
|
||||
return ListTile(
|
||||
leading: RotationTransition(
|
||||
turns: Tween<double>(begin: 0, end: -1).animate(controller),
|
||||
child: const Icon(Icons.sync),
|
||||
),
|
||||
title: const Text('Sync Now'),
|
||||
onTap: () async {
|
||||
await ref.read(syncRepositoryProvider.notifier).syncNow();
|
||||
|
||||
final openedTabs = await ref
|
||||
.read(syncRepositoryProvider.notifier)
|
||||
.pollIncomingTabsAndOpen();
|
||||
|
||||
if (context.mounted) {
|
||||
if (openedTabs > 0) {
|
||||
ui_helper.showOpenedTabsFromAnotherDeviceMessage(
|
||||
context,
|
||||
openedTabs,
|
||||
);
|
||||
} else {
|
||||
ui_helper.showInfoMessage(
|
||||
context,
|
||||
'Synchronization complete',
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,6 @@ class _GesturesSection extends StatelessWidget {
|
||||
children: [
|
||||
SettingSection(name: 'Gestures'),
|
||||
_PullToRefreshTile(),
|
||||
_DrawerGestureTile(),
|
||||
_DoubleBackCloseTabTile(),
|
||||
],
|
||||
);
|
||||
@@ -509,31 +508,6 @@ class _PullToRefreshTile extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _DrawerGestureTile extends HookConsumerWidget {
|
||||
const _DrawerGestureTile();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final drawerGestureEnabled = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select((s) => s.drawerGestureEnabled),
|
||||
);
|
||||
|
||||
return SwitchListTile.adaptive(
|
||||
title: const Text('Drawer Swipe Gesture'),
|
||||
subtitle: const Text('Swipe from screen edge to open navigation drawer'),
|
||||
secondary: const Icon(MdiIcons.gestureSwipe),
|
||||
value: drawerGestureEnabled,
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.drawerGestureEnabled(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DoubleBackCloseTabTile extends HookConsumerWidget {
|
||||
const _DoubleBackCloseTabTile();
|
||||
|
||||
@@ -58,7 +58,6 @@ class _AppearanceDisplayTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Appearance & Display'),
|
||||
@@ -83,7 +82,6 @@ class _PrivacySecurityTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Privacy & Security'),
|
||||
@@ -108,7 +106,6 @@ class _SearchContentTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Search & Content'),
|
||||
@@ -133,7 +130,6 @@ class _TabsBehaviorTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Tabs & Behavior'),
|
||||
@@ -158,7 +154,6 @@ class _FingerprintingTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Fingerprinting'),
|
||||
@@ -183,7 +178,6 @@ class _SyncTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Firefox Sync'),
|
||||
@@ -208,7 +202,6 @@ class _AdvancedTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).highlightColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: const Text('Advanced'),
|
||||
|
||||
@@ -91,7 +91,6 @@ class GeneralSettings with FastEquatable {
|
||||
final bool tabListShowFavicons;
|
||||
final bool quickTabSwitcherShowTitles;
|
||||
final bool quickTabSwitcherShowHistorySuggestions;
|
||||
final bool drawerGestureEnabled;
|
||||
final String syncServerOverride;
|
||||
final String syncTokenServerOverride;
|
||||
final bool urlCleanerEnabled;
|
||||
@@ -137,7 +136,6 @@ class GeneralSettings with FastEquatable {
|
||||
required this.tabListShowFavicons,
|
||||
required this.quickTabSwitcherShowTitles,
|
||||
required this.quickTabSwitcherShowHistorySuggestions,
|
||||
required this.drawerGestureEnabled,
|
||||
required this.syncServerOverride,
|
||||
required this.syncTokenServerOverride,
|
||||
required this.urlCleanerEnabled,
|
||||
@@ -184,7 +182,6 @@ class GeneralSettings with FastEquatable {
|
||||
bool? tabListShowFavicons,
|
||||
bool? quickTabSwitcherShowTitles,
|
||||
bool? quickTabSwitcherShowHistorySuggestions,
|
||||
bool? drawerGestureEnabled,
|
||||
String? syncServerOverride,
|
||||
String? syncTokenServerOverride,
|
||||
bool? urlCleanerEnabled,
|
||||
@@ -234,7 +231,6 @@ class GeneralSettings with FastEquatable {
|
||||
quickTabSwitcherShowTitles = quickTabSwitcherShowTitles ?? true,
|
||||
quickTabSwitcherShowHistorySuggestions =
|
||||
quickTabSwitcherShowHistorySuggestions ?? true,
|
||||
drawerGestureEnabled = drawerGestureEnabled ?? false,
|
||||
syncServerOverride = syncServerOverride ?? '',
|
||||
syncTokenServerOverride = syncTokenServerOverride ?? '',
|
||||
urlCleanerEnabled = urlCleanerEnabled ?? true,
|
||||
@@ -305,7 +301,6 @@ class GeneralSettings with FastEquatable {
|
||||
tabListShowFavicons,
|
||||
quickTabSwitcherShowTitles,
|
||||
quickTabSwitcherShowHistorySuggestions,
|
||||
drawerGestureEnabled,
|
||||
syncServerOverride,
|
||||
syncTokenServerOverride,
|
||||
urlCleanerEnabled,
|
||||
|
||||
@@ -85,8 +85,6 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
bool quickTabSwitcherShowHistorySuggestions,
|
||||
);
|
||||
|
||||
GeneralSettings drawerGestureEnabled(bool drawerGestureEnabled);
|
||||
|
||||
GeneralSettings syncServerOverride(String syncServerOverride);
|
||||
|
||||
GeneralSettings syncTokenServerOverride(String syncTokenServerOverride);
|
||||
@@ -152,7 +150,6 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
bool tabListShowFavicons,
|
||||
bool quickTabSwitcherShowTitles,
|
||||
bool quickTabSwitcherShowHistorySuggestions,
|
||||
bool drawerGestureEnabled,
|
||||
String syncServerOverride,
|
||||
String syncTokenServerOverride,
|
||||
bool urlCleanerEnabled,
|
||||
@@ -309,10 +306,6 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
quickTabSwitcherShowHistorySuggestions,
|
||||
);
|
||||
|
||||
@override
|
||||
GeneralSettings drawerGestureEnabled(bool drawerGestureEnabled) =>
|
||||
call(drawerGestureEnabled: drawerGestureEnabled);
|
||||
|
||||
@override
|
||||
GeneralSettings syncServerOverride(String syncServerOverride) =>
|
||||
call(syncServerOverride: syncServerOverride);
|
||||
@@ -404,7 +397,6 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
Object? quickTabSwitcherShowTitles = const $CopyWithPlaceholder(),
|
||||
Object? quickTabSwitcherShowHistorySuggestions =
|
||||
const $CopyWithPlaceholder(),
|
||||
Object? drawerGestureEnabled = const $CopyWithPlaceholder(),
|
||||
Object? syncServerOverride = const $CopyWithPlaceholder(),
|
||||
Object? syncTokenServerOverride = const $CopyWithPlaceholder(),
|
||||
Object? urlCleanerEnabled = const $CopyWithPlaceholder(),
|
||||
@@ -602,12 +594,6 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
? _value.quickTabSwitcherShowHistorySuggestions
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: quickTabSwitcherShowHistorySuggestions as bool,
|
||||
drawerGestureEnabled:
|
||||
drawerGestureEnabled == const $CopyWithPlaceholder() ||
|
||||
drawerGestureEnabled == null
|
||||
? _value.drawerGestureEnabled
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: drawerGestureEnabled as bool,
|
||||
syncServerOverride:
|
||||
syncServerOverride == const $CopyWithPlaceholder() ||
|
||||
syncServerOverride == null
|
||||
@@ -761,7 +747,6 @@ GeneralSettings _$GeneralSettingsFromJson(
|
||||
quickTabSwitcherShowTitles: json['quickTabSwitcherShowTitles'] as bool?,
|
||||
quickTabSwitcherShowHistorySuggestions:
|
||||
json['quickTabSwitcherShowHistorySuggestions'] as bool?,
|
||||
drawerGestureEnabled: json['drawerGestureEnabled'] as bool?,
|
||||
syncServerOverride: json['syncServerOverride'] as String?,
|
||||
syncTokenServerOverride: json['syncTokenServerOverride'] as String?,
|
||||
urlCleanerEnabled: json['urlCleanerEnabled'] as bool?,
|
||||
@@ -823,7 +808,6 @@ Map<String, dynamic> _$GeneralSettingsToJson(
|
||||
'quickTabSwitcherShowTitles': instance.quickTabSwitcherShowTitles,
|
||||
'quickTabSwitcherShowHistorySuggestions':
|
||||
instance.quickTabSwitcherShowHistorySuggestions,
|
||||
'drawerGestureEnabled': instance.drawerGestureEnabled,
|
||||
'syncServerOverride': instance.syncServerOverride,
|
||||
'syncTokenServerOverride': instance.syncTokenServerOverride,
|
||||
'urlCleanerEnabled': instance.urlCleanerEnabled,
|
||||
|
||||
@@ -164,10 +164,6 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
),
|
||||
'drawerGestureEnabled': settings['drawerGestureEnabled']?.readAs(
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
),
|
||||
'syncServerOverride': settings['syncServerOverride']?.readAs(
|
||||
DriftSqlType.string,
|
||||
db.typeMapping,
|
||||
|
||||
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
|
||||
}
|
||||
|
||||
String _$generalSettingsRepositoryHash() =>
|
||||
r'51cfcf20b24705f5c6f51d4e204fff5a8e4eee89';
|
||||
r'4c2ee264cafd243916e59a78b0d5723997eab823';
|
||||
|
||||
abstract class _$GeneralSettingsRepository
|
||||
extends $StreamNotifier<GeneralSettings> {
|
||||
|
||||
+63
-3
@@ -32,6 +32,7 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:material_color_utilities/material_color_utilities.dart';
|
||||
import 'package:nullability/nullability.dart';
|
||||
import 'package:weblibre/core/design/app_colors.dart';
|
||||
import 'package:weblibre/core/error_observer.dart';
|
||||
@@ -48,6 +49,38 @@ import 'package:weblibre/features/web_feed/utils/fetch_entrypoint.dart';
|
||||
import 'package:weblibre/presentation/hooks/on_initialization.dart';
|
||||
import 'package:weblibre/presentation/main_app.dart';
|
||||
|
||||
ColorScheme _fixSurfaceContainerColors(
|
||||
ColorScheme scheme,
|
||||
TonalPalette neutralPalette,
|
||||
Brightness brightness,
|
||||
) {
|
||||
if (brightness == Brightness.light) {
|
||||
return scheme.copyWith(
|
||||
surfaceContainerLowest: Color(neutralPalette.get(100)),
|
||||
surfaceContainerLow: Color(neutralPalette.get(96)),
|
||||
surfaceContainer: Color(neutralPalette.get(94)),
|
||||
surfaceContainerHigh: Color(neutralPalette.get(92)),
|
||||
surfaceContainerHighest: Color(neutralPalette.get(90)),
|
||||
);
|
||||
} else {
|
||||
return scheme.copyWith(
|
||||
surfaceContainerLowest: Color(neutralPalette.get(4)),
|
||||
surfaceContainerLow: Color(neutralPalette.get(10)),
|
||||
surfaceContainer: Color(neutralPalette.get(12)),
|
||||
surfaceContainerHigh: Color(neutralPalette.get(17)),
|
||||
surfaceContainerHighest: Color(neutralPalette.get(22)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasBrokenSurfaceContainerColors(ColorScheme scheme) {
|
||||
return scheme.surfaceContainerLowest == scheme.surface &&
|
||||
scheme.surfaceContainerLow == scheme.surface &&
|
||||
scheme.surfaceContainer == scheme.surface &&
|
||||
scheme.surfaceContainerHigh == scheme.surface &&
|
||||
scheme.surfaceContainerHighest == scheme.surface;
|
||||
}
|
||||
|
||||
class _MainWidget extends HookConsumerWidget {
|
||||
const _MainWidget();
|
||||
|
||||
@@ -159,18 +192,45 @@ class _MainWidget extends HookConsumerWidget {
|
||||
}
|
||||
});
|
||||
|
||||
final corePaletteSnapshot = useFuture(
|
||||
useMemoized(() => DynamicColorPlugin.getCorePalette()),
|
||||
);
|
||||
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
ColorScheme lightColorScheme;
|
||||
ColorScheme darkColorScheme;
|
||||
|
||||
if (lightDynamic != null && darkDynamic != null) {
|
||||
final corePalette = corePaletteSnapshot.data;
|
||||
|
||||
// On Android S+ devices, use the provided dynamic color scheme.
|
||||
// (Recommended) Harmonize the dynamic color scheme' built-in semantic colors.
|
||||
lightColorScheme = lightDynamic.harmonized();
|
||||
final harmonizedLight = lightDynamic.harmonized();
|
||||
final harmonizedDark = darkDynamic.harmonized();
|
||||
|
||||
// Repeat for the dark color scheme.
|
||||
darkColorScheme = darkDynamic.harmonized();
|
||||
// Workaround for https://github.com/material-foundation/flutter-packages/issues/649
|
||||
// dynamic_color package returns broken surfaceContainer* colors.
|
||||
// Fix them using the neutral tonal palette from CorePalette.
|
||||
if (corePalette != null) {
|
||||
lightColorScheme = _hasBrokenSurfaceContainerColors(harmonizedLight)
|
||||
? _fixSurfaceContainerColors(
|
||||
harmonizedLight,
|
||||
corePalette.neutral,
|
||||
Brightness.light,
|
||||
)
|
||||
: harmonizedLight;
|
||||
darkColorScheme = _hasBrokenSurfaceContainerColors(harmonizedDark)
|
||||
? _fixSurfaceContainerColors(
|
||||
harmonizedDark,
|
||||
corePalette.neutral,
|
||||
Brightness.dark,
|
||||
)
|
||||
: harmonizedDark;
|
||||
} else {
|
||||
lightColorScheme = harmonizedLight;
|
||||
darkColorScheme = harmonizedDark;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, use fallback schemes.
|
||||
lightColorScheme = ColorScheme.fromSeed(
|
||||
|
||||
@@ -56,6 +56,7 @@ dependencies:
|
||||
path: ../packages/locale_resolver
|
||||
logger: ^2.6.2
|
||||
markdown: ^7.3.0
|
||||
material_color_utilities: ^0.13.0
|
||||
mime: ^2.0.0
|
||||
nullability: ^1.0.0
|
||||
package_info_plus: ^9.0.0
|
||||
|
||||
+56
-1
@@ -9,15 +9,21 @@ package eu.weblibre.flutter_mozilla_components.api
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||
import eu.weblibre.flutter_mozilla_components.addons.AddonInternalSettingsActivity
|
||||
import eu.weblibre.flutter_mozilla_components.addons.AddonsActivity
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonsApi
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.WebExtensionActionType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.concept.engine.webextension.InstallationMethod
|
||||
|
||||
class GeckoAddonsApiImpl(private val context: Context) : GeckoAddonsApi {
|
||||
private val components by lazy {
|
||||
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||
}
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override fun startAddonManagerActivity() {
|
||||
val intent = Intent(context, AddonsActivity::class.java)
|
||||
@@ -25,6 +31,55 @@ class GeckoAddonsApiImpl(private val context: Context) : GeckoAddonsApi {
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun startAddonSettingsActivity(extensionId: String) {
|
||||
scope.launch {
|
||||
val addon = runCatching {
|
||||
components.core.addonManager.getAddons()
|
||||
.find { it.id == extensionId }
|
||||
}.getOrNull()
|
||||
|
||||
if (addon == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
startAddonManagerActivity()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
val optionsPageUrl = addon.installedState?.optionsPageUrl
|
||||
if (optionsPageUrl.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
startAddonManagerActivity()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (addon.installedState?.openOptionsPageInTab == true) {
|
||||
components.useCases.tabsUseCases.selectOrAddTab(
|
||||
url = optionsPageUrl,
|
||||
ignoreFragment = true,
|
||||
)
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
||||
launchIntent?.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP,
|
||||
)
|
||||
if (launchIntent != null) {
|
||||
context.startActivity(launchIntent)
|
||||
} else {
|
||||
startAddonManagerActivity()
|
||||
}
|
||||
} else {
|
||||
val intent = Intent(context, AddonInternalSettingsActivity::class.java)
|
||||
intent.putExtra("add_on", addon)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invokeAddonAction(extensionId: String, actionType: WebExtensionActionType) {
|
||||
when(actionType) {
|
||||
WebExtensionActionType.BROWSER -> components.features.webExtensionToolbarFeature.invokeAddonBrowserAction(extensionId)
|
||||
@@ -50,4 +105,4 @@ class GeckoAddonsApiImpl(private val context: Context) : GeckoAddonsApi {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -6815,6 +6815,7 @@ class GeckoSelectionActionEvents(private val binaryMessenger: BinaryMessenger, p
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface GeckoAddonsApi {
|
||||
fun startAddonManagerActivity()
|
||||
fun startAddonSettingsActivity(extensionId: String)
|
||||
fun invokeAddonAction(extensionId: String, actionType: WebExtensionActionType)
|
||||
fun installAddon(url: String, callback: (Result<Unit>) -> Unit)
|
||||
|
||||
@@ -6843,6 +6844,24 @@ interface GeckoAddonsApi {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoAddonsApi.startAddonSettingsActivity$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val extensionIdArg = args[0] as String
|
||||
val wrapped: List<Any?> = try {
|
||||
api.startAddonSettingsActivity(extensionIdArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
GeckoPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoAddonsApi.invokeAddonAction$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
|
||||
@@ -38,6 +38,10 @@ class GeckoAddonService extends GeckoAddonEvents {
|
||||
return _api.startAddonManagerActivity();
|
||||
}
|
||||
|
||||
Future<void> startAddonSettingsActivity(String extensionId) {
|
||||
return _api.startAddonSettingsActivity(extensionId);
|
||||
}
|
||||
|
||||
Future<void> invokeAddonAction(
|
||||
String extensionId,
|
||||
WebExtensionActionType actionType,
|
||||
|
||||
@@ -8550,6 +8550,31 @@ class GeckoAddonsApi {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> startAddonSettingsActivity(String extensionId) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoAddonsApi.startAddonSettingsActivity$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||
<Object?>[extensionId],
|
||||
);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> invokeAddonAction(
|
||||
String extensionId,
|
||||
WebExtensionActionType actionType,
|
||||
|
||||
@@ -1602,6 +1602,8 @@ abstract class GeckoSelectionActionEvents {
|
||||
abstract class GeckoAddonsApi {
|
||||
void startAddonManagerActivity();
|
||||
|
||||
void startAddonSettingsActivity(String extensionId);
|
||||
|
||||
void invokeAddonAction(String extensionId, WebExtensionActionType actionType);
|
||||
|
||||
@async
|
||||
|
||||
Reference in New Issue
Block a user