mainly refactoring & addons

This commit is contained in:
Fabian Freund
2024-10-29 10:03:24 +01:00
parent 91a4319b28
commit 0710116feb
127 changed files with 4234 additions and 1815 deletions
@@ -7,6 +7,7 @@ import 'package:lensai/features/bangs/domain/repositories/data.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@@ -28,7 +29,7 @@ class SelectedBangTrigger extends _$SelectedBangTrigger {
}
@Riverpod()
Stream<BangData?> selectedBangData(SelectedBangDataRef ref, {String? domain}) {
Stream<BangData?> selectedBangData(Ref ref, {String? domain}) {
final repository = ref.watch(bangDataRepositoryProvider.notifier);
final selectedBangTrigger =
ref.watch(selectedBangTriggerProvider(domain: domain));
@@ -85,7 +86,7 @@ class ShowFindInPage extends _$ShowFindInPage {
@Riverpod()
List<String> availableTabIds(
AvailableTabIdsRef ref,
Ref ref,
String? containerId,
) {
final containerTabs = ref.watch(
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$selectedBangDataHash() => r'40dd3c51694394909fe83659ff3e19d51a06ec55';
String _$selectedBangDataHash() => r'd43e679c40d35c4f4c692ee286dc21789d7287dd';
/// Copied from Dart SDK
class _SystemHash {
@@ -142,6 +142,8 @@ class SelectedBangDataProvider extends AutoDisposeStreamProvider<BangData?> {
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin SelectedBangDataRef on AutoDisposeStreamProviderRef<BangData?> {
/// The parameter `domain` of this provider.
String? get domain;
@@ -156,7 +158,7 @@ class _SelectedBangDataProviderElement
String? get domain => (origin as SelectedBangDataProvider).domain;
}
String _$availableTabIdsHash() => r'427ea2e58c6bc27e2f16cffb850dc6621875e247';
String _$availableTabIdsHash() => r'646d42c747d815496849c7342903caf48258a8ab';
/// See also [availableTabIds].
@ProviderFor(availableTabIds)
@@ -271,6 +273,8 @@ class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin AvailableTabIdsRef on AutoDisposeProviderRef<List<String>> {
/// The parameter `containerId` of this provider.
String? get containerId;
@@ -413,6 +417,8 @@ class SelectedBangTriggerProvider
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin SelectedBangTriggerRef on NotifierProviderRef<String?> {
/// The parameter `domain` of this provider.
String? get domain;
@@ -493,4 +499,4 @@ final showFindInPageProvider = NotifierProvider<ShowFindInPage, bool>.internal(
typedef _$ShowFindInPage = Notifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
// 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
@@ -23,4 +23,4 @@ final createTabStreamProvider =
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTabSheet>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
// 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,9 +1,10 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'engine_settings.g.dart';
@Riverpod(keepAlive: true)
GeckoEngineSettingsService engineSettingsService(EngineSettingsServiceRef ref) {
GeckoEngineSettingsService engineSettingsService(Ref ref) {
return GeckoEngineSettingsService();
}
@@ -7,7 +7,7 @@ part of 'engine_settings.dart';
// **************************************************************************
String _$engineSettingsServiceHash() =>
r'9d9abb3601a3782f1f3ef91e31049db40acbf64a';
r'051d7b6f65eb5dcb07405b4af1bdadd8dd74fdc0';
/// See also [engineSettingsService].
@ProviderFor(engineSettingsService)
@@ -22,6 +22,8 @@ final engineSettingsServiceProvider =
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef EngineSettingsServiceRef = ProviderRef<GeckoEngineSettingsService>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
// 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
@@ -0,0 +1,133 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/logger.dart';
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';
class BrowserView extends StatefulHookConsumerWidget {
final Duration screenshotPeriod;
const BrowserView({this.screenshotPeriod = const Duration(seconds: 10)});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _BrowserViewState();
}
class _BrowserViewState extends ConsumerState<BrowserView>
with WidgetsBindingObserver {
Timer? _periodicScreenshotUpdate;
//This is managed by widget state changes to resume a timer
bool _timerPaused = false;
Future<void> _timerTick(Timer timer) async {
await ref
.read(selectedTabSessionNotifierProvider)
.requestScreenshot(requireImageResult: false)
.onError((error, stackTrace) {
logger.e(error, stackTrace: stackTrace);
timer.cancel();
return null;
});
}
@override
Widget build(BuildContext context) {
ref.listen(
selectedTabStateProvider.select(
(state) => (tabId: state?.id, isLoading: state?.isLoading),
),
(previous, next) {
if (previous?.tabId != next.tabId || next.isLoading == true) {
_periodicScreenshotUpdate?.cancel();
_periodicScreenshotUpdate = null;
_timerPaused = false;
}
if (next.isLoading == false &&
(_periodicScreenshotUpdate?.isActive ?? false) == false) {
_timerPaused = false;
_periodicScreenshotUpdate =
Timer.periodic(widget.screenshotPeriod, _timerTick);
}
},
);
return GeckoView(
preInitializationStep: () async {
await ref
.read(eventServiceProvider)
.viewReadyStateEvents
.firstWhere((state) => state == true)
.timeout(
const Duration(seconds: 3),
onTimeout: () {
logger.e(
'Browser fragement not reported ready, trying to intitialize anyways',
);
return true;
},
);
},
);
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
//Initialize and register dependencies
ref.listenManual(
selectionActionServiceProvider,
(previous, next) {},
);
ref.listenManual(
webExtensionsStateProvider(WebExtensionActionType.browser),
(previous, next) {},
);
ref.listenManual(
webExtensionsStateProvider(WebExtensionActionType.page),
(previous, next) {},
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.detached:
case AppLifecycleState.inactive:
case AppLifecycleState.hidden:
case AppLifecycleState.paused:
if (_periodicScreenshotUpdate?.isActive == true) {
_periodicScreenshotUpdate?.cancel();
_timerPaused = true;
}
case AppLifecycleState.resumed:
if (_timerPaused) {
_periodicScreenshotUpdate =
Timer.periodic(widget.screenshotPeriod, _timerTick);
_timerPaused = false;
}
}
}
@override
Future<void> dispose() async {
WidgetsBinding.instance.removeObserver(this);
_periodicScreenshotUpdate?.cancel();
super.dispose();
}
}
@@ -6,18 +6,19 @@ 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:lensai/core/logger.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.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/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/dialogs/web_page_dialog.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/sheets/create_tab.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
@@ -219,7 +220,8 @@ class BrowserScreen extends HookConsumerWidget {
onPressed: () {
ref
.read(
createTabStreamProvider.notifier)
createTabStreamProvider.notifier,
)
.createTab(
CreateTabSheet(
preferredTool: KagiTool.assistant,
@@ -278,7 +280,8 @@ class BrowserScreen extends HookConsumerWidget {
horizontal: 8.0,
),
child: Icon(
quickActionVoice ? Icons.mic : quickAction.icon),
quickActionVoice ? Icons.mic : quickAction.icon,
),
),
),
TabsActionButton(
@@ -310,13 +313,60 @@ class BrowserScreen extends HookConsumerWidget {
},
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 15.0, horizontal: 8.0),
vertical: 15.0,
horizontal: 8.0,
),
child: Icon(Icons.more_vert),
),
),
);
},
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 ref
.read(addonServiceProvider)
.invokeAddonAction(
extension.extensionId,
WebExtensionActionType.page,
);
},
icon: Badge(
isLabelVisible:
extension.badgeText?.isNotEmpty ??
false,
label: (extension.badgeText?.isNotEmpty ==
true)
? Text(extension.badgeText!)
: null,
textColor: extension.badgeTextColor,
backgroundColor:
extension.badgeBackgroundColor,
child: RawImage(
image: extension.icon?.value,
width: 24,
height: 24,
),
),
),
),
],
);
},
),
MenuItemButton(
onPressed: () async {
await context.push(AboutRoute().location);
@@ -332,6 +382,50 @@ class BrowserScreen extends HookConsumerWidget {
leadingIcon: const Icon(Icons.settings),
child: const Text('Settings'),
),
Consumer(
builder: (context, childRef, child) {
final browserExtensions = childRef.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 ref
.read(addonServiceProvider)
.invokeAddonAction(
extension.extensionId,
WebExtensionActionType.browser,
);
},
leadingIcon: RawImage(
image: extension.icon?.value,
width: 24,
height: 24,
),
child: Text(extension.title ?? ''),
),
),
MenuItemButton(
onPressed: () async {
await childRef
.read(addonServiceProvider)
.startAddonManagerActivity();
},
leadingIcon: const Icon(MdiIcons.puzzleEdit),
child: const Text('Addon Manager'),
),
],
leadingIcon: const Icon(MdiIcons.puzzle),
child: const Text('Addons'),
);
},
),
const Divider(),
MenuItemButton(
onPressed: () async {
@@ -340,53 +434,56 @@ class BrowserScreen extends HookConsumerWidget {
leadingIcon: const Icon(MdiIcons.exclamationThick),
child: const Text('Bangs'),
),
if (showEarlyAccessFeatures)
MenuItemButton(
onPressed: () async {
await context
.push(ChatArchiveListRoute().location);
},
leadingIcon: const Icon(MdiIcons.archive),
child: const Text('Chat Archive'),
),
const Divider(),
if (showEarlyAccessFeatures)
MenuItemButton(
onPressed: () {
ref
.read(createTabStreamProvider.notifier)
.createTab(
CreateTabSheet(
preferredTool: KagiTool.assistant),
);
},
leadingIcon: Icon(KagiTool.assistant.icon),
child: const Text('Assistant'),
),
MenuItemButton(
onPressed: () {
ref
.read(createTabStreamProvider.notifier)
.createTab(
CreateTabSheet(
preferredTool: KagiTool.summarizer),
);
},
leadingIcon: Icon(KagiTool.summarizer.icon),
child: const Text('Summarizer'),
),
MenuItemButton(
onPressed: () {
ref
.read(createTabStreamProvider.notifier)
.createTab(
CreateTabSheet(
preferredTool: KagiTool.search),
);
},
leadingIcon: Icon(KagiTool.search.icon),
child: const Text('Search'),
),
// if (showEarlyAccessFeatures)
// MenuItemButton(
// onPressed: () async {
// await context
// .push(ChatArchiveListRoute().location);
// },
// leadingIcon: const Icon(MdiIcons.archive),
// child: const Text('Chat Archive'),
// ),
// const Divider(),
// if (showEarlyAccessFeatures)
// MenuItemButton(
// onPressed: () {
// ref
// .read(createTabStreamProvider.notifier)
// .createTab(
// CreateTabSheet(
// preferredTool: KagiTool.assistant,
// ),
// );
// },
// leadingIcon: Icon(KagiTool.assistant.icon),
// child: const Text('Assistant'),
// ),
// MenuItemButton(
// onPressed: () {
// ref
// .read(createTabStreamProvider.notifier)
// .createTab(
// CreateTabSheet(
// preferredTool: KagiTool.summarizer,
// ),
// );
// },
// leadingIcon: Icon(KagiTool.summarizer.icon),
// child: const Text('Summarizer'),
// ),
// MenuItemButton(
// onPressed: () {
// ref
// .read(createTabStreamProvider.notifier)
// .createTab(
// CreateTabSheet(
// preferredTool: KagiTool.search,
// ),
// );
// },
// leadingIcon: Icon(KagiTool.search.icon),
// child: const Text('Search'),
// ),
const Divider(),
if (selectedTabId != null)
MenuItemButton(
@@ -419,8 +516,10 @@ class BrowserScreen extends HookConsumerWidget {
MenuItemButton(
onPressed: () {
ref
.read(findInPageVisibilityControllerProvider
.notifier)
.read(
findInPageVisibilityControllerProvider
.notifier,
)
.show();
},
leadingIcon: const Icon(Icons.search),
@@ -431,7 +530,8 @@ class BrowserScreen extends HookConsumerWidget {
MenuItemButton(
onPressed: () async {
final controller = ref.read(
tabSessionProvider(selectedTabId).notifier);
tabSessionProvider(selectedTabId).notifier,
);
await controller.reload();
menuController.close();
@@ -479,7 +579,9 @@ class BrowserScreen extends HookConsumerWidget {
),
),
const SizedBox(
height: 48, child: VerticalDivider()),
height: 48,
child: VerticalDivider(),
),
Expanded(
child: IconButton(
onPressed: (history?.canGoForward == true)
@@ -607,30 +709,7 @@ class BrowserScreen extends HookConsumerWidget {
SafeArea(
child: Stack(
children: [
Consumer(
builder: (context, ref, child) {
//Initialize dependencies
ref.watch(selectionActionServiceProvider);
return GeckoView(
preInitializationStep: () async {
await ref
.read(eventServiceProvider)
.viewReadyStateEvents
.firstWhere((state) => state == true)
.timeout(
const Duration(seconds: 3),
onTimeout: () {
logger.e(
'Browser fragement not reported ready, trying to intitialize anyways',
);
return true;
},
);
},
);
},
),
const BrowserView(),
Positioned(
bottom: 0,
left: 0,