one step closer

This commit is contained in:
Fabian Freund
2024-10-22 07:27:37 +02:00
parent c4d93e8fe7
commit 8d351e55c0
92 changed files with 3829 additions and 2694 deletions
+1
View File
@@ -24,6 +24,7 @@ analyzer:
plugins:
- custom_lint
exclude:
- "**.drift"
- "**.g.dart"
- "**.swagger.dart"
- "**.freezed.dart"
+2 -1
View File
@@ -39,7 +39,8 @@
android:label="Lensai"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:usesCleartextTraffic="true">
android:usesCleartextTraffic="true"
android:enableOnBackInvokedCallback="true">
<activity android:name="eu.lensai.flutter_mozilla_components.NotificationActivity" android:theme="@style/Theme.AppCompat.Translucent" />
<activity
@@ -1,5 +1,20 @@
package me.movenext.bang_navigator
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterFragmentActivity()
class MainActivity: FlutterFragmentActivity() {
private val CHANNEL = "me.movenext.flutter_mozilla_components/trim_memory"
private lateinit var channel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
channel.invokeMethod("onTrimMemory", level)
}
}
+4
View File
@@ -11,6 +11,10 @@ targets:
dialect: sqlite
options:
version: "3.45"
known_functions:
lexo_rank_next: "text (int, text null)"
lexo_rank_previous: "text (int, text null)"
lexo_rank_reorder_after: "text (text null, text null)"
modules:
- json1
- fts5
+6 -6
View File
@@ -9,7 +9,7 @@ import 'package:lensai/features/chat_archive/presentation/screens/detail.dart';
import 'package:lensai/features/chat_archive/presentation/screens/list.dart';
import 'package:lensai/features/chat_archive/presentation/screens/search.dart';
import 'package:lensai/features/geckoview/features/browser/screens/browser.dart';
import 'package:lensai/features/geckoview/features/topics/presentation/screens/topic_list.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_list.dart';
import 'package:lensai/features/settings/presentation/screens/settings.dart';
part 'routes.g.dart';
@@ -42,9 +42,9 @@ part 'routes.g.dart';
),
],
),
TypedGoRoute<TopicListRoute>(
name: 'TopicsRoute',
path: 'topics',
TypedGoRoute<ContainerListRoute>(
name: 'ContainerListRoute',
path: 'containers',
),
],
)
@@ -102,10 +102,10 @@ class BangSearchRoute extends GoRouteData {
}
}
class TopicListRoute extends GoRouteData {
class ContainerListRoute extends GoRouteData {
@override
Widget build(BuildContext context, GoRouterState state) {
return const TopicListScreen();
return const ContainerListScreen();
}
}
+7 -6
View File
@@ -47,9 +47,9 @@ RouteBase get $browserRoute => GoRouteData.$route(
],
),
GoRouteData.$route(
path: 'topics',
name: 'TopicsRoute',
factory: $TopicListRouteExtension._fromState,
path: 'containers',
name: 'ContainerListRoute',
factory: $ContainerListRouteExtension._fromState,
),
],
);
@@ -163,11 +163,12 @@ extension $BangSubCategoryRouteExtension on BangSubCategoryRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $TopicListRouteExtension on TopicListRoute {
static TopicListRoute _fromState(GoRouterState state) => TopicListRoute();
extension $ContainerListRouteExtension on ContainerListRoute {
static ContainerListRoute _fromState(GoRouterState state) =>
ContainerListRoute();
String get location => GoRouteData.$location(
'/topics',
'/containers',
);
void go(BuildContext context) => context.go(location);
@@ -0,0 +1,48 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:flutter/widgets.dart';
import 'package:json_annotation/json_annotation.dart';
class IconDataJsonConverter
implements JsonConverter<IconData, Map<String, dynamic>> {
const IconDataJsonConverter();
@override
IconData fromJson(Map<String, dynamic> json) {
return IconData(
json['codePoint'] as int,
fontFamily: json['fontFamily'] as String,
fontPackage: json['fontPackage'] as String,
);
}
@override
Map<String, dynamic> toJson(IconData iconData) {
return <String, dynamic>{
'codePoint': iconData.codePoint,
'fontFamily': iconData.fontFamily,
'fontPackage': iconData.fontPackage,
};
}
}
class IconDataTypeConverter implements TypeConverter<IconData, String> {
const IconDataTypeConverter();
@override
IconData fromSql(String fromDb) {
final json = jsonDecode(fromDb) as Map<String, dynamic>;
return const IconDataJsonConverter().fromJson(json);
}
@override
String toSql(IconData value) {
assert(
value.fontFamily != null,
'Font family must be provided to identify icon',
);
return jsonEncode(const IconDataJsonConverter().toJson(value));
}
}
@@ -0,0 +1,58 @@
import 'package:lexo_rank/lexo_rank.dart';
import 'package:lexo_rank/lexo_rank/lexo_rank_bucket.dart';
import 'package:sqlite3/common.dart';
String _nextRankOrMiddle(List<Object?> args) {
final parsedBucket = LexoRankBucket.resolve(args[0]! as int);
if (args[1] != null) {
final parsedRank = LexoRank.parse(args[1]! as String);
return parsedRank.genNext().value;
} else {
return LexoRank.middle(bucket: parsedBucket).value;
}
}
String _previousRankOrMiddle(List<Object?> args) {
final parsedBucket = LexoRankBucket.resolve(args[0]! as int);
if (args[1] != null) {
final parsedRank = LexoRank.parse(args[1]! as String);
return parsedRank.genPrev().value;
} else {
return LexoRank.middle(bucket: parsedBucket).value;
}
}
String _reorderAfter(List<Object?> args) {
final first = (args[0] != null) ? LexoRank.parse(args[0]! as String) : null;
final last = (args[1] != null) ? LexoRank.parse(args[1]! as String) : null;
if (first == null) {
throw Exception('Tab not found');
} else if (last == null) {
return first.genNext().value;
} else {
return first.genBetween(last).value;
}
}
void registerLexorankFunctions(CommonDatabase database) {
database.createFunction(
functionName: 'lexo_rank_next',
argumentCount: const AllowedArgumentCount(2),
function: _nextRankOrMiddle,
);
database.createFunction(
functionName: 'lexo_rank_previous',
argumentCount: const AllowedArgumentCount(2),
function: _previousRankOrMiddle,
);
database.createFunction(
functionName: 'lexo_rank_reorder_after',
argumentCount: const AllowedArgumentCount(2),
function: _reorderAfter,
);
}
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -10,8 +11,6 @@ part 'providers.g.dart';
GeckoEventService eventService(EventServiceRef ref) {
final service = GeckoEventService.setUp();
unawaited(GeckoTabService().syncEvents());
ref.onDispose(() {
service.dispose();
});
@@ -19,6 +18,41 @@ GeckoEventService eventService(EventServiceRef ref) {
return service;
}
@Riverpod(keepAlive: true)
class EngineReadyState extends _$EngineReadyState {
@override
bool build() {
final eventService = ref.watch(eventServiceProvider);
final currentState =
eventService.engineReadyStateEvents.valueOrNull ?? false;
if (!currentState) {
unawaited(
eventService.engineReadyStateEvents
.firstWhere((value) => value == true)
.timeout(
const Duration(seconds: 5),
onTimeout: () {
logger.w('Waiting for engine ready state timed out');
return true;
},
).whenComplete(() => state = true),
);
}
final sub = eventService.engineReadyStateEvents.listen((value) {
state = value;
});
ref.onDispose(() async {
await sub.cancel();
});
return currentState;
}
}
@Riverpod()
TabSession selectedTabSessionNotifier(SelectedTabSessionNotifierRef ref) {
return ref.watch(tabSessionProvider(null).notifier);
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$eventServiceHash() => r'c36c6d895629002945c7b4eafba8389771e2eb2d';
String _$eventServiceHash() => r'5aa357fdf0d217677a9a66ecb50417ac18929cad';
/// See also [eventService].
@ProviderFor(eventService)
@@ -37,5 +37,21 @@ final selectedTabSessionNotifierProvider =
);
typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef<TabSession>;
String _$engineReadyStateHash() => r'c682333e2e07cf0635aa7ae793a2088ca648c950';
/// See also [EngineReadyState].
@ProviderFor(EngineReadyState)
final engineReadyStateProvider =
NotifierProvider<EngineReadyState, bool>.internal(
EngineReadyState.new,
name: r'engineReadyStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$engineReadyStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$EngineReadyState = Notifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,8 +1,9 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
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:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_tab.g.dart';
@@ -13,21 +14,29 @@ class SelectedTab extends _$SelectedTab {
@override
String? build() {
_db = ref.watch(tabDatabaseProvider);
final eventSerivce = ref.watch(eventServiceProvider);
final selectedTabSub = eventSerivce.selectedTabEvents.listen(
(tabId) {
state = tabId;
_db = ref.watch(tabDatabaseProvider);
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(previous, next) async {
if (next) {
await GeckoTabService().syncEvents(onSelectedTabChange: true);
}
},
);
ref.listenSelf((previous, next) async {
if (next != null) {
await _db.tabLinkDao.touchTabLink(next, timestamp: DateTime.now());
}
});
final selectedTabSub = eventSerivce.selectedTabEvents.listen(
(tabId) async {
state = tabId;
if (tabId != null) {
await _db.tabDao.touchTab(tabId, timestamp: DateTime.now());
}
},
);
ref.onDispose(() {
unawaited(selectedTabSub.cancel());
@@ -6,7 +6,7 @@ part of 'selected_tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$selectedTabHash() => r'd57003aa20d8a6f711918821ae355e5537a55685';
String _$selectedTabHash() => r'e49fb1c3938a97f384673e158742e56479aa8a8f';
/// See also [SelectedTab].
@ProviderFor(SelectedTab)
@@ -0,0 +1,53 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab_list.g.dart';
@Riverpod(keepAlive: true)
class TabList extends _$TabList {
late TabDatabase _db;
List<String> build() {
final eventService = ref.watch(eventServiceProvider);
_db = ref.watch(tabDatabaseProvider);
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(previous, next) async {
if (next) {
await GeckoTabService().syncEvents(onTabListChange: true);
}
},
);
final tabListSub = eventService.tabListEvents.listen(
(tabs) async {
if (!const DeepCollectionEquality().equals(state, tabs)) {
//Make sure we only syncing empty lists when there has been tabs
//before as security measurement
final syncTabs = tabs.isNotEmpty || state.isNotEmpty;
state = tabs;
if (syncTabs) {
await _db.tabDao.syncTabs(retainTabIds: tabs);
}
}
},
);
ref.onDispose(() {
unawaited(tabListSub.cancel());
});
return [];
}
}
@@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$tabListHash() => r'fd97d4692b1c74e91c5ebd4a75e6ff45ceb82ef9';
/// See also [TabList].
@ProviderFor(TabList)
final tabListProvider = NotifierProvider<TabList, List<String>>.internal(
TabList.new,
name: r'tabListProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$tabListHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TabList = Notifier<List<String>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -33,6 +33,10 @@ class TabSession extends _$TabSession {
return _sessionService.goForward();
}
Future<void> exitFullscreen() {
return _sessionService.exitFullscreen();
}
Future<Uint8List?> requestScreenshot() {
return _sessionService.requestScreenshot();
}
@@ -6,7 +6,7 @@ part of 'tab_session.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabSessionHash() => r'25afbead5c1110ca865e48a5009da70eb20d3a28';
String _$tabSessionHash() => r'29b20f40af01e599ef80e198aec5f093d74efc6a';
/// Copied from Dart SDK
class _SystemHash {
@@ -1,6 +1,5 @@
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';
@@ -10,33 +9,25 @@ 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';
import 'package:synchronized/synchronized.dart';
part 'tab_state.g.dart';
@Riverpod(keepAlive: true)
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),
};
}
final _lock = Lock();
void _onTabContentStateChange(TabContentState contentState) {
final current =
state[contentState.id] ?? TabState.$default(contentState.id);
state = {...state}..[contentState.id] = current.copyWith(
contextId: contentState.contextId,
url: Uri.parse(contentState.url),
title: contentState.title,
title: (contentState.title.isNotEmpty)
? contentState.title
: current.title,
progress: contentState.progress,
isPrivate: contentState.isPrivate,
isFullScreen: contentState.isFullScreen,
@@ -47,26 +38,24 @@ class TabStates extends _$TabStates {
Future<void> _onIconChange(IconEvent event) async {
final IconEvent(:tabId, :bytes) = event;
final current = state[tabId] ?? TabState.$default(tabId);
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.icon(image);
}
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
final ThumbnailEvent(:tabId, :bytes) = event;
final current = state[tabId] ?? TabState.$default(tabId);
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
}
@@ -74,7 +63,6 @@ class TabStates extends _$TabStates {
final SecurityInfoEvent(:tabId, :securityInfo) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.securityInfoState(
SecurityState(
secure: securityInfo.secure,
@@ -88,7 +76,6 @@ class TabStates extends _$TabStates {
final HistoryEvent(:tabId, :history) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.historyState(
HistoryState(
items: history.items.nonNulls
@@ -108,7 +95,6 @@ class TabStates extends _$TabStates {
final ReaderableEvent(:tabId, :readerable) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.readerableState(
ReaderableState(
readerable: readerable.readerable,
@@ -117,13 +103,13 @@ class TabStates extends _$TabStates {
);
}
void _onFindResults(FindResultsEvent event) {
void _onFindResultsChange(FindResultsEvent event) {
final FindResultsEvent(:tabId, :results) = event;
if (results.isNotEmpty) {
final current = state[tabId] ?? TabState.$default(tabId);
final result = results.last;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.findResultState(
FindResultState(
activeMatchOrdinal: result.activeMatchOrdinal,
@@ -134,64 +120,61 @@ class TabStates extends _$TabStates {
}
}
Future<String> addTab({
Uri? url,
bool selectTab = true,
bool startLoading = true,
String? parentId,
LoadUrlFlags flags = LoadUrlFlags.NONE,
String? contextId,
Source source = Internal.newTab,
bool private = false,
HistoryMetadataKey? historyMetadata,
Map<String, String>? additionalHeaders,
}) {
return _tabsService.addTab(
url: url,
selectTab: selectTab,
startLoading: startLoading,
parentId: parentId,
flags: flags,
contextId: contextId,
source: source,
private: private,
historyMetadata: historyMetadata,
additionalHeaders: additionalHeaders,
);
}
Future<void> closeTabs(List<String> tabIds) {
return _tabsService.removeTabs(ids: tabIds);
}
Future<void> closeTab(String tabIds) {
return _tabsService.removeTab(tabId: tabIds);
}
@override
Map<String, TabState> build() {
final eventService = ref.watch(eventServiceProvider);
_db = ref.watch(tabDatabaseProvider);
final subscriptions = [
eventService.tabListEvents.listen(_onTabListChange),
eventService.tabContentEvents.listen(_onTabContentStateChange),
eventService.iconEvents.listen(_onIconChange),
eventService.thumbnailEvents.listen(_onThumbnailChange),
eventService.securityInfoEvents.listen(_onSecurityInfoStateChange),
eventService.historyEvents.listen(_onHistoryStateChange),
eventService.readerableEvents.listen(_onReaderableStateChange),
eventService.findResultsEvent.listen(_onFindResults),
eventService.tabContentEvents.listen(
(event) async {
await _lock.synchronized(() => _onTabContentStateChange(event));
},
),
eventService.iconEvents.listen(
(event) async {
await _lock.synchronized(() => _onIconChange(event));
},
),
eventService.thumbnailEvents.listen(
(event) async {
await _lock.synchronized(() => _onThumbnailChange(event));
},
),
eventService.securityInfoEvents.listen(
(event) async {
await _lock.synchronized(() => _onSecurityInfoStateChange(event));
},
),
eventService.historyEvents.listen(
(event) async {
await _lock.synchronized(() => _onHistoryStateChange(event));
},
),
eventService.readerableEvents.listen(
(event) async {
await _lock.synchronized(() => _onReaderableStateChange(event));
},
),
eventService.findResultsEvent.listen(
(event) async {
await _lock.synchronized(() => _onFindResultsChange(event));
},
),
];
ref.listenSelf(
ref.listen(
fireImmediately: true,
engineReadyStateProvider,
(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);
}
if (next) {
await GeckoTabService().syncEvents(
onTabContentStateChange: true,
onIconChange: true,
onThumbnailChange: true,
onSecurityInfoStateChange: true,
onHistoryStateChange: true,
onFindResults: true,
);
}
},
);
@@ -169,7 +169,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
);
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$tabStatesHash() => r'f3ae09e49150955dcb29ec89f0f3fe63155a1d6e';
String _$tabStatesHash() => r'dcd293279a901eeaab7728979e39e5ca3bee8c66';
/// See also [TabStates].
@ProviderFor(TabStates)
@@ -1,6 +1,11 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab.g.dart';
@@ -9,6 +14,8 @@ part 'tab.g.dart';
class TabRepository extends _$TabRepository {
final _tabsService = GeckoTabService();
late TabDatabase _db;
Future<String> addTab({
Uri? url,
bool selectTab = true,
@@ -48,5 +55,20 @@ class TabRepository extends _$TabRepository {
}
@override
void build() {}
void build() {
final eventSerivce = ref.watch(eventServiceProvider);
_db = ref.watch(tabDatabaseProvider);
final tabAddedSub = eventSerivce.tabAddedStream.listen(
(tabId) async {
final containerId = ref.read(selectedContainerProvider);
await _db.tabDao.upsertTab(tabId, containerId: Value(containerId));
},
);
ref.onDispose(() {
unawaited(tabAddedSub.cancel());
});
}
}
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabRepositoryHash() => r'0abf38ef42f29015d81ccd11eb5990b741ebc616';
String _$tabRepositoryHash() => r'05040223b83cd5ff80afaf89407043a10e4777cb';
/// See also [TabRepository].
@ProviderFor(TabRepository)
@@ -2,11 +2,10 @@
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/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -85,27 +84,15 @@ class ShowFindInPage extends _$ShowFindInPage {
}
@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;
List<String> availableTabIds(
AvailableTabIdsRef ref,
String? containerId,
) {
final containerTabs = ref.watch(
containerTabIdsProvider(containerId).select((value) => value.valueOrNull),
);
final tabStates = ref.watch(tabListProvider);
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(),
);
}
return containerTabs?.where((tabId) => tabStates.contains(tabId)).toList() ??
[];
}
@@ -156,23 +156,23 @@ class _SelectedBangDataProviderElement
String? get domain => (origin as SelectedBangDataProvider).domain;
}
String _$availableTabIdsHash() => r'bea231cdf6211fc876b8031b5d72999eae5d7b15';
String _$availableTabIdsHash() => r'427ea2e58c6bc27e2f16cffb850dc6621875e247';
/// See also [availableTabIds].
@ProviderFor(availableTabIds)
const availableTabIdsProvider = AvailableTabIdsFamily();
/// See also [availableTabIds].
class AvailableTabIdsFamily extends Family<AsyncValue<List<String>>> {
class AvailableTabIdsFamily extends Family<List<String>> {
/// See also [availableTabIds].
const AvailableTabIdsFamily();
/// See also [availableTabIds].
AvailableTabIdsProvider call(
String? topicId,
String? containerId,
) {
return AvailableTabIdsProvider(
topicId,
containerId,
);
}
@@ -181,7 +181,7 @@ class AvailableTabIdsFamily extends Family<AsyncValue<List<String>>> {
covariant AvailableTabIdsProvider provider,
) {
return call(
provider.topicId,
provider.containerId,
);
}
@@ -201,14 +201,14 @@ class AvailableTabIdsFamily extends Family<AsyncValue<List<String>>> {
}
/// See also [availableTabIds].
class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
/// See also [availableTabIds].
AvailableTabIdsProvider(
String? topicId,
String? containerId,
) : this._internal(
(ref) => availableTabIds(
ref as AvailableTabIdsRef,
topicId,
containerId,
),
from: availableTabIdsProvider,
name: r'availableTabIdsProvider',
@@ -219,7 +219,7 @@ class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
dependencies: AvailableTabIdsFamily._dependencies,
allTransitiveDependencies:
AvailableTabIdsFamily._allTransitiveDependencies,
topicId: topicId,
containerId: containerId,
);
AvailableTabIdsProvider._internal(
@@ -229,14 +229,14 @@ class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.topicId,
required this.containerId,
}) : super.internal();
final String? topicId;
final String? containerId;
@override
Override overrideWith(
Stream<List<String>> Function(AvailableTabIdsRef provider) create,
List<String> Function(AvailableTabIdsRef provider) create,
) {
return ProviderOverride(
origin: this,
@@ -247,42 +247,41 @@ class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
topicId: topicId,
containerId: containerId,
),
);
}
@override
AutoDisposeStreamProviderElement<List<String>> createElement() {
AutoDisposeProviderElement<List<String>> createElement() {
return _AvailableTabIdsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is AvailableTabIdsProvider && other.topicId == topicId;
return other is AvailableTabIdsProvider && other.containerId == containerId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, topicId.hashCode);
hash = _SystemHash.combine(hash, containerId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin AvailableTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
/// The parameter `topicId` of this provider.
String? get topicId;
mixin AvailableTabIdsRef on AutoDisposeProviderRef<List<String>> {
/// The parameter `containerId` of this provider.
String? get containerId;
}
class _AvailableTabIdsProviderElement
extends AutoDisposeStreamProviderElement<List<String>>
with AvailableTabIdsRef {
extends AutoDisposeProviderElement<List<String>> with AvailableTabIdsRef {
_AvailableTabIdsProviderElement(super.provider);
@override
String? get topicId => (origin as AvailableTabIdsProvider).topicId;
String? get containerId => (origin as AvailableTabIdsProvider).containerId;
}
String _$selectedBangTriggerHash() =>
@@ -5,8 +5,8 @@ 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';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
class TabActionDialog extends HookConsumerWidget {
final TabState initialTab;
@@ -22,20 +22,21 @@ class TabActionDialog extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
final tabTopicId = ref.watch(
tabTopicIdProvider(initialTab.id).select((value) => value.valueOrNull),
final tabContainerId = ref.watch(
tabContainerIdProvider(initialTab.id)
.select((value) => value.valueOrNull),
);
final topics = ref
final containers = ref
.watch(
topicsWithCountProvider.select(
containersWithCountProvider.select(
(value) => EquatableCollection(value.valueOrNull, immutable: true),
),
)
.collection;
final selectedTopic =
topics?.firstWhereOrNull((topic) => topic.id == tabTopicId);
final selectedContainer = containers
?.firstWhereOrNull((container) => container.id == tabContainerId);
final expansionController = useExpansionTileController();
@@ -68,22 +69,23 @@ class TabActionDialog extends HookConsumerWidget {
width: double.maxFinite,
child: ExpansionTile(
controller: expansionController,
leading: (selectedTopic != null)
? CircleAvatar(backgroundColor: selectedTopic.color)
leading: (selectedContainer != null)
? CircleAvatar(backgroundColor: selectedContainer.color)
: null,
title: (selectedTopic != null)
? Text(selectedTopic.name ?? 'New Topic')
: const Text('Assign a Topic'),
children: topics
?.where((topic) => topic.id != tabTopicId)
title: (selectedContainer != null)
? Text(selectedContainer.name ?? 'New Container')
: const Text('Assign a Container'),
children: containers
?.where((container) => container.id != tabContainerId)
.map(
(topic) => ListTile(
leading: CircleAvatar(backgroundColor: topic.color),
title: Text(topic.name ?? 'New Topic'),
(container) => ListTile(
leading:
CircleAvatar(backgroundColor: container.color),
title: Text(container.name ?? 'New Container'),
onTap: () async {
await ref
.read(tabLinkRepositoryProvider.notifier)
.assignTab(initialTab.id, topic.id);
.read(tabDataRepositoryProvider.notifier)
.assignContainer(initialTab.id, container.id);
expansionController.collapse();
},
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:text_scroll/text_scroll.dart';
class AppBarTitle extends StatelessWidget {
@@ -23,11 +24,16 @@ class AppBarTitle extends StatelessWidget {
color: Theme.of(context).colorScheme.errorContainer,
size: 14,
);
} else {
} else if (!tab.isLoading) {
return const Icon(
MdiIcons.lock,
size: 14,
);
} else {
return const Icon(
MdiIcons.timerSand,
size: 14,
);
}
}
@@ -38,10 +44,18 @@ class AppBarTitle extends StatelessWidget {
onTap: onTap,
child: Row(
children: [
RawImage(
image: tab.icon?.value,
height: 16,
width: 16,
Skeletonizer(
enabled: tab.icon == null,
child: Skeleton.replace(
replacement: const Bone.icon(
size: 16,
),
child: RawImage(
image: tab.icon?.value,
height: 16,
width: 16,
),
),
),
const SizedBox(
width: 8,
@@ -51,20 +65,29 @@ class AppBarTitle extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
TextScroll(
key: ValueKey(tab.title),
tab.title,
style: theme.textTheme.bodyLarge
?.copyWith(color: theme.colorScheme.onSurface),
// mode: TextScrollMode.bouncing,
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
delayBefore: const Duration(milliseconds: 500),
pauseBetween: const Duration(milliseconds: 5000),
fadedBorder: true,
fadeBorderSide: FadeBorderSide.right,
fadedBorderWidth: 0.05,
intervalSpaces: 4,
numberOfReps: 2,
Skeletonizer(
enabled: tab.title.isEmpty,
child: Skeleton.replace(
replacement: const Padding(
padding: EdgeInsets.only(right: 4, top: 1, bottom: 1),
child: Bone.text(),
),
child: TextScroll(
key: ValueKey(tab.title),
tab.title,
style: theme.textTheme.bodyLarge
?.copyWith(color: theme.colorScheme.onSurface),
// mode: TextScrollMode.bouncing,
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
delayBefore: const Duration(milliseconds: 500),
pauseBetween: const Duration(milliseconds: 5000),
fadedBorder: true,
fadeBorderSide: FadeBorderSide.right,
fadedBorderWidth: 0.05,
intervalSpaces: 4,
numberOfReps: 2,
),
),
),
Row(
children: [
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -11,9 +12,11 @@ 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/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';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
import 'package:reorderable_grid/reorderable_grid.dart';
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
static const _headerSize = 104.0;
@@ -52,10 +55,10 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
),
TextButton.icon(
onPressed: () async {
final topic = ref.read(selectedTopicProvider);
final container = ref.read(selectedContainerProvider);
await ref
.read(tabLinkRepositoryProvider.notifier)
.closeAllTabs(topic);
.read(tabDataRepositoryProvider.notifier)
.closeAllTabs(container);
onClose();
},
@@ -64,7 +67,7 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
),
],
),
TopicChips(),
ContainerChips(),
const SizedBox(height: 8),
],
),
@@ -125,14 +128,11 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
),
HookConsumer(
builder: (context, ref, child) {
final topic = ref.watch(selectedTopicProvider);
final container = ref.watch(selectedContainerProvider);
final availableTabs = ref
.watch(
availableTabIdsProvider(topic).select(
(value) => EquatableCollection(
value.valueOrNull ?? [],
immutable: true,
),
availableTabIdsProvider(container).select(
(value) => EquatableCollection(value, immutable: true),
),
)
.collection;
@@ -156,8 +156,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
availableTabs.indexWhere((webView) => webView == activeTab);
if (index > -1) {
final reversedIndex = availableTabs.length - 1 - index;
final offset = (reversedIndex ~/ 2) * itemHeight;
final offset = (index ~/ 2) * itemHeight;
if (offset != sheetScrollController.offset) {
unawaited(
@@ -175,21 +174,15 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
[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),
final tabs = useMemoized(
() => availableTabs
.mapIndexed(
(index, tabId) => ReorderableGridDelayedDragStartListener(
key: ValueKey(tabId),
index: index,
child: Consumer(
builder: (context, ref, child) {
final tab = ref.watch(tabStateProvider(tabId));
return (tab != null)
? TabPreview(
tab: tab,
@@ -207,7 +200,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
onClose();
}
},
onLongPress: () {
onDoubleTap: () {
ref
.read(
overlayDialogControllerProvider
@@ -236,8 +229,52 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
: const SizedBox.shrink();
},
),
)
.toList(),
),
)
.toList(),
[availableTabs, activeTab],
);
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
sliver: SliverReorderableGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
//Sync values for itemHeight calculation _calculateItemHeight
childAspectRatio: 0.75,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
crossAxisCount: 2,
),
itemCount: tabs.length,
itemBuilder: (context, index) => tabs[index],
onReorder: (oldIndex, newIndex) async {
final containerRepository =
ref.read(containerRepositoryProvider.notifier);
final tabId = availableTabs[oldIndex];
final containerId = await ref
.read(tabDataRepositoryProvider.notifier)
.containerTabId(tabId);
final String key;
if (newIndex <= 0) {
key = await containerRepository
.getLeadingOrderKey(containerId);
} else if (newIndex >= availableTabs.length - 1) {
key = await containerRepository
.getTrailingOrderKey(containerId);
} else {
final orderAfterIndex = newIndex - 1;
key = await containerRepository.getOrderKeyAfterTab(
availableTabs[orderAfterIndex],
containerId,
);
}
await ref
.read(tabDataRepositoryProvider.notifier)
.assignOrderKey(tabId, key);
},
),
);
},
@@ -6,14 +6,14 @@ class TabPreview extends StatelessWidget {
final bool isActive;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final VoidCallback? onDoubleTap;
final VoidCallback? onDelete;
const TabPreview({
required this.tab,
required this.isActive,
this.onTap,
this.onLongPress,
this.onDoubleTap,
this.onDelete,
super.key,
});
@@ -35,7 +35,7 @@ class TabPreview extends StatelessWidget {
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
onTap: onTap,
onLongPress: onLongPress,
onDoubleTap: onDoubleTap,
child: Column(
children: [
Row(
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
class TabsActionButton extends HookConsumerWidget {
final bool isActive;
@@ -16,7 +16,7 @@ class TabsActionButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final tabCount = ref.watch(tabStatesProvider.select((tabs) => tabs.length));
final tabCount = ref.watch(tabListProvider.select((tabs) => tabs.length));
return InkWell(
onTap: onTap,
File diff suppressed because it is too large Load Diff
@@ -11,6 +11,7 @@ class ReaderableScreenController extends _$ReaderableScreenController {
Future<void> toggleReaderView(bool enable) async {
state = const AsyncValue.loading();
final eventChange = ref.read(eventServiceProvider).readerableEvents.first;
final toggle = _service.toggleReaderView(enable);
@@ -0,0 +1,76 @@
import 'dart:ui';
import 'package:drift/drift.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
part 'container.g.dart';
@DriftAccessor()
class ContainerDao extends DatabaseAccessor<TabDatabase>
with _$ContainerDaoMixin {
ContainerDao(super.db);
Future<void> addContainer({String? name, required Color color}) {
return db.container.insertOne(
ContainerCompanion.insert(
id: uuid.v7(),
name: Value(name),
color: color,
),
);
}
Future<void> replaceContainer(
String id, {
required String? name,
required Color color,
}) {
return db.container.replaceOne(
ContainerCompanion(
id: Value(id),
name: Value(name),
color: Value(color),
),
);
}
Future<void> deleteContainer(String id) {
return db.container.deleteOne(ContainerCompanion.custom(id: Variable(id)));
}
SingleOrNullSelectable<ContainerData> getContainerData(String id) {
return select(db.container)..where((t) => t.id.equals(id));
}
Selectable<Color> getDistinctColors() {
final query = db.selectOnly(db.container, distinct: true)
..addColumns([db.container.color])
..where(db.container.color.isNotNull());
return query
.map((row) => row.readWithConverter<Color?, int>(db.container.color)!);
}
SingleSelectable<String> generateLeadingOrderKey(
String? containerId, {
int bucket = 0,
}) {
return db.leadingOrderKey(bucket: bucket, containerId: containerId);
}
SingleSelectable<String> generateTrailingOrderKey(
String? containerId, {
int bucket = 0,
}) {
return db.trailingOrderKey(bucket: bucket, containerId: containerId);
}
SingleSelectable<String> generateOrderKeyAfterTabId(
String? containerId,
String tabId,
) {
return db.orderKeyAfterTab(containerId: containerId, tabId: tabId);
}
}
@@ -0,0 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'container.dart';
// ignore_for_file: type=lint
mixin _$ContainerDaoMixin on DatabaseAccessor<TabDatabase> {}
@@ -0,0 +1,128 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lexo_rank/lexo_rank.dart';
part 'tab.g.dart';
@DriftAccessor()
class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
TabDao(super.db);
Selectable<String> containerTabIds(String? containerId) {
final query = selectOnly(db.tab)
..addColumns([db.tab.id])
..where(
(containerId != null)
? db.tab.containerId.equals(containerId)
: db.tab.containerId.isNull(),
)
..orderBy([OrderingTerm.asc(db.tab.orderKey)]);
return query.map((row) => row.read(db.tab.id)!);
}
Selectable<String> allTabIds() {
final query = selectOnly(db.tab)
..addColumns([db.tab.id])
..orderBy([OrderingTerm.asc(db.tab.orderKey)]);
return query.map((row) => row.read(db.tab.id)!);
}
SingleSelectable<String?> tabContainerId(String tabId) {
final query = selectOnly(db.tab)
..addColumns([db.tab.containerId])
..where(db.tab.id.equals(tabId));
return query.map((row) => row.read(db.tab.containerId));
}
Future<String> upsertTab(
String tabId, {
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
}) {
return db.transaction(() async {
final currentOrderKey = orderKey.value ??
await db.containerDao
.generateLeadingOrderKey(containerId.value)
.getSingle();
await db.tab.insertOne(
TabCompanion.insert(
id: tabId,
timestamp: DateTime.now(),
containerId: containerId,
orderKey: currentOrderKey,
),
onConflict: DoUpdate(
(old) => TabCompanion.custom(
containerId:
(containerId.present) ? Variable(containerId.value) : null,
orderKey: (orderKey.present) ? Variable(orderKey.value) : null,
),
),
);
return tabId;
});
}
Future<void> assignContainer(
String id, {
required String? containerId,
}) {
final statement = db.tab.update()..where((t) => t.id.equals(id));
return statement.write(
TabCompanion(containerId: Value(containerId)),
);
}
Future<void> assignOrderKey(
String id, {
required String orderKey,
}) {
final statement = db.tab.update()..where((t) => t.id.equals(id));
return statement.write(
TabCompanion(orderKey: Value(orderKey)),
);
}
Future<void> touchTab(
String id, {
required DateTime timestamp,
}) {
final statement = db.tab.update()..where((t) => t.id.equals(id));
return statement.write(
TabCompanion(timestamp: Value(timestamp)),
);
}
Future<void> syncTabs({required List<String> retainTabIds}) async {
return db.transaction(() async {
await (db.tab.delete()..where((t) => t.id.isNotIn(retainTabIds))).go();
var currentOrderKey =
await db.containerDao.generateLeadingOrderKey(null).getSingle();
await db.tab.insertAll(
retainTabIds.map(
(id) {
final insertable = TabCompanion.insert(
id: id,
orderKey: currentOrderKey,
timestamp: DateTime.now(),
);
currentOrderKey = LexoRank.parse(currentOrderKey).genPrev().value;
return insertable;
},
),
mode: InsertMode.insertOrIgnore,
);
});
}
}
@@ -3,4 +3,4 @@
part of 'tab.dart';
// ignore_for_file: type=lint
mixin _$TabLinkDaoMixin on DatabaseAccessor<TabDatabase> {}
mixin _$TabDaoMixin on DatabaseAccessor<TabDatabase> {}
@@ -1,17 +1,17 @@
import 'dart:ui';
import 'package:drift/drift.dart';
import 'package:flutter/foundation.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/daos/tab.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/daos/topic.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/drift/converters/color.dart';
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
import 'package:flutter/widgets.dart' show Color, IconData;
import 'package:lensai/data/database/converters/color.dart';
import 'package:lensai/data/database/converters/icon_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/container.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
part 'database.g.dart';
@DriftDatabase(
include: {'database.drift'},
daos: [TopicDao, TabLinkDao],
daos: [ContainerDao, TabDao],
)
class TabDatabase extends _$TabDatabase {
@override
@@ -25,13 +25,11 @@ class TabDatabase extends _$TabDatabase {
if (from < 2) {
await transaction(() async {
await m.renameTable(tabLink, 'tab');
// await m.dropColumn(tabLink, 'url');
// await m.dropColumn(tabLink, 'title');
// await m.dropColumn(tabLink, 'screenshot');
await m.dropColumn(tabLink, 'url');
await m.dropColumn(tabLink, 'title');
await m.dropColumn(tabLink, 'screenshot');
await m.alterTable(TableMigration(tabLink));
// await m.alterTable(TableMigration(tab));
});
}
@@ -0,0 +1,69 @@
import 'package:lensai/data/database/converters/color.dart';
import 'package:lensai/data/database/converters/icon_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
CREATE TABLE container (
id TEXT PRIMARY KEY NOT NULL,
contextual_identity TEXT,
name TEXT,
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`,
icon TEXT MAPPED BY `const IconDataTypeConverter()`
) WITH ContainerData;
CREATE TABLE tab (
id TEXT PRIMARY KEY NOT NULL,
container_id TEXT REFERENCES container (id) ON DELETE CASCADE,
order_key TEXT NOT NULL,
timestamp DATETIME NOT NULL
);
containersWithCount WITH ContainerDataWithCount:
SELECT
container.*,
tab_agg.tab_count
FROM container
LEFT JOIN (
SELECT
container_id,
COUNT(*) AS tab_count,
MAX(timestamp) AS last_updated
FROM tab
GROUP BY container_id
) AS tab_agg ON container.id = tab_agg.container_id
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
leadingOrderKey(:container_id AS TEXT OR NULL, :bucket AS INTEGER):
SELECT lexo_rank_previous(
:bucket,
(
SELECT order_key
FROM tab
WHERE container_id IS :container_id
ORDER BY order_key
LIMIT 1
)
);
trailingOrderKey(:container_id AS TEXT OR NULL, :bucket AS INTEGER):
SELECT lexo_rank_next(
:bucket,
(
SELECT order_key
FROM tab
WHERE container_id IS :container_id
ORDER BY order_key DESC
LIMIT 1
)
);
orderKeyAfterTab(:tab_id AS TEXT, :container_id AS TEXT OR NULL):
WITH ordered_table AS (
SELECT id,
order_key,
LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key
FROM tab
WHERE container_id IS :container_id
)
SELECT lexo_rank_reorder_after(order_key, next_order_key)
FROM ordered_table
WHERE id = :tab_id;
@@ -0,0 +1,996 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'database.dart';
// ignore_for_file: type=lint
class Container extends Table with TableInfo<Container, ContainerData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Container(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL');
late final GeneratedColumn<String> contextualIdentity =
GeneratedColumn<String>('contextual_identity', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
late final GeneratedColumnWithTypeConverter<Color, int> color =
GeneratedColumn<int>('color', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL')
.withConverter<Color>(Container.$convertercolor);
late final GeneratedColumnWithTypeConverter<IconData?, String> icon =
GeneratedColumn<String>('icon', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '')
.withConverter<IconData?>(Container.$convertericonn);
@override
List<GeneratedColumn> get $columns =>
[id, contextualIdentity, name, color, icon];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'container';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
ContainerData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return ContainerData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
contextualIdentity: attachedDatabase.typeMapping.read(
DriftSqlType.string, data['${effectivePrefix}contextual_identity']),
name: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}name']),
color: Container.$convertercolor.fromSql(attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}color'])!),
icon: Container.$convertericonn.fromSql(attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}icon'])),
);
}
@override
Container createAlias(String alias) {
return Container(attachedDatabase, alias);
}
static TypeConverter<Color, int> $convertercolor = const ColorConverter();
static TypeConverter<IconData, String> $convertericon =
const IconDataTypeConverter();
static TypeConverter<IconData?, String?> $convertericonn =
NullAwareTypeConverter.wrap($convertericon);
@override
bool get dontWriteConstraints => true;
}
class ContainerCompanion extends UpdateCompanion<ContainerData> {
final Value<String> id;
final Value<String?> contextualIdentity;
final Value<String?> name;
final Value<Color> color;
final Value<IconData?> icon;
final Value<int> rowid;
const ContainerCompanion({
this.id = const Value.absent(),
this.contextualIdentity = const Value.absent(),
this.name = const Value.absent(),
this.color = const Value.absent(),
this.icon = const Value.absent(),
this.rowid = const Value.absent(),
});
ContainerCompanion.insert({
required String id,
this.contextualIdentity = const Value.absent(),
this.name = const Value.absent(),
required Color color,
this.icon = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
color = Value(color);
static Insertable<ContainerData> custom({
Expression<String>? id,
Expression<String>? contextualIdentity,
Expression<String>? name,
Expression<int>? color,
Expression<String>? icon,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (contextualIdentity != null) 'contextual_identity': contextualIdentity,
if (name != null) 'name': name,
if (color != null) 'color': color,
if (icon != null) 'icon': icon,
if (rowid != null) 'rowid': rowid,
});
}
ContainerCompanion copyWith(
{Value<String>? id,
Value<String?>? contextualIdentity,
Value<String?>? name,
Value<Color>? color,
Value<IconData?>? icon,
Value<int>? rowid}) {
return ContainerCompanion(
id: id ?? this.id,
contextualIdentity: contextualIdentity ?? this.contextualIdentity,
name: name ?? this.name,
color: color ?? this.color,
icon: icon ?? this.icon,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (contextualIdentity.present) {
map['contextual_identity'] = Variable<String>(contextualIdentity.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (color.present) {
map['color'] =
Variable<int>(Container.$convertercolor.toSql(color.value));
}
if (icon.present) {
map['icon'] =
Variable<String>(Container.$convertericonn.toSql(icon.value));
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('ContainerCompanion(')
..write('id: $id, ')
..write('contextualIdentity: $contextualIdentity, ')
..write('name: $name, ')
..write('color: $color, ')
..write('icon: $icon, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
class Tab extends Table with TableInfo<Tab, TabData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Tab(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL');
late final GeneratedColumn<String> containerId = GeneratedColumn<String>(
'container_id', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: 'REFERENCES container(id)ON DELETE CASCADE');
late final GeneratedColumn<String> orderKey = GeneratedColumn<String>(
'order_key', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<DateTime> timestamp = GeneratedColumn<DateTime>(
'timestamp', aliasedName, false,
type: DriftSqlType.dateTime,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
@override
List<GeneratedColumn> get $columns => [id, containerId, orderKey, timestamp];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'tab';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
TabData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TabData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
containerId: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}container_id']),
orderKey: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}order_key'])!,
timestamp: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!,
);
}
@override
Tab createAlias(String alias) {
return Tab(attachedDatabase, alias);
}
@override
bool get dontWriteConstraints => true;
}
class TabData extends DataClass implements Insertable<TabData> {
final String id;
final String? containerId;
final String orderKey;
final DateTime timestamp;
const TabData(
{required this.id,
this.containerId,
required this.orderKey,
required this.timestamp});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<String>(id);
if (!nullToAbsent || containerId != null) {
map['container_id'] = Variable<String>(containerId);
}
map['order_key'] = Variable<String>(orderKey);
map['timestamp'] = Variable<DateTime>(timestamp);
return map;
}
factory TabData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TabData(
id: serializer.fromJson<String>(json['id']),
containerId: serializer.fromJson<String?>(json['container_id']),
orderKey: serializer.fromJson<String>(json['order_key']),
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'container_id': serializer.toJson<String?>(containerId),
'order_key': serializer.toJson<String>(orderKey),
'timestamp': serializer.toJson<DateTime>(timestamp),
};
}
TabData copyWith(
{String? id,
Value<String?> containerId = const Value.absent(),
String? orderKey,
DateTime? timestamp}) =>
TabData(
id: id ?? this.id,
containerId: containerId.present ? containerId.value : this.containerId,
orderKey: orderKey ?? this.orderKey,
timestamp: timestamp ?? this.timestamp,
);
TabData copyWithCompanion(TabCompanion data) {
return TabData(
id: data.id.present ? data.id.value : this.id,
containerId:
data.containerId.present ? data.containerId.value : this.containerId,
orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey,
timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp,
);
}
@override
String toString() {
return (StringBuffer('TabData(')
..write('id: $id, ')
..write('containerId: $containerId, ')
..write('orderKey: $orderKey, ')
..write('timestamp: $timestamp')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, containerId, orderKey, timestamp);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TabData &&
other.id == this.id &&
other.containerId == this.containerId &&
other.orderKey == this.orderKey &&
other.timestamp == this.timestamp);
}
class TabCompanion extends UpdateCompanion<TabData> {
final Value<String> id;
final Value<String?> containerId;
final Value<String> orderKey;
final Value<DateTime> timestamp;
final Value<int> rowid;
const TabCompanion({
this.id = const Value.absent(),
this.containerId = const Value.absent(),
this.orderKey = const Value.absent(),
this.timestamp = const Value.absent(),
this.rowid = const Value.absent(),
});
TabCompanion.insert({
required String id,
this.containerId = const Value.absent(),
required String orderKey,
required DateTime timestamp,
this.rowid = const Value.absent(),
}) : id = Value(id),
orderKey = Value(orderKey),
timestamp = Value(timestamp);
static Insertable<TabData> custom({
Expression<String>? id,
Expression<String>? containerId,
Expression<String>? orderKey,
Expression<DateTime>? timestamp,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (containerId != null) 'container_id': containerId,
if (orderKey != null) 'order_key': orderKey,
if (timestamp != null) 'timestamp': timestamp,
if (rowid != null) 'rowid': rowid,
});
}
TabCompanion copyWith(
{Value<String>? id,
Value<String?>? containerId,
Value<String>? orderKey,
Value<DateTime>? timestamp,
Value<int>? rowid}) {
return TabCompanion(
id: id ?? this.id,
containerId: containerId ?? this.containerId,
orderKey: orderKey ?? this.orderKey,
timestamp: timestamp ?? this.timestamp,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (containerId.present) {
map['container_id'] = Variable<String>(containerId.value);
}
if (orderKey.present) {
map['order_key'] = Variable<String>(orderKey.value);
}
if (timestamp.present) {
map['timestamp'] = Variable<DateTime>(timestamp.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TabCompanion(')
..write('id: $id, ')
..write('containerId: $containerId, ')
..write('orderKey: $orderKey, ')
..write('timestamp: $timestamp, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
abstract class _$TabDatabase extends GeneratedDatabase {
_$TabDatabase(QueryExecutor e) : super(e);
$TabDatabaseManager get managers => $TabDatabaseManager(this);
late final Container container = Container(this);
late final Tab tab = Tab(this);
late final ContainerDao containerDao = ContainerDao(this as TabDatabase);
late final TabDao tabDao = TabDao(this as TabDatabase);
Selectable<ContainerDataWithCount> containersWithCount() {
return customSelect(
'SELECT container.*, tab_agg.tab_count FROM container LEFT JOIN (SELECT container_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY container_id) AS tab_agg ON container.id = tab_agg.container_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
variables: [],
readsFrom: {
container,
tab,
}).map((QueryRow row) => ContainerDataWithCount(
id: row.read<String>('id'),
contextualIdentity: row.readNullable<String>('contextual_identity'),
name: row.readNullable<String>('name'),
color: Container.$convertercolor.fromSql(row.read<int>('color')),
icon: NullAwareTypeConverter.wrapFromSql(
Container.$convertericon, row.readNullable<String>('icon')),
tabCount: row.readNullable<int>('tab_count'),
));
}
Selectable<String> leadingOrderKey(
{required int bucket, String? containerId}) {
return customSelect(
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM tab WHERE container_id IS ?2 ORDER BY order_key LIMIT 1)) AS _c0',
variables: [
Variable<int>(bucket),
Variable<String>(containerId)
],
readsFrom: {
tab,
}).map((QueryRow row) => row.read<String>('_c0'));
}
Selectable<String> trailingOrderKey(
{required int bucket, String? containerId}) {
return customSelect(
'SELECT lexo_rank_next(?1, (SELECT order_key FROM tab WHERE container_id IS ?2 ORDER BY order_key DESC LIMIT 1)) AS _c0',
variables: [
Variable<int>(bucket),
Variable<String>(containerId)
],
readsFrom: {
tab,
}).map((QueryRow row) => row.read<String>('_c0'));
}
Selectable<String> orderKeyAfterTab(
{String? containerId, required String tabId}) {
return customSelect(
'WITH ordered_table AS (SELECT id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM tab WHERE container_id IS ?1) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE id = ?2',
variables: [
Variable<String>(containerId),
Variable<String>(tabId)
],
readsFrom: {
tab,
}).map((QueryRow row) => row.read<String>('_c0'));
}
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [container, tab];
@override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
[
WritePropagation(
on: TableUpdateQuery.onTableName('container',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('tab', kind: UpdateKind.delete),
],
),
],
);
}
typedef $ContainerCreateCompanionBuilder = ContainerCompanion Function({
required String id,
Value<String?> contextualIdentity,
Value<String?> name,
required Color color,
Value<IconData?> icon,
Value<int> rowid,
});
typedef $ContainerUpdateCompanionBuilder = ContainerCompanion Function({
Value<String> id,
Value<String?> contextualIdentity,
Value<String?> name,
Value<Color> color,
Value<IconData?> icon,
Value<int> rowid,
});
final class $ContainerReferences
extends BaseReferences<_$TabDatabase, Container, ContainerData> {
$ContainerReferences(super.$_db, super.$_table, super.$_typedResult);
static MultiTypedResultKey<Tab, List<TabData>> _tabRefsTable(
_$TabDatabase db) =>
MultiTypedResultKey.fromTable(db.tab,
aliasName: $_aliasNameGenerator(db.container.id, db.tab.containerId));
$TabProcessedTableManager get tabRefs {
final manager = $TabTableManager($_db, $_db.tab)
.filter((f) => f.containerId.id($_item.id));
final cache = $_typedResult.readTableOrNull(_tabRefsTable($_db));
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: cache));
}
}
class $ContainerFilterComposer extends Composer<_$TabDatabase, Container> {
$ContainerFilterComposer({
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 contextualIdentity => $composableBuilder(
column: $table.contextualIdentity,
builder: (column) => ColumnFilters(column));
ColumnFilters<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<Color, Color, int> get color =>
$composableBuilder(
column: $table.color,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnWithTypeConverterFilters<IconData?, IconData, String> get icon =>
$composableBuilder(
column: $table.icon,
builder: (column) => ColumnWithTypeConverterFilters(column));
Expression<bool> tabRefs(Expression<bool> Function($TabFilterComposer f) f) {
final $TabFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.tab,
getReferencedColumn: (t) => t.containerId,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$TabFilterComposer(
$db: $db,
$table: $db.tab,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return f(composer);
}
}
class $ContainerOrderingComposer extends Composer<_$TabDatabase, Container> {
$ContainerOrderingComposer({
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 contextualIdentity => $composableBuilder(
column: $table.contextualIdentity,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get color => $composableBuilder(
column: $table.color, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get icon => $composableBuilder(
column: $table.icon, builder: (column) => ColumnOrderings(column));
}
class $ContainerAnnotationComposer extends Composer<_$TabDatabase, Container> {
$ContainerAnnotationComposer({
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 contextualIdentity => $composableBuilder(
column: $table.contextualIdentity, 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);
GeneratedColumnWithTypeConverter<IconData?, String> get icon =>
$composableBuilder(column: $table.icon, builder: (column) => column);
Expression<T> tabRefs<T extends Object>(
Expression<T> Function($TabAnnotationComposer a) f) {
final $TabAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.tab,
getReferencedColumn: (t) => t.containerId,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$TabAnnotationComposer(
$db: $db,
$table: $db.tab,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return f(composer);
}
}
class $ContainerTableManager extends RootTableManager<
_$TabDatabase,
Container,
ContainerData,
$ContainerFilterComposer,
$ContainerOrderingComposer,
$ContainerAnnotationComposer,
$ContainerCreateCompanionBuilder,
$ContainerUpdateCompanionBuilder,
(ContainerData, $ContainerReferences),
ContainerData,
PrefetchHooks Function({bool tabRefs})> {
$ContainerTableManager(_$TabDatabase db, Container table)
: super(TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$ContainerFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$ContainerOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$ContainerAnnotationComposer($db: db, $table: table),
updateCompanionCallback: ({
Value<String> id = const Value.absent(),
Value<String?> contextualIdentity = const Value.absent(),
Value<String?> name = const Value.absent(),
Value<Color> color = const Value.absent(),
Value<IconData?> icon = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
ContainerCompanion(
id: id,
contextualIdentity: contextualIdentity,
name: name,
color: color,
icon: icon,
rowid: rowid,
),
createCompanionCallback: ({
required String id,
Value<String?> contextualIdentity = const Value.absent(),
Value<String?> name = const Value.absent(),
required Color color,
Value<IconData?> icon = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
ContainerCompanion.insert(
id: id,
contextualIdentity: contextualIdentity,
name: name,
color: color,
icon: icon,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) =>
(e.readTable(table), $ContainerReferences(db, table, e)))
.toList(),
prefetchHooksCallback: ({tabRefs = false}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [if (tabRefs) db.tab],
addJoins: null,
getPrefetchedDataCallback: (items) async {
return [
if (tabRefs)
await $_getPrefetchedData(
currentTable: table,
referencedTable: $ContainerReferences._tabRefsTable(db),
managerFromTypedResult: (p0) =>
$ContainerReferences(db, table, p0).tabRefs,
referencedItemsForCurrentItem:
(item, referencedItems) => referencedItems
.where((e) => e.containerId == item.id),
typedResults: items)
];
},
);
},
));
}
typedef $ContainerProcessedTableManager = ProcessedTableManager<
_$TabDatabase,
Container,
ContainerData,
$ContainerFilterComposer,
$ContainerOrderingComposer,
$ContainerAnnotationComposer,
$ContainerCreateCompanionBuilder,
$ContainerUpdateCompanionBuilder,
(ContainerData, $ContainerReferences),
ContainerData,
PrefetchHooks Function({bool tabRefs})>;
typedef $TabCreateCompanionBuilder = TabCompanion Function({
required String id,
Value<String?> containerId,
required String orderKey,
required DateTime timestamp,
Value<int> rowid,
});
typedef $TabUpdateCompanionBuilder = TabCompanion Function({
Value<String> id,
Value<String?> containerId,
Value<String> orderKey,
Value<DateTime> timestamp,
Value<int> rowid,
});
final class $TabReferences extends BaseReferences<_$TabDatabase, Tab, TabData> {
$TabReferences(super.$_db, super.$_table, super.$_typedResult);
static Container _containerIdTable(_$TabDatabase db) => db.container
.createAlias($_aliasNameGenerator(db.tab.containerId, db.container.id));
$ContainerProcessedTableManager? get containerId {
if ($_item.containerId == null) return null;
final manager = $ContainerTableManager($_db, $_db.container)
.filter((f) => f.id($_item.containerId!));
final item = $_typedResult.readTableOrNull(_containerIdTable($_db));
if (item == null) return manager;
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: [item]));
}
}
class $TabFilterComposer extends Composer<_$TabDatabase, Tab> {
$TabFilterComposer({
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 orderKey => $composableBuilder(
column: $table.orderKey, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get timestamp => $composableBuilder(
column: $table.timestamp, builder: (column) => ColumnFilters(column));
$ContainerFilterComposer get containerId {
final $ContainerFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.containerId,
referencedTable: $db.container,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$ContainerFilterComposer(
$db: $db,
$table: $db.container,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
class $TabOrderingComposer extends Composer<_$TabDatabase, Tab> {
$TabOrderingComposer({
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 orderKey => $composableBuilder(
column: $table.orderKey, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get timestamp => $composableBuilder(
column: $table.timestamp, builder: (column) => ColumnOrderings(column));
$ContainerOrderingComposer get containerId {
final $ContainerOrderingComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.containerId,
referencedTable: $db.container,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$ContainerOrderingComposer(
$db: $db,
$table: $db.container,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
class $TabAnnotationComposer extends Composer<_$TabDatabase, Tab> {
$TabAnnotationComposer({
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 orderKey =>
$composableBuilder(column: $table.orderKey, builder: (column) => column);
GeneratedColumn<DateTime> get timestamp =>
$composableBuilder(column: $table.timestamp, builder: (column) => column);
$ContainerAnnotationComposer get containerId {
final $ContainerAnnotationComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.containerId,
referencedTable: $db.container,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$ContainerAnnotationComposer(
$db: $db,
$table: $db.container,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
class $TabTableManager extends RootTableManager<
_$TabDatabase,
Tab,
TabData,
$TabFilterComposer,
$TabOrderingComposer,
$TabAnnotationComposer,
$TabCreateCompanionBuilder,
$TabUpdateCompanionBuilder,
(TabData, $TabReferences),
TabData,
PrefetchHooks Function({bool containerId})> {
$TabTableManager(_$TabDatabase db, Tab table)
: super(TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$TabFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$TabOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$TabAnnotationComposer($db: db, $table: table),
updateCompanionCallback: ({
Value<String> id = const Value.absent(),
Value<String?> containerId = const Value.absent(),
Value<String> orderKey = const Value.absent(),
Value<DateTime> timestamp = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
TabCompanion(
id: id,
containerId: containerId,
orderKey: orderKey,
timestamp: timestamp,
rowid: rowid,
),
createCompanionCallback: ({
required String id,
Value<String?> containerId = const Value.absent(),
required String orderKey,
required DateTime timestamp,
Value<int> rowid = const Value.absent(),
}) =>
TabCompanion.insert(
id: id,
containerId: containerId,
orderKey: orderKey,
timestamp: timestamp,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), $TabReferences(db, table, e)))
.toList(),
prefetchHooksCallback: ({containerId = false}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [],
addJoins: <
T extends TableManagerState<
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic>>(state) {
if (containerId) {
state = state.withJoin(
currentTable: table,
currentColumn: table.containerId,
referencedTable: $TabReferences._containerIdTable(db),
referencedColumn: $TabReferences._containerIdTable(db).id,
) as T;
}
return state;
},
getPrefetchedDataCallback: (items) async {
return [];
},
);
},
));
}
typedef $TabProcessedTableManager = ProcessedTableManager<
_$TabDatabase,
Tab,
TabData,
$TabFilterComposer,
$TabOrderingComposer,
$TabAnnotationComposer,
$TabCreateCompanionBuilder,
$TabUpdateCompanionBuilder,
(TabData, $TabReferences),
TabData,
PrefetchHooks Function({bool containerId})>;
class $TabDatabaseManager {
final _$TabDatabase _db;
$TabDatabaseManager(this._db);
$ContainerTableManager get container =>
$ContainerTableManager(_db, _db.container);
$TabTableManager get tab => $TabTableManager(_db, _db.tab);
}
@@ -1,13 +1,20 @@
import 'dart:ui';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/widgets.dart';
class TopicData with FastEquatable {
class ContainerData with FastEquatable {
final String id;
final String? contextualIdentity;
final String? name;
final Color color;
final IconData? icon;
TopicData({required this.id, this.name, required this.color});
ContainerData({
required this.id,
this.contextualIdentity,
this.name,
required this.color,
this.icon,
});
@override
bool get cacheHash => true;
@@ -15,18 +22,22 @@ class TopicData with FastEquatable {
@override
List<Object?> get hashParameters => [
id,
contextualIdentity,
name,
color,
icon,
];
}
class TopicDataWithCount extends TopicData {
class ContainerDataWithCount extends ContainerData {
final int? tabCount;
TopicDataWithCount({
ContainerDataWithCount({
required super.id,
super.contextualIdentity,
super.name,
required super.color,
super.icon,
required this.tabCount,
});
@@ -1,6 +1,7 @@
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
import 'package:lensai/data/database/functions/lexo_rank_functions.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -17,7 +18,7 @@ TabDatabase tabDatabase(TabDatabaseRef ref) {
// put the database file, called db.sqlite here, into the documents folder
// for your app.
final dbFolder = await path_provider.getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'tab.db'));
final file = File(p.join(dbFolder.path, 'tab2.db'));
// Also work around limitations on old Android versions
if (Platform.isAndroid) {
@@ -31,7 +32,12 @@ TabDatabase tabDatabase(TabDatabaseRef ref) {
// Explicitly tell it about the correct temporary directory.
sqlite3.tempDirectory = cachebase;
return NativeDatabase.createInBackground(file);
return NativeDatabase.createInBackground(
file,
setup: (database) {
registerLexorankFunctions(database);
},
);
}),
);
}
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabDatabaseHash() => r'b886b089ae5fdb9bdcc0982efe5fcbe9bc66486f';
String _$tabDatabaseHash() => r'567872961683d21156a9af463466f8ddbdd02f1f';
/// See also [tabDatabase].
@ProviderFor(tabDatabase)
@@ -0,0 +1,50 @@
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/utils/color_palette.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@Riverpod()
Future<Color> unusedRandomContainerColor(
UnusedRandomContainerColorRef ref,
) async {
final repository = ref.watch(containerRepositoryProvider.notifier);
final allColors = colorTypes.flattened.toList();
final usedColors = await repository.getDistinctColors();
Color randomColor;
do {
randomColor = randomColorShade(allColors);
} while (usedColors.contains(randomColor));
return randomColor;
}
@Riverpod()
Stream<List<ContainerDataWithCount>> containersWithCount(
ContainersWithCountRef ref,
) {
final db = ref.watch(tabDatabaseProvider);
return db.containersWithCount().watch();
}
@Riverpod()
Stream<String?> tabContainerId(TabContainerIdRef ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.tabContainerId(tabId).watchSingle();
}
@Riverpod()
Stream<List<String>> containerTabIds(
ContainerTabIdsRef ref,
String? containerId,
) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.containerTabIds(containerId).watch();
}
@@ -0,0 +1,322 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$unusedRandomContainerColorHash() =>
r'129cefaefa895012a6d7b35da69773e28e3289cc';
/// See also [unusedRandomContainerColor].
@ProviderFor(unusedRandomContainerColor)
final unusedRandomContainerColorProvider =
AutoDisposeFutureProvider<Color>.internal(
unusedRandomContainerColor,
name: r'unusedRandomContainerColorProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$unusedRandomContainerColorHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef UnusedRandomContainerColorRef = AutoDisposeFutureProviderRef<Color>;
String _$containersWithCountHash() =>
r'e1bab28091dd1b9ecfd7d76e6393f459604c803a';
/// See also [containersWithCount].
@ProviderFor(containersWithCount)
final containersWithCountProvider =
AutoDisposeStreamProvider<List<ContainerDataWithCount>>.internal(
containersWithCount,
name: r'containersWithCountProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$containersWithCountHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ContainersWithCountRef
= AutoDisposeStreamProviderRef<List<ContainerDataWithCount>>;
String _$tabContainerIdHash() => r'8adbfdc2ef46219569073fe9e58967f4935a45e5';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [tabContainerId].
@ProviderFor(tabContainerId)
const tabContainerIdProvider = TabContainerIdFamily();
/// See also [tabContainerId].
class TabContainerIdFamily extends Family<AsyncValue<String?>> {
/// See also [tabContainerId].
const TabContainerIdFamily();
/// See also [tabContainerId].
TabContainerIdProvider call(
String tabId,
) {
return TabContainerIdProvider(
tabId,
);
}
@override
TabContainerIdProvider getProviderOverride(
covariant TabContainerIdProvider provider,
) {
return call(
provider.tabId,
);
}
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'tabContainerIdProvider';
}
/// See also [tabContainerId].
class TabContainerIdProvider extends AutoDisposeStreamProvider<String?> {
/// See also [tabContainerId].
TabContainerIdProvider(
String tabId,
) : this._internal(
(ref) => tabContainerId(
ref as TabContainerIdRef,
tabId,
),
from: tabContainerIdProvider,
name: r'tabContainerIdProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabContainerIdHash,
dependencies: TabContainerIdFamily._dependencies,
allTransitiveDependencies:
TabContainerIdFamily._allTransitiveDependencies,
tabId: tabId,
);
TabContainerIdProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.tabId,
}) : super.internal();
final String tabId;
@override
Override overrideWith(
Stream<String?> Function(TabContainerIdRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TabContainerIdProvider._internal(
(ref) => create(ref as TabContainerIdRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeStreamProviderElement<String?> createElement() {
return _TabContainerIdProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabContainerIdProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin TabContainerIdRef on AutoDisposeStreamProviderRef<String?> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _TabContainerIdProviderElement
extends AutoDisposeStreamProviderElement<String?> with TabContainerIdRef {
_TabContainerIdProviderElement(super.provider);
@override
String get tabId => (origin as TabContainerIdProvider).tabId;
}
String _$containerTabIdsHash() => r'79e9bccfb17cf3ce802638c8b806c5a76fa8f732';
/// See also [containerTabIds].
@ProviderFor(containerTabIds)
const containerTabIdsProvider = ContainerTabIdsFamily();
/// See also [containerTabIds].
class ContainerTabIdsFamily extends Family<AsyncValue<List<String>>> {
/// See also [containerTabIds].
const ContainerTabIdsFamily();
/// See also [containerTabIds].
ContainerTabIdsProvider call(
String? containerId,
) {
return ContainerTabIdsProvider(
containerId,
);
}
@override
ContainerTabIdsProvider getProviderOverride(
covariant ContainerTabIdsProvider provider,
) {
return call(
provider.containerId,
);
}
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'containerTabIdsProvider';
}
/// See also [containerTabIds].
class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
/// See also [containerTabIds].
ContainerTabIdsProvider(
String? containerId,
) : this._internal(
(ref) => containerTabIds(
ref as ContainerTabIdsRef,
containerId,
),
from: containerTabIdsProvider,
name: r'containerTabIdsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$containerTabIdsHash,
dependencies: ContainerTabIdsFamily._dependencies,
allTransitiveDependencies:
ContainerTabIdsFamily._allTransitiveDependencies,
containerId: containerId,
);
ContainerTabIdsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.containerId,
}) : super.internal();
final String? containerId;
@override
Override overrideWith(
Stream<List<String>> Function(ContainerTabIdsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: ContainerTabIdsProvider._internal(
(ref) => create(ref as ContainerTabIdsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
containerId: containerId,
),
);
}
@override
AutoDisposeStreamProviderElement<List<String>> createElement() {
return _ContainerTabIdsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ContainerTabIdsProvider && other.containerId == containerId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, containerId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin ContainerTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
/// The parameter `containerId` of this provider.
String? get containerId;
}
class _ContainerTabIdsProviderElement
extends AutoDisposeStreamProviderElement<List<String>>
with ContainerTabIdsRef {
_ContainerTabIdsProviderElement(super.provider);
@override
String? get containerId => (origin as ContainerTabIdsProvider).containerId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,43 @@
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_container.g.dart';
@Riverpod(keepAlive: true)
class SelectedContainer extends _$SelectedContainer {
void setContainerId(String id) {
state = id;
}
void toggleContainer(String id) {
if (state == id) {
clearContainer();
} else {
setContainerId(id);
}
}
void clearContainer() {
state = null;
}
@override
String? build() {
return null;
}
}
@Riverpod()
Stream<ContainerData?> selectedContainerData(SelectedContainerDataRef ref) {
final db = ref.watch(tabDatabaseProvider);
final selectedContainer = ref.watch(selectedContainerProvider);
if (selectedContainer != null) {
return db.containerDao
.getContainerData(selectedContainer)
.watchSingleOrNull();
}
return Stream.value(null);
}
@@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_container.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$selectedContainerDataHash() =>
r'dc930d1bb47b3c7989c6c119b0459f37d687af84';
/// See also [selectedContainerData].
@ProviderFor(selectedContainerData)
final selectedContainerDataProvider =
AutoDisposeStreamProvider<ContainerData?>.internal(
selectedContainerData,
name: r'selectedContainerDataProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedContainerDataHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
String _$selectedContainerHash() => r'e38e86db5bd0584af9561156c26ceaea3d23aebf';
/// See also [SelectedContainer].
@ProviderFor(SelectedContainer)
final selectedContainerProvider =
NotifierProvider<SelectedContainer, String?>.internal(
SelectedContainer.new,
name: r'selectedContainerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedContainerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SelectedContainer = Notifier<String?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,54 @@
import 'dart:ui';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'container.g.dart';
@Riverpod(keepAlive: true)
class ContainerRepository extends _$ContainerRepository {
late TabDatabase _db;
@override
void build() {
_db = ref.watch(tabDatabaseProvider);
}
Future<void> addContainer({required String? name, required Color color}) {
return _db.containerDao.addContainer(name: name, color: color);
}
Future<void> replaceContainer({
required String id,
required String? name,
required Color color,
}) {
return _db.containerDao.replaceContainer(id, name: name, color: color);
}
Future<void> deleteContainer(String id) {
return _db.containerDao.deleteContainer(id);
}
Future<Set<Color>> getDistinctColors() {
return _db.containerDao
.getDistinctColors()
.get()
.then((colors) => colors.toSet());
}
Future<String> getLeadingOrderKey(String? containerId) {
return _db.containerDao.generateLeadingOrderKey(containerId).getSingle();
}
Future<String> getTrailingOrderKey(String? containerId) {
return _db.containerDao.generateTrailingOrderKey(containerId).getSingle();
}
Future<String> getOrderKeyAfterTab(String tabId, String? containerId) {
return _db.containerDao
.generateOrderKeyAfterTabId(containerId, tabId)
.getSingle();
}
}
@@ -1,26 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_link.dart';
part of 'container.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$tabLinkRepositoryHash() => r'cdc23ca6ea215925f39842b16f9abcc289b0efbb';
String _$containerRepositoryHash() =>
r'89ad15861fb213d6a3fa51232fd0ff9943f51e92';
/// See also [TabLinkRepository].
@ProviderFor(TabLinkRepository)
final tabLinkRepositoryProvider =
NotifierProvider<TabLinkRepository, void>.internal(
TabLinkRepository.new,
name: r'tabLinkRepositoryProvider',
/// See also [ContainerRepository].
@ProviderFor(ContainerRepository)
final containerRepositoryProvider =
NotifierProvider<ContainerRepository, void>.internal(
ContainerRepository.new,
name: r'containerRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$tabLinkRepositoryHash,
: _$containerRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TabLinkRepository = Notifier<void>;
typedef _$ContainerRepository = Notifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,41 @@
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab.g.dart';
@Riverpod(keepAlive: true)
class TabDataRepository extends _$TabDataRepository {
late TabDatabase _db;
@override
void build() {
_db = ref.watch(tabDatabaseProvider);
}
Future<void> assignContainer(String tabId, String? containerId) {
return _db.tabDao.assignContainer(
tabId,
containerId: containerId,
);
}
Future<void> assignOrderKey(String tabId, String orderKey) {
return _db.tabDao.assignOrderKey(
tabId,
orderKey: orderKey,
);
}
Future<void> closeAllTabs(String? containerId) async {
final tabIds = await _db.tabDao.containerTabIds(containerId).get();
if (tabIds.isNotEmpty) {
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
}
}
Future<String?> containerTabId(String tabId) {
return _db.tabDao.tabContainerId(tabId).getSingle();
}
}
@@ -1,26 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'topic.dart';
part of 'tab.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$topicRepositoryHash() => r'6c456c17af4ea7d7ef1747c92d0442459d061080';
String _$tabDataRepositoryHash() => r'c067bbbf319cd95ad869655740fdf49d13159b94';
/// See also [TopicRepository].
@ProviderFor(TopicRepository)
final topicRepositoryProvider =
NotifierProvider<TopicRepository, void>.internal(
TopicRepository.new,
name: r'topicRepositoryProvider',
/// See also [TabDataRepository].
@ProviderFor(TabDataRepository)
final tabDataRepositoryProvider =
NotifierProvider<TabDataRepository, void>.internal(
TabDataRepository.new,
name: r'tabDataRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$topicRepositoryHash,
: _$tabDataRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TopicRepository = Notifier<void>;
typedef _$TabDataRepository = Notifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -2,23 +2,23 @@ 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/features/geckoview/features/topics/data/models/topic_data.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/topic.dart';
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_dialog.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_dialog.dart';
import 'package:skeletonizer/skeletonizer.dart';
class _TopicTile extends HookWidget {
final TopicData topic;
class _ContainerTile extends HookWidget {
final ContainerData container;
final bool isSelected;
final void Function(TopicResult edited) onEdit;
final void Function(ContainerResult edited) onEdit;
final void Function() onDelete;
final void Function() onTap;
const _TopicTile(
this.topic, {
const _ContainerTile(
this.container, {
required this.isSelected,
required this.onEdit,
required this.onDelete,
@@ -32,8 +32,8 @@ class _TopicTile extends HookWidget {
return ListTile(
selected: isSelected,
leading: CircleAvatar(backgroundColor: topic.color),
title: Text(topic.name ?? 'New Topic'),
leading: CircleAvatar(backgroundColor: container.color),
title: Text(container.name ?? 'New Container'),
trailing: MenuAnchor(
controller: menuController,
builder: (context, controller, child) {
@@ -57,11 +57,11 @@ class _TopicTile extends HookWidget {
menuChildren: [
MenuItemButton(
onPressed: () async {
final result = await showDialog<TopicResult?>(
final result = await showDialog<ContainerResult?>(
context: context,
builder: (context) => TopicDialog.edit(
name: topic.name,
initialColor: topic.color,
builder: (context) => ContainerDialog.edit(
name: container.name,
initialColor: container.color,
),
);
@@ -78,9 +78,9 @@ class _TopicTile extends HookWidget {
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Delete Topic'),
title: const Text('Delete Container'),
content: const Text(
'Are you sure you want to delete this topic and all attached tabs?',
'Are you sure you want to delete this container and close all attached tabs?',
),
actions: <Widget>[
TextButton(
@@ -114,32 +114,32 @@ class _TopicTile extends HookWidget {
}
}
class TopicListScreen extends HookConsumerWidget {
const TopicListScreen();
class ContainerListScreen extends HookConsumerWidget {
const ContainerListScreen();
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('Topics'),
title: const Text('Containers'),
actions: [
IconButton(
onPressed: () async {
final initialColor =
await ref.read(unusedRandomTopicColorProvider.future);
await ref.read(unusedRandomContainerColorProvider.future);
if (context.mounted) {
final result = await showDialog<TopicResult?>(
final result = await showDialog<ContainerResult?>(
context: context,
builder: (context) => TopicDialog.create(
builder: (context) => ContainerDialog.create(
initialColor: initialColor,
),
);
if (result != null) {
await ref
.read(topicRepositoryProvider.notifier)
.addTopic(name: result.name, color: result.color);
.read(containerRepositoryProvider.notifier)
.addContainer(name: result.name, color: result.color);
}
}
},
@@ -149,42 +149,42 @@ class TopicListScreen extends HookConsumerWidget {
),
body: HookConsumer(
builder: (context, ref, child) {
final topicsAsync = ref.watch(topicsWithCountProvider);
final selectedTopic = ref.watch(selectedTopicProvider);
final containersAsync = ref.watch(containersWithCountProvider);
final selectedContainer = ref.watch(selectedContainerProvider);
return Skeletonizer(
enabled: topicsAsync.isLoading,
child: topicsAsync.when(
data: (topics) => FadingScroll(
enabled: containersAsync.isLoading,
child: containersAsync.when(
data: (containers) => FadingScroll(
fadingSize: 25,
builder: (context, controller) {
return ListView.builder(
controller: controller,
itemCount: topics.length,
itemCount: containers.length,
itemBuilder: (context, index) {
final topic = topics[index];
return _TopicTile(
topic,
key: ValueKey(topic.id),
isSelected: topic.id == selectedTopic,
final container = containers[index];
return _ContainerTile(
container,
key: ValueKey(container.id),
isSelected: container.id == selectedContainer,
onEdit: (edited) async {
await ref
.read(topicRepositoryProvider.notifier)
.replaceTopic(
id: topic.id,
.read(containerRepositoryProvider.notifier)
.replaceContainer(
id: container.id,
name: edited.name,
color: edited.color,
);
},
onDelete: () async {
await ref
.read(topicRepositoryProvider.notifier)
.deleteTopic(topic.id);
.read(containerRepositoryProvider.notifier)
.deleteContainer(container.id);
},
onTap: () {
ref
.read(selectedTopicProvider.notifier)
.toggleTopic(topic.id);
.read(selectedContainerProvider.notifier)
.toggleContainer(container.id);
},
);
},
@@ -194,8 +194,8 @@ class TopicListScreen extends HookConsumerWidget {
error: (error, stackTrace) => SizedBox.shrink(),
loading: () => ListView.builder(
itemCount: 3,
itemBuilder: (context, index) => _TopicTile(
TopicData(id: 'null', color: Colors.transparent),
itemBuilder: (context, index) => _ContainerTile(
ContainerData(id: 'null', color: Colors.transparent),
isSelected: false,
onEdit: (_) {},
onDelete: () {},
@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:lensai/presentation/widgets/selectable_chips.dart';
class ContainerChips extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final containersAsync = ref.watch(containersWithCountProvider);
final selectedContainer = ref.watch(
selectedContainerDataProvider.select((value) => value.valueOrNull),
);
return containersAsync.when(
data: (availableContainers) => SizedBox(
height: 48,
child: Row(
children: [
const SizedBox(width: 16),
if (selectedContainer != null || availableContainers.isNotEmpty)
Expanded(
child: SelectableChips(
deleteIcon: false,
itemId: (container) => container.id,
itemAvatar: (container) => Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
color: container.color,
shape: BoxShape.circle,
),
),
itemLabel: (container) =>
Text(container.name ?? 'New Container'),
itemBadgeCount: (container) => container.tabCount,
availableItems: availableContainers,
selectedItem: selectedContainer,
onSelected: (container) {
ref
.read(selectedContainerProvider.notifier)
.setContainerId(container.id);
},
onDeleted: (container) async {
ref
.read(selectedContainerProvider.notifier)
.clearContainer();
},
),
)
else
Expanded(
child: Text(
"Press '>' to manage Containers.",
style: TextStyle(
color: Theme.of(context).hintColor,
fontStyle: FontStyle.italic,
),
),
),
IconButton(
onPressed: () async {
await context.push(ContainerListRoute().location);
},
icon: const Icon(Icons.chevron_right),
),
],
),
),
error: (error, stackTrace) => const SizedBox.shrink(),
loading: () => const SizedBox(
height: 48,
width: double.infinity,
),
);
}
}
@@ -1,35 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/material_color_picker.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
typedef TopicResult = ({String? name, Color color});
typedef ContainerResult = ({String? name, Color color});
enum _DialogMode { create, edit }
class TopicDialog extends HookWidget {
class ContainerDialog extends HookWidget {
final _DialogMode _mode;
final String? initialName;
final Color initialColor;
const TopicDialog._({
const ContainerDialog._({
required _DialogMode mode,
required this.initialColor,
this.initialName,
}) : _mode = mode;
factory TopicDialog.create({required Color initialColor}) {
return TopicDialog._(
factory ContainerDialog.create({required Color initialColor}) {
return ContainerDialog._(
mode: _DialogMode.create,
initialColor: initialColor,
);
}
factory TopicDialog.edit({
factory ContainerDialog.edit({
required String? name,
required Color initialColor,
}) {
return TopicDialog._(
return ContainerDialog._(
mode: _DialogMode.edit,
initialColor: initialColor,
initialName: name,
@@ -54,8 +54,8 @@ class TopicDialog extends HookWidget {
),
title: Text(
switch (_mode) {
_DialogMode.create => 'New Topic',
_DialogMode.edit => 'Edit Topic',
_DialogMode.create => 'New Container',
_DialogMode.edit => 'Edit Container',
},
),
children: [
@@ -94,14 +94,14 @@ class TopicDialog extends HookWidget {
children: [
TextButton(
onPressed: () {
Navigator.pop<TopicResult?>(context);
Navigator.pop<ContainerResult?>(context);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
final name = textController.text.trim();
Navigator.pop<TopicResult?>(
Navigator.pop<ContainerResult?>(
context,
(
name: name.isNotEmpty ? name : null,
@@ -2,7 +2,7 @@
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
import 'package:lensai/features/geckoview/features/tabs/utils/color_palette.dart';
class MaterialPicker extends StatefulWidget {
const MaterialPicker({
@@ -1,63 +0,0 @@
import 'package:drift/drift.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
part 'tab.g.dart';
@DriftAccessor()
class TabLinkDao extends DatabaseAccessor<TabDatabase> with _$TabLinkDaoMixin {
TabLinkDao(super.db);
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])
..orderBy([OrderingTerm.asc(db.tabLink.id)]);
return query.map((row) => row.read(db.tabLink.id)!);
}
SingleOrNullSelectable<String> tabTopicId(String tabId) {
final query = selectOnly(db.tabLink)
..addColumns([db.tabLink.topicId])
..where(db.tabLink.id.equals(tabId));
return query.map((row) => row.read(db.tabLink.topicId)!);
}
Future<void> upsertTabLink(
String tabId, {
required DateTime timestamp,
required String topicId,
}) {
return db.tabLink.insertOne(
TabLinkCompanion.insert(
id: tabId,
timestamp: timestamp,
topicId: topicId,
),
mode: InsertMode.insertOrReplace,
);
}
Future<void> touchTabLink(
String id, {
required DateTime timestamp,
}) {
final statement = db.tabLink.update()..where((t) => t.id.equals(id));
return statement.write(
TabLinkCompanion(timestamp: Value(timestamp)),
);
}
Future<void> syncTabLinks({required List<String> retainTabIds}) {
return (db.tabLink.delete()..where((t) => t.id.isNotIn(retainTabIds))).go();
}
}
@@ -1,54 +0,0 @@
import 'dart:ui';
import 'package:drift/drift.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
part 'topic.g.dart';
@DriftAccessor()
class TopicDao extends DatabaseAccessor<TabDatabase> with _$TopicDaoMixin {
TopicDao(super.db);
Future<void> addTopic({String? name, required Color color}) {
return db.topic.insertOne(
TopicCompanion.insert(
id: uuid.v7(),
name: Value(name),
color: color,
),
);
}
Future<void> replaceTopic(
String id, {
required String? name,
required Color color,
}) {
return db.topic.replaceOne(
TopicCompanion(
id: Value(id),
name: Value(name),
color: Value(color),
),
);
}
Future<void> deleteTopic(String id) {
return db.topic.deleteOne(TopicCompanion.custom(id: Variable(id)));
}
SingleOrNullSelectable<TopicData> getTopicData(String id) {
return select(db.topic)..where((t) => t.id.equals(id));
}
Selectable<Color> getDistinctColors() {
final query = db.selectOnly(db.topic, distinct: true)
..addColumns([db.topic.color])
..where(db.topic.color.isNotNull());
return query
.map((row) => row.readWithConverter<Color?, int>(db.topic.color)!);
}
}
@@ -1,6 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'topic.dart';
// ignore_for_file: type=lint
mixin _$TopicDaoMixin on DatabaseAccessor<TabDatabase> {}
@@ -1,30 +0,0 @@
import 'package:lensai/features/geckoview/features/topics/data/database/drift/converters/color.dart';
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
CREATE TABLE topic (
id TEXT PRIMARY KEY NOT NULL,
name TEXT,
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`
) WITH TopicData;
CREATE TABLE tab_link (
id TEXT PRIMARY KEY NOT NULL,
topic_id TEXT NOT NULL REFERENCES topic (id) ON DELETE CASCADE,
timestamp DATETIME NOT NULL
);
topicsWithCount WITH TopicDataWithCount:
SELECT
topic.*,
tab_agg.tab_count
FROM topic
LEFT JOIN (
SELECT
topic_id,
COUNT(*) AS tab_count,
MAX(timestamp) AS last_updated
FROM tab_link
GROUP BY topic_id
) AS tab_agg ON topic.id = tab_agg.topic_id
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
@@ -1,826 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'database.dart';
// ignore_for_file: type=lint
class Topic extends Table with TableInfo<Topic, TopicData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Topic(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL');
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
late final GeneratedColumnWithTypeConverter<Color, int> color =
GeneratedColumn<int>('color', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL')
.withConverter<Color>(Topic.$convertercolor);
@override
List<GeneratedColumn> get $columns => [id, name, color];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'topic';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
TopicData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TopicData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
name: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}name']),
color: Topic.$convertercolor.fromSql(attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}color'])!),
);
}
@override
Topic createAlias(String alias) {
return Topic(attachedDatabase, alias);
}
static TypeConverter<Color, int> $convertercolor = const ColorConverter();
@override
bool get dontWriteConstraints => true;
}
class TopicCompanion extends UpdateCompanion<TopicData> {
final Value<String> id;
final Value<String?> name;
final Value<Color> color;
final Value<int> rowid;
const TopicCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
this.color = const Value.absent(),
this.rowid = const Value.absent(),
});
TopicCompanion.insert({
required String id,
this.name = const Value.absent(),
required Color color,
this.rowid = const Value.absent(),
}) : id = Value(id),
color = Value(color);
static Insertable<TopicData> custom({
Expression<String>? id,
Expression<String>? name,
Expression<int>? color,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (name != null) 'name': name,
if (color != null) 'color': color,
if (rowid != null) 'rowid': rowid,
});
}
TopicCompanion copyWith(
{Value<String>? id,
Value<String?>? name,
Value<Color>? color,
Value<int>? rowid}) {
return TopicCompanion(
id: id ?? this.id,
name: name ?? this.name,
color: color ?? this.color,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (color.present) {
map['color'] = Variable<int>(Topic.$convertercolor.toSql(color.value));
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TopicCompanion(')
..write('id: $id, ')
..write('name: $name, ')
..write('color: $color, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
class TabLink extends Table with TableInfo<TabLink, TabLinkData> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
TabLink(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> id = GeneratedColumn<String>(
'id', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL');
late final GeneratedColumn<String> topicId = GeneratedColumn<String>(
'topic_id', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES topic(id)ON DELETE CASCADE');
late final GeneratedColumn<DateTime> timestamp = GeneratedColumn<DateTime>(
'timestamp', aliasedName, false,
type: DriftSqlType.dateTime,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
@override
List<GeneratedColumn> get $columns => [id, topicId, timestamp];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'tab_link';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
TabLinkData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return TabLinkData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
topicId: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}topic_id'])!,
timestamp: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!,
);
}
@override
TabLink createAlias(String alias) {
return TabLink(attachedDatabase, alias);
}
@override
bool get dontWriteConstraints => true;
}
class TabLinkData extends DataClass implements Insertable<TabLinkData> {
final String id;
final String topicId;
final DateTime 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);
map['topic_id'] = Variable<String>(topicId);
map['timestamp'] = Variable<DateTime>(timestamp);
return map;
}
factory TabLinkData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TabLinkData(
id: serializer.fromJson<String>(json['id']),
topicId: serializer.fromJson<String>(json['topic_id']),
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'topic_id': serializer.toJson<String>(topicId),
'timestamp': serializer.toJson<DateTime>(timestamp),
};
}
TabLinkData copyWith({String? id, String? topicId, DateTime? timestamp}) =>
TabLinkData(
id: id ?? this.id,
topicId: topicId ?? this.topicId,
timestamp: timestamp ?? this.timestamp,
);
TabLinkData copyWithCompanion(TabLinkCompanion data) {
return TabLinkData(
id: data.id.present ? data.id.value : this.id,
topicId: data.topicId.present ? data.topicId.value : this.topicId,
timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp,
);
}
@override
String toString() {
return (StringBuffer('TabLinkData(')
..write('id: $id, ')
..write('topicId: $topicId, ')
..write('timestamp: $timestamp')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, topicId, timestamp);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is TabLinkData &&
other.id == this.id &&
other.topicId == this.topicId &&
other.timestamp == this.timestamp);
}
class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
final Value<String> id;
final Value<String> topicId;
final Value<DateTime> timestamp;
final Value<int> rowid;
const TabLinkCompanion({
this.id = const Value.absent(),
this.topicId = const Value.absent(),
this.timestamp = const Value.absent(),
this.rowid = const Value.absent(),
});
TabLinkCompanion.insert({
required String id,
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,
Expression<String>? topicId,
Expression<DateTime>? timestamp,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (topicId != null) 'topic_id': topicId,
if (timestamp != null) 'timestamp': timestamp,
if (rowid != null) 'rowid': rowid,
});
}
TabLinkCompanion copyWith(
{Value<String>? id,
Value<String>? topicId,
Value<DateTime>? timestamp,
Value<int>? rowid}) {
return TabLinkCompanion(
id: id ?? this.id,
topicId: topicId ?? this.topicId,
timestamp: timestamp ?? this.timestamp,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (topicId.present) {
map['topic_id'] = Variable<String>(topicId.value);
}
if (timestamp.present) {
map['timestamp'] = Variable<DateTime>(timestamp.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TabLinkCompanion(')
..write('id: $id, ')
..write('topicId: $topicId, ')
..write('timestamp: $timestamp, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
abstract class _$TabDatabase extends GeneratedDatabase {
_$TabDatabase(QueryExecutor e) : super(e);
$TabDatabaseManager get managers => $TabDatabaseManager(this);
late final Topic topic = Topic(this);
late final TabLink tabLink = TabLink(this);
late final TopicDao topicDao = TopicDao(this as TabDatabase);
late final TabLinkDao tabLinkDao = TabLinkDao(this as TabDatabase);
Selectable<TopicDataWithCount> topicsWithCount() {
return customSelect(
'SELECT topic.*, tab_agg.tab_count FROM topic LEFT JOIN (SELECT topic_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab_link GROUP BY topic_id) AS tab_agg ON topic.id = tab_agg.topic_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
variables: [],
readsFrom: {
topic,
tabLink,
}).map((QueryRow row) => TopicDataWithCount(
id: row.read<String>('id'),
name: row.readNullable<String>('name'),
color: Topic.$convertercolor.fromSql(row.read<int>('color')),
tabCount: row.readNullable<int>('tab_count'),
));
}
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [topic, tabLink];
@override
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
[
WritePropagation(
on: TableUpdateQuery.onTableName('topic',
limitUpdateKind: UpdateKind.delete),
result: [
TableUpdate('tab_link', kind: UpdateKind.delete),
],
),
],
);
}
typedef $TopicCreateCompanionBuilder = TopicCompanion Function({
required String id,
Value<String?> name,
required Color color,
Value<int> rowid,
});
typedef $TopicUpdateCompanionBuilder = TopicCompanion Function({
Value<String> id,
Value<String?> name,
Value<Color> color,
Value<int> rowid,
});
final class $TopicReferences
extends BaseReferences<_$TabDatabase, Topic, TopicData> {
$TopicReferences(super.$_db, super.$_table, super.$_typedResult);
static MultiTypedResultKey<TabLink, List<TabLinkData>> _tabLinkRefsTable(
_$TabDatabase db) =>
MultiTypedResultKey.fromTable(db.tabLink,
aliasName: $_aliasNameGenerator(db.topic.id, db.tabLink.topicId));
$TabLinkProcessedTableManager get tabLinkRefs {
final manager = $TabLinkTableManager($_db, $_db.tabLink)
.filter((f) => f.topicId.id($_item.id));
final cache = $_typedResult.readTableOrNull(_tabLinkRefsTable($_db));
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: cache));
}
}
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 => $composableBuilder(
column: $table.name, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<Color, Color, int> get color =>
$composableBuilder(
column: $table.color,
builder: (column) => ColumnWithTypeConverterFilters(column));
Expression<bool> tabLinkRefs(
Expression<bool> Function($TabLinkFilterComposer f) f) {
final $TabLinkFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.id,
referencedTable: $db.tabLink,
getReferencedColumn: (t) => t.topicId,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$TabLinkFilterComposer(
$db: $db,
$table: $db.tabLink,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return f(composer);
}
}
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 => $composableBuilder(
column: $table.name, builder: (column) => ColumnOrderings(column));
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<
_$TabDatabase,
Topic,
TopicData,
$TopicFilterComposer,
$TopicOrderingComposer,
$TopicAnnotationComposer,
$TopicCreateCompanionBuilder,
$TopicUpdateCompanionBuilder,
(TopicData, $TopicReferences),
TopicData,
PrefetchHooks Function({bool tabLinkRefs})> {
$TopicTableManager(_$TabDatabase db, Topic table)
: super(TableManagerState(
db: db,
table: 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(),
Value<Color> color = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
TopicCompanion(
id: id,
name: name,
color: color,
rowid: rowid,
),
createCompanionCallback: ({
required String id,
Value<String?> name = const Value.absent(),
required Color color,
Value<int> rowid = const Value.absent(),
}) =>
TopicCompanion.insert(
id: id,
name: name,
color: color,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), $TopicReferences(db, table, e)))
.toList(),
prefetchHooksCallback: ({tabLinkRefs = false}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [if (tabLinkRefs) db.tabLink],
addJoins: null,
getPrefetchedDataCallback: (items) async {
return [
if (tabLinkRefs)
await $_getPrefetchedData(
currentTable: table,
referencedTable: $TopicReferences._tabLinkRefsTable(db),
managerFromTypedResult: (p0) =>
$TopicReferences(db, table, p0).tabLinkRefs,
referencedItemsForCurrentItem: (item,
referencedItems) =>
referencedItems.where((e) => e.topicId == item.id),
typedResults: items)
];
},
);
},
));
}
typedef $TopicProcessedTableManager = ProcessedTableManager<
_$TabDatabase,
Topic,
TopicData,
$TopicFilterComposer,
$TopicOrderingComposer,
$TopicAnnotationComposer,
$TopicCreateCompanionBuilder,
$TopicUpdateCompanionBuilder,
(TopicData, $TopicReferences),
TopicData,
PrefetchHooks Function({bool tabLinkRefs})>;
typedef $TabLinkCreateCompanionBuilder = TabLinkCompanion Function({
required String id,
required String topicId,
required DateTime timestamp,
Value<int> rowid,
});
typedef $TabLinkUpdateCompanionBuilder = TabLinkCompanion Function({
Value<String> id,
Value<String> topicId,
Value<DateTime> timestamp,
Value<int> rowid,
});
final class $TabLinkReferences
extends BaseReferences<_$TabDatabase, TabLink, TabLinkData> {
$TabLinkReferences(super.$_db, super.$_table, super.$_typedResult);
static Topic _topicIdTable(_$TabDatabase db) => db.topic
.createAlias($_aliasNameGenerator(db.tabLink.topicId, db.topic.id));
$TopicProcessedTableManager? get topicId {
if ($_item.topicId == null) return null;
final manager = $TopicTableManager($_db, $_db.topic)
.filter((f) => f.id($_item.topicId!));
final item = $_typedResult.readTableOrNull(_topicIdTable($_db));
if (item == null) return manager;
return ProcessedTableManager(
manager.$state.copyWith(prefetchedData: [item]));
}
}
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 => $composableBuilder(
column: $table.timestamp, builder: (column) => ColumnFilters(column));
$TopicFilterComposer get topicId {
final $TopicFilterComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.topicId,
referencedTable: $db.topic,
getReferencedColumn: (t) => t.id,
builder: (joinBuilder,
{$addJoinBuilderToRootComposer,
$removeJoinBuilderFromRootComposer}) =>
$TopicFilterComposer(
$db: $db,
$table: $db.topic,
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
joinBuilder: joinBuilder,
$removeJoinBuilderFromRootComposer:
$removeJoinBuilderFromRootComposer,
));
return composer;
}
}
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 => $composableBuilder(
column: $table.timestamp, builder: (column) => ColumnOrderings(column));
$TopicOrderingComposer get topicId {
final $TopicOrderingComposer composer = $composerBuilder(
composer: this,
getCurrentColumn: (t) => t.topicId,
referencedTable: $db.topic,
getReferencedColumn: (t) => t.id,
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;
}
}
class $TabLinkTableManager extends RootTableManager<
_$TabDatabase,
TabLink,
TabLinkData,
$TabLinkFilterComposer,
$TabLinkOrderingComposer,
$TabLinkAnnotationComposer,
$TabLinkCreateCompanionBuilder,
$TabLinkUpdateCompanionBuilder,
(TabLinkData, $TabLinkReferences),
TabLinkData,
PrefetchHooks Function({bool topicId})> {
$TabLinkTableManager(_$TabDatabase db, TabLink table)
: super(TableManagerState(
db: db,
table: 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<DateTime> timestamp = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
TabLinkCompanion(
id: id,
topicId: topicId,
timestamp: timestamp,
rowid: rowid,
),
createCompanionCallback: ({
required String id,
required String topicId,
required DateTime timestamp,
Value<int> rowid = const Value.absent(),
}) =>
TabLinkCompanion.insert(
id: id,
topicId: topicId,
timestamp: timestamp,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map(
(e) => (e.readTable(table), $TabLinkReferences(db, table, e)))
.toList(),
prefetchHooksCallback: ({topicId = false}) {
return PrefetchHooks(
db: db,
explicitlyWatchedTables: [],
addJoins: <
T extends TableManagerState<
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic,
dynamic>>(state) {
if (topicId) {
state = state.withJoin(
currentTable: table,
currentColumn: table.topicId,
referencedTable: $TabLinkReferences._topicIdTable(db),
referencedColumn: $TabLinkReferences._topicIdTable(db).id,
) as T;
}
return state;
},
getPrefetchedDataCallback: (items) async {
return [];
},
);
},
));
}
typedef $TabLinkProcessedTableManager = ProcessedTableManager<
_$TabDatabase,
TabLink,
TabLinkData,
$TabLinkFilterComposer,
$TabLinkOrderingComposer,
$TabLinkAnnotationComposer,
$TabLinkCreateCompanionBuilder,
$TabLinkUpdateCompanionBuilder,
(TabLinkData, $TabLinkReferences),
TabLinkData,
PrefetchHooks Function({bool topicId})>;
class $TabDatabaseManager {
final _$TabDatabase _db;
$TabDatabaseManager(this._db);
$TopicTableManager get topic => $TopicTableManager(_db, _db.topic);
$TabLinkTableManager get tabLink => $TabLinkTableManager(_db, _db.tabLink);
}
@@ -1,43 +0,0 @@
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@Riverpod()
Future<Color> unusedRandomTopicColor(UnusedRandomTopicColorRef ref) async {
final repository = ref.watch(topicRepositoryProvider.notifier);
final allColors = colorTypes.flattened.toList();
final usedColors = await repository.getDistinctColors();
Color randomColor;
do {
randomColor = randomColorShade(allColors);
} while (usedColors.contains(randomColor));
return randomColor;
}
@Riverpod()
Stream<List<TopicDataWithCount>> topicsWithCount(TopicsWithCountRef ref) {
final db = ref.watch(tabDatabaseProvider);
return db.topicsWithCount().watch();
}
@Riverpod()
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String topicId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabLinkDao.topicTabIds(topicId).watch();
}
@Riverpod()
Stream<String?> tabTopicId(TabTopicIdRef ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabLinkDao.tabTopicId(tabId).watchSingleOrNull();
}
@@ -1,320 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$unusedRandomTopicColorHash() =>
r'099730b0d987cc37bcae4ef5c999cbebfa67d7da';
/// See also [unusedRandomTopicColor].
@ProviderFor(unusedRandomTopicColor)
final unusedRandomTopicColorProvider =
AutoDisposeFutureProvider<Color>.internal(
unusedRandomTopicColor,
name: r'unusedRandomTopicColorProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$unusedRandomTopicColorHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef UnusedRandomTopicColorRef = AutoDisposeFutureProviderRef<Color>;
String _$topicsWithCountHash() => r'cd9792b0e0af12cd8796a3f0b76b42171a4200e6';
/// See also [topicsWithCount].
@ProviderFor(topicsWithCount)
final topicsWithCountProvider =
AutoDisposeStreamProvider<List<TopicDataWithCount>>.internal(
topicsWithCount,
name: r'topicsWithCountProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$topicsWithCountHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef TopicsWithCountRef
= AutoDisposeStreamProviderRef<List<TopicDataWithCount>>;
String _$topicTabIdsHash() => r'43719c70e01cf9e030d75171405ab9650936ddf8';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [topicTabIds].
@ProviderFor(topicTabIds)
const topicTabIdsProvider = TopicTabIdsFamily();
/// See also [topicTabIds].
class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
/// See also [topicTabIds].
const TopicTabIdsFamily();
/// See also [topicTabIds].
TopicTabIdsProvider call(
String topicId,
) {
return TopicTabIdsProvider(
topicId,
);
}
@override
TopicTabIdsProvider getProviderOverride(
covariant TopicTabIdsProvider 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'topicTabIdsProvider';
}
/// See also [topicTabIds].
class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
/// See also [topicTabIds].
TopicTabIdsProvider(
String topicId,
) : this._internal(
(ref) => topicTabIds(
ref as TopicTabIdsRef,
topicId,
),
from: topicTabIdsProvider,
name: r'topicTabIdsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$topicTabIdsHash,
dependencies: TopicTabIdsFamily._dependencies,
allTransitiveDependencies:
TopicTabIdsFamily._allTransitiveDependencies,
topicId: topicId,
);
TopicTabIdsProvider._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(TopicTabIdsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TopicTabIdsProvider._internal(
(ref) => create(ref as TopicTabIdsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
topicId: topicId,
),
);
}
@override
AutoDisposeStreamProviderElement<List<String>> createElement() {
return _TopicTabIdsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TopicTabIdsProvider && 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 TopicTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
/// The parameter `topicId` of this provider.
String get topicId;
}
class _TopicTabIdsProviderElement
extends AutoDisposeStreamProviderElement<List<String>> with TopicTabIdsRef {
_TopicTabIdsProviderElement(super.provider);
@override
String get topicId => (origin as TopicTabIdsProvider).topicId;
}
String _$tabTopicIdHash() => r'284164ac3acd9be3ba4e15eba99fc8d97602ee84';
/// See also [tabTopicId].
@ProviderFor(tabTopicId)
const tabTopicIdProvider = TabTopicIdFamily();
/// See also [tabTopicId].
class TabTopicIdFamily extends Family<AsyncValue<String?>> {
/// See also [tabTopicId].
const TabTopicIdFamily();
/// See also [tabTopicId].
TabTopicIdProvider call(
String tabId,
) {
return TabTopicIdProvider(
tabId,
);
}
@override
TabTopicIdProvider getProviderOverride(
covariant TabTopicIdProvider provider,
) {
return call(
provider.tabId,
);
}
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'tabTopicIdProvider';
}
/// See also [tabTopicId].
class TabTopicIdProvider extends AutoDisposeStreamProvider<String?> {
/// See also [tabTopicId].
TabTopicIdProvider(
String tabId,
) : this._internal(
(ref) => tabTopicId(
ref as TabTopicIdRef,
tabId,
),
from: tabTopicIdProvider,
name: r'tabTopicIdProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabTopicIdHash,
dependencies: TabTopicIdFamily._dependencies,
allTransitiveDependencies:
TabTopicIdFamily._allTransitiveDependencies,
tabId: tabId,
);
TabTopicIdProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.tabId,
}) : super.internal();
final String tabId;
@override
Override overrideWith(
Stream<String?> Function(TabTopicIdRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TabTopicIdProvider._internal(
(ref) => create(ref as TabTopicIdRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeStreamProviderElement<String?> createElement() {
return _TabTopicIdProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabTopicIdProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin TabTopicIdRef on AutoDisposeStreamProviderRef<String?> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _TabTopicIdProviderElement
extends AutoDisposeStreamProviderElement<String?> with TabTopicIdRef {
_TabTopicIdProviderElement(super.provider);
@override
String get tabId => (origin as TabTopicIdProvider).tabId;
}
// 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,41 +0,0 @@
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_topic.g.dart';
@Riverpod(keepAlive: true)
class SelectedTopic extends _$SelectedTopic {
void setTopic(String id) {
state = id;
}
void toggleTopic(String id) {
if (state == id) {
clearTopic();
} else {
setTopic(id);
}
}
void clearTopic() {
state = null;
}
@override
String? build() {
return null;
}
}
@Riverpod()
Stream<TopicData?> selectedTopicData(SelectedTopicDataRef ref) {
final db = ref.watch(tabDatabaseProvider);
final selectedTopic = ref.watch(selectedTopicProvider);
if (selectedTopic != null) {
return db.topicDao.getTopicData(selectedTopic).watchSingleOrNull();
}
return Stream.value(null);
}
@@ -1,27 +0,0 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_topic.g.dart';
@Riverpod(keepAlive: true)
class SelectedTopicRepository extends _$SelectedTopicRepository {
void setTopic(String id) {
state = id;
}
void toggleTopic(String id) {
if (state == id) {
clearTopic();
} else {
setTopic(id);
}
}
void clearTopic() {
state = null;
}
@override
String? build() {
return null;
}
}
@@ -1,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_topic.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$selectedTopicRepositoryHash() =>
r'd7ffe4ff5501aff747e5155d74b07974784f26f9';
/// See also [SelectedTopicRepository].
@ProviderFor(SelectedTopicRepository)
final selectedTopicRepositoryProvider =
NotifierProvider<SelectedTopicRepository, String?>.internal(
SelectedTopicRepository.new,
name: r'selectedTopicRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SelectedTopicRepository = Notifier<String?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,42 +0,0 @@
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';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab_link.g.dart';
@Riverpod(keepAlive: true)
class TabLinkRepository extends _$TabLinkRepository {
late TabDatabase _db;
@override
void build() {
_db = ref.watch(tabDatabaseProvider);
}
Future<void> assignTab(String tabId, String topicId) {
return _db.tabLinkDao.upsertTabLink(
tabId,
timestamp: DateTime.now(),
topicId: topicId,
);
}
Future<void> closeAllTabs(String? topicId) async {
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);
}
}
}
@@ -1,40 +0,0 @@
import 'dart:ui';
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 'topic.g.dart';
@Riverpod(keepAlive: true)
class TopicRepository extends _$TopicRepository {
late TabDatabase _db;
@override
void build() {
_db = ref.watch(tabDatabaseProvider);
}
Future<void> addTopic({required String? name, required Color color}) {
return _db.topicDao.addTopic(name: name, color: color);
}
Future<void> replaceTopic({
required String id,
required String? name,
required Color color,
}) {
return _db.topicDao.replaceTopic(id, name: name, color: color);
}
Future<void> deleteTopic(String id) {
return _db.topicDao.deleteTopic(id);
}
Future<Set<Color>> getDistinctColors() {
return _db.topicDao
.getDistinctColors()
.get()
.then((colors) => colors.toSet());
}
}
@@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
import 'package:lensai/presentation/widgets/selectable_chips.dart';
class TopicChips extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final topicsAsync = ref.watch(topicsWithCountProvider);
final selectedTopic = ref
.watch(selectedTopicDataProvider.select((value) => value.valueOrNull));
return topicsAsync.when(
data: (availableTopics) => SizedBox(
height: 48,
child: Row(
children: [
const SizedBox(width: 16),
if (selectedTopic != null || availableTopics.isNotEmpty)
Expanded(
child: SelectableChips(
deleteIcon: false,
itemId: (topic) => topic.id,
itemAvatar: (topic) => Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
color: topic.color,
shape: BoxShape.circle,
),
),
itemLabel: (topic) => Text(topic.name ?? 'New Topic'),
itemBadgeCount: (topic) => topic.tabCount,
availableItems: availableTopics,
selectedItem: selectedTopic,
onSelected: (topic) {
ref.read(selectedTopicProvider.notifier).setTopic(topic.id);
},
onDeleted: (topic) async {
ref.read(selectedTopicProvider.notifier).clearTopic();
},
),
)
else
Expanded(
child: Text(
"Press '>' to manage Topics.",
style: TextStyle(
color: Theme.of(context).hintColor,
fontStyle: FontStyle.italic,
),
),
),
IconButton(
onPressed: () async {
await context.push(TopicListRoute().location);
},
icon: const Icon(Icons.chevron_right),
),
],
),
),
error: (error, stackTrace) => const SizedBox.shrink(),
loading: () => const SizedBox(
height: 48,
width: double.infinity,
),
);
}
}
@@ -1 +1 @@
/home/fafre/.pub-cache/hosted/pub.dev/file_picker-8.1.2/
/home/fafre/.pub-cache/hosted/pub.dev/file_picker-8.1.3/
@@ -1 +1 @@
/home/fafre/.pub-cache/hosted/pub.dev/package_info_plus-8.0.3/
/home/fafre/.pub-cache/hosted/pub.dev/package_info_plus-8.1.0/
@@ -1 +1 @@
/home/fafre/.pub-cache/hosted/pub.dev/share_plus-10.0.3/
/home/fafre/.pub-cache/hosted/pub.dev/share_plus-10.1.0/
@@ -1 +1 @@
/home/fafre/.pub-cache/hosted/pub.dev/sqlite3_flutter_libs-0.5.24/
/home/fafre/.pub-cache/hosted/pub.dev/sqlite3_flutter_libs-0.5.25/
+53 -28
View File
@@ -202,10 +202,10 @@ packages:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.1.2"
copy_with_extension:
dependency: "direct main"
description:
@@ -234,10 +234,10 @@ packages:
dependency: "direct main"
description:
name: crypto
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.6"
csslib:
dependency: transitive
description:
@@ -378,18 +378,18 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12"
sha256: aac85f20436608e01a6ffd1fdd4e746a7f33c93a2c83752e626bdfaea139b877
url: "https://pub.dev"
source: hosted
version: "8.1.2"
version: "8.1.3"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@@ -696,18 +696,18 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4"
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
version: "4.1.1"
image:
dependency: transitive
description:
name: image
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.3.0"
intl:
dependency: transitive
description:
@@ -772,6 +772,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lexo_rank:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "2922c07bc9d3865cc5566efe1976ce0955029512"
url: "https://github.com/FaFre/lexo_rank.git"
source: git
version: "0.1.0"
lint:
dependency: "direct dev"
description:
@@ -792,10 +801,10 @@ packages:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
macros:
dependency: transitive
description:
@@ -856,10 +865,10 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "894f37107424311bdae3e476552229476777b8752c5a2a2369c0cb9a2d5442ef"
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
url: "https://pub.dev"
source: hosted
version: "8.0.3"
version: "8.1.0"
package_info_plus_platform_interface:
dependency: transitive
description:
@@ -936,10 +945,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -980,6 +989,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
reorderable_grid:
dependency: "direct main"
description:
name: reorderable_grid
sha256: "0b9cd95ef0f070ef99f92affe9cf85a4aa127099cd1334e5940950ce58cd981d"
url: "https://pub.dev"
source: hosted
version: "1.0.10"
riverpod:
dependency: "direct main"
description:
@@ -1032,10 +1049,10 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: fec12c3c39f01e4df1ec6ad92b6e85503c5ca64ffd6e28d18c9ffe53fcc4cb11
sha256: "334fcdf0ef9c0df0e3b428faebcac9568f35c747d59831474b2fc56e156d244e"
url: "https://pub.dev"
source: hosted
version: "10.0.3"
version: "10.1.0"
share_plus_platform_interface:
dependency: transitive
description:
@@ -1180,10 +1197,10 @@ packages:
dependency: "direct main"
description:
name: sqlite3_flutter_libs
sha256: "62bbb4073edbcdf53f40c80775f33eea01d301b7b81417e5b3fb7395416258c1"
sha256: ccd29dd6cf6fb9351fa07cd6f92895809adbf0779c1d986acf5e3d53b3250e33
url: "https://pub.dev"
source: hosted
version: "0.5.24"
version: "0.5.25"
sqlparser:
dependency: transitive
description:
@@ -1232,6 +1249,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
synchronized:
dependency: "direct main"
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
term_glyph:
dependency: transitive
description:
@@ -1276,10 +1301,10 @@ packages:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.4.0"
universal_io:
dependency: "direct main"
description:
@@ -1355,10 +1380,10 @@ packages:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.1.3"
uuid:
dependency: "direct main"
description:
@@ -1419,10 +1444,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: e5c39a90447e7c81cfec14b041cdbd0d0916bd9ebbc7fe02ab69568be703b9bd
sha256: "2735daae5150e8b1dfeb3eb0544b4d3af0061e9e82cef063adcd583bdae4306a"
url: "https://pub.dev"
source: hosted
version: "5.6.0"
version: "5.7.0"
xdg_directories:
dependency: transitive
description:
+10 -5
View File
@@ -10,14 +10,14 @@ dependencies:
animations: ^2.0.11
collection: ^1.19.0
copy_with_extension: ^5.0.4
crypto: ^3.0.5
crypto: ^3.0.6
drift: ^2.21.0
dynamic_color: ^1.7.0
exceptions: ^0.6.1
expandable_page_view: ^1.0.17
fading_scroll: ^0.9.1
fast_equatable: ^1.1.0
file_picker: ^8.1.2
file_picker: ^8.1.3
flutter:
sdk: flutter
flutter_hooks: ^0.20.5
@@ -36,22 +36,27 @@ dependencies:
html: ^0.15.4
http: ^1.2.2
json_annotation: ^4.9.0
lexo_rank:
git:
url: https://github.com/FaFre/lexo_rank.git
logger: ^2.4.0
markdown: ^7.2.2
mime: ^2.0.0
package_info_plus: ^8.0.3
package_info_plus: ^8.1.0
path: ^1.9.0
path_provider: ^2.1.4
reorderable_grid: ^1.0.10
riverpod: ^2.5.3
riverpod_annotation: ^2.5.3
rxdart: ^0.28.0
share_plus: ^10.0.3
share_plus: ^10.1.0
shared_preferences: ^2.3.2
skeletonizer: ^1.4.2
speech_to_text_google_dialog:
path: ../../speech_to_text_google_dialog
sqlite3: ^2.4.6
sqlite3_flutter_libs: ^0.5.24
sqlite3_flutter_libs: ^0.5.25
synchronized: ^3.3.0+3
text_scroll: ^0.2.0
timeago: ^3.7.0
universal_io: ^2.2.2
@@ -3,6 +3,8 @@ version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "1.9.22"
ext.mozillaComponentsVersion = '131.0.3'
repositories {
google()
mavenCentral()
@@ -56,37 +58,36 @@ android {
}
dependencies {
implementation 'org.mozilla.components:support-utils:131.0b1'
implementation 'org.mozilla.components:support-ktx:131.0b1'
implementation 'org.mozilla.components:support-webextensions:131.0b1'
implementation 'org.mozilla.components:support-locale:131.0b1'
implementation 'org.mozilla.components:lib-fetch-httpurlconnection:131.0b1'
implementation 'org.mozilla.components:lib-crash:131.0b1'
implementation 'org.mozilla.components:lib-publicsuffixlist:131.0b1'
implementation 'org.mozilla.components:lib-dataprotect:131.0b1'
implementation 'org.mozilla.components:concept-engine:131.0b1'
implementation 'org.mozilla.components:concept-fetch:131.0b1'
implementation 'org.mozilla.components:browser-engine-gecko:131.0b1'
implementation 'org.mozilla.components:browser-state:131.0b1'
implementation 'org.mozilla.components:browser-session-storage:131.0b1'
implementation 'org.mozilla.components:browser-icons:131.0b1'
//implementation 'org.mozilla.components:browser-storage-sync:131.0b1'
implementation 'org.mozilla.components:browser-thumbnails:131.0b1'
implementation 'org.mozilla.components:feature-addons:131.0b1'
implementation 'org.mozilla.components:feature-app-links:131.0b1'
implementation 'org.mozilla.components:feature-autofill:131.0b1'
implementation 'org.mozilla.components:feature-downloads:131.0b1'
implementation 'org.mozilla.components:feature-media:131.0b1'
implementation 'org.mozilla.components:feature-tabs:131.0b1'
implementation 'org.mozilla.components:feature-prompts:131.0b1'
implementation 'org.mozilla.components:feature-session:131.0b1'
implementation 'org.mozilla.components:feature-readerview:131.0b1'
implementation 'org.mozilla.components:feature-privatemode:131.0b1'
implementation 'org.mozilla.components:feature-sitepermissions:131.0b1'
implementation 'org.mozilla.components:feature-webcompat:131.0b1'
implementation 'org.mozilla.components:feature-webnotifications:131.0b1'
implementation 'org.mozilla.components:service-digitalassetlinks:131.0b1'
implementation 'org.mozilla.components:ui-widgets:131.0b1'
implementation "org.mozilla.components:support-utils:$mozillaComponentsVersion"
implementation "org.mozilla.components:support-ktx:$mozillaComponentsVersion"
implementation "org.mozilla.components:support-webextensions:$mozillaComponentsVersion"
implementation "org.mozilla.components:support-locale:$mozillaComponentsVersion"
implementation "org.mozilla.components:lib-fetch-httpurlconnection:$mozillaComponentsVersion"
implementation "org.mozilla.components:lib-crash:$mozillaComponentsVersion"
implementation "org.mozilla.components:lib-publicsuffixlist:$mozillaComponentsVersion"
implementation "org.mozilla.components:lib-dataprotect:$mozillaComponentsVersion"
implementation "org.mozilla.components:concept-engine:$mozillaComponentsVersion"
implementation "org.mozilla.components:concept-fetch:$mozillaComponentsVersion"
implementation "org.mozilla.components:browser-engine-gecko:$mozillaComponentsVersion"
implementation "org.mozilla.components:browser-state:$mozillaComponentsVersion"
implementation "org.mozilla.components:browser-session-storage:$mozillaComponentsVersion"
implementation "org.mozilla.components:browser-icons:$mozillaComponentsVersion"
implementation "org.mozilla.components:browser-thumbnails:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-addons:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-app-links:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-autofill:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-downloads:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-media:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-tabs:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-prompts:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-session:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-readerview:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-privatemode:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-sitepermissions:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-webcompat:$mozillaComponentsVersion"
implementation "org.mozilla.components:feature-webnotifications:$mozillaComponentsVersion"
implementation "org.mozilla.components:service-digitalassetlinks:$mozillaComponentsVersion"
implementation "org.mozilla.components:ui-widgets:$mozillaComponentsVersion"
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
@@ -229,22 +229,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// consumeFlow(components.store) { flow ->
// flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(sessionId) }
// .ifAnyChanged { tab ->
// arrayOf(
// tab.content.loading,
// tab.content.canGoBack,
// tab.content.canGoForward,
// )
// }
// .collect {
// binding.toolbar.invalidateActions()
// }
// }
}
@CallSuper
override fun onBackPressed(): Boolean =
listOf(sessionFeature).any { it.onBackPressed() }
@@ -31,9 +31,9 @@ class BrowserFragment(private val context: Context) : BaseBrowserFragment(), Use
override fun createEngine(components: Components): EngineView {
return components.engine.createView(context).apply {
selectionActionDelegate = DefaultSelectionActionDelegate(
components.selectionAction
)
// selectionActionDelegate = DefaultSelectionActionDelegate(
// components.selectionAction
// )
}
}
@@ -88,6 +88,7 @@ open class DefaultComponents(
}
var engineView: EngineView? = null
var engineReportedInitialized = false
val preferences: SharedPreferences =
applicationContext.getSharedPreferences(SAMPLE_BROWSER_PREFERENCES, Context.MODE_PRIVATE)
@@ -166,6 +167,7 @@ open class DefaultComponents(
.distinctUntilChanged()
.collect { tabId ->
flutterEvents.onSelectedTabChange(
System.currentTimeMillis(),
tabId
) { _ -> }
}
@@ -181,6 +183,7 @@ open class DefaultComponents(
.collect { tab ->
val iconBytes = tab.content.icon?.toWebPBytes()
flutterEvents.onIconChange(
System.currentTimeMillis(),
tab.id,
iconBytes
) { _ -> }
@@ -195,6 +198,7 @@ open class DefaultComponents(
.debounce { 50 }
.collect { tab ->
flutterEvents.onSecurityInfoStateChange(
System.currentTimeMillis(),
tab.id,
SecurityInfoState(
tab.content.securityInfo.secure,
@@ -218,6 +222,7 @@ open class DefaultComponents(
.debounce { 50 }
.collect { tab ->
flutterEvents.onReaderableStateChange(
System.currentTimeMillis(),
tab.id,
ReaderableState(
tab.readerState.readerable,
@@ -241,6 +246,7 @@ open class DefaultComponents(
.debounce { 50 }
.collect { tab ->
flutterEvents.onHistoryStateChange(
System.currentTimeMillis(),
tab.id,
HistoryState(
items = tab.content.history.items.map { item -> HistoryItem(
@@ -259,7 +265,7 @@ open class DefaultComponents(
flow.mapNotNull { state -> state.tabs.map {tab -> tab.id} }
.distinctUntilChanged()
.collect { tabs ->
flutterEvents.onTabListChange(tabs) { _ -> }
flutterEvents.onTabListChange(System.currentTimeMillis(), tabs) { _ -> }
}
}
@@ -280,6 +286,7 @@ open class DefaultComponents(
.collect { tab ->
logger.info("title: ${tab.content.title} ${tab.content.url}")
flutterEvents.onTabContentStateChange(
System.currentTimeMillis(),
TabContentState(
id = tab.id,
contextId = tab.contextId,
@@ -303,6 +310,7 @@ open class DefaultComponents(
.collect { tab ->
tab.content.findResults
flutterEvents.onFindResults(
System.currentTimeMillis(),
tab.id,
tab.content.findResults.map { result -> FindResultState(
activeMatchOrdinal = result.activeMatchOrdinal.toLong(),
@@ -40,7 +40,9 @@ private class NativeFragmentView(
override fun onFlutterViewAttached(flutterView: View) {
super.onFlutterViewAttached(flutterView)
flutterEvents.onFragmentReadyStateChange(true) { _ -> }
GlobalComponents.components!!.engineReportedInitialized = false;
flutterEvents.onViewReadyStateChange(System.currentTimeMillis(),true) { _ -> }
}
override fun getView(): View {
@@ -84,8 +84,6 @@ object GlobalComponents {
Logger.error("Failed to initialize web extension support", e)
}
newComponents.tabsUseCases.addTab.invoke("https://google.com", selectTab = true)
_components = newComponents
}
}
@@ -1,9 +1,21 @@
package eu.lensai.flutter_mozilla_components.api
import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
import mozilla.components.browser.state.action.SystemAction
import mozilla.components.feature.addons.logger
class GeckoBrowserApiImpl(private val showFragmentCallback: () -> Unit) : GeckoBrowserApi {
override fun showNativeFragment() {
showFragmentCallback.invoke();
}
override fun onTrimMemory(level: Long) {
logger.debug("onTrimMemory: $level")
val components = GlobalComponents.components!!
components.store.dispatch(SystemAction.LowMemoryAction(level.toInt()))
components.icons.onTrimMemory(level.toInt())
}
}
@@ -19,7 +19,13 @@ 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.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.session.storage.RecoverableBrowserState
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.selector.findTab
@@ -43,6 +49,7 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
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 icons: BrowserIcons by lazy { GlobalComponents.components!!.icons }
private val events: GeckoStateEvents by lazy { GlobalComponents.components!!.flutterEvents }
private fun restoreSource(source: SourceValue ) : SessionState.Source {
@@ -115,19 +122,27 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
onFindResults: Boolean,
onThumbnailChange: Boolean,
) {
val tabs = state.tabs.map { x -> x.copy() }.toList()
val selectedTab = state.selectedTabId
if(onSelectedTabChange) {
events.onSelectedTabChange(
state.selectedTabId
System.currentTimeMillis(),
selectedTab
) { _ -> }
}
if(onTabListChange) {
events.onTabListChange(state.tabs.map {tab -> tab.id}) { _ -> }
events.onTabListChange(
System.currentTimeMillis(),
tabs.map {tab -> tab.id}
) { _ -> }
}
if(onTabContentStateChange) {
state.tabs.forEach { tab ->
tabs.forEach { tab ->
events.onTabContentStateChange(
System.currentTimeMillis(),
TabContentState(
id = tab.id,
contextId = tab.contextId,
@@ -143,21 +158,35 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
}
if(onIconChange) {
state.tabs.forEach { tab ->
tabs.forEach { tab ->
if(tab.content.icon != null) {
val iconBytes = tab.content.icon?.toWebPBytes()
events.onIconChange(
System.currentTimeMillis(),
tab.id,
iconBytes
) { _ -> }
} else {
CoroutineScope(Dispatchers.Default).launch {
val result = icons.loadIcon(IconRequest(url = tab.content.url)).await()
val iconBytes = result.bitmap.toWebPBytes()
runOnUiThread {
events.onIconChange(
System.currentTimeMillis(),
tab.id,
iconBytes
) { _ -> }
}
}
}
}
}
if(onSecurityInfoStateChange) {
state.tabs.forEach { tab ->
val iconBytes = tab.content.icon?.toWebPBytes()
tabs.forEach { tab ->
events.onSecurityInfoStateChange(
System.currentTimeMillis(),
tab.id,
SecurityInfoState(
tab.content.securityInfo.secure,
@@ -169,8 +198,9 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
}
if(onReaderableStateChange) {
state.tabs.forEach { tab ->
tabs.forEach { tab ->
events.onReaderableStateChange(
System.currentTimeMillis(),
tab.id,
ReaderableState(
tab.readerState.readerable,
@@ -181,8 +211,9 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
}
if(onHistoryStateChange) {
state.tabs.forEach { tab ->
tabs.forEach { tab ->
events.onHistoryStateChange(
System.currentTimeMillis(),
tab.id,
HistoryState(
items = tab.content.history.items.map { item -> HistoryItem(
@@ -198,8 +229,9 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
}
if(onFindResults) {
state.tabs.forEach { tab ->
tabs.forEach { tab ->
events.onFindResults(
System.currentTimeMillis(),
tab.id,
tab.content.findResults.map { result -> FindResultState(
activeMatchOrdinal = result.activeMatchOrdinal.toLong(),
@@ -211,13 +243,13 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
}
if(onThumbnailChange) {
state.tabs.forEach { tab ->
tabs.forEach { tab ->
CoroutineScope(Dispatchers.Default).launch {
val bitmap = thumbnailStorage.loadThumbnail(
ImageLoadRequest(
id = tab.id,
//TODO: make this configurable
size = 600,
size = 1024,
isPrivate = tab.content.private
)
).await()
@@ -225,7 +257,7 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
if(bitmap != null) {
val bytes = bitmap.toWebPBytes()
runOnUiThread {
events.onThumbnailChange(tab.id, bytes) { _ -> }
events.onThumbnailChange(System.currentTimeMillis(), tab.id, bytes) { _ -> }
}
}
}
@@ -4,6 +4,23 @@ import android.graphics.Bitmap
import android.os.Build
import java.io.ByteArrayOutputStream
fun Bitmap.resize(maxWidth: Int, maxHeight: Int): Bitmap {
var width = this.width
var height = this.height
val aspectRatio: Float = width.toFloat() / height.toFloat()
if (width > height) {
width = maxWidth
height = (width / aspectRatio).toInt()
} else {
height = maxHeight
width = (height * aspectRatio).toInt()
}
return Bitmap.createScaledBitmap(this, width, height, true)
}
fun Bitmap.toWebPBytes(): ByteArray {
val stream = ByteArrayOutputStream()
val compressFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@@ -5,16 +5,22 @@ package eu.lensai.flutter_mozilla_components.middleware
import android.graphics.Bitmap
import android.util.Log
import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.ext.resize
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.LastAccessAction
import mozilla.components.browser.state.action.ReaderAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.feature.addons.logger
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
import java.io.ByteArrayOutputStream
import kotlin.reflect.typeOf
/**
@@ -30,13 +36,37 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
) {
when (action) {
is ContentAction.UpdateThumbnailAction -> {
val bytes = action.thumbnail.toWebPBytes()
val resized = action.thumbnail.resize(maxWidth = 640, maxHeight = 480);
val bytes = resized.toWebPBytes()
runOnUiThread {
flutterEvents.onThumbnailChange(action.sessionId, bytes) { _ -> }
flutterEvents.onThumbnailChange(System.currentTimeMillis(), action.sessionId, bytes) { _ -> }
}
}
//UpdateReaderConnectRequiredAction seems to be the only event that is called predictable
//after a hot reload
is ReaderAction.UpdateReaderConnectRequiredAction -> {
if(!GlobalComponents.components!!.engineReportedInitialized) {
runOnUiThread {
flutterEvents.onEngineReadyStateChange(
System.currentTimeMillis(),
true
) { _ -> }
}
GlobalComponents.components!!.engineReportedInitialized = true
}
}
is TabListAction.AddTabAction -> {
runOnUiThread {
flutterEvents.onTabAdded(
System.currentTimeMillis(),
action.tab.id
) { _ -> }
}
}
else -> {
// no-op
//logger.debug("Event fired: " + action.javaClass.name)
}
}
next(action)
@@ -1158,6 +1158,7 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface GeckoBrowserApi {
fun showNativeFragment()
fun onTrimMemory(level: Long)
companion object {
/** The codec used by GeckoBrowserApi. */
@@ -1184,6 +1185,24 @@ interface GeckoBrowserApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.onTrimMemory$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val levelArg = args[0] as Long
val wrapped: List<Any?> = try {
api.onTrimMemory(levelArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -2153,12 +2172,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
GeckoPigeonCodec()
}
}
fun onFragmentReadyStateChange(stateArg: Boolean, callback: (Result<Unit>) -> Unit)
fun onViewReadyStateChange(timestampArg: Long, 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 channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(stateArg)) {
channel.send(listOf(timestampArg, 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?)))
@@ -2170,12 +2189,46 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onTabListChange(tabIdsArg: List<String>, callback: (Result<Unit>) -> Unit)
fun onEngineReadyStateChange(timestampArg: Long, stateArg: Boolean, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onEngineReadyStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(timestampArg, 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 onTabAdded(timestampArg: Long, tabIdArg: String, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabAdded$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(timestampArg, tabIdArg)) {
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(timestampArg: Long, tabIdsArg: List<String>, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(tabIdsArg)) {
channel.send(listOf(timestampArg, tabIdsArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -2187,12 +2240,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onSelectedTabChange(idArg: String?, callback: (Result<Unit>) -> Unit)
fun onSelectedTabChange(timestampArg: Long, idArg: String?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSelectedTabChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg)) {
channel.send(listOf(timestampArg, idArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -2204,12 +2257,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onTabContentStateChange(stateArg: TabContentState, callback: (Result<Unit>) -> Unit)
fun onTabContentStateChange(timestampArg: Long, stateArg: TabContentState, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(stateArg)) {
channel.send(listOf(timestampArg, 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?)))
@@ -2221,12 +2274,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onHistoryStateChange(idArg: String, stateArg: HistoryState, callback: (Result<Unit>) -> Unit)
fun onHistoryStateChange(timestampArg: Long, idArg: String, stateArg: HistoryState, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg, stateArg)) {
channel.send(listOf(timestampArg, idArg, 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?)))
@@ -2238,12 +2291,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onReaderableStateChange(idArg: String, stateArg: ReaderableState, callback: (Result<Unit>) -> Unit)
fun onReaderableStateChange(timestampArg: Long, idArg: String, stateArg: ReaderableState, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg, stateArg)) {
channel.send(listOf(timestampArg, idArg, 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?)))
@@ -2255,12 +2308,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onSecurityInfoStateChange(idArg: String, stateArg: SecurityInfoState, callback: (Result<Unit>) -> Unit)
fun onSecurityInfoStateChange(timestampArg: Long, idArg: String, stateArg: SecurityInfoState, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg, stateArg)) {
channel.send(listOf(timestampArg, idArg, 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?)))
@@ -2272,12 +2325,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onIconChange(idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
fun onIconChange(timestampArg: Long, idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg, bytesArg)) {
channel.send(listOf(timestampArg, idArg, bytesArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -2289,12 +2342,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onThumbnailChange(idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
fun onThumbnailChange(timestampArg: Long, idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg, bytesArg)) {
channel.send(listOf(timestampArg, idArg, bytesArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -2306,12 +2359,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
}
}
}
fun onFindResults(idArg: String, resultsArg: List<FindResultState>, callback: (Result<Unit>) -> Unit)
fun onFindResults(timestampArg: Long, idArg: String, resultsArg: List<FindResultState>, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(idArg, resultsArg)) {
channel.send(listOf(timestampArg, idArg, resultsArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
@@ -1,5 +1,20 @@
package eu.lensai.flutter_mozilla_components_example
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterFragmentActivity()
class MainActivity: FlutterFragmentActivity() {
private val CHANNEL = "me.movenext.flutter_mozilla_components/trim_memory"
private lateinit var channel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
channel.invokeMethod("onTrimMemory", level)
}
}
@@ -10,4 +10,8 @@ class GeckoBrowserService {
Future<void> showNativeFragment() {
return _api.showNativeFragment();
}
Future<void> onTrimMemory(int level) {
return _api.onTrimMemory(level);
}
}
@@ -12,22 +12,31 @@ typedef ThumbnailEvent = ({String tabId, Uint8List? bytes});
typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
class GeckoEventService extends GeckoStateEvents {
final _lastEventTimes = <Subject, Map<dynamic, int>>{};
// Stream controllers
final _fragmentStateSubject = BehaviorSubject.seeded(false);
final _viewStateSubject = BehaviorSubject.seeded(false);
final _engineStateSubject = 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>();
final _tabContentSubject = ReplaySubject<TabContentState>();
final _historySubject = ReplaySubject<HistoryEvent>();
final _securityInfoSubject = ReplaySubject<SecurityInfoEvent>();
final _readerableSubject = ReplaySubject<ReaderableEvent>();
final _iconSubject = PublishSubject<IconEvent>();
final _thumbnailSubject = PublishSubject<ThumbnailEvent>();
final _findResultsSubject = PublishSubject<FindResultsEvent>();
final _tabAddedSubject = PublishSubject<String>();
// Event streams
Stream<bool> get fragmentReadyStateEvents => _fragmentStateSubject.stream;
Stream<List<String>> get tabListEvents => _tabListSubject.stream;
Stream<String?> get selectedTabEvents => _selectedTabSubject.stream;
ValueStream<bool> get viewReadyStateEvents => _viewStateSubject.stream;
ValueStream<bool> get engineReadyStateEvents => _engineStateSubject.stream;
ValueStream<List<String>> get tabListEvents => _tabListSubject.stream;
ValueStream<String?> get selectedTabEvents => _selectedTabSubject.stream;
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
Stream<ReaderableEvent> get readerableEvents => _readerableSubject.stream;
@@ -37,55 +46,124 @@ class GeckoEventService extends GeckoStateEvents {
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream;
Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream;
Stream<String> get tabAddedStream => _tabAddedSubject.stream;
void _addWhenMoreRecent<T>(
Subject<T> subject,
int timestamp,
dynamic identifier,
T value,
) {
_lastEventTimes[subject] ??= {};
if ((_lastEventTimes[subject]?[identifier] ?? 0) < timestamp) {
_lastEventTimes[subject]![identifier] = timestamp;
subject.add(value);
}
}
@override
void onFragmentReadyStateChange(bool state) {
_fragmentStateSubject.add(state);
void onViewReadyStateChange(int timestamp, bool state) {
_addWhenMoreRecent(_viewStateSubject, timestamp, null, state);
}
@override
void onEngineReadyStateChange(int timestamp, bool state) {
_addWhenMoreRecent(_engineStateSubject, timestamp, null, state);
}
// Overridden methods
@override
void onTabListChange(List<String?> tabIds) {
_tabListSubject.add(tabIds.nonNulls.toList());
void onTabListChange(int timestamp, List<String?> tabIds) {
_addWhenMoreRecent(
_tabListSubject,
timestamp,
null,
tabIds.nonNulls.toList(),
);
}
@override
void onSelectedTabChange(String? id) {
_selectedTabSubject.add(id);
void onSelectedTabChange(int timestamp, String? id) {
_addWhenMoreRecent(_selectedTabSubject, timestamp, id, id);
}
@override
void onTabContentStateChange(TabContentState state) {
_tabContentSubject.add(state);
void onTabContentStateChange(int timestamp, TabContentState state) {
_addWhenMoreRecent(_tabContentSubject, timestamp, state.id, state);
}
@override
void onHistoryStateChange(String id, HistoryState state) {
_historySubject.add((tabId: id, history: state));
void onHistoryStateChange(int timestamp, String id, HistoryState state) {
_addWhenMoreRecent(
_historySubject,
timestamp,
id,
(tabId: id, history: state),
);
}
@override
void onReaderableStateChange(String id, ReaderableState state) {
_readerableSubject.add((tabId: id, readerable: state));
void onReaderableStateChange(
int timestamp,
String id,
ReaderableState state,
) {
_addWhenMoreRecent(
_readerableSubject,
timestamp,
id,
(tabId: id, readerable: state),
);
}
@override
void onSecurityInfoStateChange(String id, SecurityInfoState state) {
_securityInfoSubject.add((tabId: id, securityInfo: state));
void onSecurityInfoStateChange(
int timestamp,
String id,
SecurityInfoState state,
) {
_addWhenMoreRecent(
_securityInfoSubject,
timestamp,
id,
(tabId: id, securityInfo: state),
);
}
@override
void onIconChange(String id, Uint8List? bytes) {
_iconSubject.add((tabId: id, bytes: bytes));
void onIconChange(int timestamp, String id, Uint8List? bytes) {
_addWhenMoreRecent(_iconSubject, timestamp, id, (tabId: id, bytes: bytes));
}
@override
void onThumbnailChange(String id, Uint8List? bytes) {
_thumbnailSubject.add((tabId: id, bytes: bytes));
void onThumbnailChange(int timestamp, String id, Uint8List? bytes) {
_addWhenMoreRecent(
_thumbnailSubject,
timestamp,
id,
(tabId: id, bytes: bytes),
);
}
@override
void onFindResults(String id, List<FindResultState?> results) {
_findResultsSubject.add((tabId: id, results: results.nonNulls.toList()));
void onFindResults(int timestamp, String id, List<FindResultState?> results) {
_addWhenMoreRecent(
_findResultsSubject,
timestamp,
id,
(tabId: id, results: results.nonNulls.toList()),
);
}
@override
void onTabAdded(int timestamp, String tabId) {
_addWhenMoreRecent(
_tabAddedSubject,
timestamp,
null,
tabId,
);
}
GeckoEventService.setUp({
@@ -109,5 +187,6 @@ class GeckoEventService extends GeckoStateEvents {
unawaited(_iconSubject.close());
unawaited(_thumbnailSubject.close());
unawaited(_findResultsSubject.close());
unawaited(_tabAddedSubject.close());
}
}
@@ -11,14 +11,14 @@ 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,
bool onSelectedTabChange = false,
bool onTabListChange = false,
bool onTabContentStateChange = false,
bool onIconChange = false,
bool onSecurityInfoStateChange = false,
bool onHistoryStateChange = false,
bool onFindResults = false,
bool onThumbnailChange = false,
}) {
return _api.syncEvents(
onSelectedTabChange: onSelectedTabChange,
@@ -8,11 +8,35 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mozilla_components/src/domain/services/gecko_browser.dart';
class GeckoView extends StatelessWidget {
class GeckoView extends StatefulWidget {
final FutureOr<void> Function()? preInitializationStep;
const GeckoView({super.key, this.preInitializationStep});
@override
State<GeckoView> createState() => _GeckoViewState();
}
class _GeckoViewState extends State<GeckoView> {
static const platform =
MethodChannel('me.movenext.flutter_mozilla_components/trim_memory');
final browserService = GeckoBrowserService();
@override
void initState() {
super.initState();
_setupMethodCallHandler();
}
void _setupMethodCallHandler() {
platform.setMethodCallHandler((MethodCall call) async {
if (call.method == 'onTrimMemory') {
await browserService.onTrimMemory(call.arguments as int);
}
});
}
@override
Widget build(BuildContext context) {
return PlatformViewLink(
@@ -39,8 +63,14 @@ class GeckoView extends StatelessWidget {
params.onPlatformViewCreated(value);
SchedulerBinding.instance.addPostFrameCallback((_) async {
await preInitializationStep?.call();
await GeckoBrowserService().showNativeFragment();
await widget.preInitializationStep?.call();
await Future.delayed(
//Wait for two more frames just to be sure view has been initialized
Duration(milliseconds: ((1000 / 60) * 2).toInt()),
).whenComplete(() async {
await browserService.showNativeFragment();
});
});
})
// ignore: discarded_futures
@@ -1187,6 +1187,28 @@ class GeckoBrowserApi {
return;
}
}
Future<void> onTrimMemory(int level) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.onTrimMemory$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?>[level]) 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;
}
}
}
class GeckoEngineSettingsApi {
@@ -2243,44 +2265,107 @@ class GeckoCookieApi {
abstract class GeckoStateEvents {
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
void onFragmentReadyStateChange(bool state);
void onViewReadyStateChange(int timestamp, bool state);
void onTabListChange(List<String> tabIds);
void onEngineReadyStateChange(int timestamp, bool state);
void onSelectedTabChange(String? id);
void onTabAdded(int timestamp, String tabId);
void onTabContentStateChange(TabContentState state);
void onTabListChange(int timestamp, List<String> tabIds);
void onHistoryStateChange(String id, HistoryState state);
void onSelectedTabChange(int timestamp, String? id);
void onReaderableStateChange(String id, ReaderableState state);
void onTabContentStateChange(int timestamp, TabContentState state);
void onSecurityInfoStateChange(String id, SecurityInfoState state);
void onHistoryStateChange(int timestamp, String id, HistoryState state);
void onIconChange(String id, Uint8List? bytes);
void onReaderableStateChange(int timestamp, String id, ReaderableState state);
void onThumbnailChange(String id, Uint8List? bytes);
void onSecurityInfoStateChange(int timestamp, String id, SecurityInfoState state);
void onFindResults(String id, List<FindResultState> results);
void onIconChange(int timestamp, String id, Uint8List? bytes);
void onThumbnailChange(int timestamp, String id, Uint8List? bytes);
void onFindResults(int timestamp, String id, List<FindResultState> results);
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,
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange$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.');
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final bool? arg_state = (args[0] as bool?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange was null, expected non-null int.');
final bool? arg_state = (args[1] as bool?);
assert(arg_state != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange was null, expected non-null bool.');
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange was null, expected non-null bool.');
try {
api.onFragmentReadyStateChange(arg_state!);
api.onViewReadyStateChange(arg_timestamp!, 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.onEngineReadyStateChange$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.onEngineReadyStateChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onEngineReadyStateChange was null, expected non-null int.');
final bool? arg_state = (args[1] as bool?);
assert(arg_state != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onEngineReadyStateChange was null, expected non-null bool.');
try {
api.onEngineReadyStateChange(arg_timestamp!, 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.onTabAdded$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.onTabAdded was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabAdded was null, expected non-null int.');
final String? arg_tabId = (args[1] as String?);
assert(arg_tabId != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabAdded was null, expected non-null String.');
try {
api.onTabAdded(arg_timestamp!, arg_tabId!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2301,11 +2386,14 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final List<String>? arg_tabIds = (args[0] as List<Object?>?)?.cast<String>();
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange was null, expected non-null int.');
final List<String>? arg_tabIds = (args[1] as List<Object?>?)?.cast<String>();
assert(arg_tabIds != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange was null, expected non-null List<String>.');
try {
api.onTabListChange(arg_tabIds!);
api.onTabListChange(arg_timestamp!, arg_tabIds!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2326,9 +2414,12 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSelectedTabChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSelectedTabChange was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
try {
api.onSelectedTabChange(arg_id);
api.onSelectedTabChange(arg_timestamp!, arg_id);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2349,11 +2440,14 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final TabContentState? arg_state = (args[0] as TabContentState?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange was null, expected non-null int.');
final TabContentState? arg_state = (args[1] as TabContentState?);
assert(arg_state != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange was null, expected non-null TabContentState.');
try {
api.onTabContentStateChange(arg_state!);
api.onTabContentStateChange(arg_timestamp!, arg_state!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2374,14 +2468,17 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null, expected non-null String.');
final HistoryState? arg_state = (args[1] as HistoryState?);
final HistoryState? arg_state = (args[2] as HistoryState?);
assert(arg_state != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null, expected non-null HistoryState.');
try {
api.onHistoryStateChange(arg_id!, arg_state!);
api.onHistoryStateChange(arg_timestamp!, arg_id!, arg_state!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2402,14 +2499,17 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null, expected non-null String.');
final ReaderableState? arg_state = (args[1] as ReaderableState?);
final ReaderableState? arg_state = (args[2] as ReaderableState?);
assert(arg_state != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null, expected non-null ReaderableState.');
try {
api.onReaderableStateChange(arg_id!, arg_state!);
api.onReaderableStateChange(arg_timestamp!, arg_id!, arg_state!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2430,14 +2530,17 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null, expected non-null String.');
final SecurityInfoState? arg_state = (args[1] as SecurityInfoState?);
final SecurityInfoState? arg_state = (args[2] as SecurityInfoState?);
assert(arg_state != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null, expected non-null SecurityInfoState.');
try {
api.onSecurityInfoStateChange(arg_id!, arg_state!);
api.onSecurityInfoStateChange(arg_timestamp!, arg_id!, arg_state!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2458,12 +2561,15 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange was null, expected non-null String.');
final Uint8List? arg_bytes = (args[1] as Uint8List?);
final Uint8List? arg_bytes = (args[2] as Uint8List?);
try {
api.onIconChange(arg_id!, arg_bytes);
api.onIconChange(arg_timestamp!, arg_id!, arg_bytes);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2484,12 +2590,15 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange was null, expected non-null String.');
final Uint8List? arg_bytes = (args[1] as Uint8List?);
final Uint8List? arg_bytes = (args[2] as Uint8List?);
try {
api.onThumbnailChange(arg_id!, arg_bytes);
api.onThumbnailChange(arg_timestamp!, arg_id!, arg_bytes);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -2510,14 +2619,17 @@ abstract class GeckoStateEvents {
assert(message != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_id = (args[0] as String?);
final int? arg_timestamp = (args[0] as int?);
assert(arg_timestamp != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null, expected non-null int.');
final String? arg_id = (args[1] as String?);
assert(arg_id != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null, expected non-null String.');
final List<FindResultState>? arg_results = (args[1] as List<Object?>?)?.cast<FindResultState>();
final List<FindResultState>? arg_results = (args[2] as List<Object?>?)?.cast<FindResultState>();
assert(arg_results != null,
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null, expected non-null List<FindResultState>.');
try {
api.onFindResults(arg_id!, arg_results!);
api.onFindResults(arg_timestamp!, arg_id!, arg_results!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
@@ -539,6 +539,7 @@ class FindResultState {
@HostApi()
abstract class GeckoBrowserApi {
void showNativeFragment();
void onTrimMemory(int level);
}
@HostApi()
@@ -782,19 +783,26 @@ abstract class GeckoCookieApi {
@FlutterApi()
abstract class GeckoStateEvents {
void onFragmentReadyStateChange(bool state);
void onViewReadyStateChange(int timestamp, bool state);
void onEngineReadyStateChange(int timestamp, bool state);
void onTabListChange(List<String> tabIds);
void onSelectedTabChange(String? id);
void onTabAdded(int timestamp, String tabId);
void onTabContentStateChange(TabContentState state);
void onHistoryStateChange(String id, HistoryState state);
void onReaderableStateChange(String id, ReaderableState state);
void onSecurityInfoStateChange(String id, SecurityInfoState state);
void onIconChange(String id, Uint8List? bytes);
void onThumbnailChange(String id, Uint8List? bytes);
void onTabListChange(int timestamp, List<String> tabIds);
void onSelectedTabChange(int timestamp, String? id);
void onFindResults(String id, List<FindResultState> results);
void onTabContentStateChange(int timestamp, TabContentState state);
void onHistoryStateChange(int timestamp, String id, HistoryState state);
void onReaderableStateChange(int timestamp, String id, ReaderableState state);
void onSecurityInfoStateChange(
int timestamp,
String id,
SecurityInfoState state,
);
void onIconChange(int timestamp, String id, Uint8List? bytes);
void onThumbnailChange(int timestamp, String id, Uint8List? bytes);
void onFindResults(int timestamp, String id, List<FindResultState> results);
}
@HostApi()