getting there

This commit is contained in:
Fabian Freund
2024-10-15 11:13:32 +02:00
parent 10d6a3de1d
commit c4d93e8fe7
56 changed files with 1830 additions and 762 deletions
+23
View File
@@ -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"]
}
]
}
@@ -1,10 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- This is needed because the android.permission.CAMERA above automatically
adds a requirements for camera hardware and we don't want add those restrictions -->
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Needed for uploading media files on devices with Android 13 and later. -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<application
android:label="Lensai"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:usesCleartextTraffic="true">
<activity android:name="eu.lensai.flutter_mozilla_components.NotificationActivity" android:theme="@style/Theme.AppCompat.Translucent" />
<activity
android:name=".MainActivity"
android:exported="true"
@@ -46,6 +83,7 @@
<data android:mimeType="application/pdf" />
</intent-filter>
</activity>
<receiver android:name="FullWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -54,6 +92,7 @@
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/full_widget_info"/>
</receiver>
<receiver android:name="CompactWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -62,6 +101,7 @@
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/compact_widget_info"/>
</receiver>
<receiver android:name="SearchBarWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -70,6 +110,15 @@
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/search_bar_widget_info"/>
</receiver>
<service
android:name="eu.lensai.flutter_mozilla_components.DownloadService"
android:foregroundServiceType="dataSync" />
<service android:name="eu.lensai.flutter_mozilla_components.MediaSessionService"
android:foregroundServiceType="mediaPlayback"
android:exported="false" />
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
@@ -1,5 +1,5 @@
package me.movenext.bang_navigator
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity: FlutterActivity()
class MainActivity: FlutterFragmentActivity()
@@ -0,0 +1,21 @@
import 'package:fast_equatable/fast_equatable.dart';
class EquatableCollection<T> 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<Object?> get hashParameters => [
collection,
immutable,
];
}
@@ -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<void> _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<void> initialize() async {
state = await Result.fromAsync(() async {
final settings = await ref.read(settingsRepositoryProvider.future);
final errors = <ErrorMessage>[];
await _initPackageInfo();
@@ -76,8 +54,6 @@ class AppInitializationService extends _$AppInitializationService {
result.onFailure(errors.add);
}
await _initIncognito(settings);
return (initialized: true, stage: null, errors: errors);
});
}
@@ -7,7 +7,7 @@ part of 'app_initialization.dart';
// **************************************************************************
String _$appInitializationServiceHash() =>
r'0d7dc3eb3ed2e17643f4c34e4e172734ed8d09e2';
r'fa383398929c8f3c8b430986304775e044b9af7a';
/// See also [AppInitializationService].
@ProviderFor(AppInitializationService)
@@ -1246,122 +1246,191 @@ final class $BangTableReferences
}
}
class $BangTableFilterComposer
extends FilterComposer<_$BangDatabase, BangTable> {
$BangTableFilterComposer(super.$state);
ColumnFilters<String> 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<String> get trigger => $composableBuilder(
column: $table.trigger, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<BangGroup, BangGroup, int> get group =>
$state.composableBuilder(
column: $state.table.group,
builder: (column, joinBuilders) => ColumnWithTypeConverterFilters(
column,
joinBuilders: joinBuilders));
$composableBuilder(
column: $table.group,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnFilters<String> get websiteName => $state.composableBuilder(
column: $state.table.websiteName,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get websiteName => $composableBuilder(
column: $table.websiteName, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get domain => $state.composableBuilder(
column: $state.table.domain,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get domain => $composableBuilder(
column: $table.domain, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get urlTemplate => $state.composableBuilder(
column: $state.table.urlTemplate,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get urlTemplate => $composableBuilder(
column: $table.urlTemplate, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get category => $state.composableBuilder(
column: $state.table.category,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get category => $composableBuilder(
column: $table.category, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get subCategory => $state.composableBuilder(
column: $state.table.subCategory,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get subCategory => $composableBuilder(
column: $table.subCategory, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<Set<BangFormat>?, Set<BangFormat>, 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<bool> bangFrequencyRefs(
Expression<bool> 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<bool> bangIconRefs(
Expression<bool> 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<String> 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<String> get trigger => $composableBuilder(
column: $table.trigger, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get group => $state.composableBuilder(
column: $state.table.group,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<int> get group => $composableBuilder(
column: $table.group, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get websiteName => $state.composableBuilder(
column: $state.table.websiteName,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get websiteName => $composableBuilder(
column: $table.websiteName, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get domain => $state.composableBuilder(
column: $state.table.domain,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get domain => $composableBuilder(
column: $table.domain, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get urlTemplate => $state.composableBuilder(
column: $state.table.urlTemplate,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get urlTemplate => $composableBuilder(
column: $table.urlTemplate, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get category => $state.composableBuilder(
column: $state.table.category,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get category => $composableBuilder(
column: $table.category, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get subCategory => $state.composableBuilder(
column: $state.table.subCategory,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get subCategory => $composableBuilder(
column: $table.subCategory, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get format => $state.composableBuilder(
column: $state.table.format,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> 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<String> get trigger =>
$composableBuilder(column: $table.trigger, builder: (column) => column);
GeneratedColumnWithTypeConverter<BangGroup, int> get group =>
$composableBuilder(column: $table.group, builder: (column) => column);
GeneratedColumn<String> get websiteName => $composableBuilder(
column: $table.websiteName, builder: (column) => column);
GeneratedColumn<String> get domain =>
$composableBuilder(column: $table.domain, builder: (column) => column);
GeneratedColumn<String> get urlTemplate => $composableBuilder(
column: $table.urlTemplate, builder: (column) => column);
GeneratedColumn<String> get category =>
$composableBuilder(column: $table.category, builder: (column) => column);
GeneratedColumn<String> get subCategory => $composableBuilder(
column: $table.subCategory, builder: (column) => column);
GeneratedColumnWithTypeConverter<Set<BangFormat>?, String> get format =>
$composableBuilder(column: $table.format, builder: (column) => column);
Expression<T> bangFrequencyRefs<T extends Object>(
Expression<T> 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<T> bangIconRefs<T extends Object>(
Expression<T> 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<String> trigger = const Value.absent(),
Value<BangGroup> 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<DateTime> 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<BangGroup, BangGroup, int> get group =>
$state.composableBuilder(
column: $state.table.group,
builder: (column, joinBuilders) => ColumnWithTypeConverterFilters(
column,
joinBuilders: joinBuilders));
$composableBuilder(
column: $table.group,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnFilters<DateTime> get lastSync => $state.composableBuilder(
column: $state.table.lastSync,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<DateTime> get lastSync => $composableBuilder(
column: $table.lastSync, builder: (column) => ColumnFilters(column));
}
class $BangSyncOrderingComposer
extends OrderingComposer<_$BangDatabase, BangSync> {
$BangSyncOrderingComposer(super.$state);
ColumnOrderings<int> 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<int> get group => $composableBuilder(
column: $table.group, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get lastSync => $state.composableBuilder(
column: $state.table.lastSync,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<DateTime> 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<BangGroup, int> get group =>
$composableBuilder(column: $table.group, builder: (column) => column);
GeneratedColumn<DateTime> 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<BangGroup> group = const Value.absent(),
Value<DateTime> 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<int> 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<int> get frequency => $composableBuilder(
column: $table.frequency, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get lastUsed => $state.composableBuilder(
column: $state.table.lastUsed,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<DateTime> 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<int> 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<int> get frequency => $composableBuilder(
column: $table.frequency, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get lastUsed => $state.composableBuilder(
column: $state.table.lastUsed,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<DateTime> 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<int> get frequency =>
$composableBuilder(column: $table.frequency, builder: (column) => column);
GeneratedColumn<DateTime> 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<String> trigger = const Value.absent(),
Value<int> 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<Uint8List> 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<Uint8List> get iconData => $composableBuilder(
column: $table.iconData, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get fetchDate => $state.composableBuilder(
column: $state.table.fetchDate,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<DateTime> 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<Uint8List> 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<Uint8List> get iconData => $composableBuilder(
column: $table.iconData, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get fetchDate => $state.composableBuilder(
column: $state.table.fetchDate,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<DateTime> 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<Uint8List> get iconData =>
$composableBuilder(column: $table.iconData, builder: (column) => column);
GeneratedColumn<DateTime> 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<String> trigger = const Value.absent(),
Value<Uint8List> 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<int> rowid,
});
class $BangFtsFilterComposer extends FilterComposer<_$BangDatabase, BangFts> {
$BangFtsFilterComposer(super.$state);
ColumnFilters<String> 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<String> get trigger => $composableBuilder(
column: $table.trigger, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get websiteName => $state.composableBuilder(
column: $state.table.websiteName,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get websiteName => $composableBuilder(
column: $table.websiteName, builder: (column) => ColumnFilters(column));
}
class $BangFtsOrderingComposer
extends OrderingComposer<_$BangDatabase, BangFts> {
$BangFtsOrderingComposer(super.$state);
ColumnOrderings<String> 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<String> get trigger => $composableBuilder(
column: $table.trigger, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get websiteName => $state.composableBuilder(
column: $state.table.websiteName,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> 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<String> get trigger =>
$composableBuilder(column: $table.trigger, builder: (column) => column);
GeneratedColumn<String> 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<String> trigger = const Value.absent(),
Value<String> websiteName = const Value.absent(),
@@ -2010,6 +2253,7 @@ typedef $BangFtsProcessedTableManager = ProcessedTableManager<
BangFt,
$BangFtsFilterComposer,
$BangFtsOrderingComposer,
$BangFtsAnnotationComposer,
$BangFtsCreateCompanionBuilder,
$BangFtsUpdateCompanionBuilder,
(BangFt, BaseReferences<_$BangDatabase, BangFts, BangFt>),
@@ -455,41 +455,58 @@ typedef $ChatUpdateCompanionBuilder = ChatCompanion Function({
Value<int> rowid,
});
class $ChatFilterComposer extends FilterComposer<_$ChatSearchDatabase, Chat> {
$ChatFilterComposer(super.$state);
ColumnFilters<String> 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<String> get fileName => $composableBuilder(
column: $table.fileName, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get title => $state.composableBuilder(
column: $state.table.title,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get title => $composableBuilder(
column: $table.title, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get content => $state.composableBuilder(
column: $state.table.content,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get content => $composableBuilder(
column: $table.content, builder: (column) => ColumnFilters(column));
}
class $ChatOrderingComposer
extends OrderingComposer<_$ChatSearchDatabase, Chat> {
$ChatOrderingComposer(super.$state);
ColumnOrderings<String> 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<String> get fileName => $composableBuilder(
column: $table.fileName, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get title => $state.composableBuilder(
column: $state.table.title,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get title => $composableBuilder(
column: $table.title, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get content => $state.composableBuilder(
column: $state.table.content,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> 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<String> get fileName =>
$composableBuilder(column: $table.fileName, builder: (column) => column);
GeneratedColumn<String> get title =>
$composableBuilder(column: $table.title, builder: (column) => column);
GeneratedColumn<String> 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<String> fileName = const Value.absent(),
Value<String> 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<int> rowid,
});
class $ChatFtsFilterComposer
extends FilterComposer<_$ChatSearchDatabase, ChatFts> {
$ChatFtsFilterComposer(super.$state);
ColumnFilters<String> 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<String> get title => $composableBuilder(
column: $table.title, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get content => $state.composableBuilder(
column: $state.table.content,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get content => $composableBuilder(
column: $table.content, builder: (column) => ColumnFilters(column));
}
class $ChatFtsOrderingComposer
extends OrderingComposer<_$ChatSearchDatabase, ChatFts> {
$ChatFtsOrderingComposer(super.$state);
ColumnOrderings<String> 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<String> get title => $composableBuilder(
column: $table.title, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get content => $state.composableBuilder(
column: $state.table.content,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> 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<String> get title =>
$composableBuilder(column: $table.title, builder: (column) => column);
GeneratedColumn<String> 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<String> title = const Value.absent(),
Value<String> content = const Value.absent(),
@@ -640,6 +686,7 @@ typedef $ChatFtsProcessedTableManager = ProcessedTableManager<
ChatFt,
$ChatFtsFilterComposer,
$ChatFtsOrderingComposer,
$ChatFtsAnnotationComposer,
$ChatFtsCreateCompanionBuilder,
$ChatFtsUpdateCompanionBuilder,
(ChatFt, BaseReferences<_$ChatSearchDatabase, ChatFts, ChatFt>),
@@ -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();
});
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$eventServiceHash() => r'5aa357fdf0d217677a9a66ecb50417ac18929cad';
String _$eventServiceHash() => r'c36c6d895629002945c7b4eafba8389771e2eb2d';
/// See also [eventService].
@ProviderFor(eventService)
@@ -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());
});
@@ -6,7 +6,7 @@ part of 'selected_tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$selectedTabHash() => r'd30f0490c7924b7de30e23f3d4d132311bf336d0';
String _$selectedTabHash() => r'd57003aa20d8a6f711918821ae355e5537a55685';
/// See also [SelectedTab].
@ProviderFor(SelectedTab)
@@ -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<Uint8List?> requestScreenshot() {
return _sessionService.requestScreenshot();
}
@override
void build(String? tabId) {
_sessionService = (tabId != null)
@@ -6,7 +6,7 @@ part of 'tab_session.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabSessionHash() => r'cdde9c7426178f50dc430284939e215f158e8535';
String _$tabSessionHash() => r'25afbead5c1110ca865e48a5009da70eb20d3a28';
/// Copied from Dart SDK
class _SystemHash {
@@ -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<String> tabs) {
state = {
for (final tabId in tabs) tabId: state[tabId] ?? TabState.$default(tabId),
@@ -166,6 +171,7 @@ class TabStates extends _$TabStates {
@override
Map<String, TabState> 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());
@@ -169,7 +169,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
);
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$tabStatesHash() => r'22fd36fe2e443fd2dfb703dc3be72716fbccef6d';
String _$tabStatesHash() => r'f3ae09e49150955dcb29ec89f0f3fe63155a1d6e';
/// See also [TabStates].
@ProviderFor(TabStates)
@@ -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<List<String>> 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(),
);
}
}
@@ -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<AsyncValue<List<String>>> {
/// 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<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'availableTabIdsProvider';
}
/// See also [availableTabIds].
class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
/// 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<List<String>> 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<List<String>> 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<List<String>> {
/// The parameter `topicId` of this provider.
String? get topicId;
}
class _AvailableTabIdsProviderElement
extends AutoDisposeStreamProviderElement<List<String>>
with AvailableTabIdsRef {
_AvailableTabIdsProviderElement(super.provider);
@override
String? get topicId => (origin as AvailableTabIdsProvider).topicId;
}
String _$selectedBangTriggerHash() =>
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385';
@@ -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);
@@ -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<FormState>());
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),
@@ -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();
@@ -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(),
),
);
},
),
],
);
}
}
@@ -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<DraggableScrollableNotification>(
onNotification: (notification) {
@@ -19,9 +19,3 @@ Stream<bool> appearanceButtonVisibility(AppearanceButtonVisibilityRef ref) {
final service = ref.watch(readerableServiceProvider);
return service.appearanceVisibility;
}
@Riverpod()
Stream<bool> readerButtonVisibility(ReaderButtonVisibilityRef ref) {
final service = ref.watch(readerableServiceProvider);
return service.readerVisibility;
}
@@ -38,21 +38,5 @@ final appearanceButtonVisibilityProvider =
);
typedef AppearanceButtonVisibilityRef = AutoDisposeStreamProviderRef<bool>;
String _$readerButtonVisibilityHash() =>
r'd58132aa4e9a5ba311a0b5d99b9f8416e952b1ab';
/// See also [readerButtonVisibility].
@ProviderFor(readerButtonVisibility)
final readerButtonVisibilityProvider = AutoDisposeStreamProvider<bool>.internal(
readerButtonVisibility,
name: r'readerButtonVisibilityProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$readerButtonVisibilityHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ReaderButtonVisibilityRef = AutoDisposeStreamProviderRef<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -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),
),
);
}
}
@@ -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
@@ -7,14 +7,18 @@ part 'tab.g.dart';
class TabLinkDao extends DatabaseAccessor<TabDatabase> with _$TabLinkDaoMixin {
TabLinkDao(super.db);
Selectable<String> topicTabIds(String? topicId) {
Selectable<String> 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<String> 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<TabDatabase> 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<void> upsertTabLink(
String tabId, {
required DateTime timestamp,
Value<String?> topicId = const Value.absent(),
required String topicId,
}) {
return db.tabLink.insertOne(
TabLinkCompanion.insert(
@@ -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));
});
}
@@ -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
);
@@ -142,10 +142,10 @@ class TabLink extends Table with TableInfo<TabLink, TabLinkData> {
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL');
late final GeneratedColumn<String> topicId = GeneratedColumn<String>(
'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<DateTime> timestamp = GeneratedColumn<DateTime>(
'timestamp', aliasedName, false,
type: DriftSqlType.dateTime,
@@ -167,7 +167,7 @@ class TabLink extends Table with TableInfo<TabLink, TabLinkData> {
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<TabLink, TabLinkData> {
class TabLinkData extends DataClass implements Insertable<TabLinkData> {
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<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<String>(id);
if (!nullToAbsent || topicId != null) {
map['topic_id'] = Variable<String>(topicId);
}
map['topic_id'] = Variable<String>(topicId);
map['timestamp'] = Variable<DateTime>(timestamp);
return map;
}
@@ -203,7 +202,7 @@ class TabLinkData extends DataClass implements Insertable<TabLinkData> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TabLinkData(
id: serializer.fromJson<String>(json['id']),
topicId: serializer.fromJson<String?>(json['topic_id']),
topicId: serializer.fromJson<String>(json['topic_id']),
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
);
}
@@ -212,18 +211,15 @@ class TabLinkData extends DataClass implements Insertable<TabLinkData> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'topic_id': serializer.toJson<String?>(topicId),
'topic_id': serializer.toJson<String>(topicId),
'timestamp': serializer.toJson<DateTime>(timestamp),
};
}
TabLinkData copyWith(
{String? id,
Value<String?> 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<TabLinkData> {
class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
final Value<String> id;
final Value<String?> topicId;
final Value<String> topicId;
final Value<DateTime> timestamp;
final Value<int> rowid;
const TabLinkCompanion({
@@ -268,10 +264,11 @@ class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
});
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<TabLinkData> custom({
Expression<String>? id,
@@ -289,7 +286,7 @@ class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
TabLinkCompanion copyWith(
{Value<String>? id,
Value<String?>? topicId,
Value<String>? topicId,
Value<DateTime>? timestamp,
Value<int>? rowid}) {
return TabLinkCompanion(
@@ -403,55 +400,102 @@ final class $TopicReferences
}
}
class $TopicFilterComposer extends FilterComposer<_$TabDatabase, Topic> {
$TopicFilterComposer(super.$state);
ColumnFilters<String> 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<String> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get name => $state.composableBuilder(
column: $state.table.name,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<Color, Color, int> 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<bool> tabLinkRefs(
Expression<bool> 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<String> 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<String> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get name => $state.composableBuilder(
column: $state.table.name,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get color => $state.composableBuilder(
column: $state.table.color,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<int> 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<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column);
GeneratedColumnWithTypeConverter<Color, int> get color =>
$composableBuilder(column: $table.color, builder: (column) => column);
Expression<T> tabLinkRefs<T extends Object>(
Expression<T> 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<String> id = const Value.absent(),
Value<String?> 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<String?> topicId,
required String topicId,
required DateTime timestamp,
Value<int> rowid,
});
typedef $TabLinkUpdateCompanionBuilder = TabLinkCompanion Function({
Value<String> id,
Value<String?> topicId,
Value<String> topicId,
Value<DateTime> timestamp,
Value<int> rowid,
});
@@ -564,53 +614,107 @@ final class $TabLinkReferences
}
}
class $TabLinkFilterComposer extends FilterComposer<_$TabDatabase, TabLink> {
$TabLinkFilterComposer(super.$state);
ColumnFilters<String> 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<String> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get timestamp => $state.composableBuilder(
column: $state.table.timestamp,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<DateTime> 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<String> 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<String> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get timestamp => $state.composableBuilder(
column: $state.table.timestamp,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<DateTime> 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<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<DateTime> 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<String> id = const Value.absent(),
Value<String?> topicId = const Value.absent(),
Value<String> topicId = const Value.absent(),
Value<DateTime> timestamp = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
@@ -646,7 +755,7 @@ class $TabLinkTableManager extends RootTableManager<
),
createCompanionCallback: ({
required String id,
Value<String?> topicId = const Value.absent(),
required String topicId,
required DateTime timestamp,
Value<int> 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),
@@ -31,7 +31,7 @@ Stream<List<TopicDataWithCount>> topicsWithCount(TopicsWithCountRef ref) {
}
@Riverpod()
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String? topicId) {
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String topicId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabLinkDao.topicTabIds(topicId).watch();
}
@@ -40,7 +40,7 @@ final topicsWithCountProvider =
typedef TopicsWithCountRef
= AutoDisposeStreamProviderRef<List<TopicDataWithCount>>;
String _$topicTabIdsHash() => r'f7ce2633804421a22df1efc42eb4d19d440d9fd3';
String _$topicTabIdsHash() => r'43719c70e01cf9e030d75171405ab9650936ddf8';
/// Copied from Dart SDK
class _SystemHash {
@@ -74,7 +74,7 @@ class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
/// See also [topicTabIds].
TopicTabIdsProvider call(
String? topicId,
String topicId,
) {
return TopicTabIdsProvider(
topicId,
@@ -109,7 +109,7 @@ class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
/// See also [topicTabIds].
TopicTabIdsProvider(
String? topicId,
String topicId,
) : this._internal(
(ref) => topicTabIds(
ref as TopicTabIdsRef,
@@ -137,7 +137,7 @@ class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
required this.topicId,
}) : super.internal();
final String? topicId;
final String topicId;
@override
Override overrideWith(
@@ -178,7 +178,7 @@ class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
mixin TopicTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
/// 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';
@@ -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<void> assignTab(String tabId, String? topicId) {
Future<void> assignTab(String tabId, String topicId) {
return _db.tabLinkDao.upsertTabLink(
tabId,
timestamp: DateTime.now(),
topicId: Value(topicId),
topicId: topicId,
);
}
Future<void> closeAllTabs(String? topicId) async {
final tabIds = await _db.tabLinkDao.topicTabIds(topicId).get();
final List<String> 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);
}
@@ -6,7 +6,7 @@ part of 'tab_link.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabLinkRepositoryHash() => r'c4944c1f6792bd725f889b1d6e12946b1439be11';
String _$tabLinkRepositoryHash() => r'cdc23ca6ea215925f39842b16f9abcc289b0efbb';
/// See also [TabLinkRepository].
@ProviderFor(TabLinkRepository)
@@ -17,12 +17,8 @@ abstract class _$SettingsCWProxy {
Settings launchUrlExternal(bool launchUrlExternal);
Settings enableContentBlocking(bool enableContentBlocking);
Settings blockHttpProtocol(bool blockHttpProtocol);
Settings enableHostList(Set<HostSource> 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<HostSource>? 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<HostSource> 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<HostSource>,
themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null
? _value.themeMode
// ignore: cast_nullable_to_non_nullable
@@ -7,7 +7,7 @@ part of 'settings_repository.dart';
// **************************************************************************
String _$settingsRepositoryHash() =>
r'f7c42efda64f0fb647e5daf0a92c2d118596cc52';
r'b0a498899546b6a0c8566458ed8848fef410c537';
/// See also [SettingsRepository].
@ProviderFor(SettingsRepository)
+25 -24
View File
@@ -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:
+7 -8
View File
@@ -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
+5
View File
@@ -1,5 +1,10 @@
name: lensai_project
command:
clean:
hooks:
pre: melos exec -c 6 -- "flutter clean"
scripts:
upgrade-major-tighten:
run: |
@@ -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
@@ -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,
@@ -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() {
@@ -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
}
@@ -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<ByteArray?>) -> 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")))
}
}
}
@@ -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)
}
@@ -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
@@ -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<String>?)
fun purgeHistory()
fun updateLastAccess(tabId: String?, lastAccess: Long?)
fun requestScreenshot(callback: (Result<ByteArray?>) -> Unit)
companion object {
/** The codec used by GeckoSessionApi. */
@@ -1550,11 +1551,30 @@ interface GeckoSessionApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.requestScreenshot$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.requestScreenshot{ result: Result<ByteArray?> ->
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, String>?): 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<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.syncEvents$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
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<Any?> = 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<Any?>(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>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(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<String>, callback: (Result<Unit>) -> 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>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.ReaderViewController.readerViewButtonVisibility$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(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 = "") {
@@ -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<FindResultState> results});
class GeckoEventService extends GeckoStateEvents {
// Stream controllers
final _tabListController = StreamController<List<String>>.broadcast();
final _selectedTabController = StreamController<String?>.broadcast();
final _tabContentController = StreamController<TabContentState>.broadcast();
final _historyController = StreamController<HistoryEvent>.broadcast();
final _readerableController = StreamController<ReaderableEvent>.broadcast();
final _securityInfoController =
StreamController<SecurityInfoEvent>.broadcast();
final _iconController = StreamController<IconEvent>.broadcast();
final _thumbnailController = StreamController<ThumbnailEvent>.broadcast();
final _findResultsController = StreamController<FindResultsEvent>.broadcast();
final _fragmentStateSubject = BehaviorSubject.seeded(false);
final _tabListSubject = BehaviorSubject<List<String>>();
final _selectedTabSubject = BehaviorSubject<String?>();
final _tabContentSubject = BehaviorSubject<TabContentState>();
final _historySubject = BehaviorSubject<HistoryEvent>();
final _readerableSubject = BehaviorSubject<ReaderableEvent>();
final _securityInfoSubject = BehaviorSubject<SecurityInfoEvent>();
final _iconSubject = BehaviorSubject<IconEvent>();
final _thumbnailSubject = BehaviorSubject<ThumbnailEvent>();
final _findResultsSubject = BehaviorSubject<FindResultsEvent>();
// Event streams
Stream<List<String>> get tabListEvents => _tabListController.stream;
Stream<String?> get selectedTabEvents => _selectedTabController.stream;
Stream<TabContentState> get tabContentEvents => _tabContentController.stream;
Stream<HistoryEvent> get historyEvents => _historyController.stream;
Stream<ReaderableEvent> get readerableEvents => _readerableController.stream;
Stream<bool> get fragmentReadyStateEvents => _fragmentStateSubject.stream;
Stream<List<String>> get tabListEvents => _tabListSubject.stream;
Stream<String?> get selectedTabEvents => _selectedTabSubject.stream;
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
Stream<ReaderableEvent> get readerableEvents => _readerableSubject.stream;
Stream<SecurityInfoEvent> get securityInfoEvents =>
_securityInfoController.stream;
Stream<IconEvent> get iconEvents => _iconController.stream;
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailController.stream;
Stream<FindResultsEvent> get findResultsEvent =>
_findResultsController.stream;
_securityInfoSubject.stream;
Stream<IconEvent> get iconEvents => _iconSubject.stream;
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream;
Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream;
@override
void onFragmentReadyStateChange(bool state) {
_fragmentStateSubject.add(state);
}
// Overridden methods
@override
void onTabListChange(List<String?> 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<FindResultState?> 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());
}
}
@@ -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<bool>.broadcast();
final _readerVisibility = StreamController<bool>.broadcast();
final _appearanceVisibility = BehaviorSubject<bool>();
final _readerVisibility = BehaviorSubject<bool>();
Stream<bool> get appearanceVisibility => _appearanceVisibility.stream;
Stream<bool> get readerVisibility => _readerVisibility.stream;
Future<void> 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,
@@ -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<Uint8List?> requestScreenshot() {
if (tabId != null) {
throw Exception('Screenshot only allowed for selected (visible) tab.');
}
return _api.requestScreenshot();
}
Future<void> updateLastAccess({
String? tabId, //If null = current tab
DateTime? lastAccess, //If null datetime.now
@@ -10,6 +10,29 @@ class GeckoTabService {
GeckoTabService({GeckoTabsApi? api}) : _api = api ?? _apiInstance;
Future<void> 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<void> selectTab({required String tabId}) {
return _api.selectTab(tabId: tabId);
}
@@ -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<void> 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();
});
})
@@ -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<Uint8List?> requestScreenshot() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.requestScreenshot$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(null) as List<Object?>?;
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<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,}) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.syncEvents$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[onSelectedTabChange, onTabListChange, onTabContentStateChange, onIconChange, onSecurityInfoStateChange, onReaderableStateChange, onHistoryStateChange, onFindResults, onThumbnailChange]) as List<Object?>?;
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<void> selectTab({required String tabId}) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectTab$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
@@ -2199,6 +2243,8 @@ class GeckoCookieApi {
abstract class GeckoStateEvents {
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
void onFragmentReadyStateChange(bool state);
void onTabListChange(List<String> 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<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'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<Object?> args = (message as List<Object?>?)!;
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<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'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<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'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<Object?> args = (message as List<Object?>?)!;
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()));
}
});
}
}
}
}
@@ -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<String> 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()
@@ -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