refactoring

This commit is contained in:
Fabian Freund
2025-02-20 12:27:34 +01:00
parent b81fc1cc42
commit 490e13000b
138 changed files with 1392 additions and 3251 deletions
@@ -1,7 +1,6 @@
import 'dart:async';
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:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
@@ -11,8 +10,7 @@ part 'bottom_sheet.g.dart';
class BottomSheetController extends _$BottomSheetController {
@override
Sheet? build() {
//Don't use select here because it will only fire on new != previous
return ref.watch(createTabStreamProvider).valueOrNull;
return null;
}
// ignore: use_setters_to_change_properties api decision
@@ -7,7 +7,7 @@ part of 'bottom_sheet.dart';
// **************************************************************************
String _$bottomSheetControllerHash() =>
r'ae91f911fc35fdc0dfb14149ac2295e58ad13a86';
r'0b0ea53a96b80b8c1cba79b1d5913274b7127d5b';
/// See also [BottomSheetController].
@ProviderFor(BottomSheetController)
@@ -1,10 +1,10 @@
import 'package:flutter/widgets.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'overlay_dialog.g.dart';
part 'overlay.g.dart';
@Riverpod()
class OverlayDialogController extends _$OverlayDialogController {
class OverlayController extends _$OverlayController {
@override
Widget? build() {
return null;
@@ -1,26 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'create_tab.dart';
part of 'overlay.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$createTabStreamHash() => r'2e802a10ecaf1862074f32b76e89c26997bdd7b2';
String _$overlayControllerHash() => r'b6dc3ca17611c4efadc7e8248d493cd3c7061e01';
/// See also [CreateTabStream].
@ProviderFor(CreateTabStream)
final createTabStreamProvider =
AutoDisposeStreamNotifierProvider<CreateTabStream, CreateTabSheet>.internal(
CreateTabStream.new,
name: r'createTabStreamProvider',
/// See also [OverlayController].
@ProviderFor(OverlayController)
final overlayControllerProvider =
AutoDisposeNotifierProvider<OverlayController, Widget?>.internal(
OverlayController.new,
name: r'overlayControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$createTabStreamHash,
: _$overlayControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTabSheet>;
typedef _$OverlayController = AutoDisposeNotifier<Widget?>;
// 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
@@ -5,12 +5,12 @@ import 'package:lensai/data/models/web_page_info.dart';
import 'package:lensai/domain/entities/equatable_image.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/find_result.dart';
import 'package:lensai/features/geckoview/domain/entities/states/history.dart';
import 'package:lensai/features/geckoview/domain/entities/states/readerable.dart';
import 'package:lensai/features/geckoview/domain/entities/states/security.dart';
part 'tab_state.g.dart';
part 'tab.g.dart';
@CopyWith()
class TabState extends WebPageInfo with FastEquatable {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_state.dart';
part of 'tab.dart';
// **************************************************************************
// CopyWithGenerator
@@ -4,7 +4,7 @@ import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:lensai/domain/entities/equatable_image.dart';
part 'web_extension_state.g.dart';
part 'web_extension.g.dart';
@CopyWith()
class WebExtensionState with FastEquatable {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'web_extension_state.dart';
part of 'web_extension.dart';
// **************************************************************************
// CopyWithGenerator
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -152,8 +151,3 @@ class EngineReadyState extends _$EngineReadyState {
return currentState;
}
}
@Riverpod()
Raw<TabSession> selectedTabSessionNotifier(Ref ref) {
return ref.watch(tabSessionProvider(tabId: null).notifier);
}
@@ -93,25 +93,6 @@ final engineSuggestionsServiceProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef EngineSuggestionsServiceRef = ProviderRef<GeckoSuggestionsService>;
String _$selectedTabSessionNotifierHash() =>
r'aef19991d5b05a2bb28e8a6ccb57dc75c2f3148f';
/// See also [selectedTabSessionNotifier].
@ProviderFor(selectedTabSessionNotifier)
final selectedTabSessionNotifierProvider =
AutoDisposeProvider<Raw<TabSession>>.internal(
selectedTabSessionNotifier,
name: r'selectedTabSessionNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTabSessionNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef<Raw<TabSession>>;
String _$engineReadyStateHash() => r'c682333e2e07cf0635aa7ae793a2088ca648c950';
/// See also [EngineReadyState].
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/domain/entities/equatable_iterable.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -10,7 +10,7 @@ part 'tab_list.g.dart';
@Riverpod(keepAlive: true)
class TabList extends _$TabList {
@override
List<String> build() {
EquatableCollection<List<String>> build() {
final eventService = ref.watch(eventServiceProvider);
ref.listen(fireImmediately: true, engineReadyStateProvider, (
@@ -23,8 +23,10 @@ class TabList extends _$TabList {
});
final tabListSub = eventService.tabListEvents.listen((tabs) async {
if (!const DeepCollectionEquality().equals(state, tabs)) {
state = tabs;
final equatableTabs = EquatableCollection(tabs, immutable: true);
if (equatableTabs != state) {
state = equatableTabs;
}
});
@@ -32,6 +34,6 @@ class TabList extends _$TabList {
unawaited(tabListSub.cancel());
});
return [];
return EquatableCollection([], immutable: true);
}
}
@@ -6,11 +6,12 @@ part of 'tab_list.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabListHash() => r'039099bfa102a86fd73c0544ea71f93f91e9d352';
String _$tabListHash() => r'bb8b6cca42a8aace8bd9808fdeac4fe4965ac867';
/// See also [TabList].
@ProviderFor(TabList)
final tabListProvider = NotifierProvider<TabList, List<String>>.internal(
final tabListProvider =
NotifierProvider<TabList, EquatableCollection<List<String>>>.internal(
TabList.new,
name: r'tabListProvider',
debugGetCreateSourceHash:
@@ -19,6 +20,6 @@ final tabListProvider = NotifierProvider<TabList, List<String>>.internal(
allTransitiveDependencies: null,
);
typedef _$TabList = Notifier<List<String>>;
typedef _$TabList = Notifier<EquatableCollection<List<String>>>;
// 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,6 +1,7 @@
import 'dart:typed_data';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab_session.g.dart';
@@ -49,3 +50,8 @@ class TabSession extends _$TabSession {
: GeckoSessionService.forActiveTab();
}
}
@Riverpod()
Raw<TabSession> selectedTabSessionNotifier(Ref ref) {
return ref.watch(tabSessionProvider(tabId: null).notifier);
}
@@ -6,6 +6,25 @@ part of 'tab_session.dart';
// RiverpodGenerator
// **************************************************************************
String _$selectedTabSessionNotifierHash() =>
r'aef19991d5b05a2bb28e8a6ccb57dc75c2f3148f';
/// See also [selectedTabSessionNotifier].
@ProviderFor(selectedTabSessionNotifier)
final selectedTabSessionNotifierProvider =
AutoDisposeProvider<Raw<TabSession>>.internal(
selectedTabSessionNotifier,
name: r'selectedTabSessionNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTabSessionNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef<Raw<TabSession>>;
String _$tabSessionHash() => r'e4fbbcd430037cc01adb799783fb7974a11a27bf';
/// Copied from Dart SDK
@@ -3,24 +3,21 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/image.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/find_result.dart';
import 'package:lensai/features/geckoview/domain/entities/states/history.dart';
import 'package:lensai/features/geckoview/domain/entities/states/readerable.dart';
import 'package:lensai/features/geckoview/domain/entities/states/security.dart';
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/utils/image_helper.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart';
part 'tab_state.g.dart';
@Riverpod(keepAlive: true)
class TabStates extends _$TabStates {
final _lock = Lock();
void _onTabContentStateChange(TabContentState contentState) {
final current =
state[contentState.id] ?? TabState.$default(contentState.id);
@@ -137,25 +134,25 @@ class TabStates extends _$TabStates {
final subscriptions = [
eventService.tabContentEvents.listen((event) async {
await _lock.synchronized(() => _onTabContentStateChange(event));
_onTabContentStateChange(event);
}),
eventService.iconChangeEvents.listen((event) async {
await _lock.synchronized(() => _onIconChange(event));
await _onIconChange(event);
}),
eventService.thumbnailEvents.listen((event) async {
await _lock.synchronized(() => _onThumbnailChange(event));
await _onThumbnailChange(event);
}),
eventService.securityInfoEvents.listen((event) async {
await _lock.synchronized(() => _onSecurityInfoStateChange(event));
_onSecurityInfoStateChange(event);
}),
eventService.historyEvents.listen((event) async {
await _lock.synchronized(() => _onHistoryStateChange(event));
_onHistoryStateChange(event);
}),
eventService.readerableEvents.listen((event) async {
await _lock.synchronized(() => _onReaderableStateChange(event));
_onReaderableStateChange(event);
}),
eventService.findResultsEvent.listen((event) async {
await _lock.synchronized(() => _onFindResultsChange(event));
_onFindResultsChange(event);
}),
];
@@ -173,7 +173,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$tabStatesHash() => r'296d85ec016b22236b5830fce6b01cda81fbc02d';
String _$tabStatesHash() => r'c2169e467962d006015bf50d09e93b3f4bf596e6';
/// See also [TabStates].
@ProviderFor(TabStates)
@@ -5,11 +5,10 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/domain/entities/equatable_image.dart';
import 'package:lensai/extensions/image.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/web_extension_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/web_extension.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/utils/image_helper.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart';
part 'web_extensions_state.g.dart';
@@ -17,8 +16,6 @@ part 'web_extensions_state.g.dart';
class WebExtensionsState extends _$WebExtensionsState {
final _imageCache = <String, EquatableImage>{};
final _lock = Lock();
void _onExtensionUpdate(ExtensionDataEvent event) {
final ExtensionDataEvent(:extensionId, :data) = event;
@@ -76,18 +73,18 @@ class WebExtensionsState extends _$WebExtensionsState {
final subscriptions = switch (actionType) {
WebExtensionActionType.browser => [
addonService.browserExtensionStream.listen((event) async {
await _lock.synchronized(() => _onExtensionUpdate(event));
_onExtensionUpdate(event);
}),
addonService.browserIconStream.listen((event) async {
await _lock.synchronized(() => _onIconChange(event));
await _onIconChange(event);
}),
],
WebExtensionActionType.page => [
addonService.pageExtensionStream.listen((event) async {
await _lock.synchronized(() => _onExtensionUpdate(event));
_onExtensionUpdate(event);
}),
addonService.pageIconStream.listen((event) async {
await _lock.synchronized(() => _onIconChange(event));
await _onIconChange(event);
}),
],
};
@@ -7,7 +7,7 @@ part of 'web_extensions_state.dart';
// **************************************************************************
String _$webExtensionsStateHash() =>
r'4e6dc46e49ff353e1c3cc4616008685c0e21501d';
r'7fd0cb313fb433f7896bfe0f89a4545f94f58793';
/// Copied from Dart SDK
class _SystemHash {
@@ -3,14 +3,16 @@ import 'dart:async';
import 'package:drift/drift.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers/intent.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/utils/debouncer.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -93,9 +95,6 @@ class TabRepository extends _$TabRepository {
final eventSerivce = ref.watch(eventServiceProvider);
final tabContentService = ref.watch(tabContentServiceProvider);
final tabStateDebouncer = Debouncer(const Duration(seconds: 3));
Map<String, TabState>? debounceStartValue;
final db = ref.watch(tabDatabaseProvider);
final tabAddedSub = eventSerivce.tabAddedStream.listen((tabId) async {
@@ -127,13 +126,18 @@ class TabRepository extends _$TabRepository {
ref.listen(tabListProvider, (previous, next) async {
//Only sync tabs if there has been a previous value or is not empty
final syncTabs = next.isNotEmpty || (previous?.isNotEmpty ?? false);
final syncTabs =
next.collection.isNotEmpty ||
(previous?.collection.isNotEmpty ?? false);
if (syncTabs) {
await db.tabDao.syncTabs(retainTabIds: next);
await db.tabDao.syncTabs(retainTabIds: next.collection);
}
});
final tabStateDebouncer = Debouncer(const Duration(seconds: 3));
Map<String, TabState>? debounceStartValue;
ref.listen(tabStatesProvider, (previous, next) async {
//Since state changes occure pretty often and our map always contains
//the latest state, we cache the value before starting debouncing and
@@ -147,6 +151,16 @@ class TabRepository extends _$TabRepository {
});
});
ref.listen(intentStreamProvider, (previous, next) {
next.whenData((value) async {
switch (value) {
case SharedUrl():
await addTab(url: value.url);
case SharedText():
}
});
});
ref.onDispose(() {
tabStateDebouncer.dispose();
unawaited(tabAddedSub.cancel());
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabRepositoryHash() => r'85af29fa18ea5a4a67fbffbb9478c801dafddac2';
String _$tabRepositoryHash() => r'ce0db685127eeb706ce047d7afcc58fafd1394ba';
/// See also [TabRepository].
@ProviderFor(TabRepository)
@@ -1,24 +1,8 @@
import 'package:fast_equatable/fast_equatable.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
sealed class Sheet with FastEquatable {}
class CreateTabSheet extends Sheet {
final String? content;
final KagiTool? preferredTool;
bool get hasParameters => content != null || preferredTool != null;
CreateTabSheet({this.content, this.preferredTool});
@override
bool get cacheHash => true;
@override
List<Object?> get hashParameters => [content, preferredTool];
}
class ViewTabsSheet extends Sheet {
final class ViewTabsSheet extends Sheet {
@override
bool get cacheHash => true;
@@ -26,7 +10,7 @@ class ViewTabsSheet extends Sheet {
List<Object?> get hashParameters => [];
}
class TabQaChatSheet extends Sheet {
final class TabQaChatSheet extends Sheet {
final String chatId;
TabQaChatSheet({required this.chatId});
@@ -1,17 +1,16 @@
import 'dart:async';
import 'package:lensai/data/models/equatable_iterable.dart';
import 'package:lensai/domain/entities/equatable_iterable.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/repositories/data.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/search/domain/entities/tab_preview.dart';
import 'package:lensai/features/geckoview/features/tabs/data/entities/container_filter.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -58,48 +57,6 @@ class SelectedBangData extends _$SelectedBangData {
}
}
@Riverpod(keepAlive: true)
class LastUsedAssistantMode extends _$LastUsedAssistantMode {
// ignore: document_ignores api decision
// ignore: use_setters_to_change_properties
void update(AssistantMode mode) {
state = mode;
}
@override
AssistantMode build() {
return AssistantMode.research;
}
}
@Riverpod(keepAlive: true)
class ActiveResearchVariant extends _$ActiveResearchVariant {
// ignore: document_ignores api decision
// ignore: use_setters_to_change_properties
void update(ResearchVariant mode) {
state = mode;
}
@override
ResearchVariant build() {
return ResearchVariant.expert;
}
}
@Riverpod(keepAlive: true)
class ActiveChatModel extends _$ActiveChatModel {
// ignore: document_ignores api decision
// ignore: use_setters_to_change_properties
void update(ChatModel model) {
state = model;
}
@override
ChatModel build() {
return ChatModel.gpt4o;
}
}
@Riverpod(keepAlive: true)
class ShowFindInPage extends _$ShowFindInPage {
// ignore: document_ignores api decision
@@ -127,7 +84,10 @@ EquatableCollection<List<String>> availableTabIds(
final tabList = ref.watch(tabListProvider);
return EquatableCollection(
containerTabs?.where((tabId) => tabList.contains(tabId)).toList() ?? [],
containerTabs
?.where((tabId) => tabList.collection.contains(tabId))
.toList() ??
[],
immutable: true,
);
}
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$availableTabIdsHash() => r'd8b55b012b38111c8d5e069987a55645d1a71b89';
String _$availableTabIdsHash() => r'e5c5f25dea67f7739dfb1d0d9e4453079817a7bc';
/// Copied from Dart SDK
class _SystemHash {
@@ -908,56 +908,6 @@ class _SelectedBangDataProviderElement
String? get domain => (origin as SelectedBangDataProvider).domain;
}
String _$lastUsedAssistantModeHash() =>
r'1255a2754a3ea5ea058d110fd9743897fe103a69';
/// See also [LastUsedAssistantMode].
@ProviderFor(LastUsedAssistantMode)
final lastUsedAssistantModeProvider =
NotifierProvider<LastUsedAssistantMode, AssistantMode>.internal(
LastUsedAssistantMode.new,
name: r'lastUsedAssistantModeProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$lastUsedAssistantModeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$LastUsedAssistantMode = Notifier<AssistantMode>;
String _$activeResearchVariantHash() =>
r'8c65a3f5ead6cc4f9e2af85b607b7a49b0764ee3';
/// See also [ActiveResearchVariant].
@ProviderFor(ActiveResearchVariant)
final activeResearchVariantProvider =
NotifierProvider<ActiveResearchVariant, ResearchVariant>.internal(
ActiveResearchVariant.new,
name: r'activeResearchVariantProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$activeResearchVariantHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ActiveResearchVariant = Notifier<ResearchVariant>;
String _$activeChatModelHash() => r'675f406af8b4aa92699d59e37b5805a81d03a799';
/// See also [ActiveChatModel].
@ProviderFor(ActiveChatModel)
final activeChatModelProvider =
NotifierProvider<ActiveChatModel, ChatModel>.internal(
ActiveChatModel.new,
name: r'activeChatModelProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$activeChatModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ActiveChatModel = Notifier<ChatModel>;
String _$showFindInPageHash() => r'7ee5703f7d0c7e8a8dd6b0849d7b1e0a41fe24d9';
/// See also [ShowFindInPage].
@@ -0,0 +1,46 @@
import 'dart:async';
import 'package:lensai/data/models/received_intent_parameter.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/app_widget/domain/services/home_widget.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/features/share_intent/domain/services/sharing_intent.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
part 'intent.g.dart';
final _contentParserTransformer =
StreamTransformer<ReceivedIntentParameter, SharedContent>.fromHandlers(
handleData: (parameter, sink) {
final parsed = parameter.content.mapNotNull(
(content) => SharedContent.parse(content),
);
if (parsed != null) {
sink.add(parsed);
}
},
);
@Riverpod()
class IntentStream extends _$IntentStream {
late StreamController<SharedContent> _streamController;
@override
Stream<SharedContent> build() {
_streamController = StreamController();
ref.onDispose(() async {
await _streamController.close();
});
final sharingItentStream = ref.watch(sharingIntentStreamProvider);
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
return MergeStream([
sharingItentStream.transform(_contentParserTransformer),
appWidgetLaunchStream.transform(_contentParserTransformer),
_streamController.stream,
]);
}
}
@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'intent.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$intentStreamHash() => r'e78268ec703cd0d1f08d41b9e10a487e6000b7da';
/// See also [IntentStream].
@ProviderFor(IntentStream)
final intentStreamProvider =
AutoDisposeStreamNotifierProvider<IntentStream, SharedContent>.internal(
IntentStream.new,
name: r'intentStreamProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$intentStreamHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$IntentStream = AutoDisposeStreamNotifier<SharedContent>;
// 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
@@ -2,10 +2,10 @@ 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';
part 'browser_data.g.dart';
@Riverpod()
class DeleteBrowserDataService extends _$DeleteBrowserDataService {
class BrowserDataService extends _$BrowserDataService {
final _service = GeckoDeleteBrowserDataService();
Future<void> deleteData(Set<DeleteBrowsingDataType>? types) async {
@@ -1,27 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'overlay_dialog.dart';
part of 'browser_data.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$overlayDialogControllerHash() =>
r'53b44d9567bcb5bd9faecbfb8bb1af5aafb369c9';
String _$browserDataServiceHash() =>
r'f56c60635505e06dbd5b05f1bcee9c25c4d0eaaf';
/// See also [OverlayDialogController].
@ProviderFor(OverlayDialogController)
final overlayDialogControllerProvider =
AutoDisposeNotifierProvider<OverlayDialogController, Widget?>.internal(
OverlayDialogController.new,
name: r'overlayDialogControllerProvider',
/// See also [BrowserDataService].
@ProviderFor(BrowserDataService)
final browserDataServiceProvider =
AutoDisposeNotifierProvider<BrowserDataService, void>.internal(
BrowserDataService.new,
name: r'browserDataServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$overlayDialogControllerHash,
: _$browserDataServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$OverlayDialogController = AutoDisposeNotifier<Widget?>;
typedef _$BrowserDataService = 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,56 +0,0 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:lensai/data/models/received_parameter.dart';
import 'package:lensai/features/app_widget/domain/services/home_widget.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/share_intent/domain/services/sharing_intent.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
part 'create_tab.g.dart';
final _parameterToCreateTabSheetTransformer =
StreamTransformer<ReceivedParameter, CreateTabSheet>.fromHandlers(
handleData: (parameter, sink) {
final createTab = CreateTabSheet(
preferredTool: KagiTool.values.firstWhereOrNull(
(tool) => tool.name == parameter.tool,
),
content: parameter.content,
);
if (createTab.hasParameters) {
sink.add(createTab);
}
},
);
@Riverpod()
class CreateTabStream extends _$CreateTabStream {
late StreamController<CreateTabSheet> _streamController;
@override
Stream<CreateTabSheet> build() {
_streamController = StreamController();
ref.onDispose(() async {
await _streamController.close();
});
final sharingItentStream = ref.watch(sharingIntentStreamProvider);
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
return MergeStream([
sharingItentStream.transform(_parameterToCreateTabSheetTransformer),
appWidgetLaunchStream.transform(_parameterToCreateTabSheetTransformer),
_streamController.stream,
]);
}
void createTab(CreateTabSheet parameter) {
if (!_streamController.isClosed) {
_streamController.add(parameter);
}
}
}
@@ -1,27 +0,0 @@
// 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,5 +1,5 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/data/models/equatable_iterable.dart';
import 'package:lensai/domain/entities/equatable_iterable.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -1,104 +0,0 @@
// 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;
// final void Function()? onDismiss;
// 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),
// );
// final containers = ref
// .watch(
// containersWithCountProvider.select(
// (value) => EquatableCollection(value.valueOrNull, immutable: true),
// ),
// )
// .collection;
// final selectedContainer = containers
// ?.firstWhereOrNull((container) => container.id == tabContainerId);
// 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?.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() ??
// [],
// ),
// ),
// ],
// ),
// ],
// );
// }
// }
@@ -3,7 +3,6 @@ import 'dart:ui' as ui;
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -14,22 +13,16 @@ import 'package:lensai/features/bangs/presentation/widgets/site_search.dart';
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
import 'package:lensai/features/chat/features/chat_store/domain/repositories/chat_metadata.dart';
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:lensai/features/geckoview/domain/providers.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/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/geckoview/features/search/domain/providers/engine_suggestions.dart';
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/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart';
import 'package:lensai/features/user/domain/providers.dart';
import 'package:lensai/presentation/hooks/listenable_callback.dart';
import 'package:lensai/presentation/widgets/auto_suggest_text_field.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;
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
import 'package:share_plus/share_plus.dart';
class WebPageDialog extends HookConsumerWidget {
@@ -43,14 +36,7 @@ class WebPageDialog extends HookConsumerWidget {
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
final availableBangsAsync = ref.watch(
bangDataListProvider(
filter: (
domain: url.host,
groups: null,
categoryFilter: null,
orderMostFrequentFirst: true,
),
),
bangListProvider(domain: url.host, orderMostFrequentFirst: true),
);
final availableBangCount = availableBangsAsync.valueOrNull?.length;
@@ -75,74 +61,9 @@ class WebPageDialog extends HookConsumerWidget {
width: double.maxFinite,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: HookConsumer(
builder: (context, ref, child) {
final addressTextController = useTextEditingController(
text: url.toString(),
);
final addressTextFocusNode = useFocusNode();
final suggestion = useState<String?>(null);
useListenableCallback(addressTextController, () async {
if (addressTextController.text.isNotEmpty) {
final result = await ref
.read(engineSuggestionsProvider.notifier)
.getAutocompleteSuggestion(
addressTextController.text,
);
suggestion.value = result;
}
});
return AutoSuggestTextField(
controller: addressTextController,
focusNode: addressTextFocusNode,
suggestion: suggestion.value,
enableIMEPersonalizedLearning: !incognitoEnabled,
keyboardType: TextInputType.url,
decoration: const InputDecoration(
label: Text('Address'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
onTap: () {
if (!addressTextFocusNode.hasFocus) {
// Select all text when the field is tapped
addressTextController.selection = TextSelection(
baseOffset: 0,
extentOffset: addressTextController.text.length,
);
}
},
onSubmitted: (value) async {
if (value.isNotEmpty) {
var newUrl = uri_parser.tryParseUrl(
value,
eagerParsing: true,
);
if (newUrl == null) {
final defaultSearchBang = await ref.read(
defaultSearchBangDataProvider.future,
);
newUrl = defaultSearchBang?.getUrl(value);
}
if (newUrl != null) {
await ref
.read(tabSessionProvider(tabId: null).notifier)
.loadUrl(url: newUrl);
if (context.mounted) {
context.pop();
}
}
}
},
);
},
child: AddressWithSuggestionsField(
url: url,
incognitoEnabled: incognitoEnabled,
),
),
),
@@ -273,7 +194,7 @@ class WebPageDialog extends HookConsumerWidget {
),
const Divider(),
ListTile(
leading: Icon(KagiTool.assistant.icon),
leading: const Icon(MdiIcons.brain),
title: const Text('QA Chat'),
onTap: () async {
final selectedTabId =
@@ -306,24 +227,24 @@ class WebPageDialog extends HookConsumerWidget {
}
},
),
ListTile(
leading: Icon(KagiTool.summarizer.icon),
title: const Text('Summarize'),
onTap: () async {
final summarizerUrl = uri_builder.summarizerUri(
document: SharedUrl(url),
mode: SummarizerMode.keyMoments,
);
// ListTile(
// leading: Icon(MdiIcons.text),
// title: const Text('Summarize'),
// onTap: () async {
// final summarizerUrl = uri_builder.summarizerUri(
// document: SharedUrl(url),
// mode: SummarizerMode.keyMoments,
// );
await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: summarizerUrl);
// await ref
// .read(tabRepositoryProvider.notifier)
// .addTab(url: summarizerUrl);
if (context.mounted) {
context.pop();
}
},
),
// if (context.mounted) {
// context.pop();
// }
// },
// ),
],
),
);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/features/search/domain/providers/engine_suggestions.dart';
import 'package:lensai/presentation/hooks/listenable_callback.dart';
import 'package:lensai/presentation/widgets/auto_suggest_text_field.dart';
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
class AddressWithSuggestionsField extends HookConsumerWidget {
const AddressWithSuggestionsField({
super.key,
required this.url,
required this.incognitoEnabled,
});
final Uri url;
final bool incognitoEnabled;
@override
Widget build(BuildContext context, WidgetRef ref) {
final addressTextController = useTextEditingController(
text: url.toString(),
);
final addressTextFocusNode = useFocusNode();
final suggestion = useState<String?>(null);
useListenableCallback(addressTextController, () async {
if (addressTextController.text.isNotEmpty) {
final result = await ref
.read(engineSuggestionsProvider.notifier)
.getAutocompleteSuggestion(addressTextController.text);
suggestion.value = result;
}
});
return AutoSuggestTextField(
controller: addressTextController,
focusNode: addressTextFocusNode,
suggestion: suggestion.value,
enableIMEPersonalizedLearning: !incognitoEnabled,
keyboardType: TextInputType.url,
decoration: const InputDecoration(
label: Text('Address'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
onTap: () {
if (!addressTextFocusNode.hasFocus) {
// Select all text when the field is tapped
addressTextController.selection = TextSelection(
baseOffset: 0,
extentOffset: addressTextController.text.length,
);
}
},
onSubmitted: (value) async {
if (value.isNotEmpty) {
var newUrl = uri_parser.tryParseUrl(value, eagerParsing: true);
if (newUrl == null) {
final defaultSearchBang = await ref.read(
defaultSearchBangDataProvider.future,
);
newUrl = defaultSearchBang?.getUrl(value);
}
if (newUrl != null) {
await ref
.read(tabSessionProvider(tabId: null).notifier)
.loadUrl(url: newUrl);
if (context.mounted) {
context.pop();
}
}
}
},
);
}
}
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:text_scroll/text_scroll.dart';
@@ -0,0 +1,373 @@
import 'package:flutter/material.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:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:lensai/features/geckoview/domain/providers.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/presentation/widgets/browser_modules/app_bar_title.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
import 'package:lensai/features/geckoview/features/find_in_page/presentation/controllers/find_in_page_visibility.dart';
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:lensai/presentation/hooks/menu_controller.dart';
import 'package:lensai/presentation/icons/tor_icons.dart';
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
import 'package:share_plus/share_plus.dart';
class BrowserBottomAppBar extends HookConsumerWidget {
const BrowserBottomAppBar({
required this.selectedTabId,
required this.displayedSheet,
});
final String? selectedTabId;
final Sheet? displayedSheet;
@override
Widget build(BuildContext context, WidgetRef ref) {
final addonService = ref.watch(addonServiceProvider);
final tabMenuController = useMenuController();
final trippleDotMenuController = useMenuController();
return BottomAppBar(
height: AppBar().preferredSize.height,
padding: EdgeInsets.zero,
child: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 8.0,
title:
(selectedTabId != null && displayedSheet is! ViewTabsSheet)
? HookConsumer(
builder: (context, ref, child) {
final tabState = ref.watch(tabStateProvider(selectedTabId));
return (tabState != null)
? AppBarTitle(
tab: tabState,
onTap: () async {
await context.push(
WebPageRoute(
url: tabState.url.toString(),
).location,
extra: tabState,
);
},
onDoubleTap: () async {
final newUrl = await showDialog<Uri?>(
context: context,
builder:
(context) =>
EditUrlDialog(initialUrl: tabState.url),
);
if (newUrl != null) {
await ref
.read(
tabSessionProvider(tabId: null).notifier,
)
.loadUrl(url: newUrl);
}
},
)
: const SizedBox.shrink();
},
)
: null,
actions: [
if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
ReaderButton(),
MenuAnchor(
controller: tabMenuController,
builder: (context, controller, child) {
return child!;
},
menuChildren: [
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(selectedTabId!);
},
leadingIcon: const Icon(Icons.close),
child: const Text('Close Tab'),
),
MenuItemButton(
onPressed: () async {
await context.push(const SearchRoute().location);
},
leadingIcon: const Icon(Icons.add),
child: const Text('Add Tab'),
),
],
child: TabsActionButton(
isActive: displayedSheet is ViewTabsSheet,
onTap: () {
if (displayedSheet case ViewTabsSheet()) {
ref.read(bottomSheetControllerProvider.notifier).dismiss();
} else {
ref
.read(bottomSheetControllerProvider.notifier)
.show(ViewTabsSheet());
}
},
onLongPress: () {
if (tabMenuController.isOpen) {
tabMenuController.close();
} else {
tabMenuController.open();
}
},
),
),
MenuAnchor(
controller: trippleDotMenuController,
builder: (context, controller, child) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: InkWell(
onTap: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 8.0,
),
child: Icon(Icons.more_vert),
),
),
);
},
menuChildren: [
Consumer(
builder: (context, childRef, child) {
final pageExtensions = childRef.watch(
webExtensionsStateProvider(
WebExtensionActionType.page,
).select((value) => value.values.toList()),
);
return Wrap(
alignment: WrapAlignment.center,
children: [
...pageExtensions.map(
(extension) => IconButton(
onPressed: () async {
//Use parents .ref because after onPressed this consumer gets disposed already
await addonService.invokeAddonAction(
extension.extensionId,
WebExtensionActionType.page,
);
},
icon: ExtensionBadgeIcon(extension),
),
),
],
);
},
),
MenuItemButton(
onPressed: () async {
await context.push(AboutRoute().location);
},
leadingIcon: const Icon(Icons.info),
child: const Text('About'),
),
const Divider(),
MenuItemButton(
onPressed: () async {
await context.push(SettingsRoute().location);
},
leadingIcon: const Icon(Icons.settings),
child: const Text('Settings'),
),
Consumer(
builder: (context, ref, child) {
final browserExtensions = ref.watch(
webExtensionsStateProvider(
WebExtensionActionType.browser,
).select((value) => value.values.toList()),
);
return SubmenuButton(
menuChildren: [
...browserExtensions.map(
(extension) => MenuItemButton(
onPressed: () async {
//Use parents .ref because after onPressed this consumer gets disposed already
await addonService.invokeAddonAction(
extension.extensionId,
WebExtensionActionType.browser,
);
},
leadingIcon: ExtensionBadgeIcon(extension),
child: Text(extension.title ?? ''),
),
),
MenuItemButton(
onPressed: () async {
await addonService.startAddonManagerActivity();
},
leadingIcon: const Icon(MdiIcons.puzzleEdit),
child: const Text('Addon Manager'),
),
],
leadingIcon: const Icon(MdiIcons.puzzle),
child: const Text('Addons'),
);
},
),
MenuItemButton(
onPressed: () async {
await context.push(TorProxyRoute().location);
},
leadingIcon: const Icon(TorIcons.onionAlt),
child: const Text('Tor'),
),
const Divider(),
MenuItemButton(
onPressed: () async {
await context.push(BangCategoriesRoute().location);
},
leadingIcon: const Icon(MdiIcons.exclamationThick),
child: const Text('Bangs'),
),
const Divider(),
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
final tabState = ref.read(tabStateProvider(selectedTabId));
if (tabState?.url case final Uri url) {
await ui_helper.launchUrlFeedback(context, url);
}
},
leadingIcon: const Icon(Icons.open_in_browser),
child: const Text('Launch External'),
),
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
final tabState = ref.read(tabStateProvider(selectedTabId));
if (tabState?.url case final Uri url) {
await Share.shareUri(url);
}
},
leadingIcon: const Icon(Icons.share),
child: const Text('Share'),
),
if (selectedTabId != null) const Divider(),
if (selectedTabId != null)
MenuItemButton(
onPressed: () {
ref
.read(findInPageVisibilityControllerProvider.notifier)
.show();
},
leadingIcon: const Icon(Icons.search),
child: const Text('Find in page'),
),
if (selectedTabId != null) const Divider(),
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
final controller = ref.read(
tabSessionProvider(tabId: selectedTabId).notifier,
);
await controller.reload();
trippleDotMenuController.close();
},
leadingIcon: const Icon(Icons.refresh),
child: const Text('Reload'),
),
if (selectedTabId != null) const Divider(),
if (selectedTabId != null)
Consumer(
builder: (context, ref, child) {
final history = ref.watch(
tabStateProvider(
selectedTabId,
).select((value) => value?.historyState),
);
return Row(
children: [
Expanded(
child:
(history?.canGoBack == true)
? IconButton(
onPressed: () async {
final controller = ref.read(
tabSessionProvider(
tabId: selectedTabId,
).notifier,
);
await controller.goBack();
trippleDotMenuController.close();
},
icon: const Icon(Icons.arrow_back),
)
: IconButton(
onPressed: () async {
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(selectedTabId!);
trippleDotMenuController.close();
},
icon: const Icon(Icons.close),
),
),
const SizedBox(height: 48, child: VerticalDivider()),
Expanded(
child: IconButton(
onPressed:
(history?.canGoForward == true)
? () async {
final controller = ref.read(
tabSessionProvider(
tabId: selectedTabId,
).notifier,
);
await controller.goForward();
trippleDotMenuController.close();
}
: null,
icon: const Icon(Icons.arrow_forward),
),
),
],
);
},
),
MenuItemButton(
onPressed: () async {
await context.push(UserAuthRoute().location);
},
leadingIcon: const Icon(Icons.info),
child: const Text('Auth'),
),
],
),
],
),
);
}
}
@@ -6,10 +6,11 @@ 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/selected_tab.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/services/delete_browser_data.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/browser_data.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
@@ -87,7 +88,7 @@ class _BrowserViewState extends ConsumerState<BrowserView>
.fetch()
.then((settings) {
ref
.read(deleteBrowserDataServiceProvider.notifier)
.read(browserDataServiceProvider.notifier)
.deleteData(settings.deleteBrowsingDataOnQuit);
});
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lensai/features/geckoview/domain/entities/web_extension_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/web_extension.dart';
class ExtensionBadgeIcon extends StatelessWidget {
final WebExtensionState state;
@@ -1,39 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
class LandingAction extends HookConsumerWidget {
const LandingAction({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
//TODO: REWORK!
final sessionTokenAvailable = false;
// ref.watch(
// generalSettingsRepositoryProvider.select(
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
// ),
// );
return Visibility(
visible: !sessionTokenAvailable,
child: ErrorContainer(
content: Markdown(
shrinkWrap: true,
onTapLink: (text, href, title) async {
if (href == 'settings') {
await context.push(SettingsRoute().location);
}
},
data:
'## No Kagi Session Provided\n'
'Please navigate to [Settings](settings) and enter your Kagi Session Token.\n'
"You must provide a valid session in order to use Kagi's features.",
),
),
);
}
}
@@ -1,151 +0,0 @@
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/data/models/equatable_iterable.dart';
import 'package:lensai/domain/services/app_initialization.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/landing/action.dart';
import 'package:lensai/presentation/hooks/cached_future.dart';
class LandingContent extends HookConsumerWidget {
const LandingContent({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final textTheme = Theme.of(context).textTheme;
final descriptionAsset = useCachedFuture(
() async => rootBundle.loadString('assets/landing/description.md'),
);
final changelogAsset = useCachedFuture(
() async => rootBundle.loadString('assets/landing/changelog.md'),
);
return FadingScroll(
fadingSize: 25,
builder: (context, controller) {
return SingleChildScrollView(
controller: controller,
child: Column(
children: [
Text(
'Lensai',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'The Privacy-Focused & AI-Powered Research Browser with Kagi integration',
style: textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
FractionallySizedBox(
widthFactor: 0.5,
child: Image.asset('assets/icon/icon.png'),
),
const LandingAction(),
Consumer(
builder: (context, ref, child) {
final errors =
ref
.watch(
appInitializationServiceProvider.select(
(result) => EquatableCollection(
result.valueOrNull?.errors,
immutable: false,
),
),
)
.collection;
if (errors == null || errors.isEmpty) {
return const SizedBox.shrink();
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
...errors.map((error) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ErrorContainer(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Error during App Initialization!',
style: theme.textTheme.titleMedium,
),
Text(
error.message,
style: theme.textTheme.titleSmall,
),
if (error.details != null)
Text(error.details.toString()),
],
),
),
);
}),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.infinity,
child: FilledButton.tonalIcon(
onPressed: () async {
await ref
.read(
appInitializationServiceProvider.notifier,
)
.reinitialize();
},
style: OutlinedButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
label: const Text('Restart App'),
icon: const Icon(Icons.refresh_outlined),
),
),
),
],
);
},
),
Markdown(
data: descriptionAsset.data ?? '',
onTapLink: (text, href, title) async {
if (href != null) {
await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: Uri.parse(href));
}
},
selectable: true,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
),
Text(
'Changelog',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
MarkdownBody(data: changelogAsset.data ?? '', selectable: true),
],
),
);
},
);
}
}
@@ -1,111 +0,0 @@
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';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/presentation/hooks/sync_page_tab.dart';
typedef OnSubmitUri = void Function(Uri url);
class CreateTabSheetWidget extends HookConsumerWidget {
final CreateTabSheet parameter;
final OnSubmitUri onSubmit;
final void Function(KagiTool tool)? onActiveToolChanged;
const CreateTabSheetWidget({
required this.parameter,
required this.onSubmit,
this.onActiveToolChanged,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final sharedContent = useMemoized(
() => parameter.content.mapNotNull(
(content) => SharedContent.parse(content),
),
[parameter],
);
final initialIndex = useMemoized(
() =>
parameter.preferredTool?.index ??
switch (sharedContent) {
SharedText(text: final _) => KagiTool.search.index,
// (showEarlyAccessFeatures && text.length > 25)
// ? KagiTool.assistant.index
// : KagiTool.search.index,
SharedUrl() => KagiTool.summarizer.index,
null => KagiTool.search.index,
// showEarlyAccessFeatures
// ? KagiTool.assistant.index
// : KagiTool.search.index,
},
[sharedContent],
);
final tabController = useTabController(
initialLength: KagiTool.values.length - 1,
initialIndex: initialIndex,
);
final pageController = usePageController(initialPage: tabController.index);
useSyncPageWithTab(
tabController,
pageController,
onIndexChanged: (index) {
onActiveToolChanged?.call(KagiTool.values[index]);
},
);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
onActiveToolChanged?.call(KagiTool.values[initialIndex]);
});
return null;
}, [initialIndex]);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TabBar(
controller: tabController,
tabs: [
// Tab(
// icon: Icon(KagiTool.search.icon),
// text: 'Search',
// ),
Tab(icon: Icon(KagiTool.summarizer.icon), text: 'Summarize'),
// if (showEarlyAccessFeatures)
// Tab(
// icon: Icon(KagiTool.assistant.icon),
// text: 'Assistant',
// ),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
child: ExpandablePageView(
controller: pageController,
children: [
// SearchTab(
// sharedContent: sharedContent,
// onSubmit: onSubmit,
// ),
SummarizeTab(sharedContent: sharedContent, onSubmit: onSubmit),
// if (showEarlyAccessFeatures)
// AssistantTab(
// sharedContent: sharedContent,
// onSubmit: onSubmit,
// ),
],
),
),
],
);
}
}
@@ -10,8 +10,6 @@ import 'package:lensai/core/providers/global_drop.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/data/models/drag_data.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
@@ -25,60 +23,12 @@ import 'package:lensai/features/tor/presentation/controllers/start_tor_proxy.dar
import 'package:lensai/presentation/hooks/listenable_callback.dart';
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
class _TabDraggable extends HookConsumerWidget {
final String tabId;
final String? activeTabId;
final void Function() onClose;
_TabDraggable({
required this.tabId,
required this.activeTabId,
required this.onClose,
}) : super(key: ValueKey(tabId));
@override
Widget build(BuildContext context, WidgetRef ref) {
final tab = ref.watch(tabStateProvider(tabId));
if (tab == null) {
return const SizedBox.shrink();
}
return TabPreview(
tab: tab,
isActive: tabId == activeTabId,
onTap: () async {
if (tabId != activeTabId) {
//Close first to avoid rebuilds
onClose();
await ref.read(tabRepositoryProvider.notifier).selectTab(tab.id);
} else {
onClose();
}
},
// 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);
},
);
}
}
class _Tab extends HookConsumerWidget {
class _TabSheetHeader extends HookConsumerWidget {
static const headerSize = 124.0;
final VoidCallback onClose;
const _Tab({required this.onClose});
const _TabSheetHeader({required this.onClose});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -245,7 +195,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
children: [
DraggableScrollableHeader(
controller: draggableScrollableController,
child: _Tab(onClose: onClose),
child: _TabSheetHeader(onClose: onClose),
),
Expanded(
child: HookConsumer(
@@ -304,7 +254,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
key: Key(tabId),
data: TabDragData(tabId),
child: Consumer(
child: _TabDraggable(
child: TabPreviewDraggable(
tabId: tabId,
activeTabId: activeTab,
onClose: onClose,
@@ -420,7 +370,10 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
],
),
Padding(
padding: const EdgeInsets.only(top: _Tab.headerSize + 4, right: 4),
padding: const EdgeInsets.only(
top: _TabSheetHeader.headerSize + 4,
right: 4,
),
child: FloatingActionButton.small(
onPressed: () async {
await context.push(const SearchRoute().location);
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
class TabPreview extends StatelessWidget {
final TabState tab;
@@ -99,3 +102,51 @@ class TabPreview extends StatelessWidget {
);
}
}
class TabPreviewDraggable extends HookConsumerWidget {
final String tabId;
final String? activeTabId;
final void Function() onClose;
TabPreviewDraggable({
required this.tabId,
required this.activeTabId,
required this.onClose,
}) : super(key: ValueKey(tabId));
@override
Widget build(BuildContext context, WidgetRef ref) {
final tab = ref.watch(tabStateProvider(tabId));
if (tab == null) {
return const SizedBox.shrink();
}
return TabPreview(
tab: tab,
isActive: tabId == activeTabId,
onTap: () async {
if (tabId != activeTabId) {
//Close first to avoid rebuilds
onClose();
await ref.read(tabRepositoryProvider.notifier).selectTab(tab.id);
} else {
onClose();
}
},
// 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);
},
);
}
}
@@ -18,7 +18,9 @@ class TabsActionButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final tabCount = ref.watch(tabListProvider.select((tabs) => tabs.length));
final tabCount = ref.watch(
tabListProvider.select((tabs) => tabs.collection.length),
);
return InkWell(
onTap: onTap,
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/readerable.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/readerview/domain/providers/readerable.dart';
@@ -2,7 +2,7 @@ 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:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/readerable.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
@@ -1,5 +1,5 @@
import 'package:lensai/features/kagi/data/services/autosuggest.dart';
import 'package:lensai/features/kagi/domain/repositories/search_suggestions.dart';
import 'package:lensai/features/geckoview/features/search/domain/repositories/search_suggestions.dart';
import 'package:lensai/features/search/domain/autosuggest/kagi.dart';
import 'package:lensai/features/search/domain/entities/abstract/i_search_suggestion_provider.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -0,0 +1,60 @@
import 'dart:async';
import 'package:lensai/features/search/domain/entities/abstract/i_search_suggestion_provider.dart';
import 'package:lensai/utils/lru_cache.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
part 'search_suggestions.g.dart';
@Riverpod(keepAlive: true)
class SearchSuggestionsRepository extends _$SearchSuggestionsRepository {
final LRUCache<String, List<String>> _cache;
late StreamController<String> _queryStreamController;
SearchSuggestionsRepository() : _cache = LRUCache(100);
void addQuery(String query) {
if (!_queryStreamController.isClosed) {
_queryStreamController.add(query);
}
}
@override
Raw<Stream<List<String>>> build(
ISearchSuggestionProvider suggestionsProvider,
) {
_queryStreamController = StreamController();
ref.onDispose(() async {
await _queryStreamController.close();
});
return _queryStreamController.stream
.sampleTime(const Duration(milliseconds: 100))
.switchMap<List<String>>((query) {
if (query.isEmpty) {
return Stream.value([]);
}
final cached = _cache.get(query);
if (cached != null) {
return Stream.value(cached);
}
return suggestionsProvider
// ignore: discarded_futures is used as stream
.getSuggestions(query)
// ignore: discarded_futures is used as stream
.then((result) {
result.onSuccess((result) {
_cache.set(query, result);
});
return result.value;
})
.asStream();
})
.asBroadcastStream();
}
}
@@ -0,0 +1,182 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search_suggestions.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$searchSuggestionsRepositoryHash() =>
r'be75d075c3479c3d5e462211dec8bbd21b106820';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$SearchSuggestionsRepository
extends BuildlessNotifier<Raw<Stream<List<String>>>> {
late final ISearchSuggestionProvider suggestionsProvider;
Raw<Stream<List<String>>> build(
ISearchSuggestionProvider suggestionsProvider,
);
}
/// See also [SearchSuggestionsRepository].
@ProviderFor(SearchSuggestionsRepository)
const searchSuggestionsRepositoryProvider = SearchSuggestionsRepositoryFamily();
/// See also [SearchSuggestionsRepository].
class SearchSuggestionsRepositoryFamily
extends Family<Raw<Stream<List<String>>>> {
/// See also [SearchSuggestionsRepository].
const SearchSuggestionsRepositoryFamily();
/// See also [SearchSuggestionsRepository].
SearchSuggestionsRepositoryProvider call(
ISearchSuggestionProvider suggestionsProvider,
) {
return SearchSuggestionsRepositoryProvider(
suggestionsProvider,
);
}
@override
SearchSuggestionsRepositoryProvider getProviderOverride(
covariant SearchSuggestionsRepositoryProvider provider,
) {
return call(
provider.suggestionsProvider,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'searchSuggestionsRepositoryProvider';
}
/// See also [SearchSuggestionsRepository].
class SearchSuggestionsRepositoryProvider extends NotifierProviderImpl<
SearchSuggestionsRepository, Raw<Stream<List<String>>>> {
/// See also [SearchSuggestionsRepository].
SearchSuggestionsRepositoryProvider(
ISearchSuggestionProvider suggestionsProvider,
) : this._internal(
() => SearchSuggestionsRepository()
..suggestionsProvider = suggestionsProvider,
from: searchSuggestionsRepositoryProvider,
name: r'searchSuggestionsRepositoryProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$searchSuggestionsRepositoryHash,
dependencies: SearchSuggestionsRepositoryFamily._dependencies,
allTransitiveDependencies:
SearchSuggestionsRepositoryFamily._allTransitiveDependencies,
suggestionsProvider: suggestionsProvider,
);
SearchSuggestionsRepositoryProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.suggestionsProvider,
}) : super.internal();
final ISearchSuggestionProvider suggestionsProvider;
@override
Raw<Stream<List<String>>> runNotifierBuild(
covariant SearchSuggestionsRepository notifier,
) {
return notifier.build(
suggestionsProvider,
);
}
@override
Override overrideWith(SearchSuggestionsRepository Function() create) {
return ProviderOverride(
origin: this,
override: SearchSuggestionsRepositoryProvider._internal(
() => create()..suggestionsProvider = suggestionsProvider,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
suggestionsProvider: suggestionsProvider,
),
);
}
@override
NotifierProviderElement<SearchSuggestionsRepository,
Raw<Stream<List<String>>>> createElement() {
return _SearchSuggestionsRepositoryProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is SearchSuggestionsRepositoryProvider &&
other.suggestionsProvider == suggestionsProvider;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, suggestionsProvider.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin SearchSuggestionsRepositoryRef
on NotifierProviderRef<Raw<Stream<List<String>>>> {
/// The parameter `suggestionsProvider` of this provider.
ISearchSuggestionProvider get suggestionsProvider;
}
class _SearchSuggestionsRepositoryProviderElement
extends NotifierProviderElement<SearchSuggestionsRepository,
Raw<Stream<List<String>>>> with SearchSuggestionsRepositoryRef {
_SearchSuggestionsRepositoryProviderElement(super.provider);
@override
ISearchSuggestionProvider get suggestionsProvider =>
(origin as SearchSuggestionsRepositoryProvider).suggestionsProvider;
}
// 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
@@ -46,9 +46,9 @@ class SearchScreen extends HookConsumerWidget {
Future<void> submitSearch(String query) async {
if (activeBang != null && (formKey.currentState?.validate() == true)) {
final searchUri = await ref.read(
triggerBangSearchProvider(activeBang, query).future,
);
final searchUri = await ref
.read(bangSearchProvider.notifier)
.triggerBangSearch(activeBang, query);
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri);
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
import 'package:lexo_rank/lexo_rank.dart';
@@ -17,7 +17,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
Selectable<TabData> allTabData() => db.tab.select();
SingleOrNullSelectable<TabData> tabDataById(String id) =>
SingleOrNullSelectable<TabData> getTabDataById(String id) =>
db.tab.select()..where((t) => t.id.equals(id));
Selectable<String> containerTabIds(String? containerId) {
@@ -34,7 +34,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
return query.map((row) => row.read(db.tab.id)!);
}
Selectable<String> allTabIds() {
Selectable<String> getAllTabIds() {
final query =
selectOnly(db.tab)
..addColumns([db.tab.id])
@@ -43,7 +43,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
return query.map((row) => row.read(db.tab.id)!);
}
SingleOrNullSelectable<String?> tabContainerId(String tabId) {
SingleOrNullSelectable<String?> getTabContainerId(String tabId) {
final query =
selectOnly(db.tab)
..addColumns([db.tab.containerId])
@@ -7,7 +7,7 @@ import 'package:lensai/features/geckoview/features/tabs/data/database/daos/tab.d
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/migrator.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart';
import 'package:lensai/features/search/domain/fts_tokenizer.dart';
@@ -1,4 +1,6 @@
class TabQueryResult {
import 'package:fast_equatable/fast_equatable.dart';
class TabQueryResult with FastEquatable {
final String id;
final String? title;
@@ -19,4 +21,18 @@ class TabQueryResult {
this.fullContent,
required this.weightedRank,
});
@override
bool get cacheHash => true;
@override
List<Object?> get hashParameters => [
id,
title,
cleanUrl,
url,
extractedContent,
fullContent,
weightedRank,
];
}
@@ -1,32 +1,12 @@
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:lensai/features/geckoview/features/tabs/data/entities/container_filter.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/utils/color_palette.dart';
import 'package:lensai/features/search/util/tokenized_filter.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@Riverpod()
Future<Color> unusedRandomContainerColor(Ref ref) async {
final repository = ref.watch(containerRepositoryProvider.notifier);
final allColors = colorTypes.flattened.toList();
final usedColors = await repository.getDistinctColors();
Color randomColor;
do {
randomColor = randomColorShade(allColors);
} while (usedColors.contains(randomColor));
return randomColor;
}
@Riverpod()
Stream<List<ContainerDataWithCount>> containersWithCount(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
@@ -64,6 +44,6 @@ Stream<List<String>> containerTabIds(Ref ref, ContainerFilter containerFilter) {
case ContainerFilterById(:final containerId):
return db.tabDao.containerTabIds(containerId).watch();
case ContainerFilterDisabled():
return db.tabDao.allTabIds().watch();
return db.tabDao.getAllTabIds().watch();
}
}
@@ -6,25 +6,6 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$unusedRandomContainerColorHash() =>
r'913499b3de281ea0020db97ed67121652f53ec08';
/// See also [unusedRandomContainerColor].
@ProviderFor(unusedRandomContainerColor)
final unusedRandomContainerColorProvider =
AutoDisposeFutureProvider<Color>.internal(
unusedRandomContainerColor,
name: r'unusedRandomContainerColorProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$unusedRandomContainerColorHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef UnusedRandomContainerColorRef = AutoDisposeFutureProviderRef<Color>;
String _$containersWithCountHash() =>
r'ee0734e4b71472b79636816616a158f54fa6bc23';
@@ -207,7 +188,7 @@ class _MatchSortedContainersWithCountProviderElement
(origin as MatchSortedContainersWithCountProvider).searchText;
}
String _$containerTabIdsHash() => r'6472a865c287d7f87ec49a401c8184bfe7528ab8';
String _$containerTabIdsHash() => r'355c65523db29f376faa293ab1f1ed8273696548';
/// See also [containerTabIds].
@ProviderFor(containerTabIds)
@@ -1,43 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_topic.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$selectedTopicDataHash() => r'fa450784052498014f592c356bde4ff5ec675281';
/// See also [selectedTopicData].
@ProviderFor(selectedTopicData)
final selectedTopicDataProvider =
AutoDisposeStreamProvider<TopicData?>.internal(
selectedTopicData,
name: r'selectedTopicDataProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicDataHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SelectedTopicDataRef = AutoDisposeStreamProviderRef<TopicData?>;
String _$selectedTopicHash() => r'f4e1c0620971a0b7501ee9aff0d5b41da3130ba0';
/// See also [SelectedTopic].
@ProviderFor(SelectedTopic)
final selectedTopicProvider = NotifierProvider<SelectedTopic, String?>.internal(
SelectedTopic.new,
name: r'selectedTopicProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SelectedTopic = Notifier<String?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,8 +1,10 @@
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/utils/color_palette.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'container.g.dart';
@@ -67,6 +69,18 @@ class ContainerRepository extends _$ContainerRepository {
.getSingle();
}
Future<Color> unusedRandomContainerColor() async {
final allColors = colorTypes.flattened.toList();
final usedColors = await getDistinctColors();
Color randomColor;
do {
randomColor = randomColorShade(allColors);
} while (usedColors.contains(randomColor));
return randomColor;
}
@override
void build() {}
}
@@ -7,7 +7,7 @@ part of 'container.dart';
// **************************************************************************
String _$containerRepositoryHash() =>
r'b965883f2279b6f0148592bbac5ff40a60798f51';
r'e91db35caae057e777fbe3b77439289844f52677';
/// See also [ContainerRepository].
@ProviderFor(ContainerRepository)
@@ -6,9 +6,6 @@ part 'tab.g.dart';
@Riverpod(keepAlive: true)
class TabDataRepository extends _$TabDataRepository {
@override
void build() {}
Future<void> assignContainer(String tabId, String? containerId) {
return ref
.read(tabDatabaseProvider)
@@ -40,7 +37,10 @@ class TabDataRepository extends _$TabDataRepository {
return ref
.read(tabDatabaseProvider)
.tabDao
.tabContainerId(tabId)
.getTabContainerId(tabId)
.getSingleOrNull();
}
@override
void build() {}
}
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabDataRepositoryHash() => r'30ae87fbcb37facd565cb28de605814cd7db4ce8';
String _$tabDataRepositoryHash() => r'6c6f4aa5eaf4033541700bf18a592555a4b946d3';
/// See also [TabDataRepository].
@ProviderFor(TabDataRepository)
@@ -1,8 +1,9 @@
import 'dart:convert';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:langchain/langchain.dart';
class VectorResult {
class VectorResult with FastEquatable {
final String id;
final String? mainDocumentId;
final String? contextId;
@@ -26,4 +27,17 @@ class VectorResult {
Document toDocument() {
return Document(id: id, pageContent: content, metadata: metadata);
}
@override
bool get cacheHash => true;
@override
List<Object?> get hashParameters => [
id,
mainDocumentId,
contextId,
content,
metadata,
distance,
];
}
@@ -109,9 +109,10 @@ class ContainerListScreen extends HookConsumerWidget {
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final initialColor = await ref.read(
unusedRandomContainerColorProvider.future,
);
final initialColor =
await ref
.read(containerRepositoryProvider.notifier)
.unusedRandomContainerColor();
if (context.mounted) {
final result = await context.push<ContainerData?>(