diff --git a/app/assets/landing/description.md b/app/assets/landing/description.md deleted file mode 100644 index 5b2fcc9c..00000000 --- a/app/assets/landing/description.md +++ /dev/null @@ -1,17 +0,0 @@ -Lensai enhances your mobile research experience while keeping your personal data and identity secure. -Powered by advanced AI tools and browser extensions, Lensai offers modern features designed to elevate your browsing experience. - -Lensai is packed with a variety of features aimed at improving usability and maintaining privacy: - -- **Privacy by Default**: Incognito mode is enabled by default to keep your searches and browsing history private. -- **[Kagi Integration](https://github.com/FaFre/lensai/wiki/Kagi-Tools)**: Seamlessly integrates Kagi's search, assistant, and summarizer tools within a user-friendly interface. -- **[Easy Sharing](https://github.com/FaFre/lensai/wiki/Kagi-Tools)**: Share URLs or text from any app directly into Lensai without the need to copy and paste, for direct use with summarizer/assistant. -- **[Quick Summarization](https://github.com/FaFre/lensai/wiki/Browser)**: Long press any link on a website to get a summary via Kagi's summarizer. -- **[Content Blocking](https://github.com/FaFre/lensai/wiki/Content-Blocking)**: A powerful content blocker is enabled by default to filter out ads, malware, and tracking sites. -- **[Home Widgets](https://github.com/FaFre/lensai/wiki/Home-Widget)**: Includes home screen widgets in three different sizes for quick actions, enhancing productivity. -- **[PDF processing](https://github.com/FaFre/lensai/wiki/Kagi-Tools)**: Open and directly summarize/prompt PDF documents. -- **Reader Mode**: Extract and simplify web pages, enhancing readability by removing ads, sidebars, and other non-essential elements. -- **[Chat Archive](https://github.com/FaFre/lensai/wiki/Chat-Archive)**: Easily archive conversations with the Kagi assistant on your device and search through them locally -- **[Bang Repository](https://github.com/FaFre/lensai/wiki/Bangs)**: Automatically synchronizes all official Kagi Bangs from the official Repository and makes them available through a convenient interface. -- **[Enhanced Text Interaction](https://github.com/FaFre/lensai/wiki/Browser)**: Custom context menu allows for easy passage of marked text to Kagi's search or assistant tools. -- **[Speech Input](https://github.com/FaFre/lensai/wiki/Kagi-Tools)**: Supports voice commands for hands-free operation and accessibility. diff --git a/app/lib/extensions/database_table_size.dart b/app/lib/data/database/extensions/database_table_size.dart similarity index 100% rename from app/lib/extensions/database_table_size.dart rename to app/lib/data/database/extensions/database_table_size.dart diff --git a/app/lib/data/models/received_intent_parameter.dart b/app/lib/data/models/received_intent_parameter.dart new file mode 100644 index 00000000..3a8b466b --- /dev/null +++ b/app/lib/data/models/received_intent_parameter.dart @@ -0,0 +1,6 @@ +class ReceivedIntentParameter { + final String? content; + final String? tool; + + ReceivedIntentParameter(this.content, this.tool); +} diff --git a/app/lib/data/models/received_parameter.dart b/app/lib/data/models/received_parameter.dart deleted file mode 100644 index 0f4b1697..00000000 --- a/app/lib/data/models/received_parameter.dart +++ /dev/null @@ -1,6 +0,0 @@ -class ReceivedParameter { - final String? content; - final String? tool; - - ReceivedParameter(this.content, this.tool); -} diff --git a/app/lib/data/models/equatable_iterable.dart b/app/lib/domain/entities/equatable_iterable.dart similarity index 100% rename from app/lib/data/models/equatable_iterable.dart rename to app/lib/domain/entities/equatable_iterable.dart diff --git a/app/lib/domain/services/app_initialization.dart b/app/lib/domain/services/app_initialization.dart index 35fb894a..021f0663 100644 --- a/app/lib/domain/services/app_initialization.dart +++ b/app/lib/domain/services/app_initialization.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:exceptions/exceptions.dart'; import 'package:lensai/features/about/data/repositories/package_info_repository.dart'; import 'package:lensai/features/bangs/data/models/bang.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/domain/repositories/sync.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/app/lib/domain/services/generic_website.dart b/app/lib/domain/services/generic_website.dart index 9e385c44..f487c2ca 100644 --- a/app/lib/domain/services/generic_website.dart +++ b/app/lib/domain/services/generic_website.dart @@ -191,7 +191,7 @@ class GenericWebsiteService extends _$GenericWebsiteService { return Result.fromAsync(() async { final response = await _client .get(url) - .timeout(const Duration(seconds: 10)); + .timeout(const Duration(seconds: 15)); final result = await compute((args) async { final document = html_parser.parse(args[0]); diff --git a/app/lib/domain/services/generic_website.g.dart b/app/lib/domain/services/generic_website.g.dart index 44d9c141..fb0a4627 100644 --- a/app/lib/domain/services/generic_website.g.dart +++ b/app/lib/domain/services/generic_website.g.dart @@ -7,7 +7,7 @@ part of 'generic_website.dart'; // ************************************************************************** String _$genericWebsiteServiceHash() => - r'4c8c1c9ad430ab01f637f02ec97ee4ce5202975e'; + r'7891376773d7e6a61eeab37e0e92bb21f1acd8e3'; /// See also [GenericWebsiteService]. @ProviderFor(GenericWebsiteService) diff --git a/app/lib/extensions/media_query.dart b/app/lib/extensions/media_query.dart new file mode 100644 index 00000000..4c8c0bb6 --- /dev/null +++ b/app/lib/extensions/media_query.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +extension RelativeSafeArea on MediaQueryData { + double relativeSafeArea() { + return 1.0 - (padding.top / size.height); + } +} diff --git a/app/lib/features/about/presentation/screens/about.dart b/app/lib/features/about/presentation/screens/about.dart index 30a6ce07..21045633 100644 --- a/app/lib/features/about/presentation/screens/about.dart +++ b/app/lib/features/about/presentation/screens/about.dart @@ -21,7 +21,7 @@ class AboutDialogScreen extends HookConsumerWidget { ), applicationName: packageInfo.appName, applicationVersion: packageInfo.version, - applicationLegalese: 'Copyright © Fabian Freund, 2024', + applicationLegalese: 'Copyright © Fabian Freund, 2025', ); } } diff --git a/app/lib/features/app_widget/domain/services/home_widget.dart b/app/lib/features/app_widget/domain/services/home_widget.dart index 649ef0c1..529e0b70 100644 --- a/app/lib/features/app_widget/domain/services/home_widget.dart +++ b/app/lib/features/app_widget/domain/services/home_widget.dart @@ -1,5 +1,5 @@ import 'package:home_widget/home_widget.dart'; -import 'package:lensai/data/models/received_parameter.dart'; +import 'package:lensai/data/models/received_intent_parameter.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:rxdart/rxdart.dart'; @@ -12,12 +12,12 @@ FutureOr widgetPinnable(Ref ref) async { } @Riverpod() -Raw> appWidgetLaunchStream(Ref ref) { +Raw> appWidgetLaunchStream(Ref ref) { // ignore: discarded_futures is used as stream final initialStream = HomeWidget.initiallyLaunchedFromHomeWidget().asStream(); return ConcatStream([ initialStream, HomeWidget.widgetClicked, - ]).whereNotNull().map((uri) => ReceivedParameter(null, uri.host)); + ]).whereNotNull().map((uri) => ReceivedIntentParameter(null, uri.host)); } diff --git a/app/lib/features/app_widget/domain/services/home_widget.g.dart b/app/lib/features/app_widget/domain/services/home_widget.g.dart index b01c11c3..db023291 100644 --- a/app/lib/features/app_widget/domain/services/home_widget.g.dart +++ b/app/lib/features/app_widget/domain/services/home_widget.g.dart @@ -24,12 +24,12 @@ final widgetPinnableProvider = FutureProvider.internal( // ignore: unused_element typedef WidgetPinnableRef = FutureProviderRef; String _$appWidgetLaunchStreamHash() => - r'8eb98d88e74cc4f7573968563c106b8e6beee289'; + r'ed042d1391ae3dbc26f806d6fa2e9a4a2464ae44'; /// See also [appWidgetLaunchStream]. @ProviderFor(appWidgetLaunchStream) final appWidgetLaunchStreamProvider = - AutoDisposeProvider>>.internal( + AutoDisposeProvider>>.internal( appWidgetLaunchStream, name: r'appWidgetLaunchStreamProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -42,6 +42,6 @@ final appWidgetLaunchStreamProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef AppWidgetLaunchStreamRef - = AutoDisposeProviderRef>>; + = AutoDisposeProviderRef>>; // 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 diff --git a/app/lib/features/bangs/data/database/daos/bang.dart b/app/lib/features/bangs/data/database/daos/bang.dart index ae1c42e2..530b1a78 100644 --- a/app/lib/features/bangs/data/database/daos/bang.dart +++ b/app/lib/features/bangs/data/database/daos/bang.dart @@ -3,6 +3,7 @@ import 'package:lensai/extensions/nullable.dart'; import 'package:lensai/features/bangs/data/database/database.dart'; import 'package:lensai/features/bangs/data/models/bang.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; part 'bang.g.dart'; @@ -121,18 +122,4 @@ class BangDao extends DatabaseAccessor with _$BangDaoMixin { Future removeSearchEntry(String searchQuery) { return db.bangHistory.deleteWhere((t) => t.searchQuery.equals(searchQuery)); } - - // Future upsertBangIcon(String trigger, BrowserIcon icon) async { - // //TODO: swithc to WEBP in future, requires 3rd party - // final compressed = await icon.image.toByteData(format: ImageByteFormat.png); - - // return db.bangIcon.insertOne( - // BangIconCompanion.insert( - // trigger: trigger, - // iconData: compressed!.buffer.asUint8List(), - // fetchDate: DateTime.now(), - // ), - // mode: InsertMode.insertOrReplace, - // ); - // } } diff --git a/app/lib/features/bangs/data/database/daos/sync.dart b/app/lib/features/bangs/data/database/daos/sync.dart index f7c41c2e..190a1a34 100644 --- a/app/lib/features/bangs/data/database/daos/sync.dart +++ b/app/lib/features/bangs/data/database/daos/sync.dart @@ -1,6 +1,7 @@ import 'package:drift/drift.dart'; import 'package:lensai/features/bangs/data/database/database.dart'; import 'package:lensai/features/bangs/data/models/bang.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; part 'sync.g.dart'; @@ -8,7 +9,7 @@ part 'sync.g.dart'; class SyncDao extends DatabaseAccessor with _$SyncDaoMixin { SyncDao(super.db); - SingleOrNullSelectable lastSyncOfGroup(BangGroup group) { + SingleOrNullSelectable getLastSyncOfGroup(BangGroup group) { final query = selectOnly(db.bangSync) ..addColumns([db.bangSync.lastSync]) diff --git a/app/lib/features/bangs/data/database/database.dart b/app/lib/features/bangs/data/database/database.dart index 5fd14847..bf17c29e 100644 --- a/app/lib/features/bangs/data/database/database.dart +++ b/app/lib/features/bangs/data/database/database.dart @@ -4,6 +4,7 @@ import 'package:lensai/features/bangs/data/database/daos/sync.dart'; import 'package:lensai/features/bangs/data/database/drift/converters/bang_format.dart'; import 'package:lensai/features/bangs/data/models/bang.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/data/models/search_history_entry.dart'; import 'package:lensai/features/search/domain/fts_tokenizer.dart'; diff --git a/app/lib/features/bangs/data/database/database.drift b/app/lib/features/bangs/data/database/database.drift index 836dce23..de1fb063 100644 --- a/app/lib/features/bangs/data/database/database.drift +++ b/app/lib/features/bangs/data/database/database.drift @@ -1,5 +1,6 @@ import 'package:lensai/features/bangs/data/database/drift/converters/bang_format.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/data/models/bang.dart'; import 'package:lensai/features/bangs/data/models/search_history_entry.dart'; diff --git a/app/lib/features/bangs/data/models/bang.dart b/app/lib/features/bangs/data/models/bang.dart index 7fc7ac86..c957dd72 100644 --- a/app/lib/features/bangs/data/models/bang.dart +++ b/app/lib/features/bangs/data/models/bang.dart @@ -4,26 +4,10 @@ import 'package:fast_equatable/fast_equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:lensai/features/bangs/data/database/database.dart' show BangCompanion; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; part 'bang.g.dart'; -enum BangGroup { - general('https://raw.githubusercontent.com/FaFre/bangs/main/data/bangs.json'), - assistant( - 'https://raw.githubusercontent.com/FaFre/bangs/main/data/assistant_bangs.json', - ), - kagi( - 'https://raw.githubusercontent.com/FaFre/bangs/main/data/kagi_bangs.json', - ), - custom( - 'https://raw.githubusercontent.com/FaFre/bangs/refs/heads/custom/data/custom.json', - ); - - final String url; - - const BangGroup(this.url); -} - enum BangFormat { ///When the bang is invoked with no query, opens the base path of the URL (/) ///instead of any path given in the template (g., /search) diff --git a/app/lib/features/bangs/data/models/bang_group.dart b/app/lib/features/bangs/data/models/bang_group.dart new file mode 100644 index 00000000..5a13f4b8 --- /dev/null +++ b/app/lib/features/bangs/data/models/bang_group.dart @@ -0,0 +1,16 @@ +enum BangGroup { + general('https://raw.githubusercontent.com/FaFre/bangs/main/data/bangs.json'), + assistant( + 'https://raw.githubusercontent.com/FaFre/bangs/main/data/assistant_bangs.json', + ), + kagi( + 'https://raw.githubusercontent.com/FaFre/bangs/main/data/kagi_bangs.json', + ), + custom( + 'https://raw.githubusercontent.com/FaFre/bangs/refs/heads/custom/data/custom.json', + ); + + final String url; + + const BangGroup(this.url); +} diff --git a/app/lib/features/bangs/data/services/source.dart b/app/lib/features/bangs/data/services/data_source.dart similarity index 79% rename from app/lib/features/bangs/data/services/source.dart rename to app/lib/features/bangs/data/services/data_source.dart index 8c9bb609..cc56272b 100644 --- a/app/lib/features/bangs/data/services/source.dart +++ b/app/lib/features/bangs/data/services/data_source.dart @@ -5,12 +5,13 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:lensai/core/http_error_handler.dart'; import 'package:lensai/features/bangs/data/models/bang.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'source.g.dart'; +part 'data_source.g.dart'; @Riverpod() -class BangSourceService extends _$BangSourceService { +class BangDataSourceService extends _$BangDataSourceService { late http.Client _client; @override @@ -20,7 +21,9 @@ class BangSourceService extends _$BangSourceService { Future>> getBangs(Uri url, BangGroup? group) async { return Result.fromAsync(() async { - final response = await _client.get(url); + final response = await _client + .get(url) + .timeout(const Duration(seconds: 30)); return await compute((args) => jsonDecode(utf8.decode(args[0])) as List, [ response.bodyBytes, ]).then( diff --git a/app/lib/features/geckoview/features/browser/domain/services/create_tab.g.dart b/app/lib/features/bangs/data/services/data_source.g.dart similarity index 54% rename from app/lib/features/geckoview/features/browser/domain/services/create_tab.g.dart rename to app/lib/features/bangs/data/services/data_source.g.dart index fc65f9f0..a674180d 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/create_tab.g.dart +++ b/app/lib/features/bangs/data/services/data_source.g.dart @@ -1,26 +1,27 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'create_tab.dart'; +part of 'data_source.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$createTabStreamHash() => r'2e802a10ecaf1862074f32b76e89c26997bdd7b2'; +String _$bangDataSourceServiceHash() => + r'fb00762858a2878505c14e2b1a0f26c1b2f60d7d'; -/// See also [CreateTabStream]. -@ProviderFor(CreateTabStream) -final createTabStreamProvider = - AutoDisposeStreamNotifierProvider.internal( - CreateTabStream.new, - name: r'createTabStreamProvider', +/// See also [BangDataSourceService]. +@ProviderFor(BangDataSourceService) +final bangDataSourceServiceProvider = + AutoDisposeNotifierProvider.internal( + BangDataSourceService.new, + name: r'bangDataSourceServiceProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$createTabStreamHash, + : _$bangDataSourceServiceHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$CreateTabStream = AutoDisposeStreamNotifier; +typedef _$BangDataSourceService = AutoDisposeNotifier; // 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 diff --git a/app/lib/features/bangs/data/services/source.g.dart b/app/lib/features/bangs/data/services/source.g.dart deleted file mode 100644 index 2e60ab3e..00000000 --- a/app/lib/features/bangs/data/services/source.g.dart +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'source.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$bangSourceServiceHash() => r'2f734d563fa20de306a72013526ef1f72f910fe1'; - -/// See also [BangSourceService]. -@ProviderFor(BangSourceService) -final bangSourceServiceProvider = - AutoDisposeNotifierProvider.internal( - BangSourceService.new, - name: r'bangSourceServiceProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$bangSourceServiceHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$BangSourceService = AutoDisposeNotifier; -// 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 diff --git a/app/lib/features/bangs/domain/providers/bangs.dart b/app/lib/features/bangs/domain/providers/bangs.dart index 79bb97ab..230b4aa2 100644 --- a/app/lib/features/bangs/domain/providers/bangs.dart +++ b/app/lib/features/bangs/domain/providers/bangs.dart @@ -1,5 +1,5 @@ -import 'package:lensai/features/bangs/data/models/bang.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/data/models/search_history_entry.dart'; import 'package:lensai/features/bangs/domain/repositories/data.dart'; import 'package:lensai/features/bangs/domain/repositories/sync.dart'; @@ -8,15 +8,10 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'bangs.g.dart'; -@Riverpod() -Stream bangData(Ref ref, String? trigger) { - final repository = ref.watch(bangDataRepositoryProvider.notifier); - return repository.watchBang(trigger); -} - @Riverpod(keepAlive: true) Stream defaultSearchBangData(Ref ref) { final repository = ref.watch(bangDataRepositoryProvider.notifier); + //TODO: Setting return repository.watchBang('lai'); } @@ -27,27 +22,24 @@ Stream>> bangCategories(Ref ref) { } @Riverpod() -Stream> bangDataList( +Stream> bangList( Ref ref, { - ({ - Iterable? groups, - String? domain, - ({String category, String? subCategory})? categoryFilter, - bool? orderMostFrequentFirst, - })? - filter, + List? groups, + String? domain, + ({String category, String? subCategory})? categoryFilter, + bool? orderMostFrequentFirst, }) { final repository = ref.watch(bangDataRepositoryProvider.notifier); return repository.watchBangs( - groups: filter?.groups, - domain: filter?.domain, - categoryFilter: filter?.categoryFilter, - orderMostFrequentFirst: filter?.orderMostFrequentFirst, + groups: groups, + domain: domain, + categoryFilter: categoryFilter, + orderMostFrequentFirst: orderMostFrequentFirst, ); } @Riverpod() -Stream> frequentBangDataList(Ref ref) { +Stream> frequentBangList(Ref ref) { final repository = ref.watch(bangDataRepositoryProvider.notifier); return repository.watchFrequentBangs(); } diff --git a/app/lib/features/bangs/domain/providers/bangs.g.dart b/app/lib/features/bangs/domain/providers/bangs.g.dart index ca6f5ac5..81f14400 100644 --- a/app/lib/features/bangs/domain/providers/bangs.g.dart +++ b/app/lib/features/bangs/domain/providers/bangs.g.dart @@ -6,156 +6,6 @@ part of 'bangs.dart'; // RiverpodGenerator // ************************************************************************** -String _$bangDataHash() => r'057a002de9789ab74c8f3b337a43faa26b788d8d'; - -/// 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)); - } -} - -/// See also [bangData]. -@ProviderFor(bangData) -const bangDataProvider = BangDataFamily(); - -/// See also [bangData]. -class BangDataFamily extends Family> { - /// See also [bangData]. - const BangDataFamily(); - - /// See also [bangData]. - BangDataProvider call( - String? trigger, - ) { - return BangDataProvider( - trigger, - ); - } - - @override - BangDataProvider getProviderOverride( - covariant BangDataProvider provider, - ) { - return call( - provider.trigger, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'bangDataProvider'; -} - -/// See also [bangData]. -class BangDataProvider extends AutoDisposeStreamProvider { - /// See also [bangData]. - BangDataProvider( - String? trigger, - ) : this._internal( - (ref) => bangData( - ref as BangDataRef, - trigger, - ), - from: bangDataProvider, - name: r'bangDataProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$bangDataHash, - dependencies: BangDataFamily._dependencies, - allTransitiveDependencies: BangDataFamily._allTransitiveDependencies, - trigger: trigger, - ); - - BangDataProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.trigger, - }) : super.internal(); - - final String? trigger; - - @override - Override overrideWith( - Stream Function(BangDataRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: BangDataProvider._internal( - (ref) => create(ref as BangDataRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - trigger: trigger, - ), - ); - } - - @override - AutoDisposeStreamProviderElement createElement() { - return _BangDataProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is BangDataProvider && other.trigger == trigger; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, trigger.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin BangDataRef on AutoDisposeStreamProviderRef { - /// The parameter `trigger` of this provider. - String? get trigger; -} - -class _BangDataProviderElement - extends AutoDisposeStreamProviderElement with BangDataRef { - _BangDataProviderElement(super.provider); - - @override - String? get trigger => (origin as BangDataProvider).trigger; -} - String _$defaultSearchBangDataHash() => r'c93026fe7b1650fe8a39bb2a8850af3ee416c306'; @@ -193,37 +43,62 @@ final bangCategoriesProvider = // ignore: unused_element typedef BangCategoriesRef = AutoDisposeStreamProviderRef>>; -String _$bangDataListHash() => r'cd034fdf046b156ac452edd0857ac054ab477917'; +String _$bangListHash() => r'dc168bd1246bea3e5f8015dd52fbf41c5f98ca13'; -/// See also [bangDataList]. -@ProviderFor(bangDataList) -const bangDataListProvider = BangDataListFamily(); +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); -/// See also [bangDataList]. -class BangDataListFamily extends Family>> { - /// See also [bangDataList]. - const BangDataListFamily(); + 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); + } - /// See also [bangDataList]. - BangDataListProvider call({ - ({ - ({String category, String? subCategory})? categoryFilter, - String? domain, - Iterable? groups, - bool? orderMostFrequentFirst - })? filter, + 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)); + } +} + +/// See also [bangList]. +@ProviderFor(bangList) +const bangListProvider = BangListFamily(); + +/// See also [bangList]. +class BangListFamily extends Family>> { + /// See also [bangList]. + const BangListFamily(); + + /// See also [bangList]. + BangListProvider call({ + List? groups, + String? domain, + ({String category, String? subCategory})? categoryFilter, + bool? orderMostFrequentFirst, }) { - return BangDataListProvider( - filter: filter, + return BangListProvider( + groups: groups, + domain: domain, + categoryFilter: categoryFilter, + orderMostFrequentFirst: orderMostFrequentFirst, ); } @override - BangDataListProvider getProviderOverride( - covariant BangDataListProvider provider, + BangListProvider getProviderOverride( + covariant BangListProvider provider, ) { return call( - filter: provider.filter, + groups: provider.groups, + domain: provider.domain, + categoryFilter: provider.categoryFilter, + orderMostFrequentFirst: provider.orderMostFrequentFirst, ); } @@ -239,85 +114,99 @@ class BangDataListFamily extends Family>> { _allTransitiveDependencies; @override - String? get name => r'bangDataListProvider'; + String? get name => r'bangListProvider'; } -/// See also [bangDataList]. -class BangDataListProvider extends AutoDisposeStreamProvider> { - /// See also [bangDataList]. - BangDataListProvider({ - ({ - ({String category, String? subCategory})? categoryFilter, - String? domain, - Iterable? groups, - bool? orderMostFrequentFirst - })? filter, +/// See also [bangList]. +class BangListProvider extends AutoDisposeStreamProvider> { + /// See also [bangList]. + BangListProvider({ + List? groups, + String? domain, + ({String category, String? subCategory})? categoryFilter, + bool? orderMostFrequentFirst, }) : this._internal( - (ref) => bangDataList( - ref as BangDataListRef, - filter: filter, + (ref) => bangList( + ref as BangListRef, + groups: groups, + domain: domain, + categoryFilter: categoryFilter, + orderMostFrequentFirst: orderMostFrequentFirst, ), - from: bangDataListProvider, - name: r'bangDataListProvider', + from: bangListProvider, + name: r'bangListProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$bangDataListHash, - dependencies: BangDataListFamily._dependencies, - allTransitiveDependencies: - BangDataListFamily._allTransitiveDependencies, - filter: filter, + : _$bangListHash, + dependencies: BangListFamily._dependencies, + allTransitiveDependencies: BangListFamily._allTransitiveDependencies, + groups: groups, + domain: domain, + categoryFilter: categoryFilter, + orderMostFrequentFirst: orderMostFrequentFirst, ); - BangDataListProvider._internal( + BangListProvider._internal( super._createNotifier, { required super.name, required super.dependencies, required super.allTransitiveDependencies, required super.debugGetCreateSourceHash, required super.from, - required this.filter, + required this.groups, + required this.domain, + required this.categoryFilter, + required this.orderMostFrequentFirst, }) : super.internal(); - final ({ - ({String category, String? subCategory})? categoryFilter, - String? domain, - Iterable? groups, - bool? orderMostFrequentFirst - })? filter; + final List? groups; + final String? domain; + final ({String category, String? subCategory})? categoryFilter; + final bool? orderMostFrequentFirst; @override Override overrideWith( - Stream> Function(BangDataListRef provider) create, + Stream> Function(BangListRef provider) create, ) { return ProviderOverride( origin: this, - override: BangDataListProvider._internal( - (ref) => create(ref as BangDataListRef), + override: BangListProvider._internal( + (ref) => create(ref as BangListRef), from: from, name: null, dependencies: null, allTransitiveDependencies: null, debugGetCreateSourceHash: null, - filter: filter, + groups: groups, + domain: domain, + categoryFilter: categoryFilter, + orderMostFrequentFirst: orderMostFrequentFirst, ), ); } @override AutoDisposeStreamProviderElement> createElement() { - return _BangDataListProviderElement(this); + return _BangListProviderElement(this); } @override bool operator ==(Object other) { - return other is BangDataListProvider && other.filter == filter; + return other is BangListProvider && + other.groups == groups && + other.domain == domain && + other.categoryFilter == categoryFilter && + other.orderMostFrequentFirst == orderMostFrequentFirst; } @override int get hashCode { var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, filter.hashCode); + hash = _SystemHash.combine(hash, groups.hashCode); + hash = _SystemHash.combine(hash, domain.hashCode); + hash = _SystemHash.combine(hash, categoryFilter.hashCode); + hash = _SystemHash.combine(hash, orderMostFrequentFirst.hashCode); return _SystemHash.finish(hash); } @@ -325,49 +214,54 @@ class BangDataListProvider extends AutoDisposeStreamProvider> { @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -mixin BangDataListRef on AutoDisposeStreamProviderRef> { - /// The parameter `filter` of this provider. - ({ - ({String category, String? subCategory})? categoryFilter, - String? domain, - Iterable? groups, - bool? orderMostFrequentFirst - })? get filter; +mixin BangListRef on AutoDisposeStreamProviderRef> { + /// The parameter `groups` of this provider. + List? get groups; + + /// The parameter `domain` of this provider. + String? get domain; + + /// The parameter `categoryFilter` of this provider. + ({String category, String? subCategory})? get categoryFilter; + + /// The parameter `orderMostFrequentFirst` of this provider. + bool? get orderMostFrequentFirst; } -class _BangDataListProviderElement - extends AutoDisposeStreamProviderElement> - with BangDataListRef { - _BangDataListProviderElement(super.provider); +class _BangListProviderElement + extends AutoDisposeStreamProviderElement> with BangListRef { + _BangListProviderElement(super.provider); @override - ({ - ({String category, String? subCategory})? categoryFilter, - String? domain, - Iterable? groups, - bool? orderMostFrequentFirst - })? get filter => (origin as BangDataListProvider).filter; + List? get groups => (origin as BangListProvider).groups; + @override + String? get domain => (origin as BangListProvider).domain; + @override + ({String category, String? subCategory})? get categoryFilter => + (origin as BangListProvider).categoryFilter; + @override + bool? get orderMostFrequentFirst => + (origin as BangListProvider).orderMostFrequentFirst; } -String _$frequentBangDataListHash() => - r'27bc10ddf502266458af3910fff09bc8282a8e5c'; +String _$frequentBangListHash() => r'2c1ecb7e9416772fc1c32d01d767e1eb4f865975'; -/// See also [frequentBangDataList]. -@ProviderFor(frequentBangDataList) -final frequentBangDataListProvider = +/// See also [frequentBangList]. +@ProviderFor(frequentBangList) +final frequentBangListProvider = AutoDisposeStreamProvider>.internal( - frequentBangDataList, - name: r'frequentBangDataListProvider', + frequentBangList, + name: r'frequentBangListProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$frequentBangDataListHash, + : _$frequentBangListHash, dependencies: null, allTransitiveDependencies: null, ); @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -typedef FrequentBangDataListRef = AutoDisposeStreamProviderRef>; +typedef FrequentBangListRef = AutoDisposeStreamProviderRef>; String _$searchHistoryHash() => r'7b97798729643de8e44fd8024cdc02f35124b08b'; /// See also [searchHistory]. diff --git a/app/lib/features/bangs/domain/providers/search.dart b/app/lib/features/bangs/domain/providers/search.dart index 727f155c..705b696f 100644 --- a/app/lib/features/bangs/domain/providers/search.dart +++ b/app/lib/features/bangs/domain/providers/search.dart @@ -1,24 +1,24 @@ import 'package:lensai/features/bangs/data/models/bang_data.dart'; import 'package:lensai/features/bangs/domain/repositories/data.dart'; -import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'search.g.dart'; @Riverpod() -Future triggerBangSearch( - Ref ref, - BangData bang, - String searchQuery, -) async { - final bangDataNotifier = ref.watch(bangDataRepositoryProvider.notifier); +class BangSearch extends _$BangSearch { + Future triggerBangSearch(BangData bang, String searchQuery) async { + final bangDataNotifier = ref.read(bangDataRepositoryProvider.notifier); - await bangDataNotifier.increaseFrequency(bang.trigger); - await bangDataNotifier.addSearchEntry( - bang.trigger, - searchQuery, - maxEntryCount: 3, - ); //TODO: make count dynamic + await bangDataNotifier.increaseFrequency(bang.trigger); + await bangDataNotifier.addSearchEntry( + bang.trigger, + searchQuery, + maxEntryCount: 3, + ); //TODO: make count dynamic - return bang.getUrl(searchQuery); + return bang.getUrl(searchQuery); + } + + @override + void build() {} } diff --git a/app/lib/features/bangs/domain/providers/search.g.dart b/app/lib/features/bangs/domain/providers/search.g.dart index f8712133..085e0008 100644 --- a/app/lib/features/bangs/domain/providers/search.g.dart +++ b/app/lib/features/bangs/domain/providers/search.g.dart @@ -6,172 +6,20 @@ part of 'search.dart'; // RiverpodGenerator // ************************************************************************** -String _$triggerBangSearchHash() => r'a5cff116ae09b26d4fa476ce6a2e6052fa6fa177'; +String _$bangSearchHash() => r'a9bc5efc6e0df7b64bb648623f65911373a8e8e4'; -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); +/// See also [BangSearch]. +@ProviderFor(BangSearch) +final bangSearchProvider = + AutoDisposeNotifierProvider.internal( + BangSearch.new, + name: r'bangSearchProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$bangSearchHash, + dependencies: null, + allTransitiveDependencies: null, +); - 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)); - } -} - -/// See also [triggerBangSearch]. -@ProviderFor(triggerBangSearch) -const triggerBangSearchProvider = TriggerBangSearchFamily(); - -/// See also [triggerBangSearch]. -class TriggerBangSearchFamily extends Family> { - /// See also [triggerBangSearch]. - const TriggerBangSearchFamily(); - - /// See also [triggerBangSearch]. - TriggerBangSearchProvider call( - BangData bang, - String searchQuery, - ) { - return TriggerBangSearchProvider( - bang, - searchQuery, - ); - } - - @override - TriggerBangSearchProvider getProviderOverride( - covariant TriggerBangSearchProvider provider, - ) { - return call( - provider.bang, - provider.searchQuery, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'triggerBangSearchProvider'; -} - -/// See also [triggerBangSearch]. -class TriggerBangSearchProvider extends AutoDisposeFutureProvider { - /// See also [triggerBangSearch]. - TriggerBangSearchProvider( - BangData bang, - String searchQuery, - ) : this._internal( - (ref) => triggerBangSearch( - ref as TriggerBangSearchRef, - bang, - searchQuery, - ), - from: triggerBangSearchProvider, - name: r'triggerBangSearchProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$triggerBangSearchHash, - dependencies: TriggerBangSearchFamily._dependencies, - allTransitiveDependencies: - TriggerBangSearchFamily._allTransitiveDependencies, - bang: bang, - searchQuery: searchQuery, - ); - - TriggerBangSearchProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.bang, - required this.searchQuery, - }) : super.internal(); - - final BangData bang; - final String searchQuery; - - @override - Override overrideWith( - FutureOr Function(TriggerBangSearchRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: TriggerBangSearchProvider._internal( - (ref) => create(ref as TriggerBangSearchRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - bang: bang, - searchQuery: searchQuery, - ), - ); - } - - @override - AutoDisposeFutureProviderElement createElement() { - return _TriggerBangSearchProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is TriggerBangSearchProvider && - other.bang == bang && - other.searchQuery == searchQuery; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, bang.hashCode); - hash = _SystemHash.combine(hash, searchQuery.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin TriggerBangSearchRef on AutoDisposeFutureProviderRef { - /// The parameter `bang` of this provider. - BangData get bang; - - /// The parameter `searchQuery` of this provider. - String get searchQuery; -} - -class _TriggerBangSearchProviderElement - extends AutoDisposeFutureProviderElement with TriggerBangSearchRef { - _TriggerBangSearchProviderElement(super.provider); - - @override - BangData get bang => (origin as TriggerBangSearchProvider).bang; - @override - String get searchQuery => (origin as TriggerBangSearchProvider).searchQuery; -} +typedef _$BangSearch = AutoDisposeNotifier; // 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 diff --git a/app/lib/features/bangs/domain/repositories/data.dart b/app/lib/features/bangs/domain/repositories/data.dart index 2f3cbabf..83a4b463 100644 --- a/app/lib/features/bangs/domain/repositories/data.dart +++ b/app/lib/features/bangs/domain/repositories/data.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:lensai/features/bangs/data/models/bang.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/data/models/search_history_entry.dart'; import 'package:lensai/features/bangs/data/providers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/app/lib/features/bangs/domain/repositories/search.dart b/app/lib/features/bangs/domain/repositories/search.dart index 463342e7..3a4a7769 100644 --- a/app/lib/features/bangs/domain/repositories/search.dart +++ b/app/lib/features/bangs/domain/repositories/search.dart @@ -57,6 +57,6 @@ class SeamlessBangProvider extends _$SeamlessBangProvider { AsyncValue> build() { return _hasSearch ? ref.watch(bangSearchProvider) - : ref.watch(frequentBangDataListProvider); + : ref.watch(frequentBangListProvider); } } diff --git a/app/lib/features/bangs/domain/repositories/search.g.dart b/app/lib/features/bangs/domain/repositories/search.g.dart index ac09eab8..a0ba15b3 100644 --- a/app/lib/features/bangs/domain/repositories/search.g.dart +++ b/app/lib/features/bangs/domain/repositories/search.g.dart @@ -22,7 +22,7 @@ final bangSearchProvider = typedef _$BangSearch = AutoDisposeStreamNotifier>; String _$seamlessBangProviderHash() => - r'd2c8e94d6305a6a37cc27f8bc3617521f513e537'; + r'c4c0b452c94ba447be5b02dfef5bc16148cc3dc7'; /// See also [SeamlessBangProvider]. @ProviderFor(SeamlessBangProvider) diff --git a/app/lib/features/bangs/domain/repositories/sync.dart b/app/lib/features/bangs/domain/repositories/sync.dart index 3b151a61..2512b58b 100644 --- a/app/lib/features/bangs/domain/repositories/sync.dart +++ b/app/lib/features/bangs/domain/repositories/sync.dart @@ -1,9 +1,9 @@ import 'package:drift/isolate.dart'; import 'package:exceptions/exceptions.dart'; import 'package:lensai/features/bangs/data/database/database.dart'; -import 'package:lensai/features/bangs/data/models/bang.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/data/providers.dart'; -import 'package:lensai/features/bangs/data/services/source.dart'; +import 'package:lensai/features/bangs/data/services/data_source.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -12,7 +12,7 @@ part 'sync.g.dart'; @Riverpod(keepAlive: true) class BangSyncRepository extends _$BangSyncRepository { static Future> _fetchAndSync({ - required BangSourceService sourceService, + required BangDataSourceService sourceService, required BangDatabase db, required Uri url, required BangGroup group, @@ -20,7 +20,7 @@ class BangSyncRepository extends _$BangSyncRepository { }) async { if (syncInterval != null) { final lastSync = - await db.syncDao.lastSyncOfGroup(group).getSingleOrNull(); + await db.syncDao.getLastSyncOfGroup(group).getSingleOrNull(); if (lastSync != null && DateTime.now().difference(lastSync) < syncInterval) { @@ -52,7 +52,9 @@ class BangSyncRepository extends _$BangSyncRepository { computation: (db) async { final ref = ProviderContainer(); final result = await _fetchAndSync( - sourceService: ref.read(bangSourceServiceProvider.notifier), + sourceService: ref.read( + bangDataSourceServiceProvider.notifier, + ), db: db, url: Uri.parse(group.url), group: group, @@ -79,7 +81,7 @@ class BangSyncRepository extends _$BangSyncRepository { return ref .read(bangDatabaseProvider) .syncDao - .lastSyncOfGroup(group) + .getLastSyncOfGroup(group) .watchSingleOrNull(); } diff --git a/app/lib/features/bangs/domain/repositories/sync.g.dart b/app/lib/features/bangs/domain/repositories/sync.g.dart index 48cbdc99..1f4f6057 100644 --- a/app/lib/features/bangs/domain/repositories/sync.g.dart +++ b/app/lib/features/bangs/domain/repositories/sync.g.dart @@ -7,7 +7,7 @@ part of 'sync.dart'; // ************************************************************************** String _$bangSyncRepositoryHash() => - r'a46813f953e550585f432b11c364d87791819c34'; + r'122dd04da82d4124e7c6be5234831bd62242845e'; /// See also [BangSyncRepository]. @ProviderFor(BangSyncRepository) diff --git a/app/lib/features/bangs/presentation/screens/list.dart b/app/lib/features/bangs/presentation/screens/list.dart index 9db671ca..b794d755 100644 --- a/app/lib/features/bangs/presentation/screens/list.dart +++ b/app/lib/features/bangs/presentation/screens/list.dart @@ -8,7 +8,6 @@ import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart'; import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart'; import 'package:lensai/features/geckoview/features/browser/domain/providers.dart'; -import 'package:lensai/features/kagi/data/entities/modes.dart'; import 'package:lensai/presentation/widgets/failure_widget.dart'; class BangListScreen extends HookConsumerWidget { @@ -20,14 +19,9 @@ class BangListScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final bangsAsync = ref.watch( - bangDataListProvider( - filter: ( - categoryFilter: category.mapNotNull( - (category) => (category: category, subCategory: subCategory), - ), - domain: null, - groups: null, - orderMostFrequentFirst: null, + bangListProvider( + categoryFilter: category.mapNotNull( + (category) => (category: category, subCategory: subCategory), ), ), ); @@ -49,16 +43,7 @@ class BangListScreen extends HookConsumerWidget { .read(selectedBangTriggerProvider().notifier) .setTrigger(bang.trigger); - if (ref.read(bottomSheetControllerProvider) - is! CreateTabSheet) { - ref - .read(bottomSheetControllerProvider.notifier) - .show( - CreateTabSheet(preferredTool: KagiTool.search), - ); - } - - context.go(BrowserRoute().location); + context.go(const SearchRoute().location); }, ); }, diff --git a/app/lib/features/bangs/presentation/screens/search.dart b/app/lib/features/bangs/presentation/screens/search.dart index f96e9fba..ccb6eba2 100644 --- a/app/lib/features/bangs/presentation/screens/search.dart +++ b/app/lib/features/bangs/presentation/screens/search.dart @@ -74,14 +74,6 @@ class BangSearchScreen extends HookConsumerWidget { .read(selectedBangTriggerProvider().notifier) .setTrigger(bang.trigger); - // if (ref.read(bottomSheetControllerProvider) - // is! CreateTabSheet) { - // ref.read(bottomSheetControllerProvider.notifier).show( - // CreateTabSheet(preferredTool: KagiTool.search), - // ); - // } - - // context.go(BrowserRoute().location); context.pop(); }, ); diff --git a/app/lib/features/bangs/presentation/widgets/site_search.dart b/app/lib/features/bangs/presentation/widgets/site_search.dart index 526e4777..e17f33d7 100644 --- a/app/lib/features/bangs/presentation/widgets/site_search.dart +++ b/app/lib/features/bangs/presentation/widgets/site_search.dart @@ -42,9 +42,9 @@ class SiteSearch extends HookConsumerWidget { Future 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); diff --git a/app/lib/features/chat/features/chat_store/data/database/daos/messages.dart b/app/lib/features/chat/features/chat_store/data/database/daos/messages.dart index 741152f5..1054a946 100644 --- a/app/lib/features/chat/features/chat_store/data/database/daos/messages.dart +++ b/app/lib/features/chat/features/chat_store/data/database/daos/messages.dart @@ -12,11 +12,11 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { MessagesDao(super.db); - Selectable messageCount({required String chatId}) { + Selectable getMessageCount({required String chatId}) { return db.chatMessage.count(where: (row) => row.chatId.equals(chatId)); } - Selectable chatMessages({required String chatId}) { + Selectable getChatMessages({required String chatId}) { return db.chatMessage.select() ..where((t) => t.chatId.equals(chatId)) ..orderBy([(row) => OrderingTerm(expression: row.orderKey)]); @@ -32,7 +32,7 @@ class MessagesDao extends DatabaseAccessor .map((row) => row.read(existsExpression)!); } - SingleOrNullSelectable messageIndex({ + SingleOrNullSelectable getMessageIndex({ required String chatId, required String messageId, }) { diff --git a/app/lib/features/chat/features/chat_store/data/database/daos/metadata.dart b/app/lib/features/chat/features/chat_store/data/database/daos/metadata.dart index e72a7982..cb8fde22 100644 --- a/app/lib/features/chat/features/chat_store/data/database/daos/metadata.dart +++ b/app/lib/features/chat/features/chat_store/data/database/daos/metadata.dart @@ -9,7 +9,7 @@ class MetadataDao extends DatabaseAccessor with _$MetadataDaoMixin { MetadataDao(super.db); - SingleOrNullSelectable chatMetadata(String chatId) { + SingleOrNullSelectable getChatMetadata(String chatId) { final statement = db.chatData.selectOnly() ..addColumns([db.chatData.metadata]) diff --git a/app/lib/features/chat/features/chat_store/data/models/chat_metadata.dart b/app/lib/features/chat/features/chat_store/data/models/chat_metadata.dart index 9b06b04e..da6a7252 100644 --- a/app/lib/features/chat/features/chat_store/data/models/chat_metadata.dart +++ b/app/lib/features/chat/features/chat_store/data/models/chat_metadata.dart @@ -1,9 +1,10 @@ +import 'package:fast_equatable/fast_equatable.dart'; import 'package:json_annotation/json_annotation.dart'; part 'chat_metadata.g.dart'; @JsonSerializable() -class ChatMetadata { +class ChatMetadata with FastEquatable { final String? mainDocumentId; final String? contextId; @@ -13,4 +14,10 @@ class ChatMetadata { _$ChatMetadataFromJson(json); Map toJson() => _$ChatMetadataToJson(this); + + @override + bool get cacheHash => true; + + @override + List get hashParameters => [mainDocumentId, contextId]; } diff --git a/app/lib/features/chat/features/chat_store/domain/drift_chat_controller.dart b/app/lib/features/chat/features/chat_store/domain/controllers/drift_chat_controller.dart similarity index 97% rename from app/lib/features/chat/features/chat_store/domain/drift_chat_controller.dart rename to app/lib/features/chat/features/chat_store/domain/controllers/drift_chat_controller.dart index d345a9b3..d3c3786f 100644 --- a/app/lib/features/chat/features/chat_store/domain/drift_chat_controller.dart +++ b/app/lib/features/chat/features/chat_store/domain/controllers/drift_chat_controller.dart @@ -19,7 +19,7 @@ class DriftChatController implements ChatController { DriftChatController(this._dao, {required this.chatId}) { final chatMessagesStream = _dao - .chatMessages(chatId: chatId) + .getChatMessages(chatId: chatId) .map( (data) => Message.fromJson( jsonDecode(data.messageJson) as Map, @@ -78,7 +78,7 @@ class DriftChatController implements ChatController { Future remove(Message message) async { final index = await _dao - .messageIndex(chatId: chatId, messageId: message.id) + .getMessageIndex(chatId: chatId, messageId: message.id) .getSingleOrNull(); if (index != null) { diff --git a/app/lib/features/chat/features/chat_store/domain/providers.dart b/app/lib/features/chat/features/chat_store/domain/providers.dart index e1824135..f15a4f04 100644 --- a/app/lib/features/chat/features/chat_store/domain/providers.dart +++ b/app/lib/features/chat/features/chat_store/domain/providers.dart @@ -1,6 +1,6 @@ import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart'; import 'package:lensai/features/chat/features/chat_store/data/providers.dart'; -import 'package:lensai/features/chat/features/chat_store/domain/drift_chat_controller.dart'; +import 'package:lensai/features/chat/features/chat_store/domain/controllers/drift_chat_controller.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -21,5 +21,5 @@ DriftChatController chatController(Ref ref, String chatId) { @Riverpod() Stream chatMetadata(Ref ref, String chatId) { final db = ref.watch(chatDatabaseProvider); - return db.metadataDao.chatMetadata(chatId).watchSingleOrNull(); + return db.metadataDao.getChatMetadata(chatId).watchSingleOrNull(); } diff --git a/app/lib/features/chat/features/chat_store/domain/providers.g.dart b/app/lib/features/chat/features/chat_store/domain/providers.g.dart index 4fac3ea2..ea81b46a 100644 --- a/app/lib/features/chat/features/chat_store/domain/providers.g.dart +++ b/app/lib/features/chat/features/chat_store/domain/providers.g.dart @@ -158,7 +158,7 @@ class _ChatControllerProviderElement String get chatId => (origin as ChatControllerProvider).chatId; } -String _$chatMetadataHash() => r'22252e4a4c4e9cb039c5eee0e0f6eb5b8bd03944'; +String _$chatMetadataHash() => r'e50f492ae69168c9f9c4df89e29424b72b681e27'; /// See also [chatMetadata]. @ProviderFor(chatMetadata) diff --git a/app/lib/features/chat/features/chat_store/domain/repositories/chat_message.dart b/app/lib/features/chat/features/chat_store/domain/repositories/chat_message.dart index 3bedca24..53b1a6d4 100644 --- a/app/lib/features/chat/features/chat_store/domain/repositories/chat_message.dart +++ b/app/lib/features/chat/features/chat_store/domain/repositories/chat_message.dart @@ -3,7 +3,7 @@ import 'package:flutter_chat_core/flutter_chat_core.dart'; import 'package:langchain/langchain.dart'; import 'package:lensai/core/uuid.dart'; import 'package:lensai/features/chat/features/chat_store/data/models/message_types.dart'; -import 'package:lensai/features/chat/features/chat_store/domain/drift_chat_controller.dart'; +import 'package:lensai/features/chat/features/chat_store/domain/controllers/drift_chat_controller.dart'; import 'package:lensai/features/chat/features/chat_store/domain/providers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/app/lib/features/geckoview/domain/controllers/bottom_sheet.dart b/app/lib/features/geckoview/domain/controllers/bottom_sheet.dart index 0874c9a6..4248f343 100644 --- a/app/lib/features/geckoview/domain/controllers/bottom_sheet.dart +++ b/app/lib/features/geckoview/domain/controllers/bottom_sheet.dart @@ -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 diff --git a/app/lib/features/geckoview/domain/controllers/bottom_sheet.g.dart b/app/lib/features/geckoview/domain/controllers/bottom_sheet.g.dart index 53b6b562..b0f980af 100644 --- a/app/lib/features/geckoview/domain/controllers/bottom_sheet.g.dart +++ b/app/lib/features/geckoview/domain/controllers/bottom_sheet.g.dart @@ -7,7 +7,7 @@ part of 'bottom_sheet.dart'; // ************************************************************************** String _$bottomSheetControllerHash() => - r'ae91f911fc35fdc0dfb14149ac2295e58ad13a86'; + r'0b0ea53a96b80b8c1cba79b1d5913274b7127d5b'; /// See also [BottomSheetController]. @ProviderFor(BottomSheetController) diff --git a/app/lib/features/geckoview/domain/controllers/overlay_dialog.dart b/app/lib/features/geckoview/domain/controllers/overlay.dart similarity index 77% rename from app/lib/features/geckoview/domain/controllers/overlay_dialog.dart rename to app/lib/features/geckoview/domain/controllers/overlay.dart index 78389a08..f93be97c 100644 --- a/app/lib/features/geckoview/domain/controllers/overlay_dialog.dart +++ b/app/lib/features/geckoview/domain/controllers/overlay.dart @@ -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; diff --git a/app/lib/features/kagi/data/services/chat.g.dart b/app/lib/features/geckoview/domain/controllers/overlay.g.dart similarity index 56% rename from app/lib/features/kagi/data/services/chat.g.dart rename to app/lib/features/geckoview/domain/controllers/overlay.g.dart index 7edee631..db80872e 100644 --- a/app/lib/features/kagi/data/services/chat.g.dart +++ b/app/lib/features/geckoview/domain/controllers/overlay.g.dart @@ -1,26 +1,26 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'chat.dart'; +part of 'overlay.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$kagiChatServiceHash() => r'a112b70b42fe9d650c9d4dc643bf965ddce7bfa6'; +String _$overlayControllerHash() => r'b6dc3ca17611c4efadc7e8248d493cd3c7061e01'; -/// See also [KagiChatService]. -@ProviderFor(KagiChatService) -final kagiChatServiceProvider = - NotifierProvider.internal( - KagiChatService.new, - name: r'kagiChatServiceProvider', +/// See also [OverlayController]. +@ProviderFor(OverlayController) +final overlayControllerProvider = + AutoDisposeNotifierProvider.internal( + OverlayController.new, + name: r'overlayControllerProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$kagiChatServiceHash, + : _$overlayControllerHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$KagiChatService = Notifier; +typedef _$OverlayController = AutoDisposeNotifier; // 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 diff --git a/app/lib/features/geckoview/domain/controllers/overlay_dialog.g.dart b/app/lib/features/geckoview/domain/controllers/overlay_dialog.g.dart deleted file mode 100644 index ccfe7094..00000000 --- a/app/lib/features/geckoview/domain/controllers/overlay_dialog.g.dart +++ /dev/null @@ -1,27 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'overlay_dialog.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$overlayDialogControllerHash() => - r'53b44d9567bcb5bd9faecbfb8bb1af5aafb369c9'; - -/// See also [OverlayDialogController]. -@ProviderFor(OverlayDialogController) -final overlayDialogControllerProvider = - AutoDisposeNotifierProvider.internal( - OverlayDialogController.new, - name: r'overlayDialogControllerProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$overlayDialogControllerHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$OverlayDialogController = AutoDisposeNotifier; -// 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 diff --git a/app/lib/features/geckoview/domain/entities/find_result_state.dart b/app/lib/features/geckoview/domain/entities/states/find_result.dart similarity index 100% rename from app/lib/features/geckoview/domain/entities/find_result_state.dart rename to app/lib/features/geckoview/domain/entities/states/find_result.dart diff --git a/app/lib/features/geckoview/domain/entities/history_state.dart b/app/lib/features/geckoview/domain/entities/states/history.dart similarity index 100% rename from app/lib/features/geckoview/domain/entities/history_state.dart rename to app/lib/features/geckoview/domain/entities/states/history.dart diff --git a/app/lib/features/geckoview/domain/entities/readerable_state.dart b/app/lib/features/geckoview/domain/entities/states/readerable.dart similarity index 100% rename from app/lib/features/geckoview/domain/entities/readerable_state.dart rename to app/lib/features/geckoview/domain/entities/states/readerable.dart diff --git a/app/lib/features/geckoview/domain/entities/security_state.dart b/app/lib/features/geckoview/domain/entities/states/security.dart similarity index 100% rename from app/lib/features/geckoview/domain/entities/security_state.dart rename to app/lib/features/geckoview/domain/entities/states/security.dart diff --git a/app/lib/features/geckoview/domain/entities/tab_state.dart b/app/lib/features/geckoview/domain/entities/states/tab.dart similarity index 87% rename from app/lib/features/geckoview/domain/entities/tab_state.dart rename to app/lib/features/geckoview/domain/entities/states/tab.dart index 75db16ba..0aca83e6 100644 --- a/app/lib/features/geckoview/domain/entities/tab_state.dart +++ b/app/lib/features/geckoview/domain/entities/states/tab.dart @@ -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 { diff --git a/app/lib/features/geckoview/domain/entities/tab_state.g.dart b/app/lib/features/geckoview/domain/entities/states/tab.g.dart similarity index 99% rename from app/lib/features/geckoview/domain/entities/tab_state.g.dart rename to app/lib/features/geckoview/domain/entities/states/tab.g.dart index 2cc7141d..09a46a0a 100644 --- a/app/lib/features/geckoview/domain/entities/tab_state.g.dart +++ b/app/lib/features/geckoview/domain/entities/states/tab.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'tab_state.dart'; +part of 'tab.dart'; // ************************************************************************** // CopyWithGenerator diff --git a/app/lib/features/geckoview/domain/entities/web_extension_state.dart b/app/lib/features/geckoview/domain/entities/states/web_extension.dart similarity index 95% rename from app/lib/features/geckoview/domain/entities/web_extension_state.dart rename to app/lib/features/geckoview/domain/entities/states/web_extension.dart index ec992e43..1506e0d5 100644 --- a/app/lib/features/geckoview/domain/entities/web_extension_state.dart +++ b/app/lib/features/geckoview/domain/entities/states/web_extension.dart @@ -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 { diff --git a/app/lib/features/geckoview/domain/entities/web_extension_state.g.dart b/app/lib/features/geckoview/domain/entities/states/web_extension.g.dart similarity index 99% rename from app/lib/features/geckoview/domain/entities/web_extension_state.g.dart rename to app/lib/features/geckoview/domain/entities/states/web_extension.g.dart index 292f01ff..cc4d263c 100644 --- a/app/lib/features/geckoview/domain/entities/web_extension_state.g.dart +++ b/app/lib/features/geckoview/domain/entities/states/web_extension.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'web_extension_state.dart'; +part of 'web_extension.dart'; // ************************************************************************** // CopyWithGenerator diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index 2d2ed309..29adc6fd 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -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 selectedTabSessionNotifier(Ref ref) { - return ref.watch(tabSessionProvider(tabId: null).notifier); -} diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index d8ed4af6..c636cfda 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -93,25 +93,6 @@ final engineSuggestionsServiceProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef EngineSuggestionsServiceRef = ProviderRef; -String _$selectedTabSessionNotifierHash() => - r'aef19991d5b05a2bb28e8a6ccb57dc75c2f3148f'; - -/// See also [selectedTabSessionNotifier]. -@ProviderFor(selectedTabSessionNotifier) -final selectedTabSessionNotifierProvider = - AutoDisposeProvider>.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>; String _$engineReadyStateHash() => r'c682333e2e07cf0635aa7ae793a2088ca648c950'; /// See also [EngineReadyState]. diff --git a/app/lib/features/geckoview/domain/providers/tab_list.dart b/app/lib/features/geckoview/domain/providers/tab_list.dart index b54361c5..9e09dbd9 100644 --- a/app/lib/features/geckoview/domain/providers/tab_list.dart +++ b/app/lib/features/geckoview/domain/providers/tab_list.dart @@ -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 build() { + EquatableCollection> 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); } } diff --git a/app/lib/features/geckoview/domain/providers/tab_list.g.dart b/app/lib/features/geckoview/domain/providers/tab_list.g.dart index 340db386..24c153f7 100644 --- a/app/lib/features/geckoview/domain/providers/tab_list.g.dart +++ b/app/lib/features/geckoview/domain/providers/tab_list.g.dart @@ -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>.internal( +final tabListProvider = + NotifierProvider>>.internal( TabList.new, name: r'tabListProvider', debugGetCreateSourceHash: @@ -19,6 +20,6 @@ final tabListProvider = NotifierProvider>.internal( allTransitiveDependencies: null, ); -typedef _$TabList = Notifier>; +typedef _$TabList = Notifier>>; // 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 diff --git a/app/lib/features/geckoview/domain/providers/tab_session.dart b/app/lib/features/geckoview/domain/providers/tab_session.dart index 90c222cf..3f9cbfb3 100644 --- a/app/lib/features/geckoview/domain/providers/tab_session.dart +++ b/app/lib/features/geckoview/domain/providers/tab_session.dart @@ -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 selectedTabSessionNotifier(Ref ref) { + return ref.watch(tabSessionProvider(tabId: null).notifier); +} diff --git a/app/lib/features/geckoview/domain/providers/tab_session.g.dart b/app/lib/features/geckoview/domain/providers/tab_session.g.dart index 72131273..f74f6582 100644 --- a/app/lib/features/geckoview/domain/providers/tab_session.g.dart +++ b/app/lib/features/geckoview/domain/providers/tab_session.g.dart @@ -6,6 +6,25 @@ part of 'tab_session.dart'; // RiverpodGenerator // ************************************************************************** +String _$selectedTabSessionNotifierHash() => + r'aef19991d5b05a2bb28e8a6ccb57dc75c2f3148f'; + +/// See also [selectedTabSessionNotifier]. +@ProviderFor(selectedTabSessionNotifier) +final selectedTabSessionNotifierProvider = + AutoDisposeProvider>.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>; String _$tabSessionHash() => r'e4fbbcd430037cc01adb799783fb7974a11a27bf'; /// Copied from Dart SDK diff --git a/app/lib/features/geckoview/domain/providers/tab_state.dart b/app/lib/features/geckoview/domain/providers/tab_state.dart index 9f400b9c..3d7d44c9 100644 --- a/app/lib/features/geckoview/domain/providers/tab_state.dart +++ b/app/lib/features/geckoview/domain/providers/tab_state.dart @@ -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); }), ]; diff --git a/app/lib/features/geckoview/domain/providers/tab_state.g.dart b/app/lib/features/geckoview/domain/providers/tab_state.g.dart index de40ce7a..fafd59b6 100644 --- a/app/lib/features/geckoview/domain/providers/tab_state.g.dart +++ b/app/lib/features/geckoview/domain/providers/tab_state.g.dart @@ -173,7 +173,7 @@ final selectedTabStateProvider = AutoDisposeProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef SelectedTabStateRef = AutoDisposeProviderRef; -String _$tabStatesHash() => r'296d85ec016b22236b5830fce6b01cda81fbc02d'; +String _$tabStatesHash() => r'c2169e467962d006015bf50d09e93b3f4bf596e6'; /// See also [TabStates]. @ProviderFor(TabStates) diff --git a/app/lib/features/geckoview/domain/providers/web_extensions_state.dart b/app/lib/features/geckoview/domain/providers/web_extensions_state.dart index 986d8282..4eca8444 100644 --- a/app/lib/features/geckoview/domain/providers/web_extensions_state.dart +++ b/app/lib/features/geckoview/domain/providers/web_extensions_state.dart @@ -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 = {}; - 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); }), ], }; diff --git a/app/lib/features/geckoview/domain/providers/web_extensions_state.g.dart b/app/lib/features/geckoview/domain/providers/web_extensions_state.g.dart index bdd4a44e..65a5e87f 100644 --- a/app/lib/features/geckoview/domain/providers/web_extensions_state.g.dart +++ b/app/lib/features/geckoview/domain/providers/web_extensions_state.g.dart @@ -7,7 +7,7 @@ part of 'web_extensions_state.dart'; // ************************************************************************** String _$webExtensionsStateHash() => - r'4e6dc46e49ff353e1c3cc4616008685c0e21501d'; + r'7fd0cb313fb433f7896bfe0f89a4545f94f58793'; /// Copied from Dart SDK class _SystemHash { diff --git a/app/lib/features/geckoview/domain/repositories/tab.dart b/app/lib/features/geckoview/domain/repositories/tab.dart index a3146690..12f8d4b9 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.dart @@ -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? 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? 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()); diff --git a/app/lib/features/geckoview/domain/repositories/tab.g.dart b/app/lib/features/geckoview/domain/repositories/tab.g.dart index 889ecae9..82322cce 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.g.dart @@ -6,7 +6,7 @@ part of 'tab.dart'; // RiverpodGenerator // ************************************************************************** -String _$tabRepositoryHash() => r'85af29fa18ea5a4a67fbffbb9478c801dafddac2'; +String _$tabRepositoryHash() => r'ce0db685127eeb706ce047d7afcc58fafd1394ba'; /// See also [TabRepository]. @ProviderFor(TabRepository) diff --git a/app/lib/features/geckoview/features/browser/domain/entities/sheet.dart b/app/lib/features/geckoview/features/browser/domain/entities/sheet.dart index d6b3b681..a911d0ea 100644 --- a/app/lib/features/geckoview/features/browser/domain/entities/sheet.dart +++ b/app/lib/features/geckoview/features/browser/domain/entities/sheet.dart @@ -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 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 get hashParameters => []; } -class TabQaChatSheet extends Sheet { +final class TabQaChatSheet extends Sheet { final String chatId; TabQaChatSheet({required this.chatId}); diff --git a/app/lib/features/geckoview/features/browser/domain/providers.dart b/app/lib/features/geckoview/features/browser/domain/providers.dart index b00cc973..7c196b3d 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.dart @@ -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> 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, ); } diff --git a/app/lib/features/geckoview/features/browser/domain/providers.g.dart b/app/lib/features/geckoview/features/browser/domain/providers.g.dart index a8af11a3..cdc27928 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -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.internal( - LastUsedAssistantMode.new, - name: r'lastUsedAssistantModeProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$lastUsedAssistantModeHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$LastUsedAssistantMode = Notifier; -String _$activeResearchVariantHash() => - r'8c65a3f5ead6cc4f9e2af85b607b7a49b0764ee3'; - -/// See also [ActiveResearchVariant]. -@ProviderFor(ActiveResearchVariant) -final activeResearchVariantProvider = - NotifierProvider.internal( - ActiveResearchVariant.new, - name: r'activeResearchVariantProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$activeResearchVariantHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$ActiveResearchVariant = Notifier; -String _$activeChatModelHash() => r'675f406af8b4aa92699d59e37b5805a81d03a799'; - -/// See also [ActiveChatModel]. -@ProviderFor(ActiveChatModel) -final activeChatModelProvider = - NotifierProvider.internal( - ActiveChatModel.new, - name: r'activeChatModelProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$activeChatModelHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$ActiveChatModel = Notifier; String _$showFindInPageHash() => r'7ee5703f7d0c7e8a8dd6b0849d7b1e0a41fe24d9'; /// See also [ShowFindInPage]. diff --git a/app/lib/features/geckoview/features/browser/domain/providers/intent.dart b/app/lib/features/geckoview/features/browser/domain/providers/intent.dart new file mode 100644 index 00000000..09af8e33 --- /dev/null +++ b/app/lib/features/geckoview/features/browser/domain/providers/intent.dart @@ -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.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 _streamController; + + @override + Stream 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, + ]); + } +} diff --git a/app/lib/features/geckoview/features/browser/domain/providers/intent.g.dart b/app/lib/features/geckoview/features/browser/domain/providers/intent.g.dart new file mode 100644 index 00000000..c6f1657f --- /dev/null +++ b/app/lib/features/geckoview/features/browser/domain/providers/intent.g.dart @@ -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.internal( + IntentStream.new, + name: r'intentStreamProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$intentStreamHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$IntentStream = AutoDisposeStreamNotifier; +// 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 diff --git a/app/lib/features/geckoview/features/browser/domain/services/delete_browser_data.dart b/app/lib/features/geckoview/features/browser/domain/services/browser_data.dart similarity index 91% rename from app/lib/features/geckoview/features/browser/domain/services/delete_browser_data.dart rename to app/lib/features/geckoview/features/browser/domain/services/browser_data.dart index b8694ec6..91ebd758 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/delete_browser_data.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/browser_data.dart @@ -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 deleteData(Set? types) async { diff --git a/app/lib/features/kagi/data/services/session.g.dart b/app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart similarity index 56% rename from app/lib/features/kagi/data/services/session.g.dart rename to app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart index bcbe0833..78c86052 100644 --- a/app/lib/features/kagi/data/services/session.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart @@ -1,27 +1,27 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'session.dart'; +part of 'browser_data.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$kagiSessionServiceHash() => - r'8bed5d74d319ff2f874f42bc8596d02f05347c1f'; +String _$browserDataServiceHash() => + r'f56c60635505e06dbd5b05f1bcee9c25c4d0eaaf'; -/// See also [KagiSessionService]. -@ProviderFor(KagiSessionService) -final kagiSessionServiceProvider = - AutoDisposeNotifierProvider.internal( - KagiSessionService.new, - name: r'kagiSessionServiceProvider', +/// See also [BrowserDataService]. +@ProviderFor(BrowserDataService) +final browserDataServiceProvider = + AutoDisposeNotifierProvider.internal( + BrowserDataService.new, + name: r'browserDataServiceProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$kagiSessionServiceHash, + : _$browserDataServiceHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$KagiSessionService = AutoDisposeNotifier; +typedef _$BrowserDataService = AutoDisposeNotifier; // 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 diff --git a/app/lib/features/geckoview/features/browser/domain/services/create_tab.dart b/app/lib/features/geckoview/features/browser/domain/services/create_tab.dart deleted file mode 100644 index cde7e8e2..00000000 --- a/app/lib/features/geckoview/features/browser/domain/services/create_tab.dart +++ /dev/null @@ -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.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 _streamController; - - @override - Stream 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); - } - } -} diff --git a/app/lib/features/geckoview/features/browser/domain/services/delete_browser_data.g.dart b/app/lib/features/geckoview/features/browser/domain/services/delete_browser_data.g.dart deleted file mode 100644 index 28eaf360..00000000 --- a/app/lib/features/geckoview/features/browser/domain/services/delete_browser_data.g.dart +++ /dev/null @@ -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.internal( - DeleteBrowserDataService.new, - name: r'deleteBrowserDataServiceProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$deleteBrowserDataServiceHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$DeleteBrowserDataService = AutoDisposeNotifier; -// 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 diff --git a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart index aa2bfeda..3ec231b9 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart @@ -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'; diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_action.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_action.dart deleted file mode 100644 index 1c10783e..00000000 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_action.dart +++ /dev/null @@ -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() ?? -// [], -// ), -// ), -// ], -// ), -// ], -// ); -// } -// } diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart index eeaaed0f..1be67b43 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/web_page_dialog.dart @@ -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(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(); + // } + // }, + // ), ], ), ); diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index a0cb4e6a..f623ed89 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -1,72 +1,46 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; -import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lensai/core/providers/global_drop.dart'; import 'package:lensai/core/routing/routes.dart'; import 'package:lensai/data/models/drag_data.dart'; +import 'package:lensai/extensions/media_query.dart'; import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart'; -import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart'; -import 'package:lensai/features/geckoview/domain/providers.dart'; +import 'package:lensai/features/geckoview/domain/controllers/overlay.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/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/browser_modules/bottom_app_bar.dart'; +import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.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/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'; -import 'package:lensai/features/geckoview/features/find_in_page/presentation/controllers/find_in_page_visibility.dart'; import 'package:lensai/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart'; import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart'; -import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart'; import 'package:lensai/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart'; -import 'package:lensai/features/kagi/data/entities/modes.dart'; import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart'; -import 'package:lensai/presentation/hooks/menu_controller.dart'; import 'package:lensai/presentation/hooks/overlay_portal_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 BrowserScreen extends HookConsumerWidget { const BrowserScreen({super.key}); - double _realtiveSafeArea(BuildContext context) { - final mediaQuery = MediaQuery.of(context); - return 1.0 - (mediaQuery.padding.top / mediaQuery.size.height); - } - @override Widget build(BuildContext context, WidgetRef ref) { - //Init here to avoid dispose errors of menu items when called - final addonService = ref.watch(addonServiceProvider); - final displayedSheet = ref.watch(bottomSheetControllerProvider); - final displayedOverlayDialog = ref.watch(overlayDialogControllerProvider); + final displayedOverlay = ref.watch(overlayControllerProvider); final selectedTabId = ref.watch( selectedTabStateProvider.select((value) => value?.id), ); - final trippleDotMenuController = useMenuController(); - final tabMenuController = useMenuController(); - final lastBackButtonPress = useRef(null); final overlayController = useOverlayPortalController(); - final activeKagiTool = useValueNotifier(null, [displayedSheet]); - - ref.listen(overlayDialogControllerProvider, (previous, next) { + ref.listen(overlayControllerProvider, (previous, next) { if (next != null) { overlayController.show(); } else { @@ -74,18 +48,6 @@ class BrowserScreen extends HookConsumerWidget { } }); - // ref.listen( - // generalSettingsRepositoryProvider - // .select((value) => value.valueOrNull?.kagiSession), - // (previous, next) async { - // if (next != null && next.isNotEmpty) { - // await ref - // .read(kagiSessionServiceProvider.notifier) - // .setKagiSession(next); - // } - // }, - // ); - return PopScope( //We need this for BackButtonListener to work downstream //No direct pop result will be handled here @@ -101,532 +63,9 @@ class BrowserScreen extends HookConsumerWidget { return Visibility( visible: !tabInFullScreen, - child: BottomAppBar( - height: AppBar().preferredSize.height, - padding: EdgeInsets.zero, - child: AppBar( - automaticallyImplyLeading: false, - titleSpacing: 8.0, - title: - (selectedTabId != null && - displayedSheet is! ViewTabsSheet) - ? Consumer( - 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( - context: context, - builder: - (context) => EditUrlDialog( - initialUrl: tabState.url, - ), - ); - - if (newUrl != null) { - await ref - .read( - tabSessionProvider( - tabId: null, - ).notifier, - ) - .loadUrl(url: newUrl); - } - }, - ) - : const SizedBox.shrink(); - }, - ) - : HookBuilder( - builder: (context) { - // final activeTool = useListenableSelector( - // activeKagiTool, - // () { - // if (displayedSheet is CreateTabSheetWidget) { - // return null; - // } - - // return activeKagiTool.value; - // }, - // ); - - return Row( - children: [ - // IconButton( - // color: (activeTool == KagiTool.search) - // ? Theme.of(context).colorScheme.primary - // : null, - // onPressed: () async { - // // ref - // // .read(createTabStreamProvider.notifier) - // // .createTab( - // // CreateTabSheet( - // // preferredTool: KagiTool.search, - // // ), - // // ); - // await context - // .push(const SearchRoute().location); - // }, - // icon: Icon(KagiTool.search.icon), - // ), - // IconButton( - // color: (activeTool == KagiTool.summarizer) - // ? Theme.of(context).colorScheme.primary - // : null, - // onPressed: () { - // ref - // .read(createTabStreamProvider.notifier) - // .createTab( - // CreateTabSheet( - // preferredTool: KagiTool.summarizer, - // ), - // ); - // }, - // icon: Icon(KagiTool.summarizer.icon), - // ), - // if (showEarlyAccessFeatures) - // IconButton( - // color: (activeTool == KagiTool.assistant) - // ? Theme.of(context).colorScheme.primary - // : null, - // onPressed: () { - // ref - // .read( - // createTabStreamProvider.notifier, - // ) - // .createTab( - // CreateTabSheet( - // preferredTool: KagiTool.assistant, - // ), - // ); - // }, - // icon: Icon(KagiTool.assistant.icon), - // ), - ], - ); - }, - ), - actions: [ - if (selectedTabId != null && - displayedSheet is! ViewTabsSheet) - ReaderButton(), - // if (quickAction != null) - // InkWell( - // onTap: () async { - // var tab = CreateTabSheet( - // preferredTool: quickAction, - // ); - - // if (quickActionVoice) { - // final completer = Completer(); - - // final isServiceAvailable = - // await SpeechToTextGoogleDialog.getInstance() - // .showGoogleDialog( - // onTextReceived: (data) { - // completer.complete(data.toString()); - // }, - // // locale: "en-US", - // ); - - // if (!isServiceAvailable) { - // if (context.mounted) { - // ui_helper.showErrorMessage( - // context, - // 'Service is not available', - // ); - // } - // } - - // tab = CreateTabSheet( - // preferredTool: quickAction, - // content: await completer.future, - // ); - // } - - // ref - // .read(createTabStreamProvider.notifier) - // .createTab(tab); - // }, - // child: Padding( - // padding: const EdgeInsets.symmetric( - // vertical: 15.0, - // horizontal: 8.0, - // ), - // child: Icon( - // quickActionVoice ? Icons.mic : quickAction.icon, - // ), - // ), - // ), - 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'), - ), - // 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( - 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'), - ), - ], - ), - ], - ), + child: BrowserBottomAppBar( + selectedTabId: selectedTabId, + displayedSheet: displayedSheet, ), ); }, @@ -649,7 +88,7 @@ class BrowserScreen extends HookConsumerWidget { return OverlayPortal( controller: overlayController, overlayChildBuilder: (context) { - return displayedOverlayDialog!; + return displayedOverlay!; }, child: Listener( onPointerDown: @@ -660,164 +99,75 @@ class BrowserScreen extends HookConsumerWidget { .dismiss(); } : null, - child: HookConsumer( - builder: (context, ref, child) { - return BackButtonListener( - onBackButtonPressed: () async { - final tabState = - (selectedTabId != null) - ? ref.read(tabStateProvider(selectedTabId)) - : null; + child: BackButtonListener( + onBackButtonPressed: () async { + final tabState = + (selectedTabId != null) + ? ref.read(tabStateProvider(selectedTabId)) + : null; - final tabCount = ref.read( - tabListProvider.select((tabs) => tabs.length), - ); - - //Don't do anything if a child route is active - if (GoRouterState.of(context).topRoute?.path != - BrowserRoute().location) { - return false; - } - - if (displayedSheet != null) { - ref - .read(bottomSheetControllerProvider.notifier) - .dismiss(); - return true; - } - - if (displayedOverlayDialog != null) { - ref - .read(overlayDialogControllerProvider.notifier) - .dismiss(); - return true; - } - - if (tabState?.isFullScreen == true) { - await ref - .read(selectedTabSessionNotifierProvider) - .exitFullscreen(); - return true; - } - - if (tabState?.historyState.canGoBack == true) { - lastBackButtonPress.value = null; - - final controller = ref.read( - tabSessionProvider(tabId: selectedTabId).notifier, - ); - - await controller.goBack(); - return true; - } - - if (lastBackButtonPress.value != null && - DateTime.now().difference( - lastBackButtonPress.value!, - ) < - const Duration(seconds: 2)) { - lastBackButtonPress.value = null; - - if (tabState != null && tabCount > 1) { - await ref - .read(tabRepositoryProvider.notifier) - .closeTab(tabState.id); - return true; - } else { - //Mark back as unhandled and navigator will pop - return false; - } - } else { - lastBackButtonPress.value = DateTime.now(); - ScaffoldMessenger.of(context) - ..clearSnackBars() - ..showSnackBar( - SnackBar( - content: - (tabCount > 1) - ? const Text( - 'Please click BACK again to close current tab', - ) - : const Text( - 'Please click BACK again to exit app', - ), - ), - ); - - return true; - } - }, - child: Stack( - children: [ - SafeArea( - child: Stack( - children: [ - const BrowserView(), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: Consumer( - builder: (context, ref, child) { - final value = ref.watch( - selectedTabStateProvider.select(( - state, - ) { - if (state?.isLoading == true) { - return state?.progress ?? 100; - } - - //When not loading we assumed finished - return 100; - }), - ); - - return Visibility( - visible: value < 100, - child: LinearProgressIndicator( - value: value / 100, - ), - ); - }, - ), - ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: Consumer( - builder: (context, ref, child) { - final value = ref.watch( - selectedTabStateProvider.select( - (state) => EdgeInsets.only( - bottom: - (state?.isLoading == true && - state?.progress != - null && - state!.progress < 100) - ? 4.0 - : 0.0, - ), - ), - ); - - return FindInPageWidget(padding: value); - }, - ), - ), - ], - ), - ), - if (displayedSheet != null) - ModalBarrier( - color: - Theme.of(context).dialogTheme.barrierColor ?? - Colors.black54, - ), - ], - ), + final tabCount = ref.read( + tabListProvider.select((tabs) => tabs.collection.length), ); + + //Don't do anything if a child route is active + if (GoRouterState.of(context).topRoute?.path != + BrowserRoute().location) { + return false; + } + + if (displayedSheet != null) { + ref + .read(bottomSheetControllerProvider.notifier) + .dismiss(); + return true; + } + + if (displayedOverlay != null) { + ref.read(overlayControllerProvider.notifier).dismiss(); + return true; + } + + if (tabState?.isFullScreen == true) { + await ref + .read(selectedTabSessionNotifierProvider) + .exitFullscreen(); + return true; + } + + if (tabState?.historyState.canGoBack == true) { + lastBackButtonPress.value = null; + + final controller = ref.read( + tabSessionProvider(tabId: selectedTabId).notifier, + ); + + await controller.goBack(); + return true; + } + + if (lastBackButtonPress.value != null && + DateTime.now().difference(lastBackButtonPress.value!) < + const Duration(seconds: 2)) { + lastBackButtonPress.value = null; + + if (tabState != null && tabCount > 1) { + await ref + .read(tabRepositoryProvider.notifier) + .closeTab(tabState.id); + return true; + } else { + //Mark back as unhandled and navigator will pop + return false; + } + } else { + lastBackButtonPress.value = DateTime.now(); + ui_helper.showTabBackButtonMessage(context, tabCount); + + return true; + } }, + child: _BrowserView(displayedSheet: displayedSheet), ), ), ); @@ -842,121 +192,12 @@ class BrowserScreen extends HookConsumerWidget { return false; }, child: switch (displayedSheet) { - ViewTabsSheet() => HookBuilder( - builder: (localContext) { - final draggableScrollableController = - useDraggableScrollableController(); - - return DraggableScrollableSheet( - key: ValueKey(displayedSheet), - controller: draggableScrollableController, - expand: false, - minChildSize: 0.1, - maxChildSize: _realtiveSafeArea(context), - builder: (context, scrollController) { - return ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - child: ViewTabsSheetWidget( - sheetScrollController: scrollController, - draggableScrollableController: - draggableScrollableController, - onClose: () { - ref - .read( - bottomSheetControllerProvider.notifier, - ) - .dismiss(); - }, - ), - ); - }, - ); - }, - ), - final CreateTabSheet parameter => DraggableScrollableSheet( + ViewTabsSheet() => _ViewTabsSheet( key: ValueKey(displayedSheet), - expand: false, - initialChildSize: 0.8, - minChildSize: 0.1, - maxChildSize: _realtiveSafeArea(context), - builder: (context, scrollController) { - return SingleChildScrollView( - controller: scrollController, - child: CreateTabSheetWidget( - key: ValueKey(parameter), - parameter: parameter, - onSubmit: (url) async { - await ref - .read(tabRepositoryProvider.notifier) - .addTab(url: url); - - ref - .read(bottomSheetControllerProvider.notifier) - .dismiss(); - }, - onActiveToolChanged: (tool) { - activeKagiTool.value = tool; - }, - ), - ); - }, ), - final TabQaChatSheet parameter => HookBuilder( - builder: (localContext) { - final draggableScrollableController = - useDraggableScrollableController(); - - return DraggableScrollableSheet( - key: ValueKey(displayedSheet), - controller: draggableScrollableController, - expand: false, - minChildSize: 0.1, - maxChildSize: _realtiveSafeArea(context), - builder: (context, scrollController) { - return ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - child: Column( - children: [ - DraggableScrollableHeader( - controller: draggableScrollableController, - child: Material( - child: SizedBox( - width: double.infinity, - child: Center( - child: Container( - width: 40, - height: 4, - margin: const EdgeInsets.symmetric( - vertical: 16.0, - ), - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: - BorderRadius.circular(2), - ), - ), - ), - ), - ), - ), - Expanded( - child: TabQaChat( - chatId: parameter.chatId, - scrollController: scrollController, - ), - ), - ], - ), - ); - }, - ); - }, + final TabQaChatSheet parameter => _QaSheet( + key: ValueKey(displayedSheet), + chatId: parameter.chatId, ), }, ) @@ -965,3 +206,161 @@ class BrowserScreen extends HookConsumerWidget { ); } } + +class _BrowserView extends StatelessWidget { + const _BrowserView({required this.displayedSheet}); + + final Sheet? displayedSheet; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + SafeArea( + child: Stack( + children: [ + const BrowserView(), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Consumer( + builder: (context, ref, child) { + final value = ref.watch( + selectedTabStateProvider.select((state) { + if (state?.isLoading == true) { + return state?.progress ?? 100; + } + + //When not loading we assumed finished + return 100; + }), + ); + + return Visibility( + visible: value < 100, + child: LinearProgressIndicator(value: value / 100), + ); + }, + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Consumer( + builder: (context, ref, child) { + final value = ref.watch( + selectedTabStateProvider.select( + (state) => EdgeInsets.only( + bottom: + (state?.isLoading == true && + state?.progress != null && + state!.progress < 100) + ? 4.0 + : 0.0, + ), + ), + ); + + return FindInPageWidget(padding: value); + }, + ), + ), + ], + ), + ), + if (displayedSheet != null) + ModalBarrier( + color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54, + ), + ], + ); + } +} + +class _QaSheet extends HookConsumerWidget { + final String chatId; + + const _QaSheet({super.key, required this.chatId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final draggableScrollableController = useDraggableScrollableController(); + + return DraggableScrollableSheet( + controller: draggableScrollableController, + expand: false, + minChildSize: 0.1, + maxChildSize: MediaQuery.of(context).relativeSafeArea(), + builder: (context, scrollController) { + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + child: Column( + children: [ + DraggableScrollableHeader( + controller: draggableScrollableController, + child: Material( + child: SizedBox( + width: double.infinity, + child: Center( + child: Container( + width: 40, + height: 4, + margin: const EdgeInsets.symmetric(vertical: 16.0), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + ), + ), + ), + ), + Expanded( + child: TabQaChat( + chatId: chatId, + scrollController: scrollController, + ), + ), + ], + ), + ); + }, + ); + } +} + +class _ViewTabsSheet extends HookConsumerWidget { + const _ViewTabsSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final draggableScrollableController = useDraggableScrollableController(); + + return DraggableScrollableSheet( + controller: draggableScrollableController, + expand: false, + minChildSize: 0.1, + maxChildSize: MediaQuery.of(context).relativeSafeArea(), + builder: (context, scrollController) { + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + child: ViewTabsSheetWidget( + sheetScrollController: scrollController, + draggableScrollableController: draggableScrollableController, + onClose: () { + ref.read(bottomSheetControllerProvider.notifier).dismiss(); + }, + ), + ); + }, + ); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart new file mode 100644 index 00000000..2f5e6764 --- /dev/null +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart @@ -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(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(); + } + } + } + }, + ); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart similarity index 97% rename from app/lib/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart rename to app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart index 963fd278..604c64a2 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart @@ -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'; diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart new file mode 100644 index 00000000..dd40b483 --- /dev/null +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.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( + 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'), + ), + ], + ), + ], + ), + ); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart similarity index 97% rename from app/lib/features/geckoview/features/browser/presentation/widgets/browser_view.dart rename to app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index feae857e..91acc7c7 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -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 .fetch() .then((settings) { ref - .read(deleteBrowserDataServiceProvider.notifier) + .read(browserDataServiceProvider.notifier) .deleteData(settings.deleteBrowsingDataOnQuit); }); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart index 1da7bdec..4559f3d5 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart @@ -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; diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/landing/action.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/landing/action.dart deleted file mode 100644 index 240467f7..00000000 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/landing/action.dart +++ /dev/null @@ -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.", - ), - ), - ); - } -} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/landing/content.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/landing/content.dart deleted file mode 100644 index 03aa5b83..00000000 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/landing/content.dart +++ /dev/null @@ -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), - ], - ), - ); - }, - ); - } -} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart deleted file mode 100644 index c234b9a1..00000000 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart +++ /dev/null @@ -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, - // ), - ], - ), - ), - ], - ); - } -} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart index aaa743fe..5a9fb6b0 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart @@ -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); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart index a4896519..1d4a47b7 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart @@ -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); + }, + ); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart index 6ddb22a9..bab25545 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart @@ -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, diff --git a/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart b/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart index 7aa2349e..4bc5c17f 100644 --- a/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart +++ b/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart @@ -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'; diff --git a/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_button.dart b/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_button.dart index c61bdc59..b4c18bd0 100644 --- a/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_button.dart +++ b/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_button.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'; diff --git a/app/lib/features/geckoview/features/search/domain/providers/search_suggestions.dart b/app/lib/features/geckoview/features/search/domain/providers/search_suggestions.dart index 80f790b8..8db0f252 100644 --- a/app/lib/features/geckoview/features/search/domain/providers/search_suggestions.dart +++ b/app/lib/features/geckoview/features/search/domain/providers/search_suggestions.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'; diff --git a/app/lib/features/kagi/domain/repositories/search_suggestions.dart b/app/lib/features/geckoview/features/search/domain/repositories/search_suggestions.dart similarity index 100% rename from app/lib/features/kagi/domain/repositories/search_suggestions.dart rename to app/lib/features/geckoview/features/search/domain/repositories/search_suggestions.dart diff --git a/app/lib/features/kagi/domain/repositories/search_suggestions.g.dart b/app/lib/features/geckoview/features/search/domain/repositories/search_suggestions.g.dart similarity index 100% rename from app/lib/features/kagi/domain/repositories/search_suggestions.g.dart rename to app/lib/features/geckoview/features/search/domain/repositories/search_suggestions.g.dart diff --git a/app/lib/features/geckoview/features/search/presentation/screens/search.dart b/app/lib/features/geckoview/features/search/presentation/screens/search.dart index 8a36e7d0..1d57ae8c 100644 --- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart +++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart @@ -46,9 +46,9 @@ class SearchScreen extends HookConsumerWidget { Future 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); diff --git a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart index 4e4c95da..8b12751a 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart @@ -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 with _$TabDaoMixin { Selectable allTabData() => db.tab.select(); - SingleOrNullSelectable tabDataById(String id) => + SingleOrNullSelectable getTabDataById(String id) => db.tab.select()..where((t) => t.id.equals(id)); Selectable containerTabIds(String? containerId) { @@ -34,7 +34,7 @@ class TabDao extends DatabaseAccessor with _$TabDaoMixin { return query.map((row) => row.read(db.tab.id)!); } - Selectable allTabIds() { + Selectable getAllTabIds() { final query = selectOnly(db.tab) ..addColumns([db.tab.id]) @@ -43,7 +43,7 @@ class TabDao extends DatabaseAccessor with _$TabDaoMixin { return query.map((row) => row.read(db.tab.id)!); } - SingleOrNullSelectable tabContainerId(String tabId) { + SingleOrNullSelectable getTabContainerId(String tabId) { final query = selectOnly(db.tab) ..addColumns([db.tab.containerId]) diff --git a/app/lib/features/geckoview/features/tabs/data/database/database.dart b/app/lib/features/geckoview/features/tabs/data/database/database.dart index 0deffb4d..92fcafcc 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/database.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/database.dart @@ -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'; diff --git a/app/lib/features/geckoview/features/tabs/data/models/tab_query_result.dart b/app/lib/features/geckoview/features/tabs/data/models/tab_query_result.dart index f896597f..a30b89c1 100644 --- a/app/lib/features/geckoview/features/tabs/data/models/tab_query_result.dart +++ b/app/lib/features/geckoview/features/tabs/data/models/tab_query_result.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 get hashParameters => [ + id, + title, + cleanUrl, + url, + extractedContent, + fullContent, + weightedRank, + ]; } diff --git a/app/lib/features/geckoview/features/tabs/domain/providers.dart b/app/lib/features/geckoview/features/tabs/domain/providers.dart index bd4ff80e..396e070e 100644 --- a/app/lib/features/geckoview/features/tabs/domain/providers.dart +++ b/app/lib/features/geckoview/features/tabs/domain/providers.dart @@ -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 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> containersWithCount(Ref ref) { final db = ref.watch(tabDatabaseProvider); @@ -64,6 +44,6 @@ Stream> 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(); } } diff --git a/app/lib/features/geckoview/features/tabs/domain/providers.g.dart b/app/lib/features/geckoview/features/tabs/domain/providers.g.dart index faa169e9..b12a79c4 100644 --- a/app/lib/features/geckoview/features/tabs/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/providers.g.dart @@ -6,25 +6,6 @@ part of 'providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$unusedRandomContainerColorHash() => - r'913499b3de281ea0020db97ed67121652f53ec08'; - -/// See also [unusedRandomContainerColor]. -@ProviderFor(unusedRandomContainerColor) -final unusedRandomContainerColorProvider = - AutoDisposeFutureProvider.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; 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) diff --git a/app/lib/features/geckoview/features/tabs/domain/providers/selected_topic.g.dart b/app/lib/features/geckoview/features/tabs/domain/providers/selected_topic.g.dart deleted file mode 100644 index b9595490..00000000 --- a/app/lib/features/geckoview/features/tabs/domain/providers/selected_topic.g.dart +++ /dev/null @@ -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.internal( - selectedTopicData, - name: r'selectedTopicDataProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$selectedTopicDataHash, - dependencies: null, - allTransitiveDependencies: null, - ); - -typedef SelectedTopicDataRef = AutoDisposeStreamProviderRef; -String _$selectedTopicHash() => r'f4e1c0620971a0b7501ee9aff0d5b41da3130ba0'; - -/// See also [SelectedTopic]. -@ProviderFor(SelectedTopic) -final selectedTopicProvider = NotifierProvider.internal( - SelectedTopic.new, - name: r'selectedTopicProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$selectedTopicHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$SelectedTopic = Notifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart index bdf1921b..2b22ccea 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart @@ -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 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() {} } diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart index 54b02740..bc7c2266 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart @@ -7,7 +7,7 @@ part of 'container.dart'; // ************************************************************************** String _$containerRepositoryHash() => - r'b965883f2279b6f0148592bbac5ff40a60798f51'; + r'e91db35caae057e777fbe3b77439289844f52677'; /// See also [ContainerRepository]. @ProviderFor(ContainerRepository) diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart index 34f3f869..20bedacc 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart @@ -6,9 +6,6 @@ part 'tab.g.dart'; @Riverpod(keepAlive: true) class TabDataRepository extends _$TabDataRepository { - @override - void build() {} - Future 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() {} } diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart index 9d0e9d59..ced86aa1 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart @@ -6,7 +6,7 @@ part of 'tab.dart'; // RiverpodGenerator // ************************************************************************** -String _$tabDataRepositoryHash() => r'30ae87fbcb37facd565cb28de605814cd7db4ce8'; +String _$tabDataRepositoryHash() => r'6c6f4aa5eaf4033541700bf18a592555a4b946d3'; /// See also [TabDataRepository]. @ProviderFor(TabDataRepository) diff --git a/app/lib/features/geckoview/features/tabs/features/vector_store/data/database/database.dart b/app/lib/features/geckoview/features/tabs/features/vector_store/data/database/migrator.dart similarity index 100% rename from app/lib/features/geckoview/features/tabs/features/vector_store/data/database/database.dart rename to app/lib/features/geckoview/features/tabs/features/vector_store/data/database/migrator.dart diff --git a/app/lib/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart b/app/lib/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart index d079fcc6..5eb61de8 100644 --- a/app/lib/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart +++ b/app/lib/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart @@ -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 get hashParameters => [ + id, + mainDocumentId, + contextId, + content, + metadata, + distance, + ]; } diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart index 90028017..e94bf5cd 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart @@ -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( diff --git a/app/lib/features/kagi/data/entities/modes.dart b/app/lib/features/kagi/data/entities/modes.dart deleted file mode 100644 index 90772ccf..00000000 --- a/app/lib/features/kagi/data/entities/modes.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; - -enum KagiTool { - search(MdiIcons.searchWeb), - summarizer(MdiIcons.text), - //Sort early access features last - assistant(MdiIcons.brain); - - final IconData icon; - - const KagiTool(this.icon); -} - -enum AssistantMode { - research(7), - code(5), - chat(4), - custom(6); - - final int value; - - const AssistantMode(this.value); -} - -enum ResearchVariant { - fast(1), - expert(2); - - final int value; - - const ResearchVariant(this.value); -} - -enum ChatModel { - gpt35Turbo('GPT 3.5 Turbo', 1), - gpt4('GPT 4', 2), - gpt4Turbo('GPT 4 Turbo', 7), - gpt4o('GPT 4o', 15), - claude3Haiku('Claude 3 Haiku', 13), - claude3Sonnet('Claude 3 Sonnet', 14), - claude3Opus('Claude 3 Opus', 12), - gemini15Pro('Gemini 1.5 Pro', 8), - mistralSmall('Mistral Small', 10), - mistralLarge('Mistral Large', 11); - - final int value; - final String label; - - const ChatModel(this.label, this.value); -} - -enum SummarizerMode { - keyMoments('takeaway'), - summary('summary'); - - final String value; - - const SummarizerMode(this.value); -} diff --git a/app/lib/features/kagi/data/services/chat.dart b/app/lib/features/kagi/data/services/chat.dart deleted file mode 100644 index 59aec1df..00000000 --- a/app/lib/features/kagi/data/services/chat.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:exceptions/exceptions.dart'; -import 'package:http/http.dart' as http; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'chat.g.dart'; - -@Riverpod(keepAlive: true) -class KagiChatService extends _$KagiChatService { - late http.Client _client; - - @override - void build() { - _client = http.Client(); - } - - Future> downloadChat(Uri url) { - throw Exception('Not implemented !'); - - // final kagiSession = - // ref.read(generalSettingsRepositoryProvider).valueOrNull?.kagiSession; - - // return Result.fromAsync( - // () async { - // final response = await _client - // .get(url.replace(queryParameters: {'token': kagiSession})); - - // return utf8.decode(response.bodyBytes); - // }, - // exceptionHandler: handleHttpError, - // ); - } -} diff --git a/app/lib/features/kagi/data/services/session.dart b/app/lib/features/kagi/data/services/session.dart deleted file mode 100644 index bddba1d2..00000000 --- a/app/lib/features/kagi/data/services/session.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'session.g.dart'; - -@Riverpod() -class KagiSessionService extends _$KagiSessionService { - final _cookieManager = GeckoCookieService(); - - Future setKagiSession(String session) async { - await _cookieManager.setCookie( - url: Uri.https('kagi.com'), - name: 'kagi_session', - value: session, - domain: Uri.https('kagi.com'), - httpOnly: true, - secure: true, - sameSite: CookieSameSiteStatus.lax, - ); - } - - @override - void build() {} -} diff --git a/app/lib/features/kagi/presentation/tabs/assistant_tab.dart b/app/lib/features/kagi/presentation/tabs/assistant_tab.dart deleted file mode 100644 index d2a301fe..00000000 --- a/app/lib/features/kagi/presentation/tabs/assistant_tab.dart +++ /dev/null @@ -1,225 +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:flutter_material_design_icons/flutter_material_design_icons.dart'; -// import 'package:hooks_riverpod/hooks_riverpod.dart'; -// import 'package:lensai/features/geckoview/features/browser/domain/providers.dart'; -// import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart'; -// import 'package:lensai/presentation/widgets/speech_to_text_button.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/user/domain/repositories/settings.dart'; -// import 'package:lensai/presentation/hooks/sync_page_tab.dart'; - -// class AssistantTab extends HookConsumerWidget { -// final SharedContent? sharedContent; -// final OnSubmitUri onSubmit; - -// const AssistantTab({ -// required this.sharedContent, -// required this.onSubmit, -// super.key, -// }); - -// @override -// Widget build(BuildContext context, WidgetRef ref) { -// final incognitoEnabled = ref.watch( -// generalSettingsRepositoryProvider.select((value) => value.incognitoMode), -// ); - -// final researchVariant = ref.watch(activeResearchVariantProvider); -// final chatModel = ref.watch(activeChatModelProvider); - -// useAutomaticKeepAlive(); - -// final formKey = useMemoized(() => GlobalKey()); -// final textController = -// useTextEditingController(text: sharedContent?.toString()); - -// final tabController = useTabController( -// initialLength: AssistantMode.values.length, -// initialIndex: ref.read(lastUsedAssistantModeProvider).index, -// ); -// final pageController = usePageController(initialPage: tabController.index); - -// useSyncPageWithTab( -// tabController, -// pageController, -// onIndexChanged: (index) { -// ref -// .read(lastUsedAssistantModeProvider.notifier) -// .update(AssistantMode.values[index]); -// }, -// ); - -// return Form( -// key: formKey, -// child: Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// TabBar.secondary( -// controller: tabController, -// tabs: const [ -// Tab( -// text: 'Research', -// icon: Icon(MdiIcons.layersSearch), -// ), -// Tab( -// text: 'Code', -// icon: Icon(MdiIcons.codeJson), -// ), -// Tab( -// text: 'Chat', -// icon: Icon(MdiIcons.commentTextMultiple), -// ), -// Tab( -// text: 'Custom', -// icon: Icon(MdiIcons.creation), -// ), -// ], -// ), -// ExpandablePageView( -// controller: pageController, -// children: [ -// Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// const SizedBox( -// height: 12, -// ), -// SegmentedButton( -// segments: const [ -// ButtonSegment( -// value: ResearchVariant.expert, -// icon: Icon(MdiIcons.textSearch), -// label: Text('Research'), -// ), -// ButtonSegment( -// value: ResearchVariant.fast, -// icon: Icon(MdiIcons.invoiceTextFast), -// label: Text('Fast'), -// ), -// ], -// selected: {researchVariant}, -// onSelectionChanged: (value) { -// ref -// .read(activeResearchVariantProvider.notifier) -// .update(value.first); -// }, -// ), -// const SizedBox( -// height: 12, -// ), -// _PromptField(textController, incognitoEnabled), -// ], -// ), -// Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// const SizedBox( -// height: 12, -// ), -// _PromptField(textController, incognitoEnabled), -// ], -// ), -// Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// const SizedBox( -// height: 12, -// ), -// DropdownMenu( -// initialSelection: chatModel, -// expandedInsets: EdgeInsets.zero, -// label: const Text('Model'), -// inputDecorationTheme: const InputDecorationTheme(), -// dropdownMenuEntries: ChatModel.values -// .map( -// (model) => DropdownMenuEntry( -// value: model, -// label: model.label, -// ), -// ) -// .toList(), -// onSelected: (value) { -// ref.read(activeChatModelProvider.notifier).update(value!); -// }, -// ), -// const SizedBox( -// height: 12, -// ), -// _PromptField(textController, incognitoEnabled), -// ], -// ), -// Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// const SizedBox( -// height: 12, -// ), -// _PromptField(textController, incognitoEnabled), -// ], -// ), -// ], -// ), -// const SizedBox( -// height: 12, -// ), -// SizedBox( -// width: double.infinity, -// child: FilledButton.icon( -// onPressed: () { -// if (formKey.currentState?.validate() ?? false) { -// onSubmit( -// uri_builder.assistantUri( -// prompt: textController.text, -// assistantMode: AssistantMode.values[tabController.index], -// researchVariant: researchVariant, -// chatModel: chatModel, -// ), -// ); -// } -// }, -// label: const Text('Submit'), -// icon: const Icon(MdiIcons.invoiceTextSend), -// ), -// ), -// ], -// ), -// ); -// } -// } - -// class _PromptField extends StatelessWidget { -// final TextEditingController textController; -// final bool incognitoEnabled; - -// const _PromptField(this.textController, this.incognitoEnabled); - -// @override -// Widget build(BuildContext context) { -// return TextFormField( -// controller: textController, -// enableIMEPersonalizedLearning: !incognitoEnabled, -// decoration: InputDecoration( -// label: const Text('Prompt'), -// hintText: 'Enter your prompt...', -// floatingLabelBehavior: FloatingLabelBehavior.always, -// suffixIcon: SpeechToTextButton( -// onTextReceived: (data) { -// textController.text = data.toString(); -// }, -// ), -// ), -// maxLines: null, -// validator: (value) { -// if (value?.isEmpty ?? true) { -// return ''; -// } - -// return null; -// }, -// ); -// } -// } diff --git a/app/lib/features/kagi/presentation/tabs/search_tab.dart b/app/lib/features/kagi/presentation/tabs/search_tab.dart deleted file mode 100644 index 36f1cdd3..00000000 --- a/app/lib/features/kagi/presentation/tabs/search_tab.dart +++ /dev/null @@ -1,195 +0,0 @@ -// 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:go_router/go_router.dart'; -// import 'package:hooks_riverpod/hooks_riverpod.dart'; -// import 'package:lensai/core/routing/routes.dart'; -// import 'package:lensai/features/bangs/domain/providers/bangs.dart'; -// import 'package:lensai/features/bangs/domain/providers/search.dart'; -// import 'package:lensai/features/bangs/domain/repositories/data.dart'; -// import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart'; -// import 'package:lensai/features/bangs/presentation/widgets/search_field.dart'; -// import 'package:lensai/features/geckoview/features/browser/domain/providers.dart'; -// import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart'; -// import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart'; -// import 'package:lensai/features/share_intent/domain/entities/shared_content.dart'; -// import 'package:lensai/presentation/widgets/selectable_chips.dart'; - -// class SearchTab extends HookConsumerWidget { -// final SharedContent? sharedContent; -// final OnSubmitUri onSubmit; - -// const SearchTab({ -// required this.sharedContent, -// required this.onSubmit, -// super.key, -// }); - -// @override -// Widget build(BuildContext context, WidgetRef ref) { -// useAutomaticKeepAlive(); - -// final formKey = useMemoized(() => GlobalKey()); - -// final selectedBang = ref -// .watch(selectedBangDataProvider().select((value) => value.valueOrNull)); -// final defaultSearchBang = ref.watch( -// defaultSearchBangDataProvider.select((value) => value.valueOrNull), -// ); - -// final activeBang = selectedBang ?? defaultSearchBang; - -// final textController = -// useTextEditingController(text: sharedContent?.toString()); - -// Future submitSearch() async { -// if (activeBang != null && (formKey.currentState?.validate() == true)) { -// final searchUri = await ref.read( -// triggerBangSearchProvider(activeBang, textController.text).future, -// ); - -// onSubmit(searchUri); -// } -// } - -// return Form( -// key: formKey, -// child: Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// const SizedBox(height: 4), -// Consumer( -// builder: (context, ref, child) { -// final frequentBangsAsync = -// ref.watch(frequentBangDataListProvider); - -// return frequentBangsAsync.when( -// data: (availableBangs) => SizedBox( -// height: 48, -// child: Row( -// children: [ -// if (selectedBang != null || availableBangs.isNotEmpty) -// Expanded( -// child: SelectableChips( -// itemId: (bang) => bang.trigger, -// itemAvatar: (bang) => BangIcon(bang, iconSize: 20), -// itemLabel: (bang) => Text(bang.websiteName), -// availableItems: availableBangs, -// selectedItem: selectedBang, -// onSelected: (bang) { -// ref -// .read(selectedBangTriggerProvider().notifier) -// .setTrigger(bang.trigger); -// }, -// onDeleted: (bang) async { -// if (ref.read(selectedBangTriggerProvider()) == -// bang.trigger) { -// ref -// .read( -// selectedBangTriggerProvider().notifier, -// ) -// .clearTrigger(); -// } else { -// final dialogResult = await showDialog( -// context: context, -// builder: (context) => AlertDialog( -// title: Text( -// 'Reset usage frequency of !${bang.trigger}?', -// ), -// content: const Text( -// 'This will remove the Bang from quick select.', -// ), -// actions: [ -// TextButton( -// onPressed: () => -// Navigator.pop(context, false), -// child: const Text('Cancel'), -// ), -// TextButton( -// onPressed: () => -// Navigator.pop(context, true), -// child: const Text('Reset'), -// ), -// ], -// ), -// ); - -// if (dialogResult == true) { -// await ref -// .read(bangDataRepositoryProvider.notifier) -// .resetFrequency(bang.trigger); -// } -// } -// }, -// ), -// ) -// else -// Expanded( -// child: Text( -// "Press '>' to search Bangs.", -// style: TextStyle( -// color: Theme.of(context).hintColor, -// fontStyle: FontStyle.italic, -// ), -// ), -// ), -// IconButton( -// onPressed: () async { -// await context.push(BangSearchRoute().location); -// }, -// icon: const Icon(Icons.chevron_right), -// ), -// ], -// ), -// ), -// error: (error, stackTrace) => const SizedBox.shrink(), -// loading: () => const SizedBox( -// height: 48, -// width: double.infinity, -// ), -// ); -// }, -// ), -// Consumer( -// builder: (context, ref, child) { -// const maxOptionsHeight = SearchField.defaultMaxOptionsHeight; - -// final openDirection = ref.watch( -// bottomSheetExtendProvider.select((value) { -// final extend = value.valueOrNull; -// if (extend == null || -// (MediaQuery.of(context).size.height * (1 - extend)) > -// maxOptionsHeight) { -// return OptionsViewOpenDirection.up; -// } else { -// return OptionsViewOpenDirection.down; -// } -// }), -// ); - -// return SearchField( -// textController: textController, -// activeBang: activeBang, -// openDirection: openDirection, -// onFieldSubmitted: (_) async { -// await submitSearch(); -// }, -// ); -// }, -// ), -// const SizedBox( -// height: 12, -// ), -// SizedBox( -// width: double.infinity, -// child: FilledButton.icon( -// onPressed: submitSearch, -// label: const Text('Search'), -// icon: const Icon(MdiIcons.cloudSearch), -// ), -// ), -// ], -// ), -// ); -// } -// } diff --git a/app/lib/features/kagi/presentation/tabs/summarize_tab.dart b/app/lib/features/kagi/presentation/tabs/summarize_tab.dart deleted file mode 100644 index baff8137..00000000 --- a/app/lib/features/kagi/presentation/tabs/summarize_tab.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'dart:async'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; -import 'package:flutter_pdf_text/flutter_pdf_text.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.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/user/domain/providers.dart'; -import 'package:lensai/presentation/widgets/website_title_tile.dart'; -import 'package:lensai/utils/uri_parser.dart' as uri_parser; - -class _InputField extends ConsumerWidget { - final TextEditingController? controller; - - const _InputField({required this.controller}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final incognitoEnabled = ref.watch(incognitoModeEnabledProvider); - - return TextFormField( - controller: controller, - enableIMEPersonalizedLearning: !incognitoEnabled, - decoration: const InputDecoration( - label: Text('Document'), - hintText: 'Enter URL or text to summarize', - floatingLabelBehavior: FloatingLabelBehavior.always, - ), - maxLines: null, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if (value?.isEmpty ?? true) { - return ''; - } - - return null; - }, - ); - } -} - -class SummarizeTab extends HookConsumerWidget { - final SharedContent? sharedContent; - final OnSubmitUri onSubmit; - - const SummarizeTab({ - required this.sharedContent, - required this.onSubmit, - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - useAutomaticKeepAlive(); - - final formKey = useMemoized(() => GlobalKey()); - final selectedMode = useState(SummarizerMode.keyMoments); - final textController = useTextEditingController( - text: sharedContent?.toString(), - ); - - return Form( - key: formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SegmentedButton( - segments: const [ - ButtonSegment( - value: SummarizerMode.keyMoments, - icon: Icon(MdiIcons.scriptTextKey), - label: Text('Key Moments'), - ), - ButtonSegment( - value: SummarizerMode.summary, - icon: Icon(MdiIcons.invoiceTextMinus), - label: Text('Summary'), - ), - ], - selected: {selectedMode.value}, - onSelectionChanged: (value) { - selectedMode.value = value.first; - }, - ), - const SizedBox(height: 12), - ...switch (sharedContent) { - SharedUrl() => [ - _InputField(controller: textController), - HookBuilder( - builder: (context) { - final url = useState( - uri_parser.tryParseUrl(textController.text), - ); - useEffect(() { - Timer? timer; - void debounce() { - timer?.cancel(); - timer = Timer(const Duration(milliseconds: 250), () { - url.value = uri_parser.tryParseUrl(textController.text); - }); - } - - textController.addListener(debounce); - return () => textController.removeListener(debounce); - }, [textController]); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (url.value != null) WebsiteTitleTile(url.value!), - ], - ); - }, - ), - const SizedBox(height: 12), - ], - SharedText() || null => [ - SizedBox( - width: double.infinity, - child: OutlinedButton.icon( - onPressed: () async { - final pickResult = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['pdf'], - ); - - if (pickResult?.files.first.path - case final String filePath) { - final content = await PDFDoc.fromPath( - filePath, - ).then((doc) => doc.text); - textController.text = content; - } - }, - icon: const Icon(MdiIcons.fileUpload), - label: const Text('Read PDF document'), - ), - ), - _InputField(controller: textController), - const SizedBox(height: 12), - ], - }, - SizedBox( - width: double.infinity, - child: FilledButton.icon( - onPressed: () { - if (formKey.currentState?.validate() ?? false) { - onSubmit( - uri_builder.summarizerUri( - document: SharedContent.parse(textController.text), - mode: selectedMode.value, - ), - ); - } - }, - label: const Text('Summarize'), - icon: const Icon(MdiIcons.invoiceTextSend), - ), - ), - ], - ), - ); - } -} diff --git a/app/lib/features/kagi/utils/url_builder.dart b/app/lib/features/kagi/utils/url_builder.dart deleted file mode 100644 index 8baa5c0f..00000000 --- a/app/lib/features/kagi/utils/url_builder.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:lensai/features/kagi/data/entities/modes.dart'; -import 'package:lensai/features/share_intent/domain/entities/shared_content.dart'; - -final _baseUrl = Uri.https('kagi.com'); - -Uri assistantUri({ - required String prompt, - required AssistantMode assistantMode, - ResearchVariant? researchVariant, - ChatModel? chatModel, -}) => _baseUrl.replace( - pathSegments: ['assistant'], - queryParameters: { - 'mode': assistantMode.value.toString(), - 'q': prompt, - ...switch (assistantMode) { - AssistantMode.research => {'sub_mode': researchVariant!.value.toString()}, - AssistantMode.code => {}, - AssistantMode.chat => {'sub_mode': chatModel!.value.toString()}, - AssistantMode.custom => {}, - }, - }, -); - -Uri summarizerUri({ - required SharedContent document, - required SummarizerMode mode, -}) => _baseUrl.replace( - pathSegments: [ - 'summarizer', - //TODO: check if really necessary - 'index.html', - ], - queryParameters: { - 'summary': mode.value, - if (document case SharedUrl(url: final url)) 'url': url.toString(), - }, - fragment: switch (document) { - SharedText(text: final text) => text, - _ => null, - }, -); diff --git a/app/lib/features/kagi/data/services/autosuggest.dart b/app/lib/features/search/domain/autosuggest/kagi.dart similarity index 97% rename from app/lib/features/kagi/data/services/autosuggest.dart rename to app/lib/features/search/domain/autosuggest/kagi.dart index 566532b3..5bbb997d 100644 --- a/app/lib/features/kagi/data/services/autosuggest.dart +++ b/app/lib/features/search/domain/autosuggest/kagi.dart @@ -6,7 +6,7 @@ import 'package:lensai/core/http_error_handler.dart'; import 'package:lensai/features/search/domain/entities/abstract/i_search_suggestion_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'autosuggest.g.dart'; +part 'kagi.g.dart'; @Riverpod(keepAlive: true) class KagiAutosuggestService extends _$KagiAutosuggestService diff --git a/app/lib/features/kagi/data/services/autosuggest.g.dart b/app/lib/features/search/domain/autosuggest/kagi.g.dart similarity index 97% rename from app/lib/features/kagi/data/services/autosuggest.g.dart rename to app/lib/features/search/domain/autosuggest/kagi.g.dart index 09b3fc93..968d2850 100644 --- a/app/lib/features/kagi/data/services/autosuggest.g.dart +++ b/app/lib/features/search/domain/autosuggest/kagi.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'autosuggest.dart'; +part of 'kagi.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/app/lib/features/settings/presentation/screens/bang_settings.dart b/app/lib/features/settings/presentation/screens/bang_settings.dart index 0ffa6292..f3177102 100644 --- a/app/lib/features/settings/presentation/screens/bang_settings.dart +++ b/app/lib/features/settings/presentation/screens/bang_settings.dart @@ -1,7 +1,7 @@ import 'package:fading_scroll/fading_scroll.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/features/bangs/data/models/bang.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/domain/repositories/data.dart'; import 'package:lensai/features/settings/presentation/widgets/bang_group_list_tile.dart'; import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart'; diff --git a/app/lib/features/settings/presentation/screens/web_engine_hardening.dart b/app/lib/features/settings/presentation/screens/web_engine_hardening.dart index 924639a1..7439a838 100644 --- a/app/lib/features/settings/presentation/screens/web_engine_hardening.dart +++ b/app/lib/features/settings/presentation/screens/web_engine_hardening.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.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/data/models/equatable_iterable.dart'; +import 'package:lensai/domain/entities/equatable_iterable.dart'; import 'package:lensai/extensions/nullable.dart'; import 'package:lensai/features/geckoview/features/preferences/data/repositories/preference_settings.dart'; import 'package:lensai/features/geckoview/features/tabs/utils/setting_groups_serializer.dart'; diff --git a/app/lib/features/settings/presentation/widgets/bang_group_list_tile.dart b/app/lib/features/settings/presentation/widgets/bang_group_list_tile.dart index 8d8d0db3..216a1a4b 100644 --- a/app/lib/features/settings/presentation/widgets/bang_group_list_tile.dart +++ b/app/lib/features/settings/presentation/widgets/bang_group_list_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/features/bangs/data/models/bang.dart'; +import 'package:lensai/features/bangs/data/models/bang_group.dart'; import 'package:lensai/features/bangs/domain/providers/bangs.dart'; import 'package:lensai/features/bangs/domain/repositories/sync.dart'; import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart'; diff --git a/app/lib/features/share_intent/domain/services/sharing_intent.dart b/app/lib/features/share_intent/domain/services/sharing_intent.dart index 36fabb34..bc0d7c7f 100644 --- a/app/lib/features/share_intent/domain/services/sharing_intent.dart +++ b/app/lib/features/share_intent/domain/services/sharing_intent.dart @@ -1,12 +1,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:flutter_pdf_text/flutter_pdf_text.dart'; import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:lensai/core/logger.dart'; -import 'package:lensai/data/models/received_parameter.dart'; -import 'package:lensai/features/kagi/data/entities/modes.dart'; +import 'package:lensai/data/models/received_intent_parameter.dart'; import 'package:mime/mime.dart' as mime; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -16,7 +14,7 @@ import 'package:uri_to_file/uri_to_file.dart' as uri_to_file; part 'sharing_intent.g.dart'; final _sharingIntentTransformer = - StreamTransformer, ReceivedParameter>.fromHandlers( + StreamTransformer, ReceivedIntentParameter>.fromHandlers( handleData: (files, sink) async { //For now only one file is supported to share with the app final data = files.firstOrNull; @@ -30,17 +28,12 @@ final _sharingIntentTransformer = final mimeType = mime.lookupMimeType(file.path); switch (mimeType) { case 'application/pdf': - final content = await PDFDoc.fromFile( - file, - ).then((doc) => doc.text); - sink.add( - ReceivedParameter(content, KagiTool.summarizer.name), - ); + sink.add(ReceivedIntentParameter(data.value, null)); default: logger.w('Unhandled mime type: $mimeType'); } } else { - sink.add(ReceivedParameter(data.value, null)); + sink.add(ReceivedIntentParameter(data.value, null)); } default: logger.w('Unhandled media type: $data'); @@ -50,7 +43,7 @@ final _sharingIntentTransformer = ); @Riverpod() -Raw> sharingIntentStream(Ref ref) { +Raw> sharingIntentStream(Ref ref) { final initialStream = FlutterSharingIntent.instance // ignore: discarded_futures is used as stream diff --git a/app/lib/features/share_intent/domain/services/sharing_intent.g.dart b/app/lib/features/share_intent/domain/services/sharing_intent.g.dart index eddee993..71c8f07e 100644 --- a/app/lib/features/share_intent/domain/services/sharing_intent.g.dart +++ b/app/lib/features/share_intent/domain/services/sharing_intent.g.dart @@ -7,12 +7,12 @@ part of 'sharing_intent.dart'; // ************************************************************************** String _$sharingIntentStreamHash() => - r'd0634c3cbff4760eb637663716970b6ad35ccf36'; + r'1559b1e55708bfe340f6663a9d6eb3bbd65d4784'; /// See also [sharingIntentStream]. @ProviderFor(sharingIntentStream) final sharingIntentStreamProvider = - AutoDisposeProvider>>.internal( + AutoDisposeProvider>>.internal( sharingIntentStream, name: r'sharingIntentStreamProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -25,6 +25,6 @@ final sharingIntentStreamProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef SharingIntentStreamRef - = AutoDisposeProviderRef>>; + = AutoDisposeProviderRef>>; // 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 diff --git a/app/lib/features/tor/presentation/controllers/start_tor_proxy.dart b/app/lib/features/tor/presentation/controllers/start_tor_proxy.dart index 24d6c8f8..9eae7ad8 100644 --- a/app/lib/features/tor/presentation/controllers/start_tor_proxy.dart +++ b/app/lib/features/tor/presentation/controllers/start_tor_proxy.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart'; +import 'package:lensai/features/geckoview/domain/controllers/overlay.dart'; import 'package:lensai/features/tor/domain/services/tor_proxy.dart'; import 'package:lensai/features/tor/presentation/widgets/tor_dialog.dart'; import 'package:lensai/features/tor/presentation/widgets/tor_notification.dart'; @@ -26,7 +26,7 @@ class StartProxyController extends _$StartProxyController { final connection = ref.read(torProxyServiceProvider.notifier).connect(); ref - .read(overlayDialogControllerProvider.notifier) + .read(overlayControllerProvider.notifier) .show(Positioned(top: 0, left: 0, child: TorNotification())); await connection; diff --git a/app/lib/features/tor/presentation/controllers/start_tor_proxy.g.dart b/app/lib/features/tor/presentation/controllers/start_tor_proxy.g.dart index 8d7c6942..1cae7c4b 100644 --- a/app/lib/features/tor/presentation/controllers/start_tor_proxy.g.dart +++ b/app/lib/features/tor/presentation/controllers/start_tor_proxy.g.dart @@ -7,7 +7,7 @@ part of 'start_tor_proxy.dart'; // ************************************************************************** String _$startProxyControllerHash() => - r'c49d4f75bb499b3e695f1c4d08dcc095cfee7143'; + r'7ba8d6eded4664a8aa3eb1360926c56e9185cac9'; /// See also [StartProxyController]. @ProviderFor(StartProxyController) diff --git a/app/lib/features/tor/presentation/widgets/tor_notification.dart b/app/lib/features/tor/presentation/widgets/tor_notification.dart index 4c33e3a1..a073bc76 100644 --- a/app/lib/features/tor/presentation/widgets/tor_notification.dart +++ b/app/lib/features/tor/presentation/widgets/tor_notification.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart'; +import 'package:lensai/features/geckoview/domain/controllers/overlay.dart'; import 'package:lensai/features/tor/domain/services/tor_proxy.dart'; import 'package:lensai/presentation/icons/tor_icons.dart'; import 'package:lensai/presentation/widgets/animate_gradient_shader.dart'; @@ -10,7 +10,7 @@ class TorNotification extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.listen(torProxyServiceProvider, (previous, next) { if (!next.isLoading) { - ref.read(overlayDialogControllerProvider.notifier).dismiss(); + ref.read(overlayControllerProvider.notifier).dismiss(); } }); @@ -45,9 +45,7 @@ class TorNotification extends HookConsumerWidget { ), IconButton( onPressed: () { - ref - .read(overlayDialogControllerProvider.notifier) - .dismiss(); + ref.read(overlayControllerProvider.notifier).dismiss(); }, icon: const Icon(Icons.close, color: Colors.white), ), diff --git a/app/lib/features/user/data/database/daos/cache.dart b/app/lib/features/user/data/database/daos/cache.dart index e214355c..3ef3d751 100644 --- a/app/lib/features/user/data/database/daos/cache.dart +++ b/app/lib/features/user/data/database/daos/cache.dart @@ -1,5 +1,5 @@ import 'package:drift/drift.dart'; -import 'package:lensai/extensions/database_table_size.dart'; +import 'package:lensai/data/database/extensions/database_table_size.dart'; import 'package:lensai/features/user/data/database/database.dart'; part 'cache.g.dart'; @@ -8,7 +8,7 @@ part 'cache.g.dart'; class CacheDao extends DatabaseAccessor with _$CacheDaoMixin { CacheDao(super.attachedDatabase); - SingleSelectable iconCacheSize() { + SingleSelectable getIconCacheSize() { return db.tableSize(db.iconCache); } diff --git a/app/lib/features/user/data/database/daos/setting.dart b/app/lib/features/user/data/database/daos/setting.dart index 3f5afd46..95b70d80 100644 --- a/app/lib/features/user/data/database/daos/setting.dart +++ b/app/lib/features/user/data/database/daos/setting.dart @@ -29,7 +29,7 @@ class SettingDao extends DatabaseAccessor with _$SettingDaoMixin { ); } - Selectable> allSettingsOfPartitionKey( + Selectable> getAllSettingsOfPartitionKey( String? partitionKey, ) { final query = diff --git a/app/lib/features/user/domain/entities/auth_exception.dart b/app/lib/features/user/domain/entities/auth_exception.dart new file mode 100644 index 00000000..5e44fcf8 --- /dev/null +++ b/app/lib/features/user/domain/entities/auth_exception.dart @@ -0,0 +1,10 @@ +class AuthException implements Exception { + final String message; + + AuthException(this.message); + + @override + String toString() { + return message; + } +} diff --git a/app/lib/features/user/domain/providers.dart b/app/lib/features/user/domain/providers.dart index f7beb3dd..8af60250 100644 --- a/app/lib/features/user/domain/providers.dart +++ b/app/lib/features/user/domain/providers.dart @@ -9,18 +9,18 @@ part 'providers.g.dart'; const _authKey = 'pb_auth'; -@Riverpod() -Stream iconCacheSizeMegabytes(Ref ref) { - final repository = ref.watch(userDatabaseProvider); - return repository.cacheDao.iconCacheSize().watchSingle(); -} - @Riverpod() Future _storedAuthData(Ref ref) { const secureStorage = FlutterSecureStorage(); return secureStorage.read(key: _authKey); } +@Riverpod() +Stream iconCacheSizeMegabytes(Ref ref) { + final repository = ref.watch(userDatabaseProvider); + return repository.cacheDao.getIconCacheSize().watchSingle(); +} + @Riverpod(keepAlive: true) AsyncAuthStore authStore(Ref ref) { const secureStorage = FlutterSecureStorage(); diff --git a/app/lib/features/user/domain/providers.g.dart b/app/lib/features/user/domain/providers.g.dart index 60f3514e..91bfbbc5 100644 --- a/app/lib/features/user/domain/providers.g.dart +++ b/app/lib/features/user/domain/providers.g.dart @@ -6,25 +6,6 @@ part of 'providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$iconCacheSizeMegabytesHash() => - r'43162fce3ce8b04a5a55ed52021b274afbde2861'; - -/// See also [iconCacheSizeMegabytes]. -@ProviderFor(iconCacheSizeMegabytes) -final iconCacheSizeMegabytesProvider = - AutoDisposeStreamProvider.internal( - iconCacheSizeMegabytes, - name: r'iconCacheSizeMegabytesProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$iconCacheSizeMegabytesHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef IconCacheSizeMegabytesRef = AutoDisposeStreamProviderRef; String _$storedAuthDataHash() => r'5f7e3ef6233a2036f7ce3728131901a46b1e548e'; /// See also [_storedAuthData]. @@ -42,6 +23,25 @@ final _storedAuthDataProvider = AutoDisposeFutureProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef _StoredAuthDataRef = AutoDisposeFutureProviderRef; +String _$iconCacheSizeMegabytesHash() => + r'5d7f5f6485060b08ce4fd8fa634f07bf8bfdbd2d'; + +/// See also [iconCacheSizeMegabytes]. +@ProviderFor(iconCacheSizeMegabytes) +final iconCacheSizeMegabytesProvider = + AutoDisposeStreamProvider.internal( + iconCacheSizeMegabytes, + name: r'iconCacheSizeMegabytesProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$iconCacheSizeMegabytesHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef IconCacheSizeMegabytesRef = AutoDisposeStreamProviderRef; String _$authStoreHash() => r'766c001bbb8d7dc4e148eff3816c414d78e77f26'; /// See also [authStore]. diff --git a/app/lib/features/user/domain/repositories/auth.dart b/app/lib/features/user/domain/repositories/auth.dart index 42420831..f3a416af 100644 --- a/app/lib/features/user/domain/repositories/auth.dart +++ b/app/lib/features/user/domain/repositories/auth.dart @@ -1,3 +1,4 @@ +import 'package:lensai/features/user/domain/entities/auth_exception.dart'; import 'package:lensai/features/user/domain/providers.dart'; import 'package:lensai/features/user/extensions/client_exception.dart'; import 'package:pocketbase/pocketbase.dart'; @@ -5,17 +6,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'auth.g.dart'; -class AuthException implements Exception { - final String message; - - AuthException(this.message); - - @override - String toString() { - return message; - } -} - @Riverpod() class AuthRepository extends _$AuthRepository { late PocketBase _pb; diff --git a/app/lib/features/user/domain/repositories/engine_settings.dart b/app/lib/features/user/domain/repositories/engine_settings.dart index a4e32c74..fbcee2ee 100644 --- a/app/lib/features/user/domain/repositories/engine_settings.dart +++ b/app/lib/features/user/domain/repositories/engine_settings.dart @@ -35,7 +35,7 @@ class EngineSettingsRepository extends _$EngineSettingsRepository { final db = ref.watch(userDatabaseProvider); final watchSub = db.settingDao - .allSettingsOfPartitionKey(_partitionKey) + .getAllSettingsOfPartitionKey(_partitionKey) .watch() .listen((entries) { final settings = Map.fromEntries(entries); diff --git a/app/lib/features/user/domain/repositories/engine_settings.g.dart b/app/lib/features/user/domain/repositories/engine_settings.g.dart index 2c1a1720..6fe2ac4c 100644 --- a/app/lib/features/user/domain/repositories/engine_settings.g.dart +++ b/app/lib/features/user/domain/repositories/engine_settings.g.dart @@ -7,7 +7,7 @@ part of 'engine_settings.dart'; // ************************************************************************** String _$engineSettingsRepositoryHash() => - r'113617b3a99377bbb83347cfcb6d432abaae6f71'; + r'940680d6d59a4a4a496589ac02eeab5a22779e5e'; /// See also [EngineSettingsRepository]. @ProviderFor(EngineSettingsRepository) diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index 9c60109e..7f1be834 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -43,7 +43,7 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { return ref .read(userDatabaseProvider) .settingDao - .allSettingsOfPartitionKey(_partitionKey) + .getAllSettingsOfPartitionKey(_partitionKey) .get() .then(_deserializeSettings); } @@ -68,7 +68,7 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { final db = ref.watch(userDatabaseProvider); final watchSub = db.settingDao - .allSettingsOfPartitionKey(_partitionKey) + .getAllSettingsOfPartitionKey(_partitionKey) .watch() .listen((event) { state = _deserializeSettings(event); diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index aa62324f..613b118e 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -7,7 +7,7 @@ part of 'general_settings.dart'; // ************************************************************************** String _$generalSettingsRepositoryHash() => - r'd21cee982ed471dff5ba11415e5557073bd2fca3'; + r'9753ffa757a34e475fe816ef990ca387ca169ef4'; /// See also [GeneralSettingsRepository]. @ProviderFor(GeneralSettingsRepository) diff --git a/app/lib/main.dart b/app/lib/main.dart index ba10503c..241492da 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -6,7 +6,7 @@ import 'package:lensai/core/error_observer.dart'; import 'package:lensai/domain/services/app_initialization.dart'; import 'package:lensai/features/user/domain/repositories/general_settings.dart'; import 'package:lensai/presentation/hooks/on_initialization.dart'; -import 'package:lensai/presentation/widgets/main_app.dart'; +import 'package:lensai/presentation/main_app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/app/lib/presentation/widgets/main_app.dart b/app/lib/presentation/main_app.dart similarity index 100% rename from app/lib/presentation/widgets/main_app.dart rename to app/lib/presentation/main_app.dart diff --git a/app/lib/utils/ui_helper.dart b/app/lib/utils/ui_helper.dart index ae39402b..35c6d83f 100644 --- a/app/lib/utils/ui_helper.dart +++ b/app/lib/utils/ui_helper.dart @@ -23,3 +23,16 @@ Future launchUrlFeedback( } } } + +void showTabBackButtonMessage(BuildContext context, int tabCount) { + ScaffoldMessenger.of(context) + ..clearSnackBars() + ..showSnackBar( + SnackBar( + content: + (tabCount > 1) + ? const Text('Please click BACK again to close current tab') + : const Text('Please click BACK again to exit app'), + ), + ); +}