From c4d93e8fe7041a944595b7038d1b32ae6de0797b Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Tue, 15 Oct 2024 11:13:32 +0200 Subject: [PATCH] getting there --- .vscode/launch.json | 23 + app/android/app/src/main/AndroidManifest.xml | 49 ++ .../movenext/bang_navigator/MainActivity.kt | 4 +- app/lib/data/models/equatable_iterable.dart | 21 + .../domain/services/app_initialization.dart | 24 - .../domain/services/app_initialization.g.dart | 2 +- .../bangs/data/database/database.g.dart | 650 ++++++++++++------ .../data/database/database.g.dart | 157 +++-- .../features/geckoview/domain/providers.dart | 4 + .../geckoview/domain/providers.g.dart | 2 +- .../domain/providers/selected_tab.dart | 12 + .../domain/providers/selected_tab.g.dart | 2 +- .../domain/providers/tab_session.dart | 6 + .../domain/providers/tab_session.g.dart | 2 +- .../geckoview/domain/providers/tab_state.dart | 18 + .../domain/providers/tab_state.g.dart | 2 +- .../features/browser/domain/providers.dart | 29 + .../features/browser/domain/providers.g.dart | 129 ++++ .../presentation/dialogs/tab_action.dart | 11 +- .../presentation/dialogs/web_page_dialog.dart | 48 ++ .../presentation/widgets/landing/content.dart | 15 +- .../widgets/sheets/view_tabs.dart | 244 ++++--- .../features/browser/screens/browser.dart | 22 +- .../domain/providers/readerable.dart | 6 - .../domain/providers/readerable.g.dart | 16 - .../widgets/reader_appearance_button.dart | 23 +- .../presentation/widgets/reader_button.dart | 10 +- .../topics/data/database/daos/tab.dart | 20 +- .../topics/data/database/database.dart | 2 + .../topics/data/database/database.drift | 2 +- .../topics/data/database/database.g.dart | 299 +++++--- .../features/topics/domain/providers.dart | 2 +- .../features/topics/domain/providers.g.dart | 12 +- .../topics/domain/repositories/tab_link.dart | 38 +- .../domain/repositories/tab_link.g.dart | 2 +- .../settings/data/models/settings.g.dart | 27 - .../repositories/settings_repository.g.dart | 2 +- app/pubspec.lock | 49 +- app/pubspec.yaml | 15 +- melos.yaml | 5 + .../BaseBrowserFragment.kt | 1 + .../DefaultComponents.kt | 31 +- .../FlutterMozillaComponentsPlugin.kt | 14 +- .../GeckoPlatformView.kt | 19 +- .../api/GeckoSessionApiImpl.kt | 35 + .../api/GeckoTabsApiImpl.kt | 145 ++++ .../middleware/FlutterEventMiddleware.kt | 5 +- .../pigeons/Gecko.g.kt | 82 ++- .../lib/src/domain/services/gecko_event.dart | 82 ++- .../src/domain/services/gecko_readerable.dart | 11 +- .../src/domain/services/gecko_session.dart | 10 + .../lib/src/domain/services/gecko_tab.dart | 23 + .../lib/src/geckoview_widget.dart | 7 +- .../lib/src/pigeons/gecko.g.dart | 100 ++- .../pigeons/gecko.dart | 18 +- .../flutter_mozilla_components/pubspec.yaml | 3 +- 56 files changed, 1830 insertions(+), 762 deletions(-) create mode 100644 app/lib/data/models/equatable_iterable.dart diff --git a/.vscode/launch.json b/.vscode/launch.json index 4a2cd249..446c308d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -26,6 +26,29 @@ "type": "dart", "flutterMode": "release", "args": ["--device-user", "0"] + }, + { + "name": "example", + "cwd": "packages/flutter_mozilla_components/example", + "request": "launch", + "type": "dart", + "args": ["--device-user", "0"] + }, + { + "name": "example (profile mode)", + "cwd": "packages/flutter_mozilla_components/example", + "request": "launch", + "type": "dart", + "flutterMode": "profile", + "args": ["--device-user", "0"] + }, + { + "name": "example (release mode)", + "cwd": "packages/flutter_mozilla_components/example", + "request": "launch", + "type": "dart", + "flutterMode": "release", + "args": ["--device-user", "0"] } ] } \ No newline at end of file diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index fd933494..bf29568b 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -54,6 +92,7 @@ + @@ -62,6 +101,7 @@ + @@ -70,6 +110,15 @@ + + + + + with FastEquatable { + final T collection; + final bool immutable; + + EquatableCollection(this.collection, {required this.immutable}) + : assert( + collection is Map || collection is Iterable, + 'Collection type not supported', + ); + + @override + bool get cacheHash => immutable; + + @override + List get hashParameters => [ + collection, + immutable, + ]; +} diff --git a/app/lib/domain/services/app_initialization.dart b/app/lib/domain/services/app_initialization.dart index 6e56ea9d..fb73eddc 100644 --- a/app/lib/domain/services/app_initialization.dart +++ b/app/lib/domain/services/app_initialization.dart @@ -4,9 +4,6 @@ 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/domain/repositories/sync.dart'; -import 'package:lensai/features/kagi/data/services/session.dart'; -import 'package:lensai/features/settings/data/models/settings.dart'; -import 'package:lensai/features/settings/data/repositories/settings_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'app_initialization.g.dart'; @@ -46,27 +43,8 @@ class AppInitializationService extends _$AppInitializationService { .syncBangGroups(syncInterval: const Duration(days: 7)); } - Future _initIncognito(Settings settings) async { - state = Result.success( - ( - initialized: false, - stage: 'Enforcing Privacy...', - errors: List.empty(), - ), - ); - - if (settings.kagiSession case final String session) { - if (session.isNotEmpty) { - await ref - .read(kagiSessionServiceProvider.notifier) - .setKagiSession(session); - } - } - } - Future initialize() async { state = await Result.fromAsync(() async { - final settings = await ref.read(settingsRepositoryProvider.future); final errors = []; await _initPackageInfo(); @@ -76,8 +54,6 @@ class AppInitializationService extends _$AppInitializationService { result.onFailure(errors.add); } - await _initIncognito(settings); - return (initialized: true, stage: null, errors: errors); }); } diff --git a/app/lib/domain/services/app_initialization.g.dart b/app/lib/domain/services/app_initialization.g.dart index 43c3ec57..91541cf3 100644 --- a/app/lib/domain/services/app_initialization.g.dart +++ b/app/lib/domain/services/app_initialization.g.dart @@ -7,7 +7,7 @@ part of 'app_initialization.dart'; // ************************************************************************** String _$appInitializationServiceHash() => - r'0d7dc3eb3ed2e17643f4c34e4e172734ed8d09e2'; + r'fa383398929c8f3c8b430986304775e044b9af7a'; /// See also [AppInitializationService]. @ProviderFor(AppInitializationService) diff --git a/app/lib/features/bangs/data/database/database.g.dart b/app/lib/features/bangs/data/database/database.g.dart index 533fe931..d7ca05a3 100644 --- a/app/lib/features/bangs/data/database/database.g.dart +++ b/app/lib/features/bangs/data/database/database.g.dart @@ -1246,122 +1246,191 @@ final class $BangTableReferences } } -class $BangTableFilterComposer - extends FilterComposer<_$BangDatabase, BangTable> { - $BangTableFilterComposer(super.$state); - ColumnFilters get trigger => $state.composableBuilder( - column: $state.table.trigger, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $BangTableFilterComposer extends Composer<_$BangDatabase, BangTable> { + $BangTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get trigger => $composableBuilder( + column: $table.trigger, builder: (column) => ColumnFilters(column)); ColumnWithTypeConverterFilters get group => - $state.composableBuilder( - column: $state.table.group, - builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( - column, - joinBuilders: joinBuilders)); + $composableBuilder( + column: $table.group, + builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get websiteName => $state.composableBuilder( - column: $state.table.websiteName, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get websiteName => $composableBuilder( + column: $table.websiteName, builder: (column) => ColumnFilters(column)); - ColumnFilters get domain => $state.composableBuilder( - column: $state.table.domain, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get domain => $composableBuilder( + column: $table.domain, builder: (column) => ColumnFilters(column)); - ColumnFilters get urlTemplate => $state.composableBuilder( - column: $state.table.urlTemplate, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get urlTemplate => $composableBuilder( + column: $table.urlTemplate, builder: (column) => ColumnFilters(column)); - ColumnFilters get category => $state.composableBuilder( - column: $state.table.category, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get category => $composableBuilder( + column: $table.category, builder: (column) => ColumnFilters(column)); - ColumnFilters get subCategory => $state.composableBuilder( - column: $state.table.subCategory, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get subCategory => $composableBuilder( + column: $table.subCategory, builder: (column) => ColumnFilters(column)); ColumnWithTypeConverterFilters?, Set, String> - get format => $state.composableBuilder( - column: $state.table.format, - builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( - column, - joinBuilders: joinBuilders)); + get format => $composableBuilder( + column: $table.format, + builder: (column) => ColumnWithTypeConverterFilters(column)); - ComposableFilter bangFrequencyRefs( - ComposableFilter Function($BangFrequencyFilterComposer f) f) { - final $BangFrequencyFilterComposer composer = $state.composerBuilder( + Expression bangFrequencyRefs( + Expression Function($BangFrequencyFilterComposer f) f) { + final $BangFrequencyFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.trigger, - referencedTable: $state.db.bangFrequency, + referencedTable: $db.bangFrequency, getReferencedColumn: (t) => t.trigger, - builder: (joinBuilder, parentComposers) => $BangFrequencyFilterComposer( - ComposerState($state.db, $state.db.bangFrequency, joinBuilder, - parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangFrequencyFilterComposer( + $db: $db, + $table: $db.bangFrequency, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return f(composer); } - ComposableFilter bangIconRefs( - ComposableFilter Function($BangIconFilterComposer f) f) { - final $BangIconFilterComposer composer = $state.composerBuilder( + Expression bangIconRefs( + Expression Function($BangIconFilterComposer f) f) { + final $BangIconFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.trigger, - referencedTable: $state.db.bangIcon, + referencedTable: $db.bangIcon, getReferencedColumn: (t) => t.trigger, - builder: (joinBuilder, parentComposers) => $BangIconFilterComposer( - ComposerState( - $state.db, $state.db.bangIcon, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangIconFilterComposer( + $db: $db, + $table: $db.bangIcon, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return f(composer); } } -class $BangTableOrderingComposer - extends OrderingComposer<_$BangDatabase, BangTable> { - $BangTableOrderingComposer(super.$state); - ColumnOrderings get trigger => $state.composableBuilder( - column: $state.table.trigger, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $BangTableOrderingComposer extends Composer<_$BangDatabase, BangTable> { + $BangTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get trigger => $composableBuilder( + column: $table.trigger, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get group => $state.composableBuilder( - column: $state.table.group, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get group => $composableBuilder( + column: $table.group, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get websiteName => $state.composableBuilder( - column: $state.table.websiteName, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get websiteName => $composableBuilder( + column: $table.websiteName, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get domain => $state.composableBuilder( - column: $state.table.domain, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get domain => $composableBuilder( + column: $table.domain, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get urlTemplate => $state.composableBuilder( - column: $state.table.urlTemplate, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get urlTemplate => $composableBuilder( + column: $table.urlTemplate, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get category => $state.composableBuilder( - column: $state.table.category, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get category => $composableBuilder( + column: $table.category, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get subCategory => $state.composableBuilder( - column: $state.table.subCategory, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get subCategory => $composableBuilder( + column: $table.subCategory, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get format => $state.composableBuilder( - column: $state.table.format, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get format => $composableBuilder( + column: $table.format, builder: (column) => ColumnOrderings(column)); +} + +class $BangTableAnnotationComposer extends Composer<_$BangDatabase, BangTable> { + $BangTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get trigger => + $composableBuilder(column: $table.trigger, builder: (column) => column); + + GeneratedColumnWithTypeConverter get group => + $composableBuilder(column: $table.group, builder: (column) => column); + + GeneratedColumn get websiteName => $composableBuilder( + column: $table.websiteName, builder: (column) => column); + + GeneratedColumn get domain => + $composableBuilder(column: $table.domain, builder: (column) => column); + + GeneratedColumn get urlTemplate => $composableBuilder( + column: $table.urlTemplate, builder: (column) => column); + + GeneratedColumn get category => + $composableBuilder(column: $table.category, builder: (column) => column); + + GeneratedColumn get subCategory => $composableBuilder( + column: $table.subCategory, builder: (column) => column); + + GeneratedColumnWithTypeConverter?, String> get format => + $composableBuilder(column: $table.format, builder: (column) => column); + + Expression bangFrequencyRefs( + Expression Function($BangFrequencyAnnotationComposer a) f) { + final $BangFrequencyAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.trigger, + referencedTable: $db.bangFrequency, + getReferencedColumn: (t) => t.trigger, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangFrequencyAnnotationComposer( + $db: $db, + $table: $db.bangFrequency, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } + + Expression bangIconRefs( + Expression Function($BangIconAnnotationComposer a) f) { + final $BangIconAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.trigger, + referencedTable: $db.bangIcon, + getReferencedColumn: (t) => t.trigger, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangIconAnnotationComposer( + $db: $db, + $table: $db.bangIcon, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } } class $BangTableTableManager extends RootTableManager< @@ -1370,6 +1439,7 @@ class $BangTableTableManager extends RootTableManager< Bang, $BangTableFilterComposer, $BangTableOrderingComposer, + $BangTableAnnotationComposer, $BangTableCreateCompanionBuilder, $BangTableUpdateCompanionBuilder, (Bang, $BangTableReferences), @@ -1379,9 +1449,12 @@ class $BangTableTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $BangTableFilterComposer(ComposerState(db, table)), - orderingComposer: - $BangTableOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $BangTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $BangTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $BangTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value trigger = const Value.absent(), Value group = const Value.absent(), @@ -1477,6 +1550,7 @@ typedef $BangTableProcessedTableManager = ProcessedTableManager< Bang, $BangTableFilterComposer, $BangTableOrderingComposer, + $BangTableAnnotationComposer, $BangTableCreateCompanionBuilder, $BangTableUpdateCompanionBuilder, (Bang, $BangTableReferences), @@ -1491,33 +1565,51 @@ typedef $BangSyncUpdateCompanionBuilder = BangSyncCompanion Function({ Value lastSync, }); -class $BangSyncFilterComposer extends FilterComposer<_$BangDatabase, BangSync> { - $BangSyncFilterComposer(super.$state); +class $BangSyncFilterComposer extends Composer<_$BangDatabase, BangSync> { + $BangSyncFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); ColumnWithTypeConverterFilters get group => - $state.composableBuilder( - column: $state.table.group, - builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( - column, - joinBuilders: joinBuilders)); + $composableBuilder( + column: $table.group, + builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get lastSync => $state.composableBuilder( - column: $state.table.lastSync, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get lastSync => $composableBuilder( + column: $table.lastSync, builder: (column) => ColumnFilters(column)); } -class $BangSyncOrderingComposer - extends OrderingComposer<_$BangDatabase, BangSync> { - $BangSyncOrderingComposer(super.$state); - ColumnOrderings get group => $state.composableBuilder( - column: $state.table.group, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $BangSyncOrderingComposer extends Composer<_$BangDatabase, BangSync> { + $BangSyncOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get group => $composableBuilder( + column: $table.group, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastSync => $state.composableBuilder( - column: $state.table.lastSync, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get lastSync => $composableBuilder( + column: $table.lastSync, builder: (column) => ColumnOrderings(column)); +} + +class $BangSyncAnnotationComposer extends Composer<_$BangDatabase, BangSync> { + $BangSyncAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumnWithTypeConverter get group => + $composableBuilder(column: $table.group, builder: (column) => column); + + GeneratedColumn get lastSync => + $composableBuilder(column: $table.lastSync, builder: (column) => column); } class $BangSyncTableManager extends RootTableManager< @@ -1526,6 +1618,7 @@ class $BangSyncTableManager extends RootTableManager< BangSyncData, $BangSyncFilterComposer, $BangSyncOrderingComposer, + $BangSyncAnnotationComposer, $BangSyncCreateCompanionBuilder, $BangSyncUpdateCompanionBuilder, (BangSyncData, BaseReferences<_$BangDatabase, BangSync, BangSyncData>), @@ -1535,8 +1628,12 @@ class $BangSyncTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $BangSyncFilterComposer(ComposerState(db, table)), - orderingComposer: $BangSyncOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $BangSyncFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $BangSyncOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $BangSyncAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value group = const Value.absent(), Value lastSync = const Value.absent(), @@ -1566,6 +1663,7 @@ typedef $BangSyncProcessedTableManager = ProcessedTableManager< BangSyncData, $BangSyncFilterComposer, $BangSyncOrderingComposer, + $BangSyncAnnotationComposer, $BangSyncCreateCompanionBuilder, $BangSyncUpdateCompanionBuilder, (BangSyncData, BaseReferences<_$BangDatabase, BangSync, BangSyncData>), @@ -1603,53 +1701,109 @@ final class $BangFrequencyReferences } class $BangFrequencyFilterComposer - extends FilterComposer<_$BangDatabase, BangFrequency> { - $BangFrequencyFilterComposer(super.$state); - ColumnFilters get frequency => $state.composableBuilder( - column: $state.table.frequency, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + extends Composer<_$BangDatabase, BangFrequency> { + $BangFrequencyFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get frequency => $composableBuilder( + column: $table.frequency, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastUsed => $state.composableBuilder( - column: $state.table.lastUsed, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get lastUsed => $composableBuilder( + column: $table.lastUsed, builder: (column) => ColumnFilters(column)); $BangTableFilterComposer get trigger { - final $BangTableFilterComposer composer = $state.composerBuilder( + final $BangTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.trigger, - referencedTable: $state.db.bang, + referencedTable: $db.bang, getReferencedColumn: (t) => t.trigger, - builder: (joinBuilder, parentComposers) => $BangTableFilterComposer( - ComposerState( - $state.db, $state.db.bang, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangTableFilterComposer( + $db: $db, + $table: $db.bang, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return composer; } } class $BangFrequencyOrderingComposer - extends OrderingComposer<_$BangDatabase, BangFrequency> { - $BangFrequencyOrderingComposer(super.$state); - ColumnOrderings get frequency => $state.composableBuilder( - column: $state.table.frequency, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + extends Composer<_$BangDatabase, BangFrequency> { + $BangFrequencyOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get frequency => $composableBuilder( + column: $table.frequency, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastUsed => $state.composableBuilder( - column: $state.table.lastUsed, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get lastUsed => $composableBuilder( + column: $table.lastUsed, builder: (column) => ColumnOrderings(column)); $BangTableOrderingComposer get trigger { - final $BangTableOrderingComposer composer = $state.composerBuilder( + final $BangTableOrderingComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.trigger, - referencedTable: $state.db.bang, + referencedTable: $db.bang, getReferencedColumn: (t) => t.trigger, - builder: (joinBuilder, parentComposers) => $BangTableOrderingComposer( - ComposerState( - $state.db, $state.db.bang, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangTableOrderingComposer( + $db: $db, + $table: $db.bang, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $BangFrequencyAnnotationComposer + extends Composer<_$BangDatabase, BangFrequency> { + $BangFrequencyAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get frequency => + $composableBuilder(column: $table.frequency, builder: (column) => column); + + GeneratedColumn get lastUsed => + $composableBuilder(column: $table.lastUsed, builder: (column) => column); + + $BangTableAnnotationComposer get trigger { + final $BangTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.trigger, + referencedTable: $db.bang, + getReferencedColumn: (t) => t.trigger, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangTableAnnotationComposer( + $db: $db, + $table: $db.bang, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return composer; } } @@ -1660,6 +1814,7 @@ class $BangFrequencyTableManager extends RootTableManager< BangFrequencyData, $BangFrequencyFilterComposer, $BangFrequencyOrderingComposer, + $BangFrequencyAnnotationComposer, $BangFrequencyCreateCompanionBuilder, $BangFrequencyUpdateCompanionBuilder, (BangFrequencyData, $BangFrequencyReferences), @@ -1669,10 +1824,12 @@ class $BangFrequencyTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: - $BangFrequencyFilterComposer(ComposerState(db, table)), - orderingComposer: - $BangFrequencyOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $BangFrequencyFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $BangFrequencyOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $BangFrequencyAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value trigger = const Value.absent(), Value frequency = const Value.absent(), @@ -1716,6 +1873,7 @@ class $BangFrequencyTableManager extends RootTableManager< dynamic, dynamic, dynamic, + dynamic, dynamic>>(state) { if (trigger) { state = state.withJoin( @@ -1743,6 +1901,7 @@ typedef $BangFrequencyProcessedTableManager = ProcessedTableManager< BangFrequencyData, $BangFrequencyFilterComposer, $BangFrequencyOrderingComposer, + $BangFrequencyAnnotationComposer, $BangFrequencyCreateCompanionBuilder, $BangFrequencyUpdateCompanionBuilder, (BangFrequencyData, $BangFrequencyReferences), @@ -1779,53 +1938,107 @@ final class $BangIconReferences } } -class $BangIconFilterComposer extends FilterComposer<_$BangDatabase, BangIcon> { - $BangIconFilterComposer(super.$state); - ColumnFilters get iconData => $state.composableBuilder( - column: $state.table.iconData, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $BangIconFilterComposer extends Composer<_$BangDatabase, BangIcon> { + $BangIconFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get iconData => $composableBuilder( + column: $table.iconData, builder: (column) => ColumnFilters(column)); - ColumnFilters get fetchDate => $state.composableBuilder( - column: $state.table.fetchDate, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get fetchDate => $composableBuilder( + column: $table.fetchDate, builder: (column) => ColumnFilters(column)); $BangTableFilterComposer get trigger { - final $BangTableFilterComposer composer = $state.composerBuilder( + final $BangTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.trigger, - referencedTable: $state.db.bang, + referencedTable: $db.bang, getReferencedColumn: (t) => t.trigger, - builder: (joinBuilder, parentComposers) => $BangTableFilterComposer( - ComposerState( - $state.db, $state.db.bang, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangTableFilterComposer( + $db: $db, + $table: $db.bang, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return composer; } } -class $BangIconOrderingComposer - extends OrderingComposer<_$BangDatabase, BangIcon> { - $BangIconOrderingComposer(super.$state); - ColumnOrderings get iconData => $state.composableBuilder( - column: $state.table.iconData, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $BangIconOrderingComposer extends Composer<_$BangDatabase, BangIcon> { + $BangIconOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get iconData => $composableBuilder( + column: $table.iconData, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get fetchDate => $state.composableBuilder( - column: $state.table.fetchDate, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get fetchDate => $composableBuilder( + column: $table.fetchDate, builder: (column) => ColumnOrderings(column)); $BangTableOrderingComposer get trigger { - final $BangTableOrderingComposer composer = $state.composerBuilder( + final $BangTableOrderingComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.trigger, - referencedTable: $state.db.bang, + referencedTable: $db.bang, getReferencedColumn: (t) => t.trigger, - builder: (joinBuilder, parentComposers) => $BangTableOrderingComposer( - ComposerState( - $state.db, $state.db.bang, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangTableOrderingComposer( + $db: $db, + $table: $db.bang, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $BangIconAnnotationComposer extends Composer<_$BangDatabase, BangIcon> { + $BangIconAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get iconData => + $composableBuilder(column: $table.iconData, builder: (column) => column); + + GeneratedColumn get fetchDate => + $composableBuilder(column: $table.fetchDate, builder: (column) => column); + + $BangTableAnnotationComposer get trigger { + final $BangTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.trigger, + referencedTable: $db.bang, + getReferencedColumn: (t) => t.trigger, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $BangTableAnnotationComposer( + $db: $db, + $table: $db.bang, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return composer; } } @@ -1836,6 +2049,7 @@ class $BangIconTableManager extends RootTableManager< BangIconData, $BangIconFilterComposer, $BangIconOrderingComposer, + $BangIconAnnotationComposer, $BangIconCreateCompanionBuilder, $BangIconUpdateCompanionBuilder, (BangIconData, $BangIconReferences), @@ -1845,8 +2059,12 @@ class $BangIconTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $BangIconFilterComposer(ComposerState(db, table)), - orderingComposer: $BangIconOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $BangIconFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $BangIconOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $BangIconAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value trigger = const Value.absent(), Value iconData = const Value.absent(), @@ -1890,6 +2108,7 @@ class $BangIconTableManager extends RootTableManager< dynamic, dynamic, dynamic, + dynamic, dynamic>>(state) { if (trigger) { state = state.withJoin( @@ -1917,6 +2136,7 @@ typedef $BangIconProcessedTableManager = ProcessedTableManager< BangIconData, $BangIconFilterComposer, $BangIconOrderingComposer, + $BangIconAnnotationComposer, $BangIconCreateCompanionBuilder, $BangIconUpdateCompanionBuilder, (BangIconData, $BangIconReferences), @@ -1933,31 +2153,49 @@ typedef $BangFtsUpdateCompanionBuilder = BangFtsCompanion Function({ Value rowid, }); -class $BangFtsFilterComposer extends FilterComposer<_$BangDatabase, BangFts> { - $BangFtsFilterComposer(super.$state); - ColumnFilters get trigger => $state.composableBuilder( - column: $state.table.trigger, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $BangFtsFilterComposer extends Composer<_$BangDatabase, BangFts> { + $BangFtsFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get trigger => $composableBuilder( + column: $table.trigger, builder: (column) => ColumnFilters(column)); - ColumnFilters get websiteName => $state.composableBuilder( - column: $state.table.websiteName, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get websiteName => $composableBuilder( + column: $table.websiteName, builder: (column) => ColumnFilters(column)); } -class $BangFtsOrderingComposer - extends OrderingComposer<_$BangDatabase, BangFts> { - $BangFtsOrderingComposer(super.$state); - ColumnOrderings get trigger => $state.composableBuilder( - column: $state.table.trigger, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $BangFtsOrderingComposer extends Composer<_$BangDatabase, BangFts> { + $BangFtsOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get trigger => $composableBuilder( + column: $table.trigger, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get websiteName => $state.composableBuilder( - column: $state.table.websiteName, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get websiteName => $composableBuilder( + column: $table.websiteName, builder: (column) => ColumnOrderings(column)); +} + +class $BangFtsAnnotationComposer extends Composer<_$BangDatabase, BangFts> { + $BangFtsAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get trigger => + $composableBuilder(column: $table.trigger, builder: (column) => column); + + GeneratedColumn get websiteName => $composableBuilder( + column: $table.websiteName, builder: (column) => column); } class $BangFtsTableManager extends RootTableManager< @@ -1966,6 +2204,7 @@ class $BangFtsTableManager extends RootTableManager< BangFt, $BangFtsFilterComposer, $BangFtsOrderingComposer, + $BangFtsAnnotationComposer, $BangFtsCreateCompanionBuilder, $BangFtsUpdateCompanionBuilder, (BangFt, BaseReferences<_$BangDatabase, BangFts, BangFt>), @@ -1975,8 +2214,12 @@ class $BangFtsTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $BangFtsFilterComposer(ComposerState(db, table)), - orderingComposer: $BangFtsOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $BangFtsFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $BangFtsOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $BangFtsAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value trigger = const Value.absent(), Value websiteName = const Value.absent(), @@ -2010,6 +2253,7 @@ typedef $BangFtsProcessedTableManager = ProcessedTableManager< BangFt, $BangFtsFilterComposer, $BangFtsOrderingComposer, + $BangFtsAnnotationComposer, $BangFtsCreateCompanionBuilder, $BangFtsUpdateCompanionBuilder, (BangFt, BaseReferences<_$BangDatabase, BangFts, BangFt>), diff --git a/app/lib/features/chat_archive/data/database/database.g.dart b/app/lib/features/chat_archive/data/database/database.g.dart index a50b8fb0..21c0f371 100644 --- a/app/lib/features/chat_archive/data/database/database.g.dart +++ b/app/lib/features/chat_archive/data/database/database.g.dart @@ -455,41 +455,58 @@ typedef $ChatUpdateCompanionBuilder = ChatCompanion Function({ Value rowid, }); -class $ChatFilterComposer extends FilterComposer<_$ChatSearchDatabase, Chat> { - $ChatFilterComposer(super.$state); - ColumnFilters get fileName => $state.composableBuilder( - column: $state.table.fileName, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $ChatFilterComposer extends Composer<_$ChatSearchDatabase, Chat> { + $ChatFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get fileName => $composableBuilder( + column: $table.fileName, builder: (column) => ColumnFilters(column)); - ColumnFilters get title => $state.composableBuilder( - column: $state.table.title, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnFilters(column)); - ColumnFilters get content => $state.composableBuilder( - column: $state.table.content, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnFilters(column)); } -class $ChatOrderingComposer - extends OrderingComposer<_$ChatSearchDatabase, Chat> { - $ChatOrderingComposer(super.$state); - ColumnOrderings get fileName => $state.composableBuilder( - column: $state.table.fileName, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $ChatOrderingComposer extends Composer<_$ChatSearchDatabase, Chat> { + $ChatOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get fileName => $composableBuilder( + column: $table.fileName, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get title => $state.composableBuilder( - column: $state.table.title, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get content => $state.composableBuilder( - column: $state.table.content, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnOrderings(column)); +} + +class $ChatAnnotationComposer extends Composer<_$ChatSearchDatabase, Chat> { + $ChatAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get fileName => + $composableBuilder(column: $table.fileName, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); } class $ChatTableManager extends RootTableManager< @@ -498,6 +515,7 @@ class $ChatTableManager extends RootTableManager< ChatData, $ChatFilterComposer, $ChatOrderingComposer, + $ChatAnnotationComposer, $ChatCreateCompanionBuilder, $ChatUpdateCompanionBuilder, (ChatData, BaseReferences<_$ChatSearchDatabase, Chat, ChatData>), @@ -507,8 +525,12 @@ class $ChatTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $ChatFilterComposer(ComposerState(db, table)), - orderingComposer: $ChatOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $ChatFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $ChatOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $ChatAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value fileName = const Value.absent(), Value title = const Value.absent(), @@ -546,6 +568,7 @@ typedef $ChatProcessedTableManager = ProcessedTableManager< ChatData, $ChatFilterComposer, $ChatOrderingComposer, + $ChatAnnotationComposer, $ChatCreateCompanionBuilder, $ChatUpdateCompanionBuilder, (ChatData, BaseReferences<_$ChatSearchDatabase, Chat, ChatData>), @@ -562,32 +585,50 @@ typedef $ChatFtsUpdateCompanionBuilder = ChatFtsCompanion Function({ Value rowid, }); -class $ChatFtsFilterComposer - extends FilterComposer<_$ChatSearchDatabase, ChatFts> { - $ChatFtsFilterComposer(super.$state); - ColumnFilters get title => $state.composableBuilder( - column: $state.table.title, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $ChatFtsFilterComposer extends Composer<_$ChatSearchDatabase, ChatFts> { + $ChatFtsFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnFilters(column)); - ColumnFilters get content => $state.composableBuilder( - column: $state.table.content, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnFilters(column)); } -class $ChatFtsOrderingComposer - extends OrderingComposer<_$ChatSearchDatabase, ChatFts> { - $ChatFtsOrderingComposer(super.$state); - ColumnOrderings get title => $state.composableBuilder( - column: $state.table.title, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $ChatFtsOrderingComposer extends Composer<_$ChatSearchDatabase, ChatFts> { + $ChatFtsOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get content => $state.composableBuilder( - column: $state.table.content, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnOrderings(column)); +} + +class $ChatFtsAnnotationComposer + extends Composer<_$ChatSearchDatabase, ChatFts> { + $ChatFtsAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); } class $ChatFtsTableManager extends RootTableManager< @@ -596,6 +637,7 @@ class $ChatFtsTableManager extends RootTableManager< ChatFt, $ChatFtsFilterComposer, $ChatFtsOrderingComposer, + $ChatFtsAnnotationComposer, $ChatFtsCreateCompanionBuilder, $ChatFtsUpdateCompanionBuilder, (ChatFt, BaseReferences<_$ChatSearchDatabase, ChatFts, ChatFt>), @@ -605,8 +647,12 @@ class $ChatFtsTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $ChatFtsFilterComposer(ComposerState(db, table)), - orderingComposer: $ChatFtsOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $ChatFtsFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $ChatFtsOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $ChatFtsAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value title = const Value.absent(), Value content = const Value.absent(), @@ -640,6 +686,7 @@ typedef $ChatFtsProcessedTableManager = ProcessedTableManager< ChatFt, $ChatFtsFilterComposer, $ChatFtsOrderingComposer, + $ChatFtsAnnotationComposer, $ChatFtsCreateCompanionBuilder, $ChatFtsUpdateCompanionBuilder, (ChatFt, BaseReferences<_$ChatSearchDatabase, ChatFts, ChatFt>), diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index baffd4f1..3864d151 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_session.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -8,6 +10,8 @@ part 'providers.g.dart'; GeckoEventService eventService(EventServiceRef ref) { final service = GeckoEventService.setUp(); + unawaited(GeckoTabService().syncEvents()); + ref.onDispose(() { service.dispose(); }); diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index f060d01e..d0034243 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -6,7 +6,7 @@ part of 'providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$eventServiceHash() => r'5aa357fdf0d217677a9a66ecb50417ac18929cad'; +String _$eventServiceHash() => r'c36c6d895629002945c7b4eafba8389771e2eb2d'; /// See also [eventService]. @ProviderFor(eventService) diff --git a/app/lib/features/geckoview/domain/providers/selected_tab.dart b/app/lib/features/geckoview/domain/providers/selected_tab.dart index ab323e6f..bf2ea931 100644 --- a/app/lib/features/geckoview/domain/providers/selected_tab.dart +++ b/app/lib/features/geckoview/domain/providers/selected_tab.dart @@ -1,14 +1,20 @@ import 'dart:async'; import 'package:lensai/features/geckoview/domain/providers.dart'; +import 'package:lensai/features/geckoview/features/topics/data/database/database.dart'; +import 'package:lensai/features/geckoview/features/topics/data/providers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'selected_tab.g.dart'; @Riverpod(keepAlive: true) class SelectedTab extends _$SelectedTab { + late TabDatabase _db; + @override String? build() { + _db = ref.watch(tabDatabaseProvider); + final eventSerivce = ref.watch(eventServiceProvider); final selectedTabSub = eventSerivce.selectedTabEvents.listen( @@ -17,6 +23,12 @@ class SelectedTab extends _$SelectedTab { }, ); + ref.listenSelf((previous, next) async { + if (next != null) { + await _db.tabLinkDao.touchTabLink(next, timestamp: DateTime.now()); + } + }); + ref.onDispose(() { unawaited(selectedTabSub.cancel()); }); diff --git a/app/lib/features/geckoview/domain/providers/selected_tab.g.dart b/app/lib/features/geckoview/domain/providers/selected_tab.g.dart index 3c315bda..14c208a8 100644 --- a/app/lib/features/geckoview/domain/providers/selected_tab.g.dart +++ b/app/lib/features/geckoview/domain/providers/selected_tab.g.dart @@ -6,7 +6,7 @@ part of 'selected_tab.dart'; // RiverpodGenerator // ************************************************************************** -String _$selectedTabHash() => r'd30f0490c7924b7de30e23f3d4d132311bf336d0'; +String _$selectedTabHash() => r'd57003aa20d8a6f711918821ae355e5537a55685'; /// See also [SelectedTab]. @ProviderFor(SelectedTab) diff --git a/app/lib/features/geckoview/domain/providers/tab_session.dart b/app/lib/features/geckoview/domain/providers/tab_session.dart index c4a2aa61..09244106 100644 --- a/app/lib/features/geckoview/domain/providers/tab_session.dart +++ b/app/lib/features/geckoview/domain/providers/tab_session.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -31,6 +33,10 @@ class TabSession extends _$TabSession { return _sessionService.goForward(); } + Future requestScreenshot() { + return _sessionService.requestScreenshot(); + } + @override void build(String? tabId) { _sessionService = (tabId != null) 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 2ae93a15..4a1b8bf2 100644 --- a/app/lib/features/geckoview/domain/providers/tab_session.g.dart +++ b/app/lib/features/geckoview/domain/providers/tab_session.g.dart @@ -6,7 +6,7 @@ part of 'tab_session.dart'; // RiverpodGenerator // ************************************************************************** -String _$tabSessionHash() => r'cdde9c7426178f50dc430284939e215f158e8535'; +String _$tabSessionHash() => r'25afbead5c1110ca865e48a5009da70eb20d3a28'; /// Copied from Dart SDK class _SystemHash { diff --git a/app/lib/features/geckoview/domain/providers/tab_state.dart b/app/lib/features/geckoview/domain/providers/tab_state.dart index 774c1afe..36c55997 100644 --- a/app/lib/features/geckoview/domain/providers/tab_state.dart +++ b/app/lib/features/geckoview/domain/providers/tab_state.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:lensai/extensions/image.dart'; import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart'; @@ -9,6 +10,8 @@ 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/providers.dart'; import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart'; +import 'package:lensai/features/geckoview/features/topics/data/database/database.dart'; +import 'package:lensai/features/geckoview/features/topics/data/providers.dart'; import 'package:lensai/features/geckoview/utils/image_helper.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -18,6 +21,8 @@ part 'tab_state.g.dart'; class TabStates extends _$TabStates { final _tabsService = GeckoTabService(); + late TabDatabase _db; + void _onTabListChange(List tabs) { state = { for (final tabId in tabs) tabId: state[tabId] ?? TabState.$default(tabId), @@ -166,6 +171,7 @@ class TabStates extends _$TabStates { @override Map build() { final eventService = ref.watch(eventServiceProvider); + _db = ref.watch(tabDatabaseProvider); final subscriptions = [ eventService.tabListEvents.listen(_onTabListChange), @@ -178,6 +184,18 @@ class TabStates extends _$TabStates { eventService.findResultsEvent.listen(_onFindResults), ]; + ref.listenSelf( + (previous, next) async { + if (!const DeepCollectionEquality() + .equals(previous?.keys.toSet(), next.keys.toSet())) { + final ids = next.keys.toList(); + if (ids.isNotEmpty) { + await _db.tabLinkDao.syncTabLinks(retainTabIds: ids); + } + } + }, + ); + ref.onDispose(() { for (final sub in subscriptions) { unawaited(sub.cancel()); 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 2c3eadd4..04c3ebc5 100644 --- a/app/lib/features/geckoview/domain/providers/tab_state.g.dart +++ b/app/lib/features/geckoview/domain/providers/tab_state.g.dart @@ -169,7 +169,7 @@ final selectedTabStateProvider = AutoDisposeProvider.internal( ); typedef SelectedTabStateRef = AutoDisposeProviderRef; -String _$tabStatesHash() => r'22fd36fe2e443fd2dfb703dc3be72716fbccef6d'; +String _$tabStatesHash() => r'f3ae09e49150955dcb29ec89f0f3fe63155a1d6e'; /// See also [TabStates]. @ProviderFor(TabStates) diff --git a/app/lib/features/geckoview/features/browser/domain/providers.dart b/app/lib/features/geckoview/features/browser/domain/providers.dart index 123f8834..668b1d7a 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.dart @@ -2,8 +2,11 @@ import 'dart:async'; +import 'package:lensai/data/models/equatable_iterable.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/providers/tab_state.dart'; +import 'package:lensai/features/geckoview/features/topics/data/providers.dart'; import 'package:lensai/features/kagi/data/entities/modes.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -80,3 +83,29 @@ class ShowFindInPage extends _$ShowFindInPage { return false; } } + +@Riverpod() +Stream> availableTabIds(AvailableTabIdsRef ref, String? topicId) { + final db = ref.watch(tabDatabaseProvider); + final openTabs = ref + .watch( + tabStatesProvider.select( + (states) => + EquatableCollection(states.keys.toSet(), immutable: false), + ), + ) + .collection; + + if (topicId != null) { + return db.tabLinkDao.topicTabIds(topicId).watch().map( + (tabIds) => + tabIds.where((tabId) => openTabs.contains(tabId)).toList(), + ); + } else { + return db.tabLinkDao.allTabIds().watch().map( + (assignedTabIds) => openTabs + .where((tabId) => !assignedTabIds.contains(tabId)) + .toList(), + ); + } +} 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 2752869a..9b3059e2 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -156,6 +156,135 @@ class _SelectedBangDataProviderElement String? get domain => (origin as SelectedBangDataProvider).domain; } +String _$availableTabIdsHash() => r'bea231cdf6211fc876b8031b5d72999eae5d7b15'; + +/// See also [availableTabIds]. +@ProviderFor(availableTabIds) +const availableTabIdsProvider = AvailableTabIdsFamily(); + +/// See also [availableTabIds]. +class AvailableTabIdsFamily extends Family>> { + /// See also [availableTabIds]. + const AvailableTabIdsFamily(); + + /// See also [availableTabIds]. + AvailableTabIdsProvider call( + String? topicId, + ) { + return AvailableTabIdsProvider( + topicId, + ); + } + + @override + AvailableTabIdsProvider getProviderOverride( + covariant AvailableTabIdsProvider provider, + ) { + return call( + provider.topicId, + ); + } + + 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'availableTabIdsProvider'; +} + +/// See also [availableTabIds]. +class AvailableTabIdsProvider extends AutoDisposeStreamProvider> { + /// See also [availableTabIds]. + AvailableTabIdsProvider( + String? topicId, + ) : this._internal( + (ref) => availableTabIds( + ref as AvailableTabIdsRef, + topicId, + ), + from: availableTabIdsProvider, + name: r'availableTabIdsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$availableTabIdsHash, + dependencies: AvailableTabIdsFamily._dependencies, + allTransitiveDependencies: + AvailableTabIdsFamily._allTransitiveDependencies, + topicId: topicId, + ); + + AvailableTabIdsProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.topicId, + }) : super.internal(); + + final String? topicId; + + @override + Override overrideWith( + Stream> Function(AvailableTabIdsRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: AvailableTabIdsProvider._internal( + (ref) => create(ref as AvailableTabIdsRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + topicId: topicId, + ), + ); + } + + @override + AutoDisposeStreamProviderElement> createElement() { + return _AvailableTabIdsProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is AvailableTabIdsProvider && other.topicId == topicId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, topicId.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin AvailableTabIdsRef on AutoDisposeStreamProviderRef> { + /// The parameter `topicId` of this provider. + String? get topicId; +} + +class _AvailableTabIdsProviderElement + extends AutoDisposeStreamProviderElement> + with AvailableTabIdsRef { + _AvailableTabIdsProviderElement(super.provider); + + @override + String? get topicId => (origin as AvailableTabIdsProvider).topicId; +} + String _$selectedBangTriggerHash() => r'b0e12bc95e93d50d04f1c658230cea9f15ff0385'; 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 index c74bc06e..7e86b704 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_action.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_action.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lensai/data/models/equatable_iterable.dart'; import 'package:lensai/features/geckoview/domain/entities/tab_state.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart'; import 'package:lensai/features/geckoview/features/topics/domain/providers.dart'; @@ -25,8 +26,14 @@ class TabActionDialog extends HookConsumerWidget { tabTopicIdProvider(initialTab.id).select((value) => value.valueOrNull), ); - final topics = - ref.watch(topicsWithCountProvider.select((value) => value.valueOrNull)); + final topics = ref + .watch( + topicsWithCountProvider.select( + (value) => EquatableCollection(value.valueOrNull, immutable: true), + ), + ) + .collection; + final selectedTopic = topics?.firstWhereOrNull((topic) => topic.id == tabTopicId); 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 38078143..a08e044c 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 @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -7,6 +9,7 @@ import 'package:lensai/data/models/web_page_info.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart'; import 'package:lensai/features/bangs/domain/providers.dart'; import 'package:lensai/features/bangs/presentation/widgets/site_search.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/repositories/tab.dart'; import 'package:lensai/features/kagi/data/entities/modes.dart'; @@ -55,6 +58,7 @@ class WebPageDialog extends HookConsumerWidget { final formKey = useMemoized(() => GlobalKey()); final urlTextController = useTextEditingController(text: url.toString()); + final urlTextFocusNode = useFocusNode(); return Stack( children: [ @@ -80,6 +84,7 @@ class WebPageDialog extends HookConsumerWidget { key: formKey, child: TextFormField( controller: urlTextController, + focusNode: urlTextFocusNode, enableIMEPersonalizedLearning: !incognitoEnabled, decoration: InputDecoration( labelText: 'URL', @@ -109,6 +114,15 @@ class WebPageDialog extends HookConsumerWidget { return 'Invalid URL'; }, + onTap: () { + if (!urlTextFocusNode.hasFocus) { + // Select all text when the field is tapped + urlTextController.selection = TextSelection( + baseOffset: 0, + extentOffset: urlTextController.text.length, + ); + } + }, ), ), ), @@ -183,6 +197,40 @@ class WebPageDialog extends HookConsumerWidget { onDismiss?.call(); }, ), + ListTile( + leading: const Icon(Icons.mobile_screen_share), + title: const Text('Share screenshot'), + onTap: () async { + final screenshot = await ref + .read(selectedTabSessionNotifierProvider) + .requestScreenshot(); + + if (screenshot != null) { + ui.decodeImageFromList( + screenshot, + (result) async { + final png = await result.toByteData( + format: ui.ImageByteFormat.png, + ); + + if (png != null) { + final file = XFile.fromData( + png.buffer.asUint8List(), + mimeType: 'image/png', + ); + + await Share.shareXFiles( + [file], + subject: precachedInfo?.title, + ); + } + }, + ); + } + + onDismiss?.call(); + }, + ), const Divider(), ListTile( leading: Icon(KagiTool.summarizer.icon), 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 index dab6236f..d41f30a8 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/landing/content.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/landing/content.dart @@ -3,6 +3,7 @@ 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'; @@ -51,10 +52,16 @@ class LandingContent extends HookConsumerWidget { const LandingAction(), Consumer( builder: (context, ref, child) { - final errors = ref.watch( - appInitializationServiceProvider - .select((result) => result.valueOrNull?.errors), - ); + final errors = ref + .watch( + appInitializationServiceProvider.select( + (result) => EquatableCollection( + result.valueOrNull?.errors, + immutable: false, + ), + ), + ) + .collection; if (errors == null || errors.isEmpty) { return const SizedBox.shrink(); 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 8940956b..0df3ca10 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 @@ -1,16 +1,16 @@ import 'dart:async'; -import 'package:fading_scroll/fading_scroll.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lensai/data/models/equatable_iterable.dart'; import 'package:lensai/features/geckoview/domain/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/dialogs/tab_action.dart'; import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart'; import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart'; -import 'package:lensai/features/geckoview/features/topics/domain/providers.dart'; import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart'; import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart'; import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_chips.dart'; @@ -116,135 +116,133 @@ class ViewTabsSheetWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return FadingScroll( - fadingSize: 25, - shaderPadding: - const EdgeInsets.only(top: _SliverHeaderDelagate._headerSize), + return CustomScrollView( controller: sheetScrollController, - builder: (context, controller) { - return CustomScrollView( - controller: controller, - slivers: [ - SliverPersistentHeader( - pinned: true, - delegate: _SliverHeaderDelagate(onClose: onClose), - ), - HookConsumer( - builder: (context, ref, child) { - final topic = ref.watch(selectedTopicProvider); - final availableTabs = ref.watch( - topicTabIdsProvider(topic) - .select((value) => value.valueOrNull ?? []), - ); - final activeTab = ref.watch(selectedTabProvider); - - final itemHeight = useMemoized( - () => _calculateItemHeight( - screenWidth: MediaQuery.of(context).size.width, - childAspectRatio: 0.75, - horizontalPadding: 4.0, - mainAxisSpacing: 8.0, - crossAxisSpacing: 8.0, - crossAxisCount: 2, + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: _SliverHeaderDelagate(onClose: onClose), + ), + HookConsumer( + builder: (context, ref, child) { + final topic = ref.watch(selectedTopicProvider); + final availableTabs = ref + .watch( + availableTabIdsProvider(topic).select( + (value) => EquatableCollection( + value.valueOrNull ?? [], + immutable: true, + ), ), - [MediaQuery.of(context).size.width], - ); + ) + .collection; + final activeTab = ref.watch(selectedTabProvider); - useEffect( - () { - final index = availableTabs - .indexWhere((webView) => webView == activeTab); + final itemHeight = useMemoized( + () => _calculateItemHeight( + screenWidth: MediaQuery.of(context).size.width, + childAspectRatio: 0.75, + horizontalPadding: 4.0, + mainAxisSpacing: 8.0, + crossAxisSpacing: 8.0, + crossAxisCount: 2, + ), + [MediaQuery.of(context).size.width], + ); - if (index > -1) { - final reversedIndex = availableTabs.length - 1 - index; - final offset = (reversedIndex ~/ 2) * itemHeight; + useEffect( + () { + final index = + availableTabs.indexWhere((webView) => webView == activeTab); - if (offset != controller.offset) { - unawaited( - controller.animateTo( - offset, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - ), - ); - } - } + if (index > -1) { + final reversedIndex = availableTabs.length - 1 - index; + final offset = (reversedIndex ~/ 2) * itemHeight; - return null; - }, - [], - ); + if (offset != sheetScrollController.offset) { + unawaited( + sheetScrollController.animateTo( + offset, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + ), + ); + } + } - return SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - sliver: SliverGrid.count( - //Sync values for itemHeight calculation _calculateItemHeight - childAspectRatio: 0.75, - mainAxisSpacing: 8.0, - crossAxisSpacing: 8.0, - crossAxisCount: 2, - children: availableTabs.reversed - .map( - (tabId) => Consumer( - key: ValueKey(tabId), - builder: (context, ref, child) { - final tab = ref.watch(tabStateProvider(tabId)); - - return (tab != null) - ? TabPreview( - tab: tab, - isActive: tabId == activeTab, - onTap: () async { - if (tabId != activeTab) { - //Close first to avoid rebuilds - onClose(); - await ref - .read( - tabRepositoryProvider.notifier, - ) - .selectTab(tab.id); - } else { - onClose(); - } - }, - onLongPress: () { - ref - .read( - overlayDialogControllerProvider - .notifier, - ) - .show( - TabActionDialog( - initialTab: tab, - onDismiss: ref - .read( - overlayDialogControllerProvider - .notifier, - ) - .dismiss, - ), - ); - }, - onDelete: () async { - await ref - .read( - tabRepositoryProvider.notifier, - ) - .closeTab(tab.id); - }, - ) - : const SizedBox.shrink(); - }, - ), - ) - .toList(), - ), - ); + return null; }, - ), - ], - ); - }, + [availableTabs, activeTab], + ); + + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + sliver: SliverGrid.count( + //Sync values for itemHeight calculation _calculateItemHeight + childAspectRatio: 0.75, + mainAxisSpacing: 8.0, + crossAxisSpacing: 8.0, + crossAxisCount: 2, + children: availableTabs.reversed + .map( + (tabId) => Consumer( + key: ValueKey(tabId), + builder: (context, ref, child) { + final tab = ref.watch(tabStateProvider(tabId)); + + return (tab != null) + ? TabPreview( + tab: tab, + isActive: tabId == activeTab, + onTap: () async { + if (tabId != activeTab) { + //Close first to avoid rebuilds + onClose(); + await ref + .read( + tabRepositoryProvider.notifier, + ) + .selectTab(tab.id); + } else { + onClose(); + } + }, + onLongPress: () { + ref + .read( + overlayDialogControllerProvider + .notifier, + ) + .show( + TabActionDialog( + initialTab: tab, + onDismiss: ref + .read( + overlayDialogControllerProvider + .notifier, + ) + .dismiss, + ), + ); + }, + onDelete: () async { + await ref + .read( + tabRepositoryProvider.notifier, + ) + .closeTab(tab.id); + }, + ) + : const SizedBox.shrink(); + }, + ), + ) + .toList(), + ), + ); + }, + ), + ], ); } } diff --git a/app/lib/features/geckoview/features/browser/screens/browser.dart b/app/lib/features/geckoview/features/browser/screens/browser.dart index 1ff882b2..672387cd 100644 --- a/app/lib/features/geckoview/features/browser/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/screens/browser.dart @@ -6,7 +6,9 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lensai/core/logger.dart'; import 'package:lensai/core/routing/routes.dart'; +import 'package:lensai/features/geckoview/domain/providers.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_session.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; @@ -22,6 +24,7 @@ import 'package:lensai/features/geckoview/features/controllers/bottom_sheet.dart import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.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/kagi/data/entities/modes.dart'; import 'package:lensai/features/kagi/data/services/session.dart'; @@ -546,7 +549,23 @@ class BrowserScreen extends HookConsumerWidget { SafeArea( child: Stack( children: [ - const GeckoView(), + GeckoView( + preInitializationStep: () async { + await ref + .read(eventServiceProvider) + .fragmentReadyStateEvents + .firstWhere((state) => state == true) + .timeout( + const Duration(seconds: 3), + onTimeout: () { + logger.e( + 'Browser fragement not reported ready, trying to intitialize anyways', + ); + return true; + }, + ); + }, + ), Positioned( bottom: 0, left: 0, @@ -604,6 +623,7 @@ class BrowserScreen extends HookConsumerWidget { ), ), ), + floatingActionButton: ReaderAppearanceButton(), bottomSheet: (displayedSheet != null) ? NotificationListener( onNotification: (notification) { diff --git a/app/lib/features/geckoview/features/readerview/domain/providers/readerable.dart b/app/lib/features/geckoview/features/readerview/domain/providers/readerable.dart index 2110e5e4..e139a77f 100644 --- a/app/lib/features/geckoview/features/readerview/domain/providers/readerable.dart +++ b/app/lib/features/geckoview/features/readerview/domain/providers/readerable.dart @@ -19,9 +19,3 @@ Stream appearanceButtonVisibility(AppearanceButtonVisibilityRef ref) { final service = ref.watch(readerableServiceProvider); return service.appearanceVisibility; } - -@Riverpod() -Stream readerButtonVisibility(ReaderButtonVisibilityRef ref) { - final service = ref.watch(readerableServiceProvider); - return service.readerVisibility; -} diff --git a/app/lib/features/geckoview/features/readerview/domain/providers/readerable.g.dart b/app/lib/features/geckoview/features/readerview/domain/providers/readerable.g.dart index 64c024cd..92b0b715 100644 --- a/app/lib/features/geckoview/features/readerview/domain/providers/readerable.g.dart +++ b/app/lib/features/geckoview/features/readerview/domain/providers/readerable.g.dart @@ -38,21 +38,5 @@ final appearanceButtonVisibilityProvider = ); typedef AppearanceButtonVisibilityRef = AutoDisposeStreamProviderRef; -String _$readerButtonVisibilityHash() => - r'd58132aa4e9a5ba311a0b5d99b9f8416e952b1ab'; - -/// See also [readerButtonVisibility]. -@ProviderFor(readerButtonVisibility) -final readerButtonVisibilityProvider = AutoDisposeStreamProvider.internal( - readerButtonVisibility, - name: r'readerButtonVisibilityProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$readerButtonVisibilityHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef ReaderButtonVisibilityRef = AutoDisposeStreamProviderRef; // 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/readerview/presentation/widgets/reader_appearance_button.dart b/app/lib/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart index 0dfd2e00..80583f15 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,6 +1,8 @@ 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/providers/tab_state.dart'; import 'package:lensai/features/geckoview/features/readerview/domain/providers/readerable.dart'; class ReaderAppearanceButton extends HookConsumerWidget { @@ -11,13 +13,20 @@ class ReaderAppearanceButton extends HookConsumerWidget { .select((value) => value.valueOrNull ?? false), ); - return FloatingActionButton( - onPressed: buttonVisible - ? () async { - await ref.read(readerableServiceProvider).onAppearanceButtonTap(); - } - : null, - child: const Icon(MdiIcons.formatFont), + final readerabilityState = ref.watch( + selectedTabStateProvider.select( + (state) => state?.readerableState ?? ReaderableState.$default(), + ), + ); + + return Visibility( + visible: readerabilityState.active && buttonVisible, + child: FloatingActionButton( + onPressed: () async { + await ref.read(readerableServiceProvider).onAppearanceButtonTap(); + }, + child: const Icon(MdiIcons.formatFont), + ), ); } } 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 11edb0ee..ef1e3744 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 @@ -4,7 +4,6 @@ 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/providers/tab_state.dart'; -import 'package:lensai/features/geckoview/features/readerview/domain/providers/readerable.dart'; import 'package:lensai/features/geckoview/features/readerview/presentation/controllers/readerable.dart'; import 'package:lensai/presentation/widgets/animate_gradient_shader.dart'; @@ -21,11 +20,6 @@ class ReaderButton extends HookConsumerWidget { ), ); - final readerButtonVisible = ref.watch( - readerButtonVisibilityProvider - .select((value) => value.valueOrNull ?? false), - ); - final icon = useMemoized( () => readerabilityState.active ? Icon( @@ -40,7 +34,7 @@ class ReaderButton extends HookConsumerWidget { ); return Visibility( - visible: readerButtonVisible, + visible: readerabilityState.readerable, child: Padding( padding: const EdgeInsets.symmetric( vertical: 15.0, @@ -48,7 +42,7 @@ class ReaderButton extends HookConsumerWidget { ), child: readerChanging.when( data: (_) => Visibility( - visible: readerButtonVisible, + visible: readerabilityState.readerable, child: InkWell( onTap: readerChanging.isLoading ? null diff --git a/app/lib/features/geckoview/features/topics/data/database/daos/tab.dart b/app/lib/features/geckoview/features/topics/data/database/daos/tab.dart index 41980423..81b8cadc 100644 --- a/app/lib/features/geckoview/features/topics/data/database/daos/tab.dart +++ b/app/lib/features/geckoview/features/topics/data/database/daos/tab.dart @@ -7,14 +7,18 @@ part 'tab.g.dart'; class TabLinkDao extends DatabaseAccessor with _$TabLinkDaoMixin { TabLinkDao(super.db); - Selectable topicTabIds(String? topicId) { + Selectable topicTabIds(String topicId) { + final query = selectOnly(db.tabLink) + ..addColumns([db.tabLink.id]) + ..where(db.tabLink.topicId.equals(topicId)) + ..orderBy([OrderingTerm.asc(db.tabLink.id)]); + + return query.map((row) => row.read(db.tabLink.id)!); + } + + Selectable allTabIds() { final query = selectOnly(db.tabLink) ..addColumns([db.tabLink.id]) - ..where( - (topicId == null) - ? db.tabLink.topicId.isNull() - : db.tabLink.topicId.equals(topicId), - ) ..orderBy([OrderingTerm.asc(db.tabLink.id)]); return query.map((row) => row.read(db.tabLink.id)!); @@ -25,13 +29,13 @@ class TabLinkDao extends DatabaseAccessor with _$TabLinkDaoMixin { ..addColumns([db.tabLink.topicId]) ..where(db.tabLink.id.equals(tabId)); - return query.map((row) => row.read(db.tabLink.id)!); + return query.map((row) => row.read(db.tabLink.topicId)!); } Future upsertTabLink( String tabId, { required DateTime timestamp, - Value topicId = const Value.absent(), + required String topicId, }) { return db.tabLink.insertOne( TabLinkCompanion.insert( diff --git a/app/lib/features/geckoview/features/topics/data/database/database.dart b/app/lib/features/geckoview/features/topics/data/database/database.dart index 8307046a..254ff583 100644 --- a/app/lib/features/geckoview/features/topics/data/database/database.dart +++ b/app/lib/features/geckoview/features/topics/data/database/database.dart @@ -30,6 +30,8 @@ class TabDatabase extends _$TabDatabase { await m.dropColumn(tabLink, 'url'); await m.dropColumn(tabLink, 'title'); await m.dropColumn(tabLink, 'screenshot'); + + await m.alterTable(TableMigration(tabLink)); }); } diff --git a/app/lib/features/geckoview/features/topics/data/database/database.drift b/app/lib/features/geckoview/features/topics/data/database/database.drift index 47730b04..62314fc2 100644 --- a/app/lib/features/geckoview/features/topics/data/database/database.drift +++ b/app/lib/features/geckoview/features/topics/data/database/database.drift @@ -9,7 +9,7 @@ CREATE TABLE topic ( CREATE TABLE tab_link ( id TEXT PRIMARY KEY NOT NULL, - topic_id TEXT REFERENCES topic (id) ON DELETE CASCADE, + topic_id TEXT NOT NULL REFERENCES topic (id) ON DELETE CASCADE, timestamp DATETIME NOT NULL ); diff --git a/app/lib/features/geckoview/features/topics/data/database/database.g.dart b/app/lib/features/geckoview/features/topics/data/database/database.g.dart index 347a212e..f98df2f8 100644 --- a/app/lib/features/geckoview/features/topics/data/database/database.g.dart +++ b/app/lib/features/geckoview/features/topics/data/database/database.g.dart @@ -142,10 +142,10 @@ class TabLink extends Table with TableInfo { requiredDuringInsert: true, $customConstraints: 'PRIMARY KEY NOT NULL'); late final GeneratedColumn topicId = GeneratedColumn( - 'topic_id', aliasedName, true, + 'topic_id', aliasedName, false, type: DriftSqlType.string, - requiredDuringInsert: false, - $customConstraints: 'REFERENCES topic(id)ON DELETE CASCADE'); + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES topic(id)ON DELETE CASCADE'); late final GeneratedColumn timestamp = GeneratedColumn( 'timestamp', aliasedName, false, type: DriftSqlType.dateTime, @@ -167,7 +167,7 @@ class TabLink extends Table with TableInfo { id: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}id'])!, topicId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}topic_id']), + .read(DriftSqlType.string, data['${effectivePrefix}topic_id'])!, timestamp: attachedDatabase.typeMapping .read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!, ); @@ -184,16 +184,15 @@ class TabLink extends Table with TableInfo { class TabLinkData extends DataClass implements Insertable { final String id; - final String? topicId; + final String topicId; final DateTime timestamp; - const TabLinkData({required this.id, this.topicId, required this.timestamp}); + const TabLinkData( + {required this.id, required this.topicId, required this.timestamp}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); - if (!nullToAbsent || topicId != null) { - map['topic_id'] = Variable(topicId); - } + map['topic_id'] = Variable(topicId); map['timestamp'] = Variable(timestamp); return map; } @@ -203,7 +202,7 @@ class TabLinkData extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return TabLinkData( id: serializer.fromJson(json['id']), - topicId: serializer.fromJson(json['topic_id']), + topicId: serializer.fromJson(json['topic_id']), timestamp: serializer.fromJson(json['timestamp']), ); } @@ -212,18 +211,15 @@ class TabLinkData extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'topic_id': serializer.toJson(topicId), + 'topic_id': serializer.toJson(topicId), 'timestamp': serializer.toJson(timestamp), }; } - TabLinkData copyWith( - {String? id, - Value topicId = const Value.absent(), - DateTime? timestamp}) => + TabLinkData copyWith({String? id, String? topicId, DateTime? timestamp}) => TabLinkData( id: id ?? this.id, - topicId: topicId.present ? topicId.value : this.topicId, + topicId: topicId ?? this.topicId, timestamp: timestamp ?? this.timestamp, ); TabLinkData copyWithCompanion(TabLinkCompanion data) { @@ -257,7 +253,7 @@ class TabLinkData extends DataClass implements Insertable { class TabLinkCompanion extends UpdateCompanion { final Value id; - final Value topicId; + final Value topicId; final Value timestamp; final Value rowid; const TabLinkCompanion({ @@ -268,10 +264,11 @@ class TabLinkCompanion extends UpdateCompanion { }); TabLinkCompanion.insert({ required String id, - this.topicId = const Value.absent(), + required String topicId, required DateTime timestamp, this.rowid = const Value.absent(), }) : id = Value(id), + topicId = Value(topicId), timestamp = Value(timestamp); static Insertable custom({ Expression? id, @@ -289,7 +286,7 @@ class TabLinkCompanion extends UpdateCompanion { TabLinkCompanion copyWith( {Value? id, - Value? topicId, + Value? topicId, Value? timestamp, Value? rowid}) { return TabLinkCompanion( @@ -403,55 +400,102 @@ final class $TopicReferences } } -class $TopicFilterComposer extends FilterComposer<_$TabDatabase, Topic> { - $TopicFilterComposer(super.$state); - ColumnFilters get id => $state.composableBuilder( - column: $state.table.id, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $TopicFilterComposer extends Composer<_$TabDatabase, Topic> { + $TopicFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get name => $state.composableBuilder( - column: $state.table.name, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnFilters(column)); ColumnWithTypeConverterFilters get color => - $state.composableBuilder( - column: $state.table.color, - builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( - column, - joinBuilders: joinBuilders)); + $composableBuilder( + column: $table.color, + builder: (column) => ColumnWithTypeConverterFilters(column)); - ComposableFilter tabLinkRefs( - ComposableFilter Function($TabLinkFilterComposer f) f) { - final $TabLinkFilterComposer composer = $state.composerBuilder( + Expression tabLinkRefs( + Expression Function($TabLinkFilterComposer f) f) { + final $TabLinkFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $state.db.tabLink, + referencedTable: $db.tabLink, getReferencedColumn: (t) => t.topicId, - builder: (joinBuilder, parentComposers) => $TabLinkFilterComposer( - ComposerState( - $state.db, $state.db.tabLink, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $TabLinkFilterComposer( + $db: $db, + $table: $db.tabLink, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return f(composer); } } -class $TopicOrderingComposer extends OrderingComposer<_$TabDatabase, Topic> { - $TopicOrderingComposer(super.$state); - ColumnOrderings get id => $state.composableBuilder( - column: $state.table.id, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $TopicOrderingComposer extends Composer<_$TabDatabase, Topic> { + $TopicOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get name => $state.composableBuilder( - column: $state.table.name, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get color => $state.composableBuilder( - column: $state.table.color, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get color => $composableBuilder( + column: $table.color, builder: (column) => ColumnOrderings(column)); +} + +class $TopicAnnotationComposer extends Composer<_$TabDatabase, Topic> { + $TopicAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumnWithTypeConverter get color => + $composableBuilder(column: $table.color, builder: (column) => column); + + Expression tabLinkRefs( + Expression Function($TabLinkAnnotationComposer a) f) { + final $TabLinkAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.tabLink, + getReferencedColumn: (t) => t.topicId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $TabLinkAnnotationComposer( + $db: $db, + $table: $db.tabLink, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } } class $TopicTableManager extends RootTableManager< @@ -460,6 +504,7 @@ class $TopicTableManager extends RootTableManager< TopicData, $TopicFilterComposer, $TopicOrderingComposer, + $TopicAnnotationComposer, $TopicCreateCompanionBuilder, $TopicUpdateCompanionBuilder, (TopicData, $TopicReferences), @@ -469,8 +514,12 @@ class $TopicTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $TopicFilterComposer(ComposerState(db, table)), - orderingComposer: $TopicOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $TopicFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $TopicOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $TopicAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value name = const Value.absent(), @@ -528,6 +577,7 @@ typedef $TopicProcessedTableManager = ProcessedTableManager< TopicData, $TopicFilterComposer, $TopicOrderingComposer, + $TopicAnnotationComposer, $TopicCreateCompanionBuilder, $TopicUpdateCompanionBuilder, (TopicData, $TopicReferences), @@ -535,13 +585,13 @@ typedef $TopicProcessedTableManager = ProcessedTableManager< PrefetchHooks Function({bool tabLinkRefs})>; typedef $TabLinkCreateCompanionBuilder = TabLinkCompanion Function({ required String id, - Value topicId, + required String topicId, required DateTime timestamp, Value rowid, }); typedef $TabLinkUpdateCompanionBuilder = TabLinkCompanion Function({ Value id, - Value topicId, + Value topicId, Value timestamp, Value rowid, }); @@ -564,53 +614,107 @@ final class $TabLinkReferences } } -class $TabLinkFilterComposer extends FilterComposer<_$TabDatabase, TabLink> { - $TabLinkFilterComposer(super.$state); - ColumnFilters get id => $state.composableBuilder( - column: $state.table.id, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); +class $TabLinkFilterComposer extends Composer<_$TabDatabase, TabLink> { + $TabLinkFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get timestamp => $state.composableBuilder( - column: $state.table.timestamp, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnFilters get timestamp => $composableBuilder( + column: $table.timestamp, builder: (column) => ColumnFilters(column)); $TopicFilterComposer get topicId { - final $TopicFilterComposer composer = $state.composerBuilder( + final $TopicFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.topicId, - referencedTable: $state.db.topic, + referencedTable: $db.topic, getReferencedColumn: (t) => t.id, - builder: (joinBuilder, parentComposers) => $TopicFilterComposer( - ComposerState( - $state.db, $state.db.topic, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $TopicFilterComposer( + $db: $db, + $table: $db.topic, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return composer; } } -class $TabLinkOrderingComposer - extends OrderingComposer<_$TabDatabase, TabLink> { - $TabLinkOrderingComposer(super.$state); - ColumnOrderings get id => $state.composableBuilder( - column: $state.table.id, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); +class $TabLinkOrderingComposer extends Composer<_$TabDatabase, TabLink> { + $TabLinkOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get timestamp => $state.composableBuilder( - column: $state.table.timestamp, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); + ColumnOrderings get timestamp => $composableBuilder( + column: $table.timestamp, builder: (column) => ColumnOrderings(column)); $TopicOrderingComposer get topicId { - final $TopicOrderingComposer composer = $state.composerBuilder( + final $TopicOrderingComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.topicId, - referencedTable: $state.db.topic, + referencedTable: $db.topic, getReferencedColumn: (t) => t.id, - builder: (joinBuilder, parentComposers) => $TopicOrderingComposer( - ComposerState( - $state.db, $state.db.topic, joinBuilder, parentComposers))); + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $TopicOrderingComposer( + $db: $db, + $table: $db.topic, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $TabLinkAnnotationComposer extends Composer<_$TabDatabase, TabLink> { + $TabLinkAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get timestamp => + $composableBuilder(column: $table.timestamp, builder: (column) => column); + + $TopicAnnotationComposer get topicId { + final $TopicAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.topicId, + referencedTable: $db.topic, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $TopicAnnotationComposer( + $db: $db, + $table: $db.topic, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); return composer; } } @@ -621,6 +725,7 @@ class $TabLinkTableManager extends RootTableManager< TabLinkData, $TabLinkFilterComposer, $TabLinkOrderingComposer, + $TabLinkAnnotationComposer, $TabLinkCreateCompanionBuilder, $TabLinkUpdateCompanionBuilder, (TabLinkData, $TabLinkReferences), @@ -630,11 +735,15 @@ class $TabLinkTableManager extends RootTableManager< : super(TableManagerState( db: db, table: table, - filteringComposer: $TabLinkFilterComposer(ComposerState(db, table)), - orderingComposer: $TabLinkOrderingComposer(ComposerState(db, table)), + createFilteringComposer: () => + $TabLinkFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $TabLinkOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $TabLinkAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), - Value topicId = const Value.absent(), + Value topicId = const Value.absent(), Value timestamp = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -646,7 +755,7 @@ class $TabLinkTableManager extends RootTableManager< ), createCompanionCallback: ({ required String id, - Value topicId = const Value.absent(), + required String topicId, required DateTime timestamp, Value rowid = const Value.absent(), }) => @@ -675,6 +784,7 @@ class $TabLinkTableManager extends RootTableManager< dynamic, dynamic, dynamic, + dynamic, dynamic>>(state) { if (topicId) { state = state.withJoin( @@ -701,6 +811,7 @@ typedef $TabLinkProcessedTableManager = ProcessedTableManager< TabLinkData, $TabLinkFilterComposer, $TabLinkOrderingComposer, + $TabLinkAnnotationComposer, $TabLinkCreateCompanionBuilder, $TabLinkUpdateCompanionBuilder, (TabLinkData, $TabLinkReferences), diff --git a/app/lib/features/geckoview/features/topics/domain/providers.dart b/app/lib/features/geckoview/features/topics/domain/providers.dart index 2a293158..3ea70025 100644 --- a/app/lib/features/geckoview/features/topics/domain/providers.dart +++ b/app/lib/features/geckoview/features/topics/domain/providers.dart @@ -31,7 +31,7 @@ Stream> topicsWithCount(TopicsWithCountRef ref) { } @Riverpod() -Stream> topicTabIds(TopicTabIdsRef ref, String? topicId) { +Stream> topicTabIds(TopicTabIdsRef ref, String topicId) { final db = ref.watch(tabDatabaseProvider); return db.tabLinkDao.topicTabIds(topicId).watch(); } diff --git a/app/lib/features/geckoview/features/topics/domain/providers.g.dart b/app/lib/features/geckoview/features/topics/domain/providers.g.dart index ea695315..5dc58b27 100644 --- a/app/lib/features/geckoview/features/topics/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/topics/domain/providers.g.dart @@ -40,7 +40,7 @@ final topicsWithCountProvider = typedef TopicsWithCountRef = AutoDisposeStreamProviderRef>; -String _$topicTabIdsHash() => r'f7ce2633804421a22df1efc42eb4d19d440d9fd3'; +String _$topicTabIdsHash() => r'43719c70e01cf9e030d75171405ab9650936ddf8'; /// Copied from Dart SDK class _SystemHash { @@ -74,7 +74,7 @@ class TopicTabIdsFamily extends Family>> { /// See also [topicTabIds]. TopicTabIdsProvider call( - String? topicId, + String topicId, ) { return TopicTabIdsProvider( topicId, @@ -109,7 +109,7 @@ class TopicTabIdsFamily extends Family>> { class TopicTabIdsProvider extends AutoDisposeStreamProvider> { /// See also [topicTabIds]. TopicTabIdsProvider( - String? topicId, + String topicId, ) : this._internal( (ref) => topicTabIds( ref as TopicTabIdsRef, @@ -137,7 +137,7 @@ class TopicTabIdsProvider extends AutoDisposeStreamProvider> { required this.topicId, }) : super.internal(); - final String? topicId; + final String topicId; @override Override overrideWith( @@ -178,7 +178,7 @@ class TopicTabIdsProvider extends AutoDisposeStreamProvider> { mixin TopicTabIdsRef on AutoDisposeStreamProviderRef> { /// The parameter `topicId` of this provider. - String? get topicId; + String get topicId; } class _TopicTabIdsProviderElement @@ -186,7 +186,7 @@ class _TopicTabIdsProviderElement _TopicTabIdsProviderElement(super.provider); @override - String? get topicId => (origin as TopicTabIdsProvider).topicId; + String get topicId => (origin as TopicTabIdsProvider).topicId; } String _$tabTopicIdHash() => r'284164ac3acd9be3ba4e15eba99fc8d97602ee84'; diff --git a/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.dart b/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.dart index 15f36eea..97206be2 100644 --- a/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.dart +++ b/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.dart @@ -1,5 +1,3 @@ -import 'package:drift/drift.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/topics/data/database/database.dart'; @@ -15,38 +13,28 @@ class TabLinkRepository extends _$TabLinkRepository { @override void build() { _db = ref.watch(tabDatabaseProvider); - - //Sync existing tabs - ref.listen( - tabStatesProvider.select((tabs) => tabs.keys.toList()), - (previous, next) async { - //No empty list to avoid data loss - if (next.isNotEmpty) { - await _db.tabLinkDao.syncTabLinks(retainTabIds: next); - } - }, - ); - - ref.listen( - selectedTabProvider, - (previous, next) async { - if (next != null) { - await _db.tabLinkDao.touchTabLink(next, timestamp: DateTime.now()); - } - }, - ); } - Future assignTab(String tabId, String? topicId) { + Future assignTab(String tabId, String topicId) { return _db.tabLinkDao.upsertTabLink( tabId, timestamp: DateTime.now(), - topicId: Value(topicId), + topicId: topicId, ); } Future closeAllTabs(String? topicId) async { - final tabIds = await _db.tabLinkDao.topicTabIds(topicId).get(); + final List tabIds; + if (topicId != null) { + tabIds = await _db.tabLinkDao.topicTabIds(topicId).get(); + } else { + final openTabs = ref.read(tabStatesProvider).keys.toSet(); + final assignedTabIds = await _db.tabLinkDao.allTabIds().get(); + + tabIds = + openTabs.where((tabId) => !assignedTabIds.contains(tabId)).toList(); + } + if (tabIds.isNotEmpty) { await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds); } diff --git a/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.g.dart b/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.g.dart index 9e564ed1..9b156e55 100644 --- a/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.g.dart +++ b/app/lib/features/geckoview/features/topics/domain/repositories/tab_link.g.dart @@ -6,7 +6,7 @@ part of 'tab_link.dart'; // RiverpodGenerator // ************************************************************************** -String _$tabLinkRepositoryHash() => r'c4944c1f6792bd725f889b1d6e12946b1439be11'; +String _$tabLinkRepositoryHash() => r'cdc23ca6ea215925f39842b16f9abcc289b0efbb'; /// See also [TabLinkRepository]. @ProviderFor(TabLinkRepository) diff --git a/app/lib/features/settings/data/models/settings.g.dart b/app/lib/features/settings/data/models/settings.g.dart index d42c970b..c5ff8b0a 100644 --- a/app/lib/features/settings/data/models/settings.g.dart +++ b/app/lib/features/settings/data/models/settings.g.dart @@ -17,12 +17,8 @@ abstract class _$SettingsCWProxy { Settings launchUrlExternal(bool launchUrlExternal); - Settings enableContentBlocking(bool enableContentBlocking); - Settings blockHttpProtocol(bool blockHttpProtocol); - Settings enableHostList(Set enableHostList); - Settings themeMode(ThemeMode themeMode); Settings quickAction(KagiTool? quickAction); @@ -43,9 +39,7 @@ abstract class _$SettingsCWProxy { bool? incognitoMode, bool? enableJavascript, bool? launchUrlExternal, - bool? enableContentBlocking, bool? blockHttpProtocol, - Set? enableHostList, ThemeMode? themeMode, KagiTool? quickAction, bool? quickActionVoiceInput, @@ -78,18 +72,10 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy { Settings launchUrlExternal(bool launchUrlExternal) => this(launchUrlExternal: launchUrlExternal); - @override - Settings enableContentBlocking(bool enableContentBlocking) => - this(enableContentBlocking: enableContentBlocking); - @override Settings blockHttpProtocol(bool blockHttpProtocol) => this(blockHttpProtocol: blockHttpProtocol); - @override - Settings enableHostList(Set enableHostList) => - this(enableHostList: enableHostList); - @override Settings themeMode(ThemeMode themeMode) => this(themeMode: themeMode); @@ -118,9 +104,7 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy { Object? incognitoMode = const $CopyWithPlaceholder(), Object? enableJavascript = const $CopyWithPlaceholder(), Object? launchUrlExternal = const $CopyWithPlaceholder(), - Object? enableContentBlocking = const $CopyWithPlaceholder(), Object? blockHttpProtocol = const $CopyWithPlaceholder(), - Object? enableHostList = const $CopyWithPlaceholder(), Object? themeMode = const $CopyWithPlaceholder(), Object? quickAction = const $CopyWithPlaceholder(), Object? quickActionVoiceInput = const $CopyWithPlaceholder(), @@ -152,22 +136,11 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy { ? _value.launchUrlExternal // ignore: cast_nullable_to_non_nullable : launchUrlExternal as bool, - enableContentBlocking: - enableContentBlocking == const $CopyWithPlaceholder() || - enableContentBlocking == null - ? _value.enableContentBlocking - // ignore: cast_nullable_to_non_nullable - : enableContentBlocking as bool, blockHttpProtocol: blockHttpProtocol == const $CopyWithPlaceholder() || blockHttpProtocol == null ? _value.blockHttpProtocol // ignore: cast_nullable_to_non_nullable : blockHttpProtocol as bool, - enableHostList: enableHostList == const $CopyWithPlaceholder() || - enableHostList == null - ? _value.enableHostList - // ignore: cast_nullable_to_non_nullable - : enableHostList as Set, themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null ? _value.themeMode // ignore: cast_nullable_to_non_nullable diff --git a/app/lib/features/settings/data/repositories/settings_repository.g.dart b/app/lib/features/settings/data/repositories/settings_repository.g.dart index 1a88eb75..fe5c9b0a 100644 --- a/app/lib/features/settings/data/repositories/settings_repository.g.dart +++ b/app/lib/features/settings/data/repositories/settings_repository.g.dart @@ -7,7 +7,7 @@ part of 'settings_repository.dart'; // ************************************************************************** String _$settingsRepositoryHash() => - r'f7c42efda64f0fb647e5daf0a92c2d118596cc52'; + r'b0a498899546b6a0c8566458ed8848fef410c537'; /// See also [SettingsRepository]. @ProviderFor(SettingsRepository) diff --git a/app/pubspec.lock b/app/pubspec.lock index e374d665..2604a200 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -50,10 +50,10 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -290,18 +290,18 @@ packages: dependency: "direct main" description: name: drift - sha256: d6ff1ec6a0f3fa097dda6b776cf601f1f3d88b53b287288e09c1306f394fb1b3 + sha256: df027d168a2985a2e9da900adeba2ab0136f0d84436592cf3cd5135f82c8579c url: "https://pub.dev" source: hosted - version: "2.20.3" + version: "2.21.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "3ee987578ca2281b5ff91eadd757cd6dd36001458d6e33784f990d67ff38f756" + sha256: "27bab15e7869b69259663590381180117873b9b273a1ea9ebb21bb73133d1233" url: "https://pub.dev" source: hosted - version: "2.20.3" + version: "2.21.0" dynamic_color: dependency: "direct main" description: @@ -479,10 +479,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: e17575ca576a34b46c58c91f9948891117a1bd97815d2e661813c7f90c647a78 + sha256: bd9c475d9aae256369edacafc29d1e74c81f78a10cdcdacbbbc9e3c43d009e4a url: "https://pub.dev" source: hosted - version: "0.7.3+2" + version: "0.7.4" flutter_material_design_icons: dependency: "direct main" description: @@ -501,10 +501,11 @@ packages: flutter_pdf_text: dependency: "direct main" description: - path: "/home/fafre/Repos/flutter-pdf-text" - relative: false - source: path - version: "0.6.0" + name: flutter_pdf_text + sha256: "456fcc92ee6297f87e2e5bd46c35f1951a9cd716b8a00ab557d9d087c37a3b9c" + url: "https://pub.dev" + source: hosted + version: "0.7.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -517,10 +518,10 @@ packages: dependency: transitive description: name: flutter_riverpod - sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + sha256: "711d916456563f715bde1e139d7cfdca009f8264befab3ac9f8ded8b6ec26405" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.3" flutter_secure_storage: dependency: "direct main" description: @@ -655,10 +656,10 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "97266a91c994951a06ef0ff3a1c7fb261e52ec7f74e87f0614ea0b7411b859b2" + sha256: "534ca8af331235f700f3916a74e4579cdfdd708e2fbed342fc8aa969d41a7af0" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.4" hotreloader: dependency: transitive description: @@ -983,10 +984,10 @@ packages: dependency: "direct main" description: name: riverpod - sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d + sha256: c86fedfb45dd1da98ee6493dd9374325cdf494e7d523ebfb0c387eecc5f7b5c9 url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.3" riverpod_analyzer_utils: dependency: transitive description: @@ -999,10 +1000,10 @@ packages: dependency: "direct main" description: name: riverpod_annotation - sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c + sha256: "77fdedb87d09344809e8b514ab864d0537b1cb580a93d09bf579b0403aa6203a" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.5.3" riverpod_generator: dependency: "direct dev" description: @@ -1187,10 +1188,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: "852cf80f9e974ac8e1b613758a8aa640215f7701352b66a7f468e95711eb570b" + sha256: c5f63dff8677407ddcddfa4744c176ea6dc44286c47ba9e69e76d8071398034d url: "https://pub.dev" source: hosted - version: "0.38.1" + version: "0.39.1" stack_trace: dependency: transitive description: @@ -1418,10 +1419,10 @@ packages: dependency: transitive description: name: win32 - sha256: "4d45dc9069dba4619dc0ebd93c7cec5e66d8482cb625a370ac806dcc8165f2ec" + sha256: e5c39a90447e7c81cfec14b041cdbd0d0916bd9ebbc7fe02ab69568be703b9bd url: "https://pub.dev" source: hosted - version: "5.5.5" + version: "5.6.0" xdg_directories: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index e39847f7..cd892999 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: collection: ^1.19.0 copy_with_extension: ^5.0.4 crypto: ^3.0.5 - drift: ^2.20.3 + drift: ^2.21.0 dynamic_color: ^1.7.0 exceptions: ^0.6.1 expandable_page_view: ^1.0.17 @@ -22,18 +22,17 @@ dependencies: sdk: flutter flutter_hooks: ^0.20.5 flutter_inappwebview: ^6.1.5 - flutter_markdown: ^0.7.3+2 + flutter_markdown: ^0.7.4 flutter_material_design_icons: ^1.1.7447 flutter_mozilla_components: path: ../packages/flutter_mozilla_components - flutter_pdf_text: - path: ../../flutter-pdf-text + flutter_pdf_text: ^0.7.0 flutter_secure_storage: ^9.2.2 flutter_sharing_intent: ^1.1.1 go_router: ^14.3.0 google_fonts: ^6.2.1 home_widget: ^0.7.0 - hooks_riverpod: ^2.5.2 + hooks_riverpod: ^2.5.4 html: ^0.15.4 http: ^1.2.2 json_annotation: ^4.9.0 @@ -43,8 +42,8 @@ dependencies: package_info_plus: ^8.0.3 path: ^1.9.0 path_provider: ^2.1.4 - riverpod: ^2.5.1 - riverpod_annotation: ^2.3.5 + riverpod: ^2.5.3 + riverpod_annotation: ^2.5.3 rxdart: ^0.28.0 share_plus: ^10.0.3 shared_preferences: ^2.3.2 @@ -67,7 +66,7 @@ dev_dependencies: copy_with_extension_gen: ^5.0.4 custom_lint: ^0.6.8 dependency_validator: ^4.1.1 - drift_dev: ^2.20.3 + drift_dev: ^2.21.0 fast_equatable_lint: ^0.3.0 flutter_launcher_icons: ^0.14.1 go_router_builder: ^2.7.1 diff --git a/melos.yaml b/melos.yaml index a1746169..bfdc6e7f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,5 +1,10 @@ name: lensai_project +command: + clean: + hooks: + pre: melos exec -c 6 -- "flutter clean" + scripts: upgrade-major-tighten: run: | diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt index 89561ce0..df15ffda 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt @@ -68,6 +68,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit _components = GlobalComponents.components _engineView = createEngine(components) + _components?.engineView = _engineView val engineNativeView = engineView.asView() // Set layout parameters diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt index b3ed4d32..6f6c5ffe 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt @@ -17,6 +17,8 @@ import eu.lensai.flutter_mozilla_components.pigeons.ReaderableState import eu.lensai.flutter_mozilla_components.pigeons.SecurityInfoState import eu.lensai.flutter_mozilla_components.pigeons.SelectionAction import eu.lensai.flutter_mozilla_components.pigeons.TabContentState +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -31,10 +33,12 @@ import mozilla.components.browser.thumbnails.storage.ThumbnailStorage import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.Engine +import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.mediaquery.PreferredColorScheme import mozilla.components.concept.fetch.Client import mozilla.components.feature.addons.AddonManager import mozilla.components.feature.addons.amo.AMOAddonsProvider +import mozilla.components.feature.addons.logger import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker import mozilla.components.feature.addons.update.DefaultAddonUpdater import mozilla.components.feature.app.links.AppLinksInterceptor @@ -73,7 +77,7 @@ private const val DAY_IN_MINUTES = 24 * 60L @Suppress("LargeClass") open class DefaultComponents( private val applicationContext: Context, - private val flutterEvents: GeckoStateEvents, + val flutterEvents: GeckoStateEvents, val readerViewController: ReaderViewController, val selectionAction: SelectionAction, ) { @@ -83,6 +87,8 @@ open class DefaultComponents( const val PREF_GLOBAL_PRIVACY_CONTROL = "sample_browser_global_privacy_control" } + var engineView: EngineView? = null + val preferences: SharedPreferences = applicationContext.getSharedPreferences(SAMPLE_BROWSER_PREFERENCES, Context.MODE_PRIVATE) @@ -140,6 +146,7 @@ open class DefaultComponents( FileUploadsDirCleaner { applicationContext.cacheDir } } + @OptIn(FlowPreview::class) val store by lazy { BrowserStore( middleware = listOf( @@ -156,6 +163,7 @@ open class DefaultComponents( ).apply { this.flowScoped { flow -> flow.map { state -> state.selectedTabId } + .distinctUntilChanged() .collect { tabId -> flutterEvents.onSelectedTabChange( tabId @@ -166,8 +174,10 @@ open class DefaultComponents( this.flowScoped { flow -> flow.mapNotNull { state -> state.tabs } .filterChanged { - it.content.icon + it.content } + .ifAnyChanged { arrayOf (it.content.icon) } + .debounce { 50 } .collect { tab -> val iconBytes = tab.content.icon?.toWebPBytes() flutterEvents.onIconChange( @@ -182,6 +192,7 @@ open class DefaultComponents( .filterChanged { it.content.securityInfo } + .debounce { 50 } .collect { tab -> flutterEvents.onSecurityInfoStateChange( tab.id, @@ -200,10 +211,11 @@ open class DefaultComponents( it.readerState } .ifAnyChanged { arrayOf( - it.readerState.readerable, - it.readerState.active, - ) + it.readerState.readerable, + it.readerState.active, + ) } + .debounce { 50 } .collect { tab -> flutterEvents.onReaderableStateChange( tab.id, @@ -226,6 +238,7 @@ open class DefaultComponents( it.content.canGoForward, ) } + .debounce { 50 } .collect { tab -> flutterEvents.onHistoryStateChange( tab.id, @@ -261,8 +274,11 @@ open class DefaultComponents( it.content.private, it.content.fullScreen, it.content.progress, - it.content.loading) } + it.content.loading) + } + .debounce { 50 } .collect { tab -> + logger.info("title: ${tab.content.title} ${tab.content.url}") flutterEvents.onTabContentStateChange( TabContentState( id = tab.id, @@ -283,6 +299,7 @@ open class DefaultComponents( .filterChanged { it.content.findResults } + .distinctUntilChanged() .collect { tab -> tab.content.findResults flutterEvents.onFindResults( @@ -296,6 +313,8 @@ open class DefaultComponents( } } + icons.install(engine, this) + WebNotificationFeature( applicationContext, engine, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt index c8c56216..a8886183 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt @@ -87,6 +87,7 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { private var activity: Activity? = null private lateinit var _flutterPluginBinding: FlutterPlugin.FlutterPluginBinding; + private lateinit var _flutterEvents : GeckoStateEvents init { Log.addSink(AndroidLogSink()) @@ -95,14 +96,15 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { _flutterPluginBinding = flutterPluginBinding - val flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger) + _flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger) + val readerViewController = ReaderViewController(_flutterPluginBinding.binaryMessenger) val selectionActionDelegate = SelectionAction(_flutterPluginBinding.binaryMessenger) GlobalComponents.setUp( flutterPluginBinding.applicationContext, - flutterEvents, + _flutterEvents, readerViewController, selectionActionDelegate ) @@ -141,7 +143,6 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { fm.beginTransaction() .replace(FRAGMENT_CONTAINER_ID, nativeFragment) .commitAllowingStateLoss() - } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { @@ -152,7 +153,12 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware { this.activity = binding.activity _flutterPluginBinding.platformViewRegistry.registerViewFactory( - "eu.lensai/gecko", GeckoViewFactory(binding.activity, FRAGMENT_CONTAINER_ID)) + "eu.lensai/gecko", GeckoViewFactory( + binding.activity, + FRAGMENT_CONTAINER_ID, + _flutterEvents + ) + ) } override fun onDetachedFromActivityForConfigChanges() { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt index b8b860c9..c02260e1 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt @@ -5,20 +5,26 @@ import android.app.Activity import android.content.Context import android.view.ViewGroup import android.widget.FrameLayout +import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformViewFactory -class GeckoViewFactory(private val activity: Activity, private val containerId: Int) : PlatformViewFactory( +class GeckoViewFactory( + private val activity: Activity, + private val containerId: Int, + private val flutterEvents: GeckoStateEvents + ) : PlatformViewFactory( StandardMessageCodec.INSTANCE) { override fun create(context: Context?, id: Int, args: Any?): PlatformView { - return NativeFragmentView(this.activity, this.containerId) + return NativeFragmentView(this.activity, this.containerId, this.flutterEvents) } } private class NativeFragmentView( - private val activity: Activity?, - private val containerId: Int + activity: Activity?, + containerId: Int, + private val flutterEvents: GeckoStateEvents ) : PlatformView { private val container: View @@ -32,6 +38,11 @@ private class NativeFragmentView( container.id = containerId } + override fun onFlutterViewAttached(flutterView: View) { + super.onFlutterViewAttached(flutterView) + flutterEvents.onFragmentReadyStateChange(true) { _ -> } + } + override fun getView(): View { return container } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt index aee10ea6..c521ee00 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt @@ -2,17 +2,35 @@ package eu.lensai.flutter_mozilla_components.api import android.util.Log import eu.lensai.flutter_mozilla_components.GlobalComponents +import eu.lensai.flutter_mozilla_components.ext.toWebPBytes import eu.lensai.flutter_mozilla_components.pigeons.GeckoSessionApi +import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.lensai.flutter_mozilla_components.pigeons.LoadUrlFlagsValue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.selector.selectedTab import eu.lensai.flutter_mozilla_components.pigeons.TranslationOptions as PigeonTranslationOptions import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.browser.thumbnails.storage.ThumbnailStorage +import mozilla.components.concept.base.images.ImageLoadRequest import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.translate.TranslationOptions import mozilla.components.feature.session.SessionUseCases +import org.mozilla.gecko.util.ThreadUtils.runOnUiThread class GeckoSessionApiImpl : GeckoSessionApi { private val sessionUseCases: SessionUseCases by lazy { GlobalComponents.components!!.sessionUseCases } + private val store: BrowserStore by lazy { GlobalComponents.components!!.store } private val state: BrowserState by lazy { GlobalComponents.components!!.store.state } + private val thumbnailStorage: ThumbnailStorage by lazy { GlobalComponents.components!!.thumbnailStorage } + private val events: GeckoStateEvents by lazy { GlobalComponents.components!!.flutterEvents } + private val engineView: EngineView? by lazy { GlobalComponents.components!!.engineView } override fun loadUrl( tabId: String?, @@ -124,4 +142,21 @@ class GeckoSessionApiImpl : GeckoSessionApi { lastAccess = lastAccess ?: System.currentTimeMillis() ) } + + override fun requestScreenshot(callback: (Result) -> Unit) { + val tab = state.selectedTab + if (tab != null) { + engineView?.captureThumbnail { bitmap -> + if (bitmap != null) { + store.dispatch(ContentAction.UpdateThumbnailAction(tab.id, bitmap)) + val compressed = bitmap.toWebPBytes() + callback(Result.success(compressed)) + } else { + callback(Result.success(null)) + } + } + } else { + callback(Result.failure(Exception("No selected tab for screenshot"))) + } + } } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt index e6c68991..5e299b24 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt @@ -8,7 +8,18 @@ import eu.lensai.flutter_mozilla_components.pigeons.RestoreLocation as PigeonRes import eu.lensai.flutter_mozilla_components.pigeons.RecoverableTab as PigeonRecoverableTab import eu.lensai.flutter_mozilla_components.pigeons.SourceValue import eu.lensai.flutter_mozilla_components.GlobalComponents +import eu.lensai.flutter_mozilla_components.ext.toWebPBytes +import eu.lensai.flutter_mozilla_components.pigeons.FindResultState +import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents +import eu.lensai.flutter_mozilla_components.pigeons.HistoryItem +import eu.lensai.flutter_mozilla_components.pigeons.HistoryState +import eu.lensai.flutter_mozilla_components.pigeons.ReaderableState import eu.lensai.flutter_mozilla_components.pigeons.RestoreLocation +import eu.lensai.flutter_mozilla_components.pigeons.SecurityInfoState +import eu.lensai.flutter_mozilla_components.pigeons.TabContentState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import mozilla.components.browser.session.storage.RecoverableBrowserState import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab @@ -18,16 +29,21 @@ import mozilla.components.browser.state.state.ReaderState import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.recover.RecoverableTab import mozilla.components.browser.state.state.recover.TabState +import mozilla.components.browser.thumbnails.storage.ThumbnailStorage +import mozilla.components.concept.base.images.ImageLoadRequest import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.storage.HistoryMetadataKey import mozilla.components.feature.tabs.TabsUseCases import org.json.JSONObject +import org.mozilla.gecko.util.ThreadUtils.runOnUiThread class GeckoTabsApiImpl() : GeckoTabsApi { private val tabsUseCases: TabsUseCases by lazy { GlobalComponents.components!!.tabsUseCases } private val engine: Engine by lazy { GlobalComponents.components!!.engine } private val state: BrowserState by lazy { GlobalComponents.components!!.store.state } + private val thumbnailStorage: ThumbnailStorage by lazy { GlobalComponents.components!!.thumbnailStorage } + private val events: GeckoStateEvents by lazy { GlobalComponents.components!!.flutterEvents } private fun restoreSource(source: SourceValue ) : SessionState.Source { return SessionState.Source.restore( @@ -88,6 +104,135 @@ class GeckoTabsApiImpl() : GeckoTabsApi { } } + override fun syncEvents( + onSelectedTabChange: Boolean, + onTabListChange: Boolean, + onTabContentStateChange: Boolean, + onIconChange: Boolean, + onSecurityInfoStateChange: Boolean, + onReaderableStateChange: Boolean, + onHistoryStateChange: Boolean, + onFindResults: Boolean, + onThumbnailChange: Boolean, + ) { + if(onSelectedTabChange) { + events.onSelectedTabChange( + state.selectedTabId + ) { _ -> } + } + + if(onTabListChange) { + events.onTabListChange(state.tabs.map {tab -> tab.id}) { _ -> } + } + + if(onTabContentStateChange) { + state.tabs.forEach { tab -> + events.onTabContentStateChange( + TabContentState( + id = tab.id, + contextId = tab.contextId, + url = tab.content.url, + title = tab.content.title, + progress = tab.content.progress.toLong(), + isPrivate = tab.content.private, + isFullScreen = tab.content.fullScreen, + isLoading = tab.content.loading + ) + ) { _ -> } + } + } + + if(onIconChange) { + state.tabs.forEach { tab -> + if(tab.content.icon != null) { + val iconBytes = tab.content.icon?.toWebPBytes() + events.onIconChange( + tab.id, + iconBytes + ) { _ -> } + } + } + } + + if(onSecurityInfoStateChange) { + state.tabs.forEach { tab -> + val iconBytes = tab.content.icon?.toWebPBytes() + events.onSecurityInfoStateChange( + tab.id, + SecurityInfoState( + tab.content.securityInfo.secure, + tab.content.securityInfo.host, + tab.content.securityInfo.issuer, + ) + ) { _ -> } + } + } + + if(onReaderableStateChange) { + state.tabs.forEach { tab -> + events.onReaderableStateChange( + tab.id, + ReaderableState( + tab.readerState.readerable, + tab.readerState.active, + ) + ) { _ -> } + } + } + + if(onHistoryStateChange) { + state.tabs.forEach { tab -> + events.onHistoryStateChange( + tab.id, + HistoryState( + items = tab.content.history.items.map { item -> HistoryItem( + url = item.uri, + title = item.title + ) }, + currentIndex = tab.content.history.currentIndex.toLong(), + canGoBack = tab.content.canGoBack, + canGoForward = tab.content.canGoForward, + ) + ) { _ -> } + } + } + + if(onFindResults) { + state.tabs.forEach { tab -> + events.onFindResults( + tab.id, + tab.content.findResults.map { result -> FindResultState( + activeMatchOrdinal = result.activeMatchOrdinal.toLong(), + numberOfMatches = result.numberOfMatches.toLong(), + isDoneCounting = result.isDoneCounting, + ) } + ) { _ -> } + } + } + + if(onThumbnailChange) { + state.tabs.forEach { tab -> + CoroutineScope(Dispatchers.Default).launch { + val bitmap = thumbnailStorage.loadThumbnail( + ImageLoadRequest( + id = tab.id, + //TODO: make this configurable + size = 600, + isPrivate = tab.content.private + ) + ).await() + + if(bitmap != null) { + val bytes = bitmap.toWebPBytes() + runOnUiThread { + events.onThumbnailChange(tab.id, bytes) { _ -> } + } + } + } + } + } + } + override fun selectTab(tabId: String) { tabsUseCases.selectTab(tabId = tabId) } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt index 68519ed6..60b81dfd 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt @@ -12,6 +12,7 @@ import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext +import org.mozilla.gecko.util.ThreadUtils.runOnUiThread import java.io.ByteArrayOutputStream @@ -30,7 +31,9 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd when (action) { is ContentAction.UpdateThumbnailAction -> { val bytes = action.thumbnail.toWebPBytes() - flutterEvents.onThumbnailChange(action.sessionId, bytes) { _ -> } + runOnUiThread { + flutterEvents.onThumbnailChange(action.sessionId, bytes) { _ -> } + } } else -> { // no-op diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt index 8bf3b6b1..550bf59d 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -1239,6 +1239,7 @@ interface GeckoSessionApi { fun crashRecovery(tabIds: List?) fun purgeHistory() fun updateLastAccess(tabId: String?, lastAccess: Long?) + fun requestScreenshot(callback: (Result) -> Unit) companion object { /** The codec used by GeckoSessionApi. */ @@ -1550,11 +1551,30 @@ interface GeckoSessionApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.requestScreenshot$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.requestScreenshot{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface GeckoTabsApi { + fun syncEvents(onSelectedTabChange: Boolean, onTabListChange: Boolean, onTabContentStateChange: Boolean, onIconChange: Boolean, onSecurityInfoStateChange: Boolean, onReaderableStateChange: Boolean, onHistoryStateChange: Boolean, onFindResults: Boolean, onThumbnailChange: Boolean) fun selectTab(tabId: String) fun removeTab(tabId: String) fun addTab(url: String, selectTab: Boolean, startLoading: Boolean, parentId: String?, flags: LoadUrlFlagsValue, contextId: String?, source: SourceValue, private: Boolean, historyMetadata: HistoryMetadataKey?, additionalHeaders: Map?): String @@ -1585,6 +1605,32 @@ interface GeckoTabsApi { @JvmOverloads fun setUp(binaryMessenger: BinaryMessenger, api: GeckoTabsApi?, messageChannelSuffix: String = "") { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.syncEvents$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val onSelectedTabChangeArg = args[0] as Boolean + val onTabListChangeArg = args[1] as Boolean + val onTabContentStateChangeArg = args[2] as Boolean + val onIconChangeArg = args[3] as Boolean + val onSecurityInfoStateChangeArg = args[4] as Boolean + val onReaderableStateChangeArg = args[5] as Boolean + val onHistoryStateChangeArg = args[6] as Boolean + val onFindResultsArg = args[7] as Boolean + val onThumbnailChangeArg = args[8] as Boolean + val wrapped: List = try { + api.syncEvents(onSelectedTabChangeArg, onTabListChangeArg, onTabContentStateChangeArg, onIconChangeArg, onSecurityInfoStateChangeArg, onReaderableStateChangeArg, onHistoryStateChangeArg, onFindResultsArg, onThumbnailChangeArg) + listOf(null) + } catch (exception: Throwable) { + wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectTab$separatedMessageChannelSuffix", codec) if (api != null) { @@ -2107,6 +2153,23 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val GeckoPigeonCodec() } } + fun onFragmentReadyStateChange(stateArg: Boolean, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(stateArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(createConnectionError(channelName))) + } + } + } fun onTabListChange(tabIdsArg: List, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" @@ -2337,23 +2400,6 @@ class ReaderViewController(private val binaryMessenger: BinaryMessenger, private } } } - fun readerViewButtonVisibility(visibleArg: Boolean, callback: (Result) -> Unit) -{ - val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = "dev.flutter.pigeon.flutter_mozilla_components.ReaderViewController.readerViewButtonVisibility$separatedMessageChannelSuffix" - val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(visibleArg)) { - if (it is List<*>) { - if (it.size > 1) { - callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) - } else { - callback(Result.success(Unit)) - } - } else { - callback(Result.failure(createConnectionError(channelName))) - } - } - } } /** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ class SelectionAction(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart index 88d5663a..eb01dce9 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; +import 'package:rxdart/rxdart.dart'; // Typedefs for record types typedef HistoryEvent = ({String tabId, HistoryState history}); @@ -12,74 +13,79 @@ typedef FindResultsEvent = ({String tabId, List results}); class GeckoEventService extends GeckoStateEvents { // Stream controllers - final _tabListController = StreamController>.broadcast(); - final _selectedTabController = StreamController.broadcast(); - final _tabContentController = StreamController.broadcast(); - final _historyController = StreamController.broadcast(); - final _readerableController = StreamController.broadcast(); - final _securityInfoController = - StreamController.broadcast(); - final _iconController = StreamController.broadcast(); - final _thumbnailController = StreamController.broadcast(); - final _findResultsController = StreamController.broadcast(); + final _fragmentStateSubject = BehaviorSubject.seeded(false); + final _tabListSubject = BehaviorSubject>(); + final _selectedTabSubject = BehaviorSubject(); + final _tabContentSubject = BehaviorSubject(); + final _historySubject = BehaviorSubject(); + final _readerableSubject = BehaviorSubject(); + final _securityInfoSubject = BehaviorSubject(); + final _iconSubject = BehaviorSubject(); + final _thumbnailSubject = BehaviorSubject(); + final _findResultsSubject = BehaviorSubject(); // Event streams - Stream> get tabListEvents => _tabListController.stream; - Stream get selectedTabEvents => _selectedTabController.stream; - Stream get tabContentEvents => _tabContentController.stream; - Stream get historyEvents => _historyController.stream; - Stream get readerableEvents => _readerableController.stream; + Stream get fragmentReadyStateEvents => _fragmentStateSubject.stream; + Stream> get tabListEvents => _tabListSubject.stream; + Stream get selectedTabEvents => _selectedTabSubject.stream; + Stream get tabContentEvents => _tabContentSubject.stream; + Stream get historyEvents => _historySubject.stream; + Stream get readerableEvents => _readerableSubject.stream; Stream get securityInfoEvents => - _securityInfoController.stream; - Stream get iconEvents => _iconController.stream; - Stream get thumbnailEvents => _thumbnailController.stream; - Stream get findResultsEvent => - _findResultsController.stream; + _securityInfoSubject.stream; + Stream get iconEvents => _iconSubject.stream; + Stream get thumbnailEvents => _thumbnailSubject.stream; + Stream get findResultsEvent => _findResultsSubject.stream; + + @override + void onFragmentReadyStateChange(bool state) { + _fragmentStateSubject.add(state); + } // Overridden methods @override void onTabListChange(List tabIds) { - _tabListController.add(tabIds.nonNulls.toList()); + _tabListSubject.add(tabIds.nonNulls.toList()); } @override void onSelectedTabChange(String? id) { - _selectedTabController.add(id); + _selectedTabSubject.add(id); } @override void onTabContentStateChange(TabContentState state) { - _tabContentController.add(state); + _tabContentSubject.add(state); } @override void onHistoryStateChange(String id, HistoryState state) { - _historyController.add((tabId: id, history: state)); + _historySubject.add((tabId: id, history: state)); } @override void onReaderableStateChange(String id, ReaderableState state) { - _readerableController.add((tabId: id, readerable: state)); + _readerableSubject.add((tabId: id, readerable: state)); } @override void onSecurityInfoStateChange(String id, SecurityInfoState state) { - _securityInfoController.add((tabId: id, securityInfo: state)); + _securityInfoSubject.add((tabId: id, securityInfo: state)); } @override void onIconChange(String id, Uint8List? bytes) { - _iconController.add((tabId: id, bytes: bytes)); + _iconSubject.add((tabId: id, bytes: bytes)); } @override void onThumbnailChange(String id, Uint8List? bytes) { - _thumbnailController.add((tabId: id, bytes: bytes)); + _thumbnailSubject.add((tabId: id, bytes: bytes)); } @override void onFindResults(String id, List results) { - _findResultsController.add((tabId: id, results: results.nonNulls.toList())); + _findResultsSubject.add((tabId: id, results: results.nonNulls.toList())); } GeckoEventService.setUp({ @@ -94,14 +100,14 @@ class GeckoEventService extends GeckoStateEvents { } void dispose() { - unawaited(_tabListController.close()); - unawaited(_selectedTabController.close()); - unawaited(_tabContentController.close()); - unawaited(_historyController.close()); - unawaited(_readerableController.close()); - unawaited(_securityInfoController.close()); - unawaited(_iconController.close()); - unawaited(_thumbnailController.close()); - unawaited(_findResultsController.close()); + unawaited(_tabListSubject.close()); + unawaited(_selectedTabSubject.close()); + unawaited(_tabContentSubject.close()); + unawaited(_historySubject.close()); + unawaited(_readerableSubject.close()); + unawaited(_securityInfoSubject.close()); + unawaited(_iconSubject.close()); + unawaited(_thumbnailSubject.close()); + unawaited(_findResultsSubject.close()); } } diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_readerable.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_readerable.dart index 20ecc076..c19576dc 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_readerable.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_readerable.dart @@ -2,15 +2,15 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; +import 'package:rxdart/rxdart.dart'; class GeckoReaderableService extends ReaderViewController { final ReaderViewEvents _events; - final _appearanceVisibility = StreamController.broadcast(); - final _readerVisibility = StreamController.broadcast(); + final _appearanceVisibility = BehaviorSubject(); + final _readerVisibility = BehaviorSubject(); Stream get appearanceVisibility => _appearanceVisibility.stream; - Stream get readerVisibility => _readerVisibility.stream; Future toggleReaderView(bool enable) { return _events.onToggleReaderView(enable); @@ -25,11 +25,6 @@ class GeckoReaderableService extends ReaderViewController { _appearanceVisibility.add(visible); } - @override - void readerViewButtonVisibility(bool visible) { - _readerVisibility.add(visible); - } - GeckoReaderableService.setUp({ ReaderViewEvents? readerEvents, BinaryMessenger? binaryMessenger, diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart index 6815c845..9d9562ed 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart'; import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'; @@ -118,6 +120,14 @@ class GeckoSessionService { return _api.purgeHistory(); } + Future requestScreenshot() { + if (tabId != null) { + throw Exception('Screenshot only allowed for selected (visible) tab.'); + } + + return _api.requestScreenshot(); + } + Future updateLastAccess({ String? tabId, //If null = current tab DateTime? lastAccess, //If null datetime.now diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tab.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tab.dart index 3a10d78c..94c8d25b 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tab.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tab.dart @@ -10,6 +10,29 @@ class GeckoTabService { GeckoTabService({GeckoTabsApi? api}) : _api = api ?? _apiInstance; + Future syncEvents({ + bool onSelectedTabChange = true, + bool onTabListChange = true, + bool onTabContentStateChange = true, + bool onIconChange = true, + bool onSecurityInfoStateChange = true, + bool onHistoryStateChange = true, + bool onFindResults = true, + bool onThumbnailChange = true, + }) { + return _api.syncEvents( + onSelectedTabChange: onSelectedTabChange, + onTabListChange: onTabListChange, + onTabContentStateChange: onTabContentStateChange, + onIconChange: onIconChange, + onSecurityInfoStateChange: onSecurityInfoStateChange, + onHistoryStateChange: onHistoryStateChange, + onFindResults: onFindResults, + onThumbnailChange: onThumbnailChange, + onReaderableStateChange: false, + ); + } + Future selectTab({required String tabId}) { return _api.selectTab(tabId: tabId); } diff --git a/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart b/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart index 74096498..4b93937d 100644 --- a/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart +++ b/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -7,7 +9,9 @@ import 'package:flutter/services.dart'; import 'package:flutter_mozilla_components/src/domain/services/gecko_browser.dart'; class GeckoView extends StatelessWidget { - const GeckoView({super.key}); + final FutureOr Function()? preInitializationStep; + + const GeckoView({super.key, this.preInitializationStep}); @override Widget build(BuildContext context) { @@ -35,6 +39,7 @@ class GeckoView extends StatelessWidget { params.onPlatformViewCreated(value); SchedulerBinding.instance.addPostFrameCallback((_) async { + await preInitializationStep?.call(); await GeckoBrowserService().showNativeFragment(); }); }) diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index f68e4ba3..d175b3f4 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -1589,6 +1589,28 @@ class GeckoSessionApi { return; } } + + Future requestScreenshot() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.requestScreenshot$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?); + } + } } class GeckoTabsApi { @@ -1604,6 +1626,28 @@ class GeckoTabsApi { final String pigeonVar_messageChannelSuffix; + Future syncEvents({required bool onSelectedTabChange, required bool onTabListChange, required bool onTabContentStateChange, required bool onIconChange, required bool onSecurityInfoStateChange, required bool onReaderableStateChange, required bool onHistoryStateChange, required bool onFindResults, required bool onThumbnailChange,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.syncEvents$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([onSelectedTabChange, onTabListChange, onTabContentStateChange, onIconChange, onSecurityInfoStateChange, onReaderableStateChange, onHistoryStateChange, onFindResults, onThumbnailChange]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + Future selectTab({required String tabId}) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectTab$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -2199,6 +2243,8 @@ class GeckoCookieApi { abstract class GeckoStateEvents { static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + void onFragmentReadyStateChange(bool state); + void onTabListChange(List tabIds); void onSelectedTabChange(String? id); @@ -2219,6 +2265,31 @@ abstract class GeckoStateEvents { static void setUp(GeckoStateEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange was null.'); + final List args = (message as List?)!; + final bool? arg_state = (args[0] as bool?); + assert(arg_state != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange was null, expected non-null bool.'); + try { + api.onFragmentReadyStateChange(arg_state!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( 'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange$messageChannelSuffix', pigeonChannelCodec, @@ -2522,8 +2593,6 @@ abstract class ReaderViewController { void appearanceButtonVisibility(bool visible); - void readerViewButtonVisibility(bool visible); - static void setUp(ReaderViewController? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { @@ -2551,31 +2620,6 @@ abstract class ReaderViewController { }); } } - { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.flutter_mozilla_components.ReaderViewController.readerViewButtonVisibility$messageChannelSuffix', pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - pigeonVar_channel.setMessageHandler(null); - } else { - pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.ReaderViewController.readerViewButtonVisibility was null.'); - final List args = (message as List?)!; - final bool? arg_visible = (args[0] as bool?); - assert(arg_visible != null, - 'Argument for dev.flutter.pigeon.flutter_mozilla_components.ReaderViewController.readerViewButtonVisibility was null, expected non-null bool.'); - try { - api.readerViewButtonVisibility(arg_visible!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } } } diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index 14efb24e..8c4db937 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -624,10 +624,25 @@ abstract class GeckoSessionApi { required String? tabId, //If null = current tab required int? lastAccess, //If null datetime.now }); + + @async + Uint8List? requestScreenshot(); } @HostApi() abstract class GeckoTabsApi { + void syncEvents({ + required bool onSelectedTabChange, + required bool onTabListChange, + required bool onTabContentStateChange, + required bool onIconChange, + required bool onSecurityInfoStateChange, + required bool onReaderableStateChange, + required bool onHistoryStateChange, + required bool onFindResults, + required bool onThumbnailChange, + }); + void selectTab({required String tabId}); void removeTab({required String tabId}); @@ -767,6 +782,8 @@ abstract class GeckoCookieApi { @FlutterApi() abstract class GeckoStateEvents { + void onFragmentReadyStateChange(bool state); + void onTabListChange(List tabIds); void onSelectedTabChange(String? id); @@ -789,7 +806,6 @@ abstract class ReaderViewEvents { @FlutterApi() abstract class ReaderViewController { void appearanceButtonVisibility(bool visible); - void readerViewButtonVisibility(bool visible); } @FlutterApi() diff --git a/packages/flutter_mozilla_components/pubspec.yaml b/packages/flutter_mozilla_components/pubspec.yaml index aa06fe7b..426ab04e 100644 --- a/packages/flutter_mozilla_components/pubspec.yaml +++ b/packages/flutter_mozilla_components/pubspec.yaml @@ -11,12 +11,13 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.8 + rxdart: ^0.28.0 dev_dependencies: flutter_test: sdk: flutter lint: ^2.3.0 - pigeon: ^22.4.2 + pigeon: ^22.5.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec