track is_private for tabs; add clsoe all private tabs button; close private tabs before exit;

This commit is contained in:
Fabian Freund
2025-12-28 16:29:15 +01:00
parent d751e9b9a1
commit ae4b18ca28
20 changed files with 3576 additions and 16 deletions
+427
View File
@@ -0,0 +1,427 @@
{
"_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": "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, \"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"
}
]
}
]
}
@@ -92,6 +92,7 @@ class TabRepository extends _$TabRepository {
},
parentId: Value(parentId),
containerId: Value(assingedContainer.value?.id),
isPrivate: Value(private),
);
if (launchedFromIntent) {
@@ -102,7 +103,7 @@ class TabRepository extends _$TabRepository {
}
Future<String> duplicateTab({
required String? selectTabId,
required String selectTabId,
String? containerId,
bool selectTab = true,
}) async {
@@ -124,6 +125,9 @@ class TabRepository extends _$TabRepository {
},
parentId: const Value.absent(),
containerId: Value(containerData?.id),
isPrivate: Value(
await tabDao.getTabIsPrivate(selectTabId).getSingleOrNull(),
),
);
}
@@ -378,6 +382,7 @@ class TabRepository extends _$TabRepository {
tabId,
parentId: const Value.absent(),
containerId: Value(containerId),
isPrivate: const Value.absent(),
);
});
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
}
}
String _$tabRepositoryHash() => r'7af24e759044d1b1b471cdf90a3eb38eef06f61b';
String _$tabRepositoryHash() => r'0411e8b3fa9c3e344106f8fa8178bd6eb613309a';
abstract class _$TabRepository extends $Notifier<void> {
void build();
@@ -219,8 +219,8 @@ class TabViewHeader extends HookConsumerWidget {
},
),
MenuItemButton(
leadingIcon: const Icon(MdiIcons.closeBoxMultiple),
child: const Text('Close All'),
leadingIcon: const Icon(MdiIcons.closeCircle),
child: const Text('Close All Tabs'),
onPressed: () async {
final result = await showDialog<bool?>(
context: context,
@@ -256,7 +256,62 @@ class TabViewHeader extends HookConsumerWidget {
final count = await ref
.read(tabDataRepositoryProvider.notifier)
.closeAllTabsByContainer(container);
.closeContainerTabs(container);
if (context.mounted) {
ui_helper.showTabUndoClose(
context,
ref
.read(tabRepositoryProvider.notifier)
.undoClose,
count: count,
);
}
}
},
),
MenuItemButton(
leadingIcon: const Icon(MdiIcons.incognitoCircleOff),
child: const Text('Close Private Tabs'),
onPressed: () async {
final result = await showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
icon: const Icon(Icons.warning),
title: const Text('Close All Private Tabs'),
content: const Text(
'Are you sure you want to close all displayed private tabs?',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Close'),
),
],
);
},
);
if (result == true) {
final container = ref.read(
selectedContainerProvider,
);
final count = await ref
.read(tabDataRepositoryProvider.notifier)
.closeContainerTabs(
container,
includeRegular: false,
);
if (context.mounted) {
ui_helper.showTabUndoClose(
@@ -72,7 +72,31 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
);
}
Selectable<String> getContainerTabIds(String? containerId) {
Selectable<String> getAllTabIds({
bool includeRegular = true,
bool includePrivate = true,
}) {
final query = selectOnly(db.tab)..addColumns([db.tab.id]);
if (!includeRegular) {
query.where(
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(false),
);
}
if (!includePrivate) {
query.where(
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(true),
);
}
return query.map((row) => row.read(db.tab.id)!);
}
Selectable<String> getContainerTabIds(
String? containerId, {
bool includeRegular = true,
bool includePrivate = true,
}) {
final query = selectOnly(db.tab)
..addColumns([db.tab.id])
..where(
@@ -82,6 +106,17 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
)
..orderBy([OrderingTerm.asc(db.tab.orderKey)]);
if (!includeRegular) {
query.where(
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(false),
);
}
if (!includePrivate) {
query.where(
db.tab.isPrivate.isNotNull() & db.tab.isPrivate.isNotValue(true),
);
}
return query.map((row) => row.read(db.tab.id)!);
}
@@ -43,6 +43,14 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
SingleOrNullSelectable<TabData> getTabDataById(String id) =>
db.tab.select()..where((t) => t.id.equals(id));
SingleOrNullSelectable<bool?> getTabIsPrivate(String tabId) {
final query = selectOnly(db.tab)
..addColumns([db.tab.isPrivate])
..where(db.tab.id.equals(tabId));
return query.map((row) => row.read(db.tab.isPrivate));
}
Selectable<String> getAllTabIds() {
final query = selectOnly(db.tab)
..addColumns([db.tab.id])
@@ -97,6 +105,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
Future<String> upsertContainerTabTransactional(
Future<String> Function() createTab, {
required Value<bool?> isPrivate,
required Value<String?> parentId,
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
@@ -113,6 +122,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
parentId: parentId,
timestamp: DateTime.now(),
containerId: containerId,
isPrivate: isPrivate,
orderKey: currentOrderKey,
),
onConflict: DoUpdate(
@@ -131,6 +141,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
//Upsert an tab only if there is no container assigned yet
Future<String> upsertUnassignedTab(
String tabId, {
required Value<bool?> isPrivate,
required Value<String?> parentId,
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
@@ -147,6 +158,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
timestamp: DateTime.now(),
containerId: containerId,
orderKey: currentOrderKey,
isPrivate: isPrivate,
),
mode: InsertMode.insertOrIgnore,
);
@@ -18,17 +18,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:drift/drift.dart';
import 'package:drift/internal/versioned_schema.dart';
import 'package:drift_dev/api/migrations_native.dart';
import 'package:flutter/foundation.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/container.dart';
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/search/domain/fts_tokenizer.dart';
@DriftDatabase(include: {'definitions.drift'}, daos: [ContainerDao, TabDao])
class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
@override
final int schemaVersion = 2;
final int schemaVersion = 3;
@override
final int ftsTokenLimit = 10;
@@ -47,7 +49,39 @@ class TabDatabase extends $TabDatabase with TrigramQueryBuilderMixin {
await customStatement('PRAGMA foreign_keys = ON');
await definitionsDrift.optimizeFtsIndex();
},
onUpgrade: (m, from, to) async {
// Following the advice from https://drift.simonbinder.eu/Migrations/api/#general-tips
await customStatement('PRAGMA foreign_keys = OFF');
await transaction(
() => VersionedSchema.runMigrationSteps(
migrator: m,
from: from,
to: to,
steps: _upgrade,
),
);
if (kDebugMode) {
final wrongForeignKeys = await customSelect(
'PRAGMA foreign_key_check',
).get();
assert(
wrongForeignKeys.isEmpty,
'${wrongForeignKeys.map((e) => e.data)}',
);
}
await customStatement('PRAGMA foreign_keys = ON');
},
);
TabDatabase(super.e);
static final _upgrade = migrationSteps(
from2To3: (m, schema) async {
final tabAtV3 = schema.tab;
await m.addColumn(tabAtV3, tabAtV3.isPrivate);
},
);
}
@@ -0,0 +1,288 @@
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class Schema3 extends i0.VersionedSchema {
Schema3({required super.database}) : super(version: 3);
@override
late final List<i1.DatabaseSchemaEntity> 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 Shape1 tab = Shape1(
source: i0.VersionedTable(
entityName: 'tab',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_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 Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get color =>
columnsByName['color']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get metadata =>
columnsByName['metadata']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_0(String aliasedName) =>
i1.GeneratedColumn<String>(
'id',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'PRIMARY KEY NOT NULL',
);
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>(
'name',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<int> _column_2(String aliasedName) =>
i1.GeneratedColumn<int>(
'color',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>(
'metadata',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get parentId =>
columnsByName['parent_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get containerId =>
columnsByName['container_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get orderKey =>
columnsByName['order_key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get url =>
columnsByName['url']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get title =>
columnsByName['title']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get isPrivate =>
columnsByName['is_private']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get isProbablyReaderable =>
columnsByName['is_probably_readerable']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get extractedContentMarkdown =>
columnsByName['extracted_content_markdown']!
as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get extractedContentPlain =>
columnsByName['extracted_content_plain']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get fullContentMarkdown =>
columnsByName['full_content_markdown']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get fullContentPlain =>
columnsByName['full_content_plain']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get timestamp =>
columnsByName['timestamp']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<String> _column_4(String aliasedName) =>
i1.GeneratedColumn<String>(
'parent_id',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: 'REFERENCES tab(id)ON DELETE SET NULL',
);
i1.GeneratedColumn<String> _column_5(String aliasedName) =>
i1.GeneratedColumn<String>(
'container_id',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: 'REFERENCES container(id)ON DELETE CASCADE',
);
i1.GeneratedColumn<String> _column_6(String aliasedName) =>
i1.GeneratedColumn<String>(
'order_key',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<String> _column_7(String aliasedName) =>
i1.GeneratedColumn<String>(
'url',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_8(String aliasedName) =>
i1.GeneratedColumn<String>(
'title',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<int> _column_9(String aliasedName) =>
i1.GeneratedColumn<int>(
'is_private',
aliasedName,
true,
type: i1.DriftSqlType.int,
$customConstraints: '',
);
i1.GeneratedColumn<int> _column_10(String aliasedName) =>
i1.GeneratedColumn<int>(
'is_probably_readerable',
aliasedName,
true,
type: i1.DriftSqlType.int,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_11(String aliasedName) =>
i1.GeneratedColumn<String>(
'extracted_content_markdown',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_12(String aliasedName) =>
i1.GeneratedColumn<String>(
'extracted_content_plain',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_13(String aliasedName) =>
i1.GeneratedColumn<String>(
'full_content_markdown',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_14(String aliasedName) =>
i1.GeneratedColumn<String>(
'full_content_plain',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<int> _column_15(String aliasedName) =>
i1.GeneratedColumn<int>(
'timestamp',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL',
);
class Shape2 extends i0.VersionedVirtualTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get title =>
columnsByName['title']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get url =>
columnsByName['url']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get extractedContentPlain =>
columnsByName['extracted_content_plain']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get fullContentPlain =>
columnsByName['full_content_plain']! as i1.GeneratedColumn<String>;
}
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
case 2:
final schema = Schema3(database: database);
final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema);
return 3;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
};
}
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(from2To3: from2To3),
);
@@ -19,6 +19,7 @@ CREATE TABLE tab(
order_key TEXT NOT NULL,
url TEXT MAPPED BY `const UriConverterNullable()`,
title TEXT,
is_private BOOL,
is_probably_readerable BOOL,
extracted_content_markdown TEXT,
extracted_content_plain TEXT,
@@ -141,6 +142,7 @@ queryTabsBasic WITH TabQueryResult:
SELECT
t.id,
t.container_id,
t.is_private,
t.title,
CAST(t.url AS TEXT) AS url,
t.url AS clean_url,
@@ -168,6 +170,7 @@ queryTabsFullContent WITH TabQueryResult:
SELECT
t.id,
t.container_id,
t.is_private,
highlight(tab_fts, 0, :beforeMatch, :afterMatch) AS title,
highlight(tab_fts, 1, :beforeMatch, :afterMatch) AS url,
snippet(tab_fts, 2, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS extracted_content,
@@ -326,6 +326,7 @@ typedef $TabCreateCompanionBuilder =
required String orderKey,
i0.Value<Uri?> url,
i0.Value<String?> title,
i0.Value<bool?> isPrivate,
i0.Value<bool?> isProbablyReaderable,
i0.Value<String?> extractedContentMarkdown,
i0.Value<String?> extractedContentPlain,
@@ -342,6 +343,7 @@ typedef $TabUpdateCompanionBuilder =
i0.Value<String> orderKey,
i0.Value<Uri?> url,
i0.Value<String?> title,
i0.Value<bool?> isPrivate,
i0.Value<bool?> isProbablyReaderable,
i0.Value<String?> extractedContentMarkdown,
i0.Value<String?> extractedContentPlain,
@@ -418,6 +420,11 @@ class $TabFilterComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<bool> get isPrivate => $composableBuilder(
column: $table.isPrivate,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<bool> get isProbablyReaderable => $composableBuilder(
column: $table.isProbablyReaderable,
builder: (column) => i0.ColumnFilters(column),
@@ -509,6 +516,11 @@ class $TabOrderingComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<bool> get isPrivate => $composableBuilder(
column: $table.isPrivate,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<bool> get isProbablyReaderable => $composableBuilder(
column: $table.isProbablyReaderable,
builder: (column) => i0.ColumnOrderings(column),
@@ -590,6 +602,9 @@ class $TabAnnotationComposer extends i0.Composer<i0.GeneratedDatabase, i3.Tab> {
i0.GeneratedColumn<String> get title =>
$composableBuilder(column: $table.title, builder: (column) => column);
i0.GeneratedColumn<bool> get isPrivate =>
$composableBuilder(column: $table.isPrivate, builder: (column) => column);
i0.GeneratedColumn<bool> get isProbablyReaderable => $composableBuilder(
column: $table.isProbablyReaderable,
builder: (column) => column,
@@ -680,6 +695,7 @@ class $TabTableManager
i0.Value<String> orderKey = const i0.Value.absent(),
i0.Value<Uri?> url = const i0.Value.absent(),
i0.Value<String?> title = const i0.Value.absent(),
i0.Value<bool?> isPrivate = const i0.Value.absent(),
i0.Value<bool?> isProbablyReaderable = const i0.Value.absent(),
i0.Value<String?> extractedContentMarkdown =
const i0.Value.absent(),
@@ -696,6 +712,7 @@ class $TabTableManager
orderKey: orderKey,
url: url,
title: title,
isPrivate: isPrivate,
isProbablyReaderable: isProbablyReaderable,
extractedContentMarkdown: extractedContentMarkdown,
extractedContentPlain: extractedContentPlain,
@@ -712,6 +729,7 @@ class $TabTableManager
required String orderKey,
i0.Value<Uri?> url = const i0.Value.absent(),
i0.Value<String?> title = const i0.Value.absent(),
i0.Value<bool?> isPrivate = const i0.Value.absent(),
i0.Value<bool?> isProbablyReaderable = const i0.Value.absent(),
i0.Value<String?> extractedContentMarkdown =
const i0.Value.absent(),
@@ -728,6 +746,7 @@ class $TabTableManager
orderKey: orderKey,
url: url,
title: title,
isPrivate: isPrivate,
isProbablyReaderable: isProbablyReaderable,
extractedContentMarkdown: extractedContentMarkdown,
extractedContentPlain: extractedContentPlain,
@@ -1219,6 +1238,14 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
requiredDuringInsert: false,
$customConstraints: '',
);
late final i0.GeneratedColumn<bool> isPrivate = i0.GeneratedColumn<bool>(
'is_private',
aliasedName,
true,
type: i0.DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: '',
);
late final i0.GeneratedColumn<bool> isProbablyReaderable =
i0.GeneratedColumn<bool>(
'is_probably_readerable',
@@ -1281,6 +1308,7 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
orderKey,
url,
title,
isPrivate,
isProbablyReaderable,
extractedContentMarkdown,
extractedContentPlain,
@@ -1325,6 +1353,10 @@ class Tab extends i0.Table with i0.TableInfo<Tab, i3.TabData> {
i0.DriftSqlType.string,
data['${effectivePrefix}title'],
),
isPrivate: attachedDatabase.typeMapping.read(
i0.DriftSqlType.bool,
data['${effectivePrefix}is_private'],
),
isProbablyReaderable: attachedDatabase.typeMapping.read(
i0.DriftSqlType.bool,
data['${effectivePrefix}is_probably_readerable'],
@@ -1370,6 +1402,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
final String orderKey;
final Uri? url;
final String? title;
final bool? isPrivate;
final bool? isProbablyReaderable;
final String? extractedContentMarkdown;
final String? extractedContentPlain;
@@ -1383,6 +1416,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
required this.orderKey,
this.url,
this.title,
this.isPrivate,
this.isProbablyReaderable,
this.extractedContentMarkdown,
this.extractedContentPlain,
@@ -1407,6 +1441,9 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
if (!nullToAbsent || title != null) {
map['title'] = i0.Variable<String>(title);
}
if (!nullToAbsent || isPrivate != null) {
map['is_private'] = i0.Variable<bool>(isPrivate);
}
if (!nullToAbsent || isProbablyReaderable != null) {
map['is_probably_readerable'] = i0.Variable<bool>(isProbablyReaderable);
}
@@ -1442,6 +1479,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
orderKey: serializer.fromJson<String>(json['order_key']),
url: serializer.fromJson<Uri?>(json['url']),
title: serializer.fromJson<String?>(json['title']),
isPrivate: serializer.fromJson<bool?>(json['is_private']),
isProbablyReaderable: serializer.fromJson<bool?>(
json['is_probably_readerable'],
),
@@ -1470,6 +1508,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
'order_key': serializer.toJson<String>(orderKey),
'url': serializer.toJson<Uri?>(url),
'title': serializer.toJson<String?>(title),
'is_private': serializer.toJson<bool?>(isPrivate),
'is_probably_readerable': serializer.toJson<bool?>(isProbablyReaderable),
'extracted_content_markdown': serializer.toJson<String?>(
extractedContentMarkdown,
@@ -1490,6 +1529,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
String? orderKey,
i0.Value<Uri?> url = const i0.Value.absent(),
i0.Value<String?> title = const i0.Value.absent(),
i0.Value<bool?> isPrivate = const i0.Value.absent(),
i0.Value<bool?> isProbablyReaderable = const i0.Value.absent(),
i0.Value<String?> extractedContentMarkdown = const i0.Value.absent(),
i0.Value<String?> extractedContentPlain = const i0.Value.absent(),
@@ -1503,6 +1543,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
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,
@@ -1530,6 +1571,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
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,
@@ -1558,6 +1600,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
..write('orderKey: $orderKey, ')
..write('url: $url, ')
..write('title: $title, ')
..write('isPrivate: $isPrivate, ')
..write('isProbablyReaderable: $isProbablyReaderable, ')
..write('extractedContentMarkdown: $extractedContentMarkdown, ')
..write('extractedContentPlain: $extractedContentPlain, ')
@@ -1576,6 +1619,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
orderKey,
url,
title,
isPrivate,
isProbablyReaderable,
extractedContentMarkdown,
extractedContentPlain,
@@ -1593,6 +1637,7 @@ class TabData extends i0.DataClass implements i0.Insertable<i3.TabData> {
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 &&
@@ -1608,6 +1653,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
final i0.Value<String> orderKey;
final i0.Value<Uri?> url;
final i0.Value<String?> title;
final i0.Value<bool?> isPrivate;
final i0.Value<bool?> isProbablyReaderable;
final i0.Value<String?> extractedContentMarkdown;
final i0.Value<String?> extractedContentPlain;
@@ -1622,6 +1668,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
this.orderKey = const i0.Value.absent(),
this.url = const i0.Value.absent(),
this.title = const i0.Value.absent(),
this.isPrivate = const i0.Value.absent(),
this.isProbablyReaderable = const i0.Value.absent(),
this.extractedContentMarkdown = const i0.Value.absent(),
this.extractedContentPlain = const i0.Value.absent(),
@@ -1637,6 +1684,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
required String orderKey,
this.url = const i0.Value.absent(),
this.title = const i0.Value.absent(),
this.isPrivate = const i0.Value.absent(),
this.isProbablyReaderable = const i0.Value.absent(),
this.extractedContentMarkdown = const i0.Value.absent(),
this.extractedContentPlain = const i0.Value.absent(),
@@ -1654,6 +1702,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
i0.Expression<String>? orderKey,
i0.Expression<String>? url,
i0.Expression<String>? title,
i0.Expression<bool>? isPrivate,
i0.Expression<bool>? isProbablyReaderable,
i0.Expression<String>? extractedContentMarkdown,
i0.Expression<String>? extractedContentPlain,
@@ -1669,6 +1718,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
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)
@@ -1690,6 +1740,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
i0.Value<String>? orderKey,
i0.Value<Uri?>? url,
i0.Value<String?>? title,
i0.Value<bool?>? isPrivate,
i0.Value<bool?>? isProbablyReaderable,
i0.Value<String?>? extractedContentMarkdown,
i0.Value<String?>? extractedContentPlain,
@@ -1705,6 +1756,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
orderKey: orderKey ?? this.orderKey,
url: url ?? this.url,
title: title ?? this.title,
isPrivate: isPrivate ?? this.isPrivate,
isProbablyReaderable: isProbablyReaderable ?? this.isProbablyReaderable,
extractedContentMarkdown:
extractedContentMarkdown ?? this.extractedContentMarkdown,
@@ -1738,6 +1790,9 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
if (title.present) {
map['title'] = i0.Variable<String>(title.value);
}
if (isPrivate.present) {
map['is_private'] = i0.Variable<bool>(isPrivate.value);
}
if (isProbablyReaderable.present) {
map['is_probably_readerable'] = i0.Variable<bool>(
isProbablyReaderable.value,
@@ -1779,6 +1834,7 @@ class TabCompanion extends i0.UpdateCompanion<i3.TabData> {
..write('orderKey: $orderKey, ')
..write('url: $url, ')
..write('title: $title, ')
..write('isPrivate: $isPrivate, ')
..write('isProbablyReaderable: $isProbablyReaderable, ')
..write('extractedContentMarkdown: $extractedContentMarkdown, ')
..write('extractedContentPlain: $extractedContentPlain, ')
@@ -2161,13 +2217,14 @@ class DefinitionsDrift extends i7.ModularAccessor {
i0.Selectable<i8.TabQueryResult> 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.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',
'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<String>(query)],
readsFrom: {tab, tabFts},
).map(
(i0.QueryRow row) => i8.TabQueryResult(
id: row.read<String>('id'),
containerId: row.readNullable<String>('container_id'),
isPrivate: row.readNullable<bool>('is_private'),
title: row.readNullable<String>('title'),
url: row.readNullable<String>('url'),
cleanUrl: i3.Tab.$converterurl.fromSql(
@@ -2186,7 +2243,7 @@ class DefinitionsDrift extends i7.ModularAccessor {
required String query,
}) {
return customSelect(
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, t.container_id, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content, t.url AS clean_url,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC',
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, t.container_id, t.is_private, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content, t.url AS clean_url,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC',
variables: [
i0.Variable<String>(beforeMatch),
i0.Variable<String>(afterMatch),
@@ -2199,6 +2256,7 @@ class DefinitionsDrift extends i7.ModularAccessor {
(i0.QueryRow row) => i8.TabQueryResult(
id: row.read<String>('id'),
containerId: row.readNullable<String>('container_id'),
isPrivate: row.readNullable<bool>('is_private'),
title: row.readNullable<String>('title'),
url: row.readNullable<String>('url'),
cleanUrl: i3.Tab.$converterurl.fromSql(
@@ -22,6 +22,7 @@ import 'package:fast_equatable/fast_equatable.dart';
class TabQueryResult with FastEquatable {
final String id;
final String? containerId;
final bool? isPrivate;
final String? title;
final Uri? cleanUrl;
@@ -35,6 +36,7 @@ class TabQueryResult with FastEquatable {
TabQueryResult({
required this.id,
required this.containerId,
required this.isPrivate,
required this.title,
required this.url,
required this.cleanUrl,
@@ -47,6 +49,7 @@ class TabQueryResult with FastEquatable {
List<Object?> get hashParameters => [
id,
containerId,
isPrivate,
title,
cleanUrl,
url,
@@ -85,9 +85,7 @@ class ContainerRepository extends _$ContainerRepository {
}
Future<void> deleteContainer(String id) async {
await ref
.read(tabDataRepositoryProvider.notifier)
.closeAllTabsByContainer(id);
await ref.read(tabDataRepositoryProvider.notifier).closeContainerTabs(id);
return ref.read(tabDatabaseProvider).containerDao.deleteContainer(id);
}
@@ -42,7 +42,7 @@ final class ContainerRepositoryProvider
}
String _$containerRepositoryHash() =>
r'8c9252a49a096c29279ad3066f6e5936e15524b2';
r'2a377774f9f18604bb90cb1a8eb7cda5f944c601';
abstract class _$ContainerRepository extends $Notifier<void> {
void build();
@@ -73,11 +73,39 @@ class TabDataRepository extends _$TabDataRepository {
.assignOrderKey(tabId, orderKey: orderKey);
}
Future<int> closeAllTabsByContainer(String? containerId) async {
Future<int> closeAllTabs({
bool includeRegular = true,
bool includePrivate = true,
}) async {
final tabIds = await ref
.read(tabDatabaseProvider)
.containerDao
.getContainerTabIds(containerId)
.getAllTabIds(
includeRegular: includeRegular,
includePrivate: includePrivate,
)
.get();
if (tabIds.isNotEmpty) {
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
}
return tabIds.length;
}
Future<int> closeContainerTabs(
String? containerId, {
bool includeRegular = true,
bool includePrivate = true,
}) async {
final tabIds = await ref
.read(tabDatabaseProvider)
.containerDao
.getContainerTabIds(
containerId,
includeRegular: includeRegular,
includePrivate: includePrivate,
)
.get();
if (tabIds.isNotEmpty) {
@@ -41,7 +41,7 @@ final class TabDataRepositoryProvider
}
}
String _$tabDataRepositoryHash() => r'3d9149efb88f256cc826eb538f13331b49438a7c';
String _$tabDataRepositoryHash() => r'63ab2765ea5be640d6c6a596852fee0df5a958e4';
abstract class _$TabDataRepository extends $Notifier<void> {
void build();
+5
View File
@@ -22,10 +22,15 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:riverpod/riverpod.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
Future<void> exitApp(ProviderContainer container) async {
logger.i('Preparing exit');
await container
.read(tabDataRepositoryProvider.notifier)
.closeAllTabs(includeRegular: false);
await SystemNavigator.pop();
logger.i('SystemNavigator popped');
+23
View File
@@ -0,0 +1,23 @@
// dart format width=80
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
import 'package:drift/drift.dart';
import 'package:drift/internal/migrations.dart';
import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
switch (version) {
case 2:
return v2.DatabaseAtV2(db);
case 3:
return v3.DatabaseAtV3(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [2, 3];
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
// dart format width=80
// ignore_for_file: unused_local_variable, unused_import
import 'package:drift/drift.dart';
import 'package:drift_dev/api/migrations_native.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
import 'package:flutter_test/flutter_test.dart';
import 'generated/schema.dart';
import 'generated/schema_v2.dart' as v2;
import 'generated/schema_v3.dart' as v3;
void main() {
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
late SchemaVerifier verifier;
setUpAll(() {
verifier = SchemaVerifier(GeneratedHelper());
});
group('simple database migrations', () {
// These simple tests verify all possible schema updates with a simple (no
// data) migration. This is a quick way to ensure that written database
// migrations properly alter the schema.
const versions = GeneratedHelper.versions;
for (final (i, fromVersion) in versions.indexed) {
group('from $fromVersion', () {
for (final toVersion in versions.skip(i + 1)) {
test('to $toVersion', () async {
final schema = await verifier.schemaAt(fromVersion);
final db = TabDatabase(schema.newConnection());
await verifier.migrateAndValidate(db, toVersion);
await db.close();
});
}
});
}
});
// The following template shows how to write tests ensuring your migrations
// preserve existing data.
// Testing this can be useful for migrations that change existing columns
// (e.g. by alterating their type or constraints). Migrations that only add
// tables or columns typically don't need these advanced tests. For more
// information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity
// TODO: This generated template shows how these tests could be written. Adopt
// it to your own needs when testing migrations with data integrity.
test('migration from v2 to v3 does not corrupt data', () async {
// Add data to insert into the old database, and the expected rows after the
// migration.
// TODO: Fill these lists
final oldContainerData = <v2.ContainerData>[];
final expectedNewContainerData = <v3.ContainerData>[];
final oldTabData = <v2.TabData>[];
final expectedNewTabData = <v3.TabData>[];
final oldTabFtsData = <v2.TabFtsData>[];
final expectedNewTabFtsData = <v3.TabFtsData>[];
await verifier.testWithDataIntegrity(
oldVersion: 2,
newVersion: 3,
createOld: v2.DatabaseAtV2.new,
createNew: v3.DatabaseAtV3.new,
openTestedDatabase: TabDatabase.new,
createItems: (batch, oldDb) {
batch.insertAll(oldDb.container, oldContainerData);
batch.insertAll(oldDb.tab, oldTabData);
batch.insertAll(oldDb.tabFts, oldTabFtsData);
},
validateItems: (newDb) async {
expect(
expectedNewContainerData,
await newDb.select(newDb.container).get(),
);
expect(expectedNewTabData, await newDb.select(newDb.tab).get());
expect(expectedNewTabFtsData, await newDb.select(newDb.tabFts).get());
},
);
});
}