diff --git a/app/drift_schemas/tabs/drift_schema_v4.json b/app/drift_schemas/tabs/drift_schema_v4.json new file mode 100644 index 00000000..bee225e4 --- /dev/null +++ b/app/drift_schemas/tabs/drift_schema_v4.json @@ -0,0 +1,441 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": false + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "container", + "was_declared_in_moor": true, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": "PRIMARY KEY NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "color", + "getter_name": "color", + "moor_type": "int", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const ColorConverter()", + "dart_type_name": "Color" + } + }, + { + "name": "metadata", + "getter_name": "metadata", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const ContainerMetadataConverter()", + "dart_type_name": "ContainerMetadata" + } + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 1, + "references": [ + 0 + ], + "type": "table", + "data": { + "name": "tab", + "was_declared_in_moor": true, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "string", + "nullable": false, + "customConstraints": "PRIMARY KEY NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "primary-key" + ] + }, + { + "name": "source", + "getter_name": "source", + "moor_type": "int", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumIndexConverter(TabSource.values)", + "dart_type_name": "TabSource" + } + }, + { + "name": "parent_id", + "getter_name": "parentId", + "moor_type": "string", + "nullable": true, + "customConstraints": "REFERENCES tab(id)ON DELETE SET NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "container_id", + "getter_name": "containerId", + "moor_type": "string", + "nullable": true, + "customConstraints": "REFERENCES container(id)ON DELETE CASCADE", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "container", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": "cascade" + } + } + ] + }, + { + "name": "order_key", + "getter_name": "orderKey", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "url", + "getter_name": "url", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const UriConverterNullable()", + "dart_type_name": "Uri?" + } + }, + { + "name": "title", + "getter_name": "title", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_private", + "getter_name": "isPrivate", + "moor_type": "bool", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "is_probably_readerable", + "getter_name": "isProbablyReaderable", + "moor_type": "bool", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extracted_content_markdown", + "getter_name": "extractedContentMarkdown", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extracted_content_plain", + "getter_name": "extractedContentPlain", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "full_content_markdown", + "getter_name": "fullContentMarkdown", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "full_content_plain", + "getter_name": "fullContentPlain", + "moor_type": "string", + "nullable": true, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "timestamp", + "getter_name": "timestamp", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 2, + "references": [ + 1 + ], + "type": "table", + "data": { + "name": "tab_fts", + "was_declared_in_moor": true, + "columns": [ + { + "name": "title", + "getter_name": "title", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "url", + "getter_name": "url", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "extracted_content_plain", + "getter_name": "extractedContentPlain", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "full_content_plain", + "getter_name": "fullContentPlain", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": true, + "create_virtual_stmt": "CREATE VIRTUAL TABLE \"tab_fts\" USING fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize=\"trigram\")", + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 3, + "references": [ + 1 + ], + "type": "trigger", + "data": { + "on": 1, + "references_in_body": [ + 1 + ], + "name": "tab_maintain_parent_chain_on_delete", + "sql": "CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN\n -- Update all children of the deleted row to point to its parent\n UPDATE tab \n SET parent_id = OLD.parent_id \n WHERE parent_id = OLD.id;\nEND;" + } + }, + { + "id": 4, + "references": [ + 1, + 2 + ], + "type": "trigger", + "data": { + "on": 1, + "references_in_body": [ + 1, + 2 + ], + "name": "tab_after_insert", + "sql": "CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN\n INSERT INTO \n tab_fts(rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES (new.rowid, new.title, new.url, new.extracted_content_plain, new.full_content_plain);\nEND;" + } + }, + { + "id": 5, + "references": [ + 1, + 2 + ], + "type": "trigger", + "data": { + "on": 1, + "references_in_body": [ + 1, + 2 + ], + "name": "tab_after_delete", + "sql": "CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN\n INSERT INTO \n tab_fts(tab_fts, rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES('delete', old.rowid, old.title, old.url, old.extracted_content_plain, old.full_content_plain);\nEND;" + } + }, + { + "id": 6, + "references": [ + 1, + 2 + ], + "type": "trigger", + "data": { + "on": 1, + "references_in_body": [ + 1, + 2 + ], + "name": "tab_after_update", + "sql": "CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN\n INSERT INTO \n tab_fts(tab_fts, rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES('delete', old.rowid, old.title, old.url, old.extracted_content_plain, old.full_content_plain);\n INSERT INTO \n tab_fts(rowid, title, url, extracted_content_plain, full_content_plain) \n VALUES (new.rowid, new.title, new.url, new.extracted_content_plain, new.full_content_plain);\nEND;" + } + } + ], + "fixed_sql": [ + { + "name": "container", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"container\" (\"id\" TEXT PRIMARY KEY NOT NULL, \"name\" TEXT, \"color\" INTEGER NOT NULL, \"metadata\" TEXT);" + } + ] + }, + { + "name": "tab", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"tab\" (\"id\" TEXT PRIMARY KEY NOT NULL, \"source\" INTEGER NOT NULL, \"parent_id\" TEXT REFERENCES tab(id)ON DELETE SET NULL, \"container_id\" TEXT REFERENCES container(id)ON DELETE CASCADE, \"order_key\" TEXT NOT NULL, \"url\" TEXT, \"title\" TEXT, \"is_private\" INTEGER, \"is_probably_readerable\" INTEGER, \"extracted_content_markdown\" TEXT, \"extracted_content_plain\" TEXT, \"full_content_markdown\" TEXT, \"full_content_plain\" TEXT, \"timestamp\" INTEGER NOT NULL);" + } + ] + }, + { + "name": "tab_fts", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE VIRTUAL TABLE IF NOT EXISTS \"tab_fts\" USING fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize=\"trigram\");" + } + ] + }, + { + "name": "tab_maintain_parent_chain_on_delete", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = OLD.parent_id WHERE parent_id = OLD.id;END" + } + ] + }, + { + "name": "tab_after_insert", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts (\"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES (new.\"rowid\", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END" + } + ] + }, + { + "name": "tab_after_delete", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN INSERT INTO tab_fts (tab_fts, \"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES ('delete', old.\"rowid\", old.title, old.url, old.extracted_content_plain, old.full_content_plain);END" + } + ] + }, + { + "name": "tab_after_update", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN INSERT INTO tab_fts (tab_fts, \"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES ('delete', old.\"rowid\", old.title, old.url, old.extracted_content_plain, old.full_content_plain);INSERT INTO tab_fts (\"rowid\", title, url, extracted_content_plain, full_content_plain) VALUES (new.\"rowid\", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END" + } + ] + } + ] +} \ No newline at end of file diff --git a/app/lib/features/bangs/presentation/widgets/bang_details.dart b/app/lib/features/bangs/presentation/widgets/bang_details.dart index db291ef0..8827c1e9 100644 --- a/app/lib/features/bangs/presentation/widgets/bang_details.dart +++ b/app/lib/features/bangs/presentation/widgets/bang_details.dart @@ -96,7 +96,11 @@ class BangDetails extends HookConsumerWidget { await ref .read(tabRepositoryProvider.notifier) - .addTab(url: url, private: isPrivate); + .addTab( + url: url, + private: isPrivate, + selectTab: true, + ); if (context.mounted) { const BrowserRoute().go(context); diff --git a/app/lib/features/bangs/presentation/widgets/site_search.dart b/app/lib/features/bangs/presentation/widgets/site_search.dart index c11b994e..3d5cc3e3 100644 --- a/app/lib/features/bangs/presentation/widgets/site_search.dart +++ b/app/lib/features/bangs/presentation/widgets/site_search.dart @@ -103,7 +103,7 @@ class SiteSearch extends HookConsumerWidget { if (searchInNewTab) { await ref .read(tabRepositoryProvider.notifier) - .addTab(url: searchUri, private: isPrivate); + .addTab(url: searchUri, private: isPrivate, selectTab: true); } else { await ref .read(tabSessionProvider(tabId: null).notifier) diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index 1eab34b0..07331060 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -80,6 +80,7 @@ GeckoSelectionActionService selectionActionService(Ref ref) { url: defaultSearchBang.getTemplateUrl(text), parentId: currentTab?.id, private: isPrivate, + selectTab: true, ); } else { logger.e('No default search bang found'); diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index cca9081d..e444424d 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -55,7 +55,7 @@ final class SelectionActionServiceProvider } String _$selectionActionServiceHash() => - r'424c2c444524ee4bf2302405cac3358c148a7b16'; + r'6250d2121375b2a41c2f0450512fc836c996f461'; @ProviderFor(eventService) final eventServiceProvider = EventServiceProvider._(); diff --git a/app/lib/features/geckoview/domain/repositories/tab.dart b/app/lib/features/geckoview/domain/repositories/tab.dart index 68f33566..1fb46c94 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.dart @@ -30,6 +30,7 @@ import 'package:weblibre/features/geckoview/domain/providers.dart'; import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; @@ -55,7 +56,7 @@ class TabRepository extends _$TabRepository { Future addTab({ Uri? url, - bool selectTab = true, + required bool selectTab, bool startLoading = true, String? parentId, LoadUrlFlags flags = LoadUrlFlags.NONE, @@ -125,6 +126,7 @@ class TabRepository extends _$TabRepository { await tabDao.insertTab( tabId, parentId: Value(tab.parentId), + source: TabSource.manual, containerId: Value(container?.value?.id), isPrivate: Value(tab.private), url: Value(Uri.tryParse(tab.url)), @@ -409,16 +411,14 @@ class TabRepository extends _$TabRepository { final db = ref.watch(tabDatabaseProvider); final tabAddedSub = eventSerivce.tabAddedStream.listen((tabId) async { - if (await db.tabDao.getTabDataById(tabId).getSingleOrNull() == null) { - // ignore: only_use_keep_alive_inside_keep_alive - final containerId = ref.read(selectedContainerProvider); - await db.tabDao.insertTab( - tabId, - parentId: const Value.absent(), - containerId: Value(containerId), - isPrivate: const Value.absent(), - ); - } + final containerId = ref.read(selectedContainerProvider); + await db.tabDao.insertTab( + tabId, + parentId: const Value.absent(), + source: TabSource.addedEvent, + containerId: Value(containerId), + isPrivate: const Value.absent(), + ); }); final containerSiteAssignementSub = eventSerivce.siteAssignementEvent.listen(( @@ -451,6 +451,7 @@ class TabRepository extends _$TabRepository { private: tabState.isPrivate, container: Value(containerData), parentId: tabState.id, + selectTab: true, ); if (tabIsEmpty) { diff --git a/app/lib/features/geckoview/domain/repositories/tab.g.dart b/app/lib/features/geckoview/domain/repositories/tab.g.dart index 9eb88d6f..deb9514d 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'de20adb4a38f8cd443be7d2f4ea07eded58803a3'; +String _$tabRepositoryHash() => r'7faec6d906e44b679d6fd67ad5e722f5d01cff0a'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart index a33fb166..de863f89 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart @@ -50,6 +50,7 @@ class OpenSharedContent extends HookConsumerWidget { private: isPrivate, container: Value(selectedContainer.value), launchedFromIntent: true, + selectTab: true, ); if (context.mounted) { diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index ef493e85..2206e9d6 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -501,7 +501,7 @@ class QuickTabSwitcher extends HookConsumerWidget { if (item.isHistory) { await ref .read(tabRepositoryProvider.notifier) - .addTab(url: item.url, private: false); + .addTab(url: item.url, private: false, selectTab: true); } else { await ref.read(tabRepositoryProvider.notifier).selectTab(item.id); } @@ -755,6 +755,7 @@ class NavigationMenuButton extends HookConsumerWidget { url: Uri.parse('https://addons.mozilla.org'), private: isPrivate, container: const Value(null), + selectTab: true, ); }, leadingIcon: const Icon(MdiIcons.puzzlePlus), diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index 76427efc..b9444b56 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -188,6 +188,7 @@ class _BrowserViewState extends ConsumerState settings.tabIntentOpenSetting == TabIntentOpenSetting.private, launchedFromIntent: true, + selectTab: true, ); case SharedText(): final bang = @@ -202,6 +203,7 @@ class _BrowserViewState extends ConsumerState settings.tabIntentOpenSetting == TabIntentOpenSetting.private, launchedFromIntent: true, + selectTab: true, ); } case TabIntentOpenSetting.ask: diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart index e6b7de23..107179b7 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -297,7 +298,7 @@ class TabViewHeader extends HookConsumerWidget { ref .read(tabRepositoryProvider.notifier) .undoClose, - count: count, + count: count.length, ); } } @@ -329,7 +330,7 @@ class TabViewHeader extends HookConsumerWidget { ref .read(tabRepositoryProvider.notifier) .undoClose, - count: count, + count: count.length, ); } } @@ -379,7 +380,7 @@ class TabViewHeader extends HookConsumerWidget { if (result == true) { try { - await ref + final closedTabIds = await ref .read( tabDataRepositoryProvider .notifier, @@ -402,25 +403,35 @@ class TabViewHeader extends HookConsumerWidget { tabRepositoryProvider.notifier, ) .addMultipleTabs( - tabs: tabs - .map( - (tab) => AddTabParams( - url: tab.url.toString(), - startLoading: true, - parentId: tab.parentId, - private: - tab.isPrivate ?? - false, - flags: LoadUrlFlags.NONE - .toValue(), - source: Internal.newTab - .toValue(), - contextId: selectedContainer - .metadata - .contextualIdentity, - ), - ) - .toList(), + tabs: tabs.map((tab) { + var parentId = tab.parentId; + while (parentId != null && + closedTabIds.contains( + parentId, + )) { + parentId = tabs + .firstWhereOrNull( + (old) => + old.id == parentId, + ) + ?.parentId; + } + + return AddTabParams( + url: tab.url.toString(), + startLoading: true, + parentId: parentId, + private: + tab.isPrivate ?? false, + flags: LoadUrlFlags.NONE + .toValue(), + source: Internal.newTab + .toValue(), + contextId: selectedContainer + .metadata + .contextualIdentity, + ); + }).toList(), container: Value( selectedContainer, ), diff --git a/app/lib/features/geckoview/features/history/presentation/screens/history.dart b/app/lib/features/geckoview/features/history/presentation/screens/history.dart index f21eac3e..0adf5387 100644 --- a/app/lib/features/geckoview/features/history/presentation/screens/history.dart +++ b/app/lib/features/geckoview/features/history/presentation/screens/history.dart @@ -473,6 +473,7 @@ class HistoryScreen extends HookConsumerWidget { .addTab( url: Uri.parse(item.url), private: false, + selectTab: true, ); if (context.mounted) { diff --git a/app/lib/features/geckoview/features/search/presentation/screens/search.dart b/app/lib/features/geckoview/features/search/presentation/screens/search.dart index 2bf0ac2e..887b8b7e 100644 --- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart +++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart @@ -116,6 +116,7 @@ class SearchScreen extends HookConsumerWidget { ? ref.read(selectedTabProvider) : null, launchedFromIntent: launchedFromIntent, + selectTab: true, ); if (context.mounted) { @@ -173,12 +174,14 @@ class SearchScreen extends HookConsumerWidget { style: switch (selectedTabType.value) { TabType.regular => null, TabType.private => SegmentedButton.styleFrom( - selectedBackgroundColor: AppColors.privateSelectionOverlay, + selectedBackgroundColor: + AppColors.privateSelectionOverlay, ), TabType.child => (currentTabTabType == TabType.private) ? SegmentedButton.styleFrom( - selectedBackgroundColor: AppColors.privateSelectionOverlay, + selectedBackgroundColor: + AppColors.privateSelectionOverlay, ) : null, }, @@ -232,6 +235,7 @@ class SearchScreen extends HookConsumerWidget { ? ref.read(selectedTabProvider) : null, launchedFromIntent: launchedFromIntent, + selectTab: true, ); if (context.mounted) { diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart index 603bd6fd..2dfa7cad 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart @@ -126,7 +126,11 @@ class HistorySuggestions extends HookConsumerWidget { if (uri != null) { await ref .read(tabRepositoryProvider.notifier) - .addTab(url: uri, private: isPrivate); + .addTab( + url: uri, + private: isPrivate, + selectTab: true, + ); if (context.mounted) { ref diff --git a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart index 06fe604d..cec11109 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart @@ -26,6 +26,7 @@ import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/tab.drift.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart'; @DriftAccessor() @@ -122,6 +123,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { TabCompanion.insert( id: tabId, parentId: parentId, + source: TabSource.manual, timestamp: DateTime.now(), containerId: containerId, url: url, @@ -131,11 +133,13 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { ), onConflict: DoUpdate( (old) => TabCompanion( + source: const Value(TabSource.manual), parentId: parentId, containerId: containerId, - orderKey: Value.absentIfNull(orderKey.value), url: url, title: title, + isPrivate: isPrivate, + orderKey: Value.absentIfNull(orderKey.value), ), ), ); @@ -147,6 +151,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { //Upsert an tab only if there is no container assigned yet Future insertTab( String tabId, { + required TabSource source, required Value isPrivate, required Value parentId, Value containerId = const Value.absent(), @@ -163,6 +168,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { TabCompanion.insert( id: tabId, parentId: parentId, + source: source, timestamp: DateTime.now(), containerId: containerId, orderKey: currentOrderKey, @@ -170,7 +176,18 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { url: url, title: title, ), - mode: InsertMode.insertOrIgnore, + onConflict: DoUpdate( + (old) => TabCompanion( + source: Value(source), + parentId: parentId, + containerId: containerId, + url: url, + title: title, + isPrivate: isPrivate, + orderKey: Value.absentIfNull(orderKey.value), + ), + where: (old) => old.source.isSmallerThanValue(source.index), + ), ); return tabId; @@ -273,6 +290,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { _undoHistory[id] ?? TabCompanion.insert( id: id, + source: TabSource.syncEvent, orderKey: currentOrderKey, timestamp: DateTime.now(), ); @@ -281,7 +299,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { return insertable; }), - mode: InsertMode.insertOrIgnore, + onConflict: DoNothing(), ); }); } diff --git a/app/lib/features/geckoview/features/tabs/data/database/database.dart b/app/lib/features/geckoview/features/tabs/data/database/database.dart index 61eb35f3..4b5c890e 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/database.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/database.dart @@ -25,12 +25,13 @@ import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/con import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/tab.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/database.drift.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/database.steps.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart'; import 'package:weblibre/features/search/domain/fts_tokenizer.dart'; @DriftDatabase(include: {'definitions.drift'}, daos: [ContainerDao, TabDao]) class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin { @override - final int schemaVersion = 3; + final int schemaVersion = 4; @override final int ftsTokenLimit = 10; @@ -83,5 +84,16 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin { final tabAtV3 = schema.tab; await m.addColumn(tabAtV3, tabAtV3.isPrivate); }, + from3To4: (m, schema) async { + await m.alterTable( + TableMigration( + schema.tab, + columnTransformer: { + schema.tab.source: Constant(TabSource.manual.index), + }, + newColumns: [schema.tab.source], + ), + ); + }, ); } diff --git a/app/lib/features/geckoview/features/tabs/data/database/database.steps.dart b/app/lib/features/geckoview/features/tabs/data/database/database.steps.dart index 11a557bb..8c1a28d3 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/database.steps.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/database.steps.dart @@ -265,8 +265,127 @@ class Shape2 extends i0.VersionedVirtualTable { columnsByName['full_content_plain']! as i1.GeneratedColumn; } +final class Schema4 extends i0.VersionedSchema { + Schema4({required super.database}) : super(version: 4); + @override + late final List entities = [ + container, + tab, + tabFts, + tabMaintainParentChainOnDelete, + tabAfterInsert, + tabAfterDelete, + tabAfterUpdate, + ]; + late final Shape0 container = Shape0( + source: i0.VersionedTable( + entityName: 'container', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [_column_0, _column_1, _column_2, _column_3], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 tab = Shape3( + source: i0.VersionedTable( + entityName: 'tab', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_16, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_13, + _column_14, + _column_15, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 tabFts = Shape2( + source: i0.VersionedVirtualTable( + entityName: 'tab_fts', + moduleAndArgs: + 'fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize="trigram")', + columns: [_column_8, _column_7, _column_12, _column_14], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Trigger tabMaintainParentChainOnDelete = i1.Trigger( + 'CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = OLD.parent_id WHERE parent_id = OLD.id;END', + 'tab_maintain_parent_chain_on_delete', + ); + final i1.Trigger tabAfterInsert = i1.Trigger( + 'CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END', + 'tab_after_insert', + ); + final i1.Trigger tabAfterDelete = i1.Trigger( + 'CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);END', + 'tab_after_delete', + ); + final i1.Trigger tabAfterUpdate = i1.Trigger( + 'CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END', + 'tab_after_update', + ); +} + +class Shape3 extends i0.VersionedTable { + Shape3({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get source => + columnsByName['source']! as i1.GeneratedColumn; + i1.GeneratedColumn get parentId => + columnsByName['parent_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get containerId => + columnsByName['container_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get orderKey => + columnsByName['order_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => + columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get title => + columnsByName['title']! as i1.GeneratedColumn; + i1.GeneratedColumn get isPrivate => + columnsByName['is_private']! as i1.GeneratedColumn; + i1.GeneratedColumn get isProbablyReaderable => + columnsByName['is_probably_readerable']! as i1.GeneratedColumn; + i1.GeneratedColumn get extractedContentMarkdown => + columnsByName['extracted_content_markdown']! + as i1.GeneratedColumn; + i1.GeneratedColumn get extractedContentPlain => + columnsByName['extracted_content_plain']! as i1.GeneratedColumn; + i1.GeneratedColumn get fullContentMarkdown => + columnsByName['full_content_markdown']! as i1.GeneratedColumn; + i1.GeneratedColumn get fullContentPlain => + columnsByName['full_content_plain']! as i1.GeneratedColumn; + i1.GeneratedColumn get timestamp => + columnsByName['timestamp']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_16(String aliasedName) => + i1.GeneratedColumn( + 'source', + aliasedName, + false, + type: i1.DriftSqlType.int, + $customConstraints: 'NOT NULL', + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -275,6 +394,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from2To3(migrator, schema); return 3; + case 3: + final schema = Schema4(database: database); + final migrator = i1.Migrator(database, schema); + await from3To4(migrator, schema); + return 4; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -283,6 +407,7 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) => i0.VersionedSchema.stepByStepHelper( - step: migrationSteps(from2To3: from2To3), + step: migrationSteps(from2To3: from2To3, from3To4: from3To4), ); diff --git a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift index 87988bba..1a5a2b1d 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift +++ b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift @@ -4,6 +4,7 @@ import 'package:weblibre/features/geckoview/features/tabs/data/models/container_ import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart'; CREATE TABLE container ( id TEXT PRIMARY KEY NOT NULL, @@ -14,6 +15,7 @@ CREATE TABLE container ( CREATE TABLE tab( id TEXT PRIMARY KEY NOT NULL, + source ENUM(TabSource) NOT NULL, parent_id TEXT REFERENCES tab (id) ON DELETE SET NULL, container_id TEXT REFERENCES container (id) ON DELETE CASCADE, order_key TEXT NOT NULL, diff --git a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart index a072a86b..fba028a0 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart @@ -9,12 +9,14 @@ import 'package:weblibre/features/geckoview/features/tabs/data/database/definiti import 'package:weblibre/data/database/converters/color.dart' as i4; import 'package:weblibre/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart' as i5; -import 'package:weblibre/data/database/converters/uri.dart' as i6; -import 'package:drift/internal/modular.dart' as i7; +import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart' + as i6; +import 'package:weblibre/data/database/converters/uri.dart' as i7; +import 'package:drift/internal/modular.dart' as i8; import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart' - as i8; -import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart' as i9; +import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart' + as i10; typedef $ContainerCreateCompanionBuilder = i3.ContainerCompanion Function({ @@ -45,10 +47,10 @@ final class $ContainerReferences static i0.MultiTypedResultKey> _tabRefsTable( i0.GeneratedDatabase db, ) => i0.MultiTypedResultKey.fromTable( - i7.ReadDatabaseContainer(db).resultSet('tab'), + i8.ReadDatabaseContainer(db).resultSet('tab'), aliasName: i0.$_aliasNameGenerator( - i7.ReadDatabaseContainer(db).resultSet('container').id, - i7.ReadDatabaseContainer(db).resultSet('tab').containerId, + i8.ReadDatabaseContainer(db).resultSet('container').id, + i8.ReadDatabaseContainer(db).resultSet('tab').containerId, ), ); @@ -56,7 +58,7 @@ final class $ContainerReferences final manager = i3 .$TabTableManager( $_db, - i7.ReadDatabaseContainer($_db).resultSet('tab'), + i8.ReadDatabaseContainer($_db).resultSet('tab'), ) .filter((f) => f.containerId.id.sqlEquals($_itemColumn('id')!)); @@ -108,7 +110,7 @@ class $ContainerFilterComposer final i3.$TabFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: i7.ReadDatabaseContainer($db).resultSet('tab'), + referencedTable: i8.ReadDatabaseContainer($db).resultSet('tab'), getReferencedColumn: (t) => t.containerId, builder: ( @@ -117,7 +119,7 @@ class $ContainerFilterComposer $removeJoinBuilderFromRootComposer, }) => i3.$TabFilterComposer( $db: $db, - $table: i7.ReadDatabaseContainer($db).resultSet('tab'), + $table: i8.ReadDatabaseContainer($db).resultSet('tab'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -186,7 +188,7 @@ class $ContainerAnnotationComposer final i3.$TabAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: i7.ReadDatabaseContainer($db).resultSet('tab'), + referencedTable: i8.ReadDatabaseContainer($db).resultSet('tab'), getReferencedColumn: (t) => t.containerId, builder: ( @@ -195,7 +197,7 @@ class $ContainerAnnotationComposer $removeJoinBuilderFromRootComposer, }) => i3.$TabAnnotationComposer( $db: $db, - $table: i7.ReadDatabaseContainer($db).resultSet('tab'), + $table: i8.ReadDatabaseContainer($db).resultSet('tab'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -273,7 +275,7 @@ class $ContainerTableManager db: db, explicitlyWatchedTables: [ if (tabRefs) - i7.ReadDatabaseContainer(db).resultSet('tab'), + i8.ReadDatabaseContainer(db).resultSet('tab'), ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -321,6 +323,7 @@ typedef $ContainerProcessedTableManager = typedef $TabCreateCompanionBuilder = i3.TabCompanion Function({ required String id, + required i6.TabSource source, i0.Value parentId, i0.Value containerId, required String orderKey, @@ -338,6 +341,7 @@ typedef $TabCreateCompanionBuilder = typedef $TabUpdateCompanionBuilder = i3.TabCompanion Function({ i0.Value id, + i0.Value source, i0.Value parentId, i0.Value containerId, i0.Value orderKey, @@ -358,12 +362,12 @@ final class $TabReferences $TabReferences(super.$_db, super.$_table, super.$_typedResult); static i3.Container _containerIdTable(i0.GeneratedDatabase db) => - i7.ReadDatabaseContainer(db) + i8.ReadDatabaseContainer(db) .resultSet('container') .createAlias( i0.$_aliasNameGenerator( - i7.ReadDatabaseContainer(db).resultSet('tab').containerId, - i7.ReadDatabaseContainer( + i8.ReadDatabaseContainer(db).resultSet('tab').containerId, + i8.ReadDatabaseContainer( db, ).resultSet('container').id, ), @@ -375,7 +379,7 @@ final class $TabReferences final manager = i3 .$ContainerTableManager( $_db, - i7.ReadDatabaseContainer($_db).resultSet('container'), + i8.ReadDatabaseContainer($_db).resultSet('container'), ) .filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_containerIdTable($_db)); @@ -399,6 +403,12 @@ class $TabFilterComposer extends i0.Composer { builder: (column) => i0.ColumnFilters(column), ); + i0.ColumnWithTypeConverterFilters + get source => $composableBuilder( + column: $table.source, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + i0.ColumnFilters get parentId => $composableBuilder( column: $table.parentId, builder: (column) => i0.ColumnFilters(column), @@ -459,7 +469,7 @@ class $TabFilterComposer extends i0.Composer { final i3.$ContainerFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.containerId, - referencedTable: i7.ReadDatabaseContainer( + referencedTable: i8.ReadDatabaseContainer( $db, ).resultSet('container'), getReferencedColumn: (t) => t.id, @@ -470,7 +480,7 @@ class $TabFilterComposer extends i0.Composer { $removeJoinBuilderFromRootComposer, }) => i3.$ContainerFilterComposer( $db: $db, - $table: i7.ReadDatabaseContainer( + $table: i8.ReadDatabaseContainer( $db, ).resultSet('container'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, @@ -496,6 +506,11 @@ class $TabOrderingComposer extends i0.Composer { builder: (column) => i0.ColumnOrderings(column), ); + i0.ColumnOrderings get source => $composableBuilder( + column: $table.source, + builder: (column) => i0.ColumnOrderings(column), + ); + i0.ColumnOrderings get parentId => $composableBuilder( column: $table.parentId, builder: (column) => i0.ColumnOrderings(column), @@ -555,7 +570,7 @@ class $TabOrderingComposer extends i0.Composer { final i3.$ContainerOrderingComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.containerId, - referencedTable: i7.ReadDatabaseContainer( + referencedTable: i8.ReadDatabaseContainer( $db, ).resultSet('container'), getReferencedColumn: (t) => t.id, @@ -566,7 +581,7 @@ class $TabOrderingComposer extends i0.Composer { $removeJoinBuilderFromRootComposer, }) => i3.$ContainerOrderingComposer( $db: $db, - $table: i7.ReadDatabaseContainer( + $table: i8.ReadDatabaseContainer( $db, ).resultSet('container'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, @@ -590,6 +605,9 @@ class $TabAnnotationComposer extends i0.Composer { i0.GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); + i0.GeneratedColumnWithTypeConverter get source => + $composableBuilder(column: $table.source, builder: (column) => column); + i0.GeneratedColumn get parentId => $composableBuilder(column: $table.parentId, builder: (column) => column); @@ -637,7 +655,7 @@ class $TabAnnotationComposer extends i0.Composer { final i3.$ContainerAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.containerId, - referencedTable: i7.ReadDatabaseContainer( + referencedTable: i8.ReadDatabaseContainer( $db, ).resultSet('container'), getReferencedColumn: (t) => t.id, @@ -648,7 +666,7 @@ class $TabAnnotationComposer extends i0.Composer { $removeJoinBuilderFromRootComposer, }) => i3.$ContainerAnnotationComposer( $db: $db, - $table: i7.ReadDatabaseContainer( + $table: i8.ReadDatabaseContainer( $db, ).resultSet('container'), $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, @@ -690,6 +708,7 @@ class $TabTableManager updateCompanionCallback: ({ i0.Value id = const i0.Value.absent(), + i0.Value source = const i0.Value.absent(), i0.Value parentId = const i0.Value.absent(), i0.Value containerId = const i0.Value.absent(), i0.Value orderKey = const i0.Value.absent(), @@ -707,6 +726,7 @@ class $TabTableManager i0.Value rowid = const i0.Value.absent(), }) => i3.TabCompanion( id: id, + source: source, parentId: parentId, containerId: containerId, orderKey: orderKey, @@ -724,6 +744,7 @@ class $TabTableManager createCompanionCallback: ({ required String id, + required i6.TabSource source, i0.Value parentId = const i0.Value.absent(), i0.Value containerId = const i0.Value.absent(), required String orderKey, @@ -741,6 +762,7 @@ class $TabTableManager i0.Value rowid = const i0.Value.absent(), }) => i3.TabCompanion.insert( id: id, + source: source, parentId: parentId, containerId: containerId, orderKey: orderKey, @@ -1196,6 +1218,15 @@ class Tab extends i0.Table with i0.TableInfo { requiredDuringInsert: true, $customConstraints: 'PRIMARY KEY NOT NULL', ); + late final i0.GeneratedColumnWithTypeConverter source = + i0.GeneratedColumn( + 'source', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ).withConverter(i3.Tab.$convertersource); late final i0.GeneratedColumn parentId = i0.GeneratedColumn( 'parent_id', aliasedName, @@ -1303,6 +1334,7 @@ class Tab extends i0.Table with i0.TableInfo { @override List get $columns => [ id, + source, parentId, containerId, orderKey, @@ -1331,6 +1363,12 @@ class Tab extends i0.Table with i0.TableInfo { i0.DriftSqlType.string, data['${effectivePrefix}id'], )!, + source: i3.Tab.$convertersource.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ), parentId: attachedDatabase.typeMapping.read( i0.DriftSqlType.string, data['${effectivePrefix}parent_id'], @@ -1389,14 +1427,17 @@ class Tab extends i0.Table with i0.TableInfo { return Tab(attachedDatabase, alias); } + static i0.JsonTypeConverter2 $convertersource = + const i0.EnumIndexConverter(i6.TabSource.values); static i0.TypeConverter $converterurl = - const i6.UriConverterNullable(); + const i7.UriConverterNullable(); @override bool get dontWriteConstraints => true; } class TabData extends i0.DataClass implements i0.Insertable { final String id; + final i6.TabSource source; final String? parentId; final String? containerId; final String orderKey; @@ -1411,6 +1452,7 @@ class TabData extends i0.DataClass implements i0.Insertable { final DateTime timestamp; const TabData({ required this.id, + required this.source, this.parentId, this.containerId, required this.orderKey, @@ -1428,6 +1470,9 @@ class TabData extends i0.DataClass implements i0.Insertable { Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = i0.Variable(id); + { + map['source'] = i0.Variable(i3.Tab.$convertersource.toSql(source)); + } if (!nullToAbsent || parentId != null) { map['parent_id'] = i0.Variable(parentId); } @@ -1474,6 +1519,9 @@ class TabData extends i0.DataClass implements i0.Insertable { serializer ??= i0.driftRuntimeOptions.defaultSerializer; return TabData( id: serializer.fromJson(json['id']), + source: i3.Tab.$convertersource.fromJson( + serializer.fromJson(json['source']), + ), parentId: serializer.fromJson(json['parent_id']), containerId: serializer.fromJson(json['container_id']), orderKey: serializer.fromJson(json['order_key']), @@ -1503,6 +1551,7 @@ class TabData extends i0.DataClass implements i0.Insertable { serializer ??= i0.driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), + 'source': serializer.toJson(i3.Tab.$convertersource.toJson(source)), 'parent_id': serializer.toJson(parentId), 'container_id': serializer.toJson(containerId), 'order_key': serializer.toJson(orderKey), @@ -1524,6 +1573,7 @@ class TabData extends i0.DataClass implements i0.Insertable { i3.TabData copyWith({ String? id, + i6.TabSource? source, i0.Value parentId = const i0.Value.absent(), i0.Value containerId = const i0.Value.absent(), String? orderKey, @@ -1538,6 +1588,7 @@ class TabData extends i0.DataClass implements i0.Insertable { DateTime? timestamp, }) => i3.TabData( id: id ?? this.id, + source: source ?? this.source, parentId: parentId.present ? parentId.value : this.parentId, containerId: containerId.present ? containerId.value : this.containerId, orderKey: orderKey ?? this.orderKey, @@ -1564,6 +1615,7 @@ class TabData extends i0.DataClass implements i0.Insertable { TabData copyWithCompanion(i3.TabCompanion data) { return TabData( id: data.id.present ? data.id.value : this.id, + source: data.source.present ? data.source.value : this.source, parentId: data.parentId.present ? data.parentId.value : this.parentId, containerId: data.containerId.present ? data.containerId.value @@ -1595,6 +1647,7 @@ class TabData extends i0.DataClass implements i0.Insertable { String toString() { return (StringBuffer('TabData(') ..write('id: $id, ') + ..write('source: $source, ') ..write('parentId: $parentId, ') ..write('containerId: $containerId, ') ..write('orderKey: $orderKey, ') @@ -1614,6 +1667,7 @@ class TabData extends i0.DataClass implements i0.Insertable { @override int get hashCode => Object.hash( id, + source, parentId, containerId, orderKey, @@ -1632,6 +1686,7 @@ class TabData extends i0.DataClass implements i0.Insertable { identical(this, other) || (other is i3.TabData && other.id == this.id && + other.source == this.source && other.parentId == this.parentId && other.containerId == this.containerId && other.orderKey == this.orderKey && @@ -1648,6 +1703,7 @@ class TabData extends i0.DataClass implements i0.Insertable { class TabCompanion extends i0.UpdateCompanion { final i0.Value id; + final i0.Value source; final i0.Value parentId; final i0.Value containerId; final i0.Value orderKey; @@ -1663,6 +1719,7 @@ class TabCompanion extends i0.UpdateCompanion { final i0.Value rowid; const TabCompanion({ this.id = const i0.Value.absent(), + this.source = const i0.Value.absent(), this.parentId = const i0.Value.absent(), this.containerId = const i0.Value.absent(), this.orderKey = const i0.Value.absent(), @@ -1679,6 +1736,7 @@ class TabCompanion extends i0.UpdateCompanion { }); TabCompanion.insert({ required String id, + required i6.TabSource source, this.parentId = const i0.Value.absent(), this.containerId = const i0.Value.absent(), required String orderKey, @@ -1693,10 +1751,12 @@ class TabCompanion extends i0.UpdateCompanion { required DateTime timestamp, this.rowid = const i0.Value.absent(), }) : id = i0.Value(id), + source = i0.Value(source), orderKey = i0.Value(orderKey), timestamp = i0.Value(timestamp); static i0.Insertable custom({ i0.Expression? id, + i0.Expression? source, i0.Expression? parentId, i0.Expression? containerId, i0.Expression? orderKey, @@ -1713,6 +1773,7 @@ class TabCompanion extends i0.UpdateCompanion { }) { return i0.RawValuesInsertable({ if (id != null) 'id': id, + if (source != null) 'source': source, if (parentId != null) 'parent_id': parentId, if (containerId != null) 'container_id': containerId, if (orderKey != null) 'order_key': orderKey, @@ -1735,6 +1796,7 @@ class TabCompanion extends i0.UpdateCompanion { i3.TabCompanion copyWith({ i0.Value? id, + i0.Value? source, i0.Value? parentId, i0.Value? containerId, i0.Value? orderKey, @@ -1751,6 +1813,7 @@ class TabCompanion extends i0.UpdateCompanion { }) { return i3.TabCompanion( id: id ?? this.id, + source: source ?? this.source, parentId: parentId ?? this.parentId, containerId: containerId ?? this.containerId, orderKey: orderKey ?? this.orderKey, @@ -1775,6 +1838,11 @@ class TabCompanion extends i0.UpdateCompanion { if (id.present) { map['id'] = i0.Variable(id.value); } + if (source.present) { + map['source'] = i0.Variable( + i3.Tab.$convertersource.toSql(source.value), + ); + } if (parentId.present) { map['parent_id'] = i0.Variable(parentId.value); } @@ -1829,6 +1897,7 @@ class TabCompanion extends i0.UpdateCompanion { String toString() { return (StringBuffer('TabCompanion(') ..write('id: $id, ') + ..write('source: $source, ') ..write('parentId: $parentId, ') ..write('containerId: $containerId, ') ..write('orderKey: $orderKey, ') @@ -2142,7 +2211,7 @@ i0.Trigger get tabAfterUpdate => i0.Trigger( 'tab_after_update', ); -class DefinitionsDrift extends i7.ModularAccessor { +class DefinitionsDrift extends i8.ModularAccessor { DefinitionsDrift(i0.GeneratedDatabase db) : super(db); Future optimizeFtsIndex() { return customInsert( @@ -2215,13 +2284,13 @@ class DefinitionsDrift extends i7.ModularAccessor { ).map((i0.QueryRow row) => row.read('_c0')); } - i0.Selectable queryTabsBasic({required String query}) { + i0.Selectable queryTabsBasic({required String query}) { return customSelect( 'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight) SELECT t.id, t.container_id, t.is_private, t.title, CAST(t.url AS TEXT) AS url, t.url AS clean_url, bm25(tab_fts, weights.title_weight, weights.url_weight) AS weighted_rank FROM tab_fts AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights WHERE fts.title LIKE ?1 OR fts.url LIKE ?1 ORDER BY weighted_rank ASC, t.timestamp DESC', variables: [i0.Variable(query)], readsFrom: {tab, tabFts}, ).map( - (i0.QueryRow row) => i8.TabQueryResult( + (i0.QueryRow row) => i9.TabQueryResult( id: row.read('id'), containerId: row.readNullable('container_id'), isPrivate: row.readNullable('is_private'), @@ -2235,7 +2304,7 @@ class DefinitionsDrift extends i7.ModularAccessor { ); } - i0.Selectable queryTabsFullContent({ + i0.Selectable queryTabsFullContent({ required String beforeMatch, required String afterMatch, required String ellipsis, @@ -2253,7 +2322,7 @@ class DefinitionsDrift extends i7.ModularAccessor { ], readsFrom: {tab, tabFts}, ).map( - (i0.QueryRow row) => i8.TabQueryResult( + (i0.QueryRow row) => i9.TabQueryResult( id: row.read('id'), containerId: row.readNullable('container_id'), isPrivate: row.readNullable('is_private'), @@ -2369,13 +2438,13 @@ class DefinitionsDrift extends i7.ModularAccessor { ).map((i0.QueryRow row) => row.read('id')); } - i0.Selectable allAssignedSites() { + i0.Selectable allAssignedSites() { return customSelect( 'SELECT container.id, COALESCE(container.metadata ->> \'\$.contextualIdentity\', \'general\') AS contextualIdentity, value AS assigned_site FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE value IS NOT NULL', variables: [], readsFrom: {container}, ).map( - (i0.QueryRow row) => i9.SiteAssignment( + (i0.QueryRow row) => i10.SiteAssignment( id: row.read('id'), contextualIdentity: row.read('contextualIdentity'), assignedSite: row.readNullable('assigned_site'), @@ -2391,14 +2460,14 @@ class DefinitionsDrift extends i7.ModularAccessor { ).map((i0.QueryRow row) => row.readNullable('contextual_identity')); } - i3.TabFts get tabFts => i7.ReadDatabaseContainer( + i3.TabFts get tabFts => i8.ReadDatabaseContainer( attachedDatabase, ).resultSet('tab_fts'); - i3.Container get container => i7.ReadDatabaseContainer( + i3.Container get container => i8.ReadDatabaseContainer( attachedDatabase, ).resultSet('container'); i3.Tab get tab => - i7.ReadDatabaseContainer(attachedDatabase).resultSet('tab'); + i8.ReadDatabaseContainer(attachedDatabase).resultSet('tab'); } class TabTreesResult { diff --git a/app/lib/features/geckoview/features/tabs/data/entities/tab_source.dart b/app/lib/features/geckoview/features/tabs/data/entities/tab_source.dart new file mode 100644 index 00000000..6bf84faf --- /dev/null +++ b/app/lib/features/geckoview/features/tabs/data/entities/tab_source.dart @@ -0,0 +1 @@ +enum TabSource { syncEvent, addedEvent, manual } diff --git a/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.dart b/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.dart index b86feacc..61234d58 100644 --- a/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.dart +++ b/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.dart @@ -123,13 +123,15 @@ class SelectedContainer extends _$SelectedContainer { selectedTabProvider, (previous, next) async { if (next != null) { - final tabContainerId = await ref + final tabData = await ref .read(tabDataRepositoryProvider.notifier) - .getTabContainerId(next); + .getTabDataById(next); - if (ref.mounted && tabContainerId != stateOrNull) { - if (tabContainerId != null) { - await setContainerId(tabContainerId); + if (ref.mounted && + tabData != null && + tabData.containerId != stateOrNull) { + if (tabData.containerId != null) { + await setContainerId(tabData.containerId!); } else { clearContainer(); } diff --git a/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.g.dart b/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.g.dart index 50dda102..36f46a88 100644 --- a/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/providers/selected_container.g.dart @@ -41,7 +41,7 @@ final class SelectedContainerProvider } } -String _$selectedContainerHash() => r'35a8fc409e563758156a197bb8b114387d31b389'; +String _$selectedContainerHash() => r'e1d20f0c2e7764e82937ba486de52c92184c0e4a'; abstract class _$SelectedContainer extends $Notifier { String? build(); diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart index c6f33698..f14d0daa 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.dart @@ -64,6 +64,7 @@ class TabDataRepository extends _$TabDataRepository { private: tabState.isPrivate, container: Value(targetContainer), parentId: tabState.parentId, + selectTab: false, ); } } @@ -95,6 +96,7 @@ class TabDataRepository extends _$TabDataRepository { private: tabState.isPrivate, container: const Value(null), parentId: tabState.parentId, + selectTab: false, ); } } @@ -127,7 +129,7 @@ class TabDataRepository extends _$TabDataRepository { return tabIds.length; } - Future closeContainerTabs( + Future> closeContainerTabs( String? containerId, { bool includeRegular = true, bool includePrivate = true, @@ -146,7 +148,7 @@ class TabDataRepository extends _$TabDataRepository { await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds); } - return tabIds.length; + return tabIds; } Future closeAllTabsByHost(String? containerId, String host) async { diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart index 71e23e7c..6c53a543 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabDataRepositoryProvider } } -String _$tabDataRepositoryHash() => r'eab2a5040541fec955ffe23947cbb57952d29c6c'; +String _$tabDataRepositoryHash() => r'2c2ba950834fac4be9ef6c7042e6ed5a76577b1c'; abstract class _$TabDataRepository extends $Notifier { void build(); diff --git a/app/lib/features/onboarding/presentation/onboarding.dart b/app/lib/features/onboarding/presentation/onboarding.dart index 0023e181..74ccaede 100644 --- a/app/lib/features/onboarding/presentation/onboarding.dart +++ b/app/lib/features/onboarding/presentation/onboarding.dart @@ -149,6 +149,7 @@ class OnboardingScreen extends HookConsumerWidget { url: ref.read(docsUriProvider), private: false, container: const Value(null), + selectTab: true, ); ref.invalidate(routerProvider); diff --git a/app/lib/features/web_feed/presentation/screens/feed_article.dart b/app/lib/features/web_feed/presentation/screens/feed_article.dart index f330ce6d..bb8b6780 100644 --- a/app/lib/features/web_feed/presentation/screens/feed_article.dart +++ b/app/lib/features/web_feed/presentation/screens/feed_article.dart @@ -207,6 +207,7 @@ class FeedArticleScreen extends HookConsumerWidget { url: articleLink.uri, private: isPrivate, container: const Value(null), + selectTab: true, ); if (context.mounted) { @@ -251,7 +252,11 @@ class FeedArticleScreen extends HookConsumerWidget { await ref .read(tabRepositoryProvider.notifier) - .addTab(url: url, private: isPrivate); + .addTab( + url: url, + private: isPrivate, + selectTab: true, + ); if (context.mounted) { showTabOpenedMessage( diff --git a/app/test/drift/tabs/generated/schema.dart b/app/test/drift/tabs/generated/schema.dart index c91114ea..e63560b8 100644 --- a/app/test/drift/tabs/generated/schema.dart +++ b/app/test/drift/tabs/generated/schema.dart @@ -5,6 +5,7 @@ import 'package:drift/drift.dart'; import 'package:drift/internal/migrations.dart'; import 'schema_v2.dart' as v2; import 'schema_v3.dart' as v3; +import 'schema_v4.dart' as v4; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -14,10 +15,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v2.DatabaseAtV2(db); case 3: return v3.DatabaseAtV3(db); + case 4: + return v4.DatabaseAtV4(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [2, 3]; + static const versions = const [2, 3, 4]; } diff --git a/app/test/drift/tabs/generated/schema_v4.dart b/app/test/drift/tabs/generated/schema_v4.dart new file mode 100644 index 00000000..7c37ed86 --- /dev/null +++ b/app/test/drift/tabs/generated/schema_v4.dart @@ -0,0 +1,1307 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class Container extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Container(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'PRIMARY KEY NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn metadata = GeneratedColumn( + 'metadata', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + @override + List get $columns => [id, name, color, metadata]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'container'; + @override + Set get $primaryKey => {id}; + @override + ContainerData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ContainerData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + ), + color: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}color'], + )!, + metadata: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}metadata'], + ), + ); + } + + @override + Container createAlias(String alias) { + return Container(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class ContainerData extends DataClass implements Insertable { + final String id; + final String? name; + final int color; + final String? metadata; + const ContainerData({ + required this.id, + this.name, + required this.color, + this.metadata, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + map['color'] = Variable(color); + if (!nullToAbsent || metadata != null) { + map['metadata'] = Variable(metadata); + } + return map; + } + + factory ContainerData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ContainerData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + color: serializer.fromJson(json['color']), + metadata: serializer.fromJson(json['metadata']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'color': serializer.toJson(color), + 'metadata': serializer.toJson(metadata), + }; + } + + ContainerData copyWith({ + String? id, + Value name = const Value.absent(), + int? color, + Value metadata = const Value.absent(), + }) => ContainerData( + id: id ?? this.id, + name: name.present ? name.value : this.name, + color: color ?? this.color, + metadata: metadata.present ? metadata.value : this.metadata, + ); + ContainerData copyWithCompanion(ContainerCompanion data) { + return ContainerData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + color: data.color.present ? data.color.value : this.color, + metadata: data.metadata.present ? data.metadata.value : this.metadata, + ); + } + + @override + String toString() { + return (StringBuffer('ContainerData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('color: $color, ') + ..write('metadata: $metadata') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, color, metadata); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ContainerData && + other.id == this.id && + other.name == this.name && + other.color == this.color && + other.metadata == this.metadata); +} + +class ContainerCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value color; + final Value metadata; + final Value rowid; + const ContainerCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.color = const Value.absent(), + this.metadata = const Value.absent(), + this.rowid = const Value.absent(), + }); + ContainerCompanion.insert({ + required String id, + this.name = const Value.absent(), + required int color, + this.metadata = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + color = Value(color); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? color, + Expression? metadata, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (color != null) 'color': color, + if (metadata != null) 'metadata': metadata, + if (rowid != null) 'rowid': rowid, + }); + } + + ContainerCompanion copyWith({ + Value? id, + Value? name, + Value? color, + Value? metadata, + Value? rowid, + }) { + return ContainerCompanion( + id: id ?? this.id, + name: name ?? this.name, + color: color ?? this.color, + metadata: metadata ?? this.metadata, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (metadata.present) { + map['metadata'] = Variable(metadata.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ContainerCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('color: $color, ') + ..write('metadata: $metadata, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class Tab extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Tab(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'PRIMARY KEY NOT NULL', + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parentId = GeneratedColumn( + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'REFERENCES tab(id)ON DELETE SET NULL', + ); + late final GeneratedColumn containerId = GeneratedColumn( + 'container_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'REFERENCES container(id)ON DELETE CASCADE', + ); + late final GeneratedColumn orderKey = GeneratedColumn( + 'order_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn title = GeneratedColumn( + 'title', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn isPrivate = GeneratedColumn( + 'is_private', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn isProbablyReaderable = GeneratedColumn( + 'is_probably_readerable', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn extractedContentMarkdown = + GeneratedColumn( + 'extracted_content_markdown', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn extractedContentPlain = + GeneratedColumn( + 'extracted_content_plain', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn fullContentMarkdown = + GeneratedColumn( + 'full_content_markdown', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn fullContentPlain = GeneratedColumn( + 'full_content_plain', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn timestamp = GeneratedColumn( + 'timestamp', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + source, + parentId, + containerId, + orderKey, + url, + title, + isPrivate, + isProbablyReaderable, + extractedContentMarkdown, + extractedContentPlain, + fullContentMarkdown, + fullContentPlain, + timestamp, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'tab'; + @override + Set get $primaryKey => {id}; + @override + TabData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TabData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + parentId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}parent_id'], + ), + containerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}container_id'], + ), + orderKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}order_key'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + ), + title: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}title'], + ), + isPrivate: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_private'], + ), + isProbablyReaderable: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_probably_readerable'], + ), + extractedContentMarkdown: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}extracted_content_markdown'], + ), + extractedContentPlain: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}extracted_content_plain'], + ), + fullContentMarkdown: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}full_content_markdown'], + ), + fullContentPlain: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}full_content_plain'], + ), + timestamp: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}timestamp'], + )!, + ); + } + + @override + Tab createAlias(String alias) { + return Tab(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class TabData extends DataClass implements Insertable { + final String id; + final int source; + final String? parentId; + final String? containerId; + final String orderKey; + final String? url; + final String? title; + final int? isPrivate; + final int? isProbablyReaderable; + final String? extractedContentMarkdown; + final String? extractedContentPlain; + final String? fullContentMarkdown; + final String? fullContentPlain; + final int timestamp; + const TabData({ + required this.id, + required this.source, + this.parentId, + this.containerId, + required this.orderKey, + this.url, + this.title, + this.isPrivate, + this.isProbablyReaderable, + this.extractedContentMarkdown, + this.extractedContentPlain, + this.fullContentMarkdown, + this.fullContentPlain, + required this.timestamp, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['source'] = Variable(source); + if (!nullToAbsent || parentId != null) { + map['parent_id'] = Variable(parentId); + } + if (!nullToAbsent || containerId != null) { + map['container_id'] = Variable(containerId); + } + map['order_key'] = Variable(orderKey); + if (!nullToAbsent || url != null) { + map['url'] = Variable(url); + } + if (!nullToAbsent || title != null) { + map['title'] = Variable(title); + } + if (!nullToAbsent || isPrivate != null) { + map['is_private'] = Variable(isPrivate); + } + if (!nullToAbsent || isProbablyReaderable != null) { + map['is_probably_readerable'] = Variable(isProbablyReaderable); + } + if (!nullToAbsent || extractedContentMarkdown != null) { + map['extracted_content_markdown'] = Variable( + extractedContentMarkdown, + ); + } + if (!nullToAbsent || extractedContentPlain != null) { + map['extracted_content_plain'] = Variable(extractedContentPlain); + } + if (!nullToAbsent || fullContentMarkdown != null) { + map['full_content_markdown'] = Variable(fullContentMarkdown); + } + if (!nullToAbsent || fullContentPlain != null) { + map['full_content_plain'] = Variable(fullContentPlain); + } + map['timestamp'] = Variable(timestamp); + return map; + } + + factory TabData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TabData( + id: serializer.fromJson(json['id']), + source: serializer.fromJson(json['source']), + parentId: serializer.fromJson(json['parentId']), + containerId: serializer.fromJson(json['containerId']), + orderKey: serializer.fromJson(json['orderKey']), + url: serializer.fromJson(json['url']), + title: serializer.fromJson(json['title']), + isPrivate: serializer.fromJson(json['isPrivate']), + isProbablyReaderable: serializer.fromJson( + json['isProbablyReaderable'], + ), + extractedContentMarkdown: serializer.fromJson( + json['extractedContentMarkdown'], + ), + extractedContentPlain: serializer.fromJson( + json['extractedContentPlain'], + ), + fullContentMarkdown: serializer.fromJson( + json['fullContentMarkdown'], + ), + fullContentPlain: serializer.fromJson(json['fullContentPlain']), + timestamp: serializer.fromJson(json['timestamp']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'source': serializer.toJson(source), + 'parentId': serializer.toJson(parentId), + 'containerId': serializer.toJson(containerId), + 'orderKey': serializer.toJson(orderKey), + 'url': serializer.toJson(url), + 'title': serializer.toJson(title), + 'isPrivate': serializer.toJson(isPrivate), + 'isProbablyReaderable': serializer.toJson(isProbablyReaderable), + 'extractedContentMarkdown': serializer.toJson( + extractedContentMarkdown, + ), + 'extractedContentPlain': serializer.toJson( + extractedContentPlain, + ), + 'fullContentMarkdown': serializer.toJson(fullContentMarkdown), + 'fullContentPlain': serializer.toJson(fullContentPlain), + 'timestamp': serializer.toJson(timestamp), + }; + } + + TabData copyWith({ + String? id, + int? source, + Value parentId = const Value.absent(), + Value containerId = const Value.absent(), + String? orderKey, + Value url = const Value.absent(), + Value title = const Value.absent(), + Value isPrivate = const Value.absent(), + Value isProbablyReaderable = const Value.absent(), + Value extractedContentMarkdown = const Value.absent(), + Value extractedContentPlain = const Value.absent(), + Value fullContentMarkdown = const Value.absent(), + Value fullContentPlain = const Value.absent(), + int? timestamp, + }) => TabData( + id: id ?? this.id, + source: source ?? this.source, + parentId: parentId.present ? parentId.value : this.parentId, + containerId: containerId.present ? containerId.value : this.containerId, + orderKey: orderKey ?? this.orderKey, + url: url.present ? url.value : this.url, + title: title.present ? title.value : this.title, + isPrivate: isPrivate.present ? isPrivate.value : this.isPrivate, + isProbablyReaderable: isProbablyReaderable.present + ? isProbablyReaderable.value + : this.isProbablyReaderable, + extractedContentMarkdown: extractedContentMarkdown.present + ? extractedContentMarkdown.value + : this.extractedContentMarkdown, + extractedContentPlain: extractedContentPlain.present + ? extractedContentPlain.value + : this.extractedContentPlain, + fullContentMarkdown: fullContentMarkdown.present + ? fullContentMarkdown.value + : this.fullContentMarkdown, + fullContentPlain: fullContentPlain.present + ? fullContentPlain.value + : this.fullContentPlain, + timestamp: timestamp ?? this.timestamp, + ); + TabData copyWithCompanion(TabCompanion data) { + return TabData( + id: data.id.present ? data.id.value : this.id, + source: data.source.present ? data.source.value : this.source, + parentId: data.parentId.present ? data.parentId.value : this.parentId, + containerId: data.containerId.present + ? data.containerId.value + : this.containerId, + orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey, + url: data.url.present ? data.url.value : this.url, + title: data.title.present ? data.title.value : this.title, + isPrivate: data.isPrivate.present ? data.isPrivate.value : this.isPrivate, + isProbablyReaderable: data.isProbablyReaderable.present + ? data.isProbablyReaderable.value + : this.isProbablyReaderable, + extractedContentMarkdown: data.extractedContentMarkdown.present + ? data.extractedContentMarkdown.value + : this.extractedContentMarkdown, + extractedContentPlain: data.extractedContentPlain.present + ? data.extractedContentPlain.value + : this.extractedContentPlain, + fullContentMarkdown: data.fullContentMarkdown.present + ? data.fullContentMarkdown.value + : this.fullContentMarkdown, + fullContentPlain: data.fullContentPlain.present + ? data.fullContentPlain.value + : this.fullContentPlain, + timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp, + ); + } + + @override + String toString() { + return (StringBuffer('TabData(') + ..write('id: $id, ') + ..write('source: $source, ') + ..write('parentId: $parentId, ') + ..write('containerId: $containerId, ') + ..write('orderKey: $orderKey, ') + ..write('url: $url, ') + ..write('title: $title, ') + ..write('isPrivate: $isPrivate, ') + ..write('isProbablyReaderable: $isProbablyReaderable, ') + ..write('extractedContentMarkdown: $extractedContentMarkdown, ') + ..write('extractedContentPlain: $extractedContentPlain, ') + ..write('fullContentMarkdown: $fullContentMarkdown, ') + ..write('fullContentPlain: $fullContentPlain, ') + ..write('timestamp: $timestamp') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + source, + parentId, + containerId, + orderKey, + url, + title, + isPrivate, + isProbablyReaderable, + extractedContentMarkdown, + extractedContentPlain, + fullContentMarkdown, + fullContentPlain, + timestamp, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TabData && + other.id == this.id && + other.source == this.source && + other.parentId == this.parentId && + other.containerId == this.containerId && + other.orderKey == this.orderKey && + other.url == this.url && + other.title == this.title && + other.isPrivate == this.isPrivate && + other.isProbablyReaderable == this.isProbablyReaderable && + other.extractedContentMarkdown == this.extractedContentMarkdown && + other.extractedContentPlain == this.extractedContentPlain && + other.fullContentMarkdown == this.fullContentMarkdown && + other.fullContentPlain == this.fullContentPlain && + other.timestamp == this.timestamp); +} + +class TabCompanion extends UpdateCompanion { + final Value id; + final Value source; + final Value parentId; + final Value containerId; + final Value orderKey; + final Value url; + final Value title; + final Value isPrivate; + final Value isProbablyReaderable; + final Value extractedContentMarkdown; + final Value extractedContentPlain; + final Value fullContentMarkdown; + final Value fullContentPlain; + final Value timestamp; + final Value rowid; + const TabCompanion({ + this.id = const Value.absent(), + this.source = const Value.absent(), + this.parentId = const Value.absent(), + this.containerId = const Value.absent(), + this.orderKey = const Value.absent(), + this.url = const Value.absent(), + this.title = const Value.absent(), + this.isPrivate = const Value.absent(), + this.isProbablyReaderable = const Value.absent(), + this.extractedContentMarkdown = const Value.absent(), + this.extractedContentPlain = const Value.absent(), + this.fullContentMarkdown = const Value.absent(), + this.fullContentPlain = const Value.absent(), + this.timestamp = const Value.absent(), + this.rowid = const Value.absent(), + }); + TabCompanion.insert({ + required String id, + required int source, + this.parentId = const Value.absent(), + this.containerId = const Value.absent(), + required String orderKey, + this.url = const Value.absent(), + this.title = const Value.absent(), + this.isPrivate = const Value.absent(), + this.isProbablyReaderable = const Value.absent(), + this.extractedContentMarkdown = const Value.absent(), + this.extractedContentPlain = const Value.absent(), + this.fullContentMarkdown = const Value.absent(), + this.fullContentPlain = const Value.absent(), + required int timestamp, + this.rowid = const Value.absent(), + }) : id = Value(id), + source = Value(source), + orderKey = Value(orderKey), + timestamp = Value(timestamp); + static Insertable custom({ + Expression? id, + Expression? source, + Expression? parentId, + Expression? containerId, + Expression? orderKey, + Expression? url, + Expression? title, + Expression? isPrivate, + Expression? isProbablyReaderable, + Expression? extractedContentMarkdown, + Expression? extractedContentPlain, + Expression? fullContentMarkdown, + Expression? fullContentPlain, + Expression? timestamp, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (source != null) 'source': source, + if (parentId != null) 'parent_id': parentId, + if (containerId != null) 'container_id': containerId, + if (orderKey != null) 'order_key': orderKey, + if (url != null) 'url': url, + if (title != null) 'title': title, + if (isPrivate != null) 'is_private': isPrivate, + if (isProbablyReaderable != null) + 'is_probably_readerable': isProbablyReaderable, + if (extractedContentMarkdown != null) + 'extracted_content_markdown': extractedContentMarkdown, + if (extractedContentPlain != null) + 'extracted_content_plain': extractedContentPlain, + if (fullContentMarkdown != null) + 'full_content_markdown': fullContentMarkdown, + if (fullContentPlain != null) 'full_content_plain': fullContentPlain, + if (timestamp != null) 'timestamp': timestamp, + if (rowid != null) 'rowid': rowid, + }); + } + + TabCompanion copyWith({ + Value? id, + Value? source, + Value? parentId, + Value? containerId, + Value? orderKey, + Value? url, + Value? title, + Value? isPrivate, + Value? isProbablyReaderable, + Value? extractedContentMarkdown, + Value? extractedContentPlain, + Value? fullContentMarkdown, + Value? fullContentPlain, + Value? timestamp, + Value? rowid, + }) { + return TabCompanion( + id: id ?? this.id, + source: source ?? this.source, + parentId: parentId ?? this.parentId, + containerId: containerId ?? this.containerId, + orderKey: orderKey ?? this.orderKey, + url: url ?? this.url, + title: title ?? this.title, + isPrivate: isPrivate ?? this.isPrivate, + isProbablyReaderable: isProbablyReaderable ?? this.isProbablyReaderable, + extractedContentMarkdown: + extractedContentMarkdown ?? this.extractedContentMarkdown, + extractedContentPlain: + extractedContentPlain ?? this.extractedContentPlain, + fullContentMarkdown: fullContentMarkdown ?? this.fullContentMarkdown, + fullContentPlain: fullContentPlain ?? this.fullContentPlain, + timestamp: timestamp ?? this.timestamp, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (parentId.present) { + map['parent_id'] = Variable(parentId.value); + } + if (containerId.present) { + map['container_id'] = Variable(containerId.value); + } + if (orderKey.present) { + map['order_key'] = Variable(orderKey.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (isPrivate.present) { + map['is_private'] = Variable(isPrivate.value); + } + if (isProbablyReaderable.present) { + map['is_probably_readerable'] = Variable(isProbablyReaderable.value); + } + if (extractedContentMarkdown.present) { + map['extracted_content_markdown'] = Variable( + extractedContentMarkdown.value, + ); + } + if (extractedContentPlain.present) { + map['extracted_content_plain'] = Variable( + extractedContentPlain.value, + ); + } + if (fullContentMarkdown.present) { + map['full_content_markdown'] = Variable( + fullContentMarkdown.value, + ); + } + if (fullContentPlain.present) { + map['full_content_plain'] = Variable(fullContentPlain.value); + } + if (timestamp.present) { + map['timestamp'] = Variable(timestamp.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TabCompanion(') + ..write('id: $id, ') + ..write('source: $source, ') + ..write('parentId: $parentId, ') + ..write('containerId: $containerId, ') + ..write('orderKey: $orderKey, ') + ..write('url: $url, ') + ..write('title: $title, ') + ..write('isPrivate: $isPrivate, ') + ..write('isProbablyReaderable: $isProbablyReaderable, ') + ..write('extractedContentMarkdown: $extractedContentMarkdown, ') + ..write('extractedContentPlain: $extractedContentPlain, ') + ..write('fullContentMarkdown: $fullContentMarkdown, ') + ..write('fullContentPlain: $fullContentPlain, ') + ..write('timestamp: $timestamp, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class TabFts extends Table + with TableInfo, VirtualTableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TabFts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn title = GeneratedColumn( + 'title', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn extractedContentPlain = + GeneratedColumn( + 'extracted_content_plain', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + late final GeneratedColumn fullContentPlain = GeneratedColumn( + 'full_content_plain', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: '', + ); + @override + List get $columns => [ + title, + url, + extractedContentPlain, + fullContentPlain, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'tab_fts'; + @override + Set get $primaryKey => const {}; + @override + TabFtsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TabFtsData( + title: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}title'], + ), + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + ), + extractedContentPlain: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}extracted_content_plain'], + ), + fullContentPlain: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}full_content_plain'], + ), + ); + } + + @override + TabFts createAlias(String alias) { + return TabFts(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; + @override + String get moduleAndArgs => + 'fts5(title, url, extracted_content_plain, full_content_plain, content=tab, tokenize="trigram")'; +} + +class TabFtsData extends DataClass implements Insertable { + final String? title; + final String? url; + final String? extractedContentPlain; + final String? fullContentPlain; + const TabFtsData({ + this.title, + this.url, + this.extractedContentPlain, + this.fullContentPlain, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || title != null) { + map['title'] = Variable(title); + } + if (!nullToAbsent || url != null) { + map['url'] = Variable(url); + } + if (!nullToAbsent || extractedContentPlain != null) { + map['extracted_content_plain'] = Variable(extractedContentPlain); + } + if (!nullToAbsent || fullContentPlain != null) { + map['full_content_plain'] = Variable(fullContentPlain); + } + return map; + } + + factory TabFtsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TabFtsData( + title: serializer.fromJson(json['title']), + url: serializer.fromJson(json['url']), + extractedContentPlain: serializer.fromJson( + json['extractedContentPlain'], + ), + fullContentPlain: serializer.fromJson(json['fullContentPlain']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'title': serializer.toJson(title), + 'url': serializer.toJson(url), + 'extractedContentPlain': serializer.toJson( + extractedContentPlain, + ), + 'fullContentPlain': serializer.toJson(fullContentPlain), + }; + } + + TabFtsData copyWith({ + Value title = const Value.absent(), + Value url = const Value.absent(), + Value extractedContentPlain = const Value.absent(), + Value fullContentPlain = const Value.absent(), + }) => TabFtsData( + title: title.present ? title.value : this.title, + url: url.present ? url.value : this.url, + extractedContentPlain: extractedContentPlain.present + ? extractedContentPlain.value + : this.extractedContentPlain, + fullContentPlain: fullContentPlain.present + ? fullContentPlain.value + : this.fullContentPlain, + ); + TabFtsData copyWithCompanion(TabFtsCompanion data) { + return TabFtsData( + title: data.title.present ? data.title.value : this.title, + url: data.url.present ? data.url.value : this.url, + extractedContentPlain: data.extractedContentPlain.present + ? data.extractedContentPlain.value + : this.extractedContentPlain, + fullContentPlain: data.fullContentPlain.present + ? data.fullContentPlain.value + : this.fullContentPlain, + ); + } + + @override + String toString() { + return (StringBuffer('TabFtsData(') + ..write('title: $title, ') + ..write('url: $url, ') + ..write('extractedContentPlain: $extractedContentPlain, ') + ..write('fullContentPlain: $fullContentPlain') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(title, url, extractedContentPlain, fullContentPlain); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TabFtsData && + other.title == this.title && + other.url == this.url && + other.extractedContentPlain == this.extractedContentPlain && + other.fullContentPlain == this.fullContentPlain); +} + +class TabFtsCompanion extends UpdateCompanion { + final Value title; + final Value url; + final Value extractedContentPlain; + final Value fullContentPlain; + final Value rowid; + const TabFtsCompanion({ + this.title = const Value.absent(), + this.url = const Value.absent(), + this.extractedContentPlain = const Value.absent(), + this.fullContentPlain = const Value.absent(), + this.rowid = const Value.absent(), + }); + TabFtsCompanion.insert({ + this.title = const Value.absent(), + this.url = const Value.absent(), + this.extractedContentPlain = const Value.absent(), + this.fullContentPlain = const Value.absent(), + this.rowid = const Value.absent(), + }); + static Insertable custom({ + Expression? title, + Expression? url, + Expression? extractedContentPlain, + Expression? fullContentPlain, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (title != null) 'title': title, + if (url != null) 'url': url, + if (extractedContentPlain != null) + 'extracted_content_plain': extractedContentPlain, + if (fullContentPlain != null) 'full_content_plain': fullContentPlain, + if (rowid != null) 'rowid': rowid, + }); + } + + TabFtsCompanion copyWith({ + Value? title, + Value? url, + Value? extractedContentPlain, + Value? fullContentPlain, + Value? rowid, + }) { + return TabFtsCompanion( + title: title ?? this.title, + url: url ?? this.url, + extractedContentPlain: + extractedContentPlain ?? this.extractedContentPlain, + fullContentPlain: fullContentPlain ?? this.fullContentPlain, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (title.present) { + map['title'] = Variable(title.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (extractedContentPlain.present) { + map['extracted_content_plain'] = Variable( + extractedContentPlain.value, + ); + } + if (fullContentPlain.present) { + map['full_content_plain'] = Variable(fullContentPlain.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TabFtsCompanion(') + ..write('title: $title, ') + ..write('url: $url, ') + ..write('extractedContentPlain: $extractedContentPlain, ') + ..write('fullContentPlain: $fullContentPlain, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV4 extends GeneratedDatabase { + DatabaseAtV4(QueryExecutor e) : super(e); + late final Container container = Container(this); + late final Tab tab = Tab(this); + late final TabFts tabFts = TabFts(this); + late final Trigger tabMaintainParentChainOnDelete = Trigger( + 'CREATE TRIGGER tab_maintain_parent_chain_on_delete BEFORE DELETE ON tab BEGIN UPDATE tab SET parent_id = OLD.parent_id WHERE parent_id = OLD.id;END', + 'tab_maintain_parent_chain_on_delete', + ); + late final Trigger tabAfterInsert = Trigger( + 'CREATE TRIGGER tab_after_insert AFTER INSERT ON tab BEGIN INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END', + 'tab_after_insert', + ); + late final Trigger tabAfterDelete = Trigger( + 'CREATE TRIGGER tab_after_delete AFTER DELETE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);END', + 'tab_after_delete', + ); + late final Trigger tabAfterUpdate = Trigger( + 'CREATE TRIGGER tab_after_update AFTER UPDATE ON tab BEGIN INSERT INTO tab_fts (tab_fts, "rowid", title, url, extracted_content_plain, full_content_plain) VALUES (\'delete\', old."rowid", old.title, old.url, old.extracted_content_plain, old.full_content_plain);INSERT INTO tab_fts ("rowid", title, url, extracted_content_plain, full_content_plain) VALUES (new."rowid", new.title, new.url, new.extracted_content_plain, new.full_content_plain);END', + 'tab_after_update', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + container, + tab, + tabFts, + tabMaintainParentChainOnDelete, + tabAfterInsert, + tabAfterDelete, + tabAfterUpdate, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'container', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('tab', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'tab', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('tab', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'tab', + limitUpdateKind: UpdateKind.insert, + ), + result: [TableUpdate('tab_fts', kind: UpdateKind.insert)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'tab', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('tab_fts', kind: UpdateKind.insert)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'tab', + limitUpdateKind: UpdateKind.update, + ), + result: [TableUpdate('tab_fts', kind: UpdateKind.insert)], + ), + ]); + @override + int get schemaVersion => 4; +}