This commit is contained in:
Fabian Freund
2025-02-03 15:09:04 +01:00
parent 36acd46318
commit 025ecaf2e3
132 changed files with 4431 additions and 1644 deletions
@@ -0,0 +1,34 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/user/data/models/general_settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'delete_browser_data.g.dart';
@Riverpod()
class DeleteBrowserDataService extends _$DeleteBrowserDataService {
final _service = GeckoDeleteBrowserDataService();
Future<void> deleteData(Set<DeleteBrowsingDataType>? types) async {
if (types != null) {
for (final type in types) {
switch (type) {
case DeleteBrowsingDataType.tabs:
await _service.deleteTabs();
case DeleteBrowsingDataType.history:
await _service.deleteBrowsingHistory();
case DeleteBrowsingDataType.cookies:
await _service.deleteCookiesAndSiteData();
case DeleteBrowsingDataType.cache:
await _service.deleteCachedFiles();
case DeleteBrowsingDataType.permissions:
await _service.deleteSitePermissions();
case DeleteBrowsingDataType.downloads:
await _service.deleteDownloads();
}
}
}
}
@override
void build() {}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'delete_browser_data.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$deleteBrowserDataServiceHash() =>
r'f5fdbac17c14693fceac913591750ca9285b496c';
/// See also [DeleteBrowserDataService].
@ProviderFor(DeleteBrowserDataService)
final deleteBrowserDataServiceProvider =
AutoDisposeNotifierProvider<DeleteBrowserDataService, void>.internal(
DeleteBrowserDataService.new,
name: r'deleteBrowserDataServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$deleteBrowserDataServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$DeleteBrowserDataService = AutoDisposeNotifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
@@ -1,10 +1,90 @@
import 'package:flutter/material.dart' show ThemeMode;
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod/riverpod.dart';
import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'engine_settings.g.dart';
@Riverpod(keepAlive: true)
GeckoEngineSettingsService engineSettingsService(Ref ref) {
return GeckoEngineSettingsService();
class EngineSettingsReplicationService
extends _$EngineSettingsReplicationService {
final _service = GeckoEngineSettingsService();
@override
void build() {
var initialSettingsSent = false;
ref.listen(
generalSettingsRepositoryProvider
.select((settings) => settings.themeMode),
(previous, next) async {
final theme = switch (next) {
ThemeMode.system => ColorScheme.system,
ThemeMode.light => ColorScheme.light,
ThemeMode.dark => ColorScheme.dark,
};
await _service.preferredColorScheme(theme);
},
);
ref.listen(
engineSettingsRepositoryProvider,
(previous, next) async {
if (initialSettingsSent && previous != null) {
if (previous.javascriptEnabled != next.javascriptEnabled) {
await _service.javascriptEnabled(next.javascriptEnabled);
}
if (previous.trackingProtectionPolicy !=
next.trackingProtectionPolicy) {
await _service
.trackingProtectionPolicy(next.trackingProtectionPolicy);
}
if (previous.httpsOnlyMode != next.httpsOnlyMode) {
await _service.httpsOnlyMode(next.httpsOnlyMode);
}
if (previous.globalPrivacyControlEnabled !=
next.globalPrivacyControlEnabled) {
await _service
.globalPrivacyControlEnabled(next.globalPrivacyControlEnabled);
}
if (previous.preferredColorScheme != next.preferredColorScheme) {
await _service.preferredColorScheme(next.preferredColorScheme);
}
if (previous.cookieBannerHandlingMode !=
next.cookieBannerHandlingMode) {
await _service
.cookieBannerHandlingMode(next.cookieBannerHandlingMode);
}
if (previous.cookieBannerHandlingModePrivateBrowsing !=
next.cookieBannerHandlingModePrivateBrowsing) {
await _service.cookieBannerHandlingModePrivateBrowsing(
next.cookieBannerHandlingModePrivateBrowsing,
);
}
if (previous.cookieBannerHandlingGlobalRules !=
next.cookieBannerHandlingGlobalRules) {
await _service.cookieBannerHandlingGlobalRules(
next.cookieBannerHandlingGlobalRules,
);
}
if (previous.cookieBannerHandlingGlobalRulesSubFrames !=
next.cookieBannerHandlingGlobalRulesSubFrames) {
await _service.cookieBannerHandlingGlobalRulesSubFrames(
next.cookieBannerHandlingGlobalRulesSubFrames,
);
}
if (previous.webContentIsolationStrategy !=
next.webContentIsolationStrategy) {
await _service
.webContentIsolationStrategy(next.webContentIsolationStrategy);
}
} else {
await _service.setDefaultSettings(next);
initialSettingsSent = true;
}
},
);
}
}
@@ -6,24 +6,22 @@ part of 'engine_settings.dart';
// RiverpodGenerator
// **************************************************************************
String _$engineSettingsServiceHash() =>
r'051d7b6f65eb5dcb07405b4af1bdadd8dd74fdc0';
String _$engineSettingsReplicationServiceHash() =>
r'0729260eeb34b58470622ac9a3a6abb257730a61';
/// See also [engineSettingsService].
@ProviderFor(engineSettingsService)
final engineSettingsServiceProvider =
Provider<GeckoEngineSettingsService>.internal(
engineSettingsService,
name: r'engineSettingsServiceProvider',
/// See also [EngineSettingsReplicationService].
@ProviderFor(EngineSettingsReplicationService)
final engineSettingsReplicationServiceProvider =
NotifierProvider<EngineSettingsReplicationService, void>.internal(
EngineSettingsReplicationService.new,
name: r'engineSettingsReplicationServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$engineSettingsServiceHash,
: _$engineSettingsReplicationServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef EngineSettingsServiceRef = ProviderRef<GeckoEngineSettingsService>;
typedef _$EngineSettingsReplicationService = Notifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
@@ -1,103 +1,104 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/data/models/equatable_iterable.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
// import 'package:collection/collection.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_hooks/flutter_hooks.dart';
// import 'package:hooks_riverpod/hooks_riverpod.dart';
// import 'package:lensai/data/models/equatable_iterable.dart';
// import 'package:lensai/extensions/nullable.dart';
// import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
// import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
// import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
// import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
class TabActionDialog extends HookConsumerWidget {
final TabState initialTab;
// class TabActionDialog extends HookConsumerWidget {
// final TabState initialTab;
final void Function()? onDismiss;
// final void Function()? onDismiss;
const TabActionDialog({
required this.initialTab,
this.onDismiss,
super.key,
});
// const TabActionDialog({
// required this.initialTab,
// this.onDismiss,
// super.key,
// });
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
final tabContainerId = ref.watch(
tabContainerIdProvider(initialTab.id)
.select((value) => value.valueOrNull),
);
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
// final tabContainerId = ref.watch(
// tabContainerIdProvider(initialTab.id)
// .select((value) => value.valueOrNull),
// );
final containers = ref
.watch(
containersWithCountProvider.select(
(value) => EquatableCollection(value.valueOrNull, immutable: true),
),
)
.collection;
// final containers = ref
// .watch(
// containersWithCountProvider.select(
// (value) => EquatableCollection(value.valueOrNull, immutable: true),
// ),
// )
// .collection;
final selectedContainer = containers
?.firstWhereOrNull((container) => container.id == tabContainerId);
// final selectedContainer = containers
// ?.firstWhereOrNull((container) => container.id == tabContainerId);
final expansionController = useExpansionTileController();
// final expansionController = useExpansionTileController();
return Stack(
children: [
ModalBarrier(
color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
onDismiss: onDismiss,
),
SimpleDialog(
titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
contentPadding: EdgeInsets.zero,
insetPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 24.0,
),
title: ListTile(
leading: RawImage(
image: tabState.icon?.value,
width: 24,
height: 24,
),
contentPadding: EdgeInsets.zero,
title: Text(tabState.title),
subtitle: Text(tabState.url.authority),
),
children: [
SizedBox(
//We need this to stretch the dialog, then padding from dialog is applied
width: double.maxFinite,
child: ExpansionTile(
controller: expansionController,
leading: (selectedContainer != null)
? CircleAvatar(backgroundColor: selectedContainer.color)
: null,
title: (selectedContainer != null)
? Text(selectedContainer.name ?? 'New Container')
: const Text('Assign a Container'),
children: containers
?.where((container) => container.id != tabContainerId)
.map(
(container) => ListTile(
leading:
CircleAvatar(backgroundColor: container.color),
title: Text(container.name ?? 'New Container'),
onTap: () async {
await ref
.read(tabDataRepositoryProvider.notifier)
.assignContainer(initialTab.id, container.id);
// return Stack(
// children: [
// ModalBarrier(
// color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
// onDismiss: onDismiss,
// ),
// SimpleDialog(
// titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
// contentPadding: EdgeInsets.zero,
// insetPadding: const EdgeInsets.symmetric(
// horizontal: 20.0,
// vertical: 24.0,
// ),
// title: ListTile(
// leading: RawImage(
// image: tabState.icon?.value,
// width: 24,
// height: 24,
// ),
// contentPadding: EdgeInsets.zero,
// title: Text(tabState.title),
// subtitle: Text(tabState.url.authority),
// ),
// children: [
// SizedBox(
// //We need this to stretch the dialog, then padding from dialog is applied
// width: double.maxFinite,
// child: ExpansionTile(
// controller: expansionController,
// leading: selectedContainer?.color.mapNotNull(
// (color) => CircleAvatar(backgroundColor: color),
// ),
// title: (selectedContainer != null)
// ? Text(selectedContainer.name ?? 'New Container')
// : const Text('Assign a Container'),
// children: containers
// ?.where((container) => container.id != tabContainerId)
// .map(
// (container) => ListTile(
// leading:
// CircleAvatar(backgroundColor: container.color),
// title: Text(container.name ?? 'New Container'),
// onTap: () async {
// await ref
// .read(tabDataRepositoryProvider.notifier)
// .assignContainer(initialTab.id, container.id);
expansionController.collapse();
},
),
)
.toList() ??
[],
),
),
],
),
],
);
}
}
// expansionController.collapse();
// },
// ),
// )
// .toList() ??
// [],
// ),
// ),
// ],
// ),
// ],
// );
// }
// }
@@ -22,7 +22,7 @@ import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/features/user/domain/repositories/settings.dart';
import 'package:lensai/features/user/domain/providers.dart';
import 'package:lensai/presentation/widgets/failure_widget.dart';
import 'package:lensai/presentation/widgets/website_title_tile.dart';
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
@@ -40,9 +40,7 @@ class WebPageDialog extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final incognitoEnabled = ref.watch(
settingsRepositoryProvider.select((value) => value.incognitoMode),
);
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
final availableBangsAsync = ref.watch(
bangDataListProvider(
@@ -17,7 +17,6 @@ import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/create_tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_view.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
@@ -31,7 +30,6 @@ import 'package:lensai/features/geckoview/features/readerview/presentation/widge
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:lensai/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/user/domain/repositories/settings.dart';
import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart';
import 'package:lensai/presentation/hooks/menu_controller.dart';
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
@@ -77,15 +75,8 @@ class BrowserScreen extends HookConsumerWidget {
},
);
ref.listen(
settingsRepositoryProvider.select((value) => value.enableJavascript),
(previous, next) async {
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
},
);
// ref.listen(
// settingsRepositoryProvider
// generalSettingsRepositoryProvider
// .select((value) => value.valueOrNull?.kagiSession),
// (previous, next) async {
// if (next != null && next.isNotEmpty) {
@@ -598,7 +589,7 @@ class BrowserScreen extends HookConsumerWidget {
MenuItemButton(
onPressed: () async {
await context
.push(BrowserHardeningRoute().location);
.push(WebEngineHardeningRoute().location);
},
leadingIcon: const Icon(Icons.info),
child: const Text('Pref'),
@@ -648,7 +639,8 @@ class BrowserScreen extends HookConsumerWidget {
: null;
final tabCount = ref.read(
tabListProvider.select((tabs) => tabs.length));
tabListProvider.select((tabs) => tabs.length),
);
//Don't do anything if a child route is active
if (GoRouterState.of(context).topRoute?.path !=
@@ -8,13 +8,21 @@ import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/delete_browser_data.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
import 'package:lensai/features/user/domain/repositories/cache.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:lensai/features/user/domain/services/local_authentication.dart';
class BrowserView extends StatefulHookConsumerWidget {
final Duration screenshotPeriod;
final FutureOr<void> Function()? postInitializationStep;
const BrowserView({this.screenshotPeriod = const Duration(seconds: 10)});
const BrowserView({
this.screenshotPeriod = const Duration(seconds: 10),
this.postInitializationStep,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _BrowserViewState();
@@ -63,6 +71,15 @@ class _BrowserViewState extends ConsumerState<BrowserView>
return GeckoView(
preInitializationStep: () async {
await ref
.read(generalSettingsRepositoryProvider.notifier)
.fetch()
.then((settings) {
ref
.read(deleteBrowserDataServiceProvider.notifier)
.deleteData(settings.deleteBrowsingDataOnQuit);
});
await ref
.read(eventServiceProvider)
.viewReadyStateEvents
@@ -77,6 +94,9 @@ class _BrowserViewState extends ConsumerState<BrowserView>
},
);
},
postInitializationStep: () async {
await widget.postInitializationStep?.call();
},
);
}
@@ -109,6 +129,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
cacheRepositoryProvider,
(previous, next) {},
);
ref.listenManual(
engineSettingsReplicationServiceProvider,
(previous, next) {},
);
}
@override
@@ -124,6 +149,10 @@ class _BrowserViewState extends ConsumerState<BrowserView>
_periodicScreenshotUpdate?.cancel();
_timerPaused = true;
}
ref
.read(localAuthenticationServiceProvider.notifier)
.evictCacheOnBackground();
case AppLifecycleState.resumed:
if (_timerPaused) {
_periodicScreenshotUpdate =
@@ -13,7 +13,7 @@ class LandingAction extends HookConsumerWidget {
final sessionTokenAvailable = false;
// ref.watch(
// settingsRepositoryProvider.select(
// generalSettingsRepositoryProvider.select(
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
// ),
// );
@@ -2,6 +2,7 @@ import 'package:expandable_page_view/expandable_page_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/kagi/presentation/tabs/summarize_tab.dart';
@@ -24,9 +25,8 @@ class CreateTabSheetWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final sharedContent = useMemoized(
() => (parameter.content != null)
? SharedContent.parse(parameter.content!)
: null,
() => parameter.content
.mapNotNull((content) => SharedContent.parse(content)),
[parameter],
);
@@ -56,15 +56,15 @@ class _TabDraggable extends HookConsumerWidget {
onClose();
}
},
onDoubleTap: () {
ref.read(overlayDialogControllerProvider.notifier).show(
TabActionDialog(
initialTab: tab,
onDismiss:
ref.read(overlayDialogControllerProvider.notifier).dismiss,
),
);
},
// onDoubleTap: () {
// ref.read(overlayDialogControllerProvider.notifier).show(
// TabActionDialog(
// initialTab: tab,
// onDismiss:
// ref.read(overlayDialogControllerProvider.notifier).dismiss,
// ),
// );
// },
onDelete: () async {
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
},
@@ -178,8 +178,8 @@ class _Tab extends HookConsumerWidget {
return ContainerChips(
selectedContainer: selectedContainer,
onSelected: (container) {
ref
onSelected: (container) async {
await ref
.read(selectedContainerProvider.notifier)
.setContainerId(container.id);
},